@@ -31,7 +31,13 @@ use crate::{
31
31
32
32
#[ cfg( feature = "transparent-inputs" ) ]
33
33
use {
34
- std:: collections:: BTreeSet , std:: convert:: Infallible ,
34
+ crate :: {
35
+ fees:: TransactionBalance ,
36
+ proposal:: { StepOutput , StepOutputIndex } ,
37
+ zip321:: Payment ,
38
+ } ,
39
+ std:: collections:: BTreeSet ,
40
+ std:: convert:: Infallible ,
35
41
zcash_primitives:: legacy:: TransparentAddress ,
36
42
zcash_primitives:: transaction:: components:: OutPoint ,
37
43
} ;
@@ -206,6 +212,8 @@ pub enum GreedyInputSelectorError<ChangeStrategyErrT, NoteRefT> {
206
212
Balance ( BalanceError ) ,
207
213
/// A unified address did not contain a supported receiver.
208
214
UnsupportedAddress ( Box < UnifiedAddress > ) ,
215
+ /// Support for transparent-source-only addresses requires the transparent-inputs feature.
216
+ UnsupportedTransparentSourceOnlyAddress ,
209
217
/// An error was encountered in change selection.
210
218
Change ( ChangeError < ChangeStrategyErrT , NoteRefT > ) ,
211
219
}
@@ -223,6 +231,9 @@ impl<CE: fmt::Display, N: fmt::Display> fmt::Display for GreedyInputSelectorErro
223
231
// don't have network parameters here
224
232
write ! ( f, "Unified address contains no supported receivers." )
225
233
}
234
+ GreedyInputSelectorError :: UnsupportedTransparentSourceOnlyAddress => {
235
+ write ! ( f, "Support for transparent-source-only addresses requires the transparent-inputs feature." )
236
+ }
226
237
GreedyInputSelectorError :: Change ( err) => {
227
238
write ! ( f, "An error occurred computing change and fees: {}" , err)
228
239
}
@@ -343,6 +354,22 @@ where
343
354
#[ cfg( feature = "orchard" ) ]
344
355
let mut orchard_outputs = vec ! [ ] ;
345
356
let mut payment_pools = BTreeMap :: new ( ) ;
357
+ #[ cfg( feature = "transparent-inputs" ) ]
358
+ let mut ephemeral_outputs: Vec < ( usize , Payment ) > = vec ! [ ] ;
359
+
360
+ // TR - a
361
+ // - ephb
362
+ // - ephc
363
+ // - d
364
+ //
365
+ // becomes
366
+ //
367
+ // TR1 - a TR2 - b
368
+ // - eph-placeholder - c
369
+ // - d Proposal2
370
+ // - spend from eph-placeholder
371
+ //
372
+
346
373
for ( idx, payment) in transaction_request. payments ( ) {
347
374
match & payment. recipient_address {
348
375
Address :: Transparent ( addr) => {
@@ -352,6 +379,28 @@ where
352
379
script_pubkey : addr. script ( ) ,
353
380
} ) ;
354
381
}
382
+ #[ cfg( feature = "transparent-inputs" ) ]
383
+ Address :: TransparentSourceOnly ( data) => {
384
+ ephemeral_outputs. push ( (
385
+ * idx,
386
+ Payment {
387
+ recipient_address : Address :: Transparent (
388
+ TransparentAddress :: PublicKeyHash ( * data) ,
389
+ ) ,
390
+ amount : payment. amount ,
391
+ memo : None ,
392
+ label : payment. label . clone ( ) ,
393
+ message : payment. message . clone ( ) ,
394
+ other_params : payment. other_params . clone ( ) ,
395
+ } ,
396
+ ) ) ;
397
+ }
398
+ #[ cfg( not( feature = "transparent-inputs" ) ) ]
399
+ Address :: TransparentSourceOnly ( _) => {
400
+ return Err ( InputSelectorError :: Selection (
401
+ GreedyInputSelectorError :: UnsupportedTransparentSourceOnlyAddress ,
402
+ ) ) ;
403
+ }
355
404
Address :: Sapling ( _) => {
356
405
payment_pools. insert ( * idx, PoolType :: Shielded ( ShieldedProtocol :: Sapling ) ) ;
357
406
sapling_outputs. push ( SaplingPayment ( payment. amount ) ) ;
@@ -386,6 +435,99 @@ where
386
435
}
387
436
}
388
437
438
+ #[ cfg( feature = "transparent-inputs" ) ]
439
+ struct Zip320SecondStep {
440
+ transaction_request : TransactionRequest ,
441
+ payment_pools : BTreeMap < usize , PoolType > ,
442
+ prior_step_inputs : StepOutput ,
443
+ balance : TransactionBalance ,
444
+ }
445
+
446
+ // If ephemeral state was detected, handle it here.
447
+ #[ cfg( feature = "transparent-inputs" ) ]
448
+ let ( transaction_request, second_step) = if ephemeral_outputs. is_empty ( ) {
449
+ ( transaction_request, None )
450
+ } else {
451
+ // Construct a new TransactionRequest from `transaction_request` that excludes
452
+ // the ephemeral outputs, and in their place includes a single transparent
453
+ // output to the legacy address for this account.
454
+
455
+ let mut proposal1_excludes = BTreeSet :: new ( ) ;
456
+ let mut proposal2_payments = vec ! [ ] ;
457
+ let mut proposal2_payment_pools = BTreeMap :: new ( ) ;
458
+ let mut total_ephemeral_plus_fee: NonNegativeAmount = ( || todo ! ( ) ) ( ) ;
459
+ let ( first_idx, _) = ephemeral_outputs[ 0 ] ;
460
+ for ( idx2, ( idx1, payment) ) in ephemeral_outputs. into_iter ( ) . enumerate ( ) {
461
+ total_ephemeral_plus_fee = ( total_ephemeral_plus_fee + payment. amount ) . ok_or (
462
+ InputSelectorError :: Selection ( GreedyInputSelectorError :: Balance (
463
+ BalanceError :: Overflow ,
464
+ ) ) ,
465
+ ) ?;
466
+ proposal1_excludes. insert ( idx1) ;
467
+ proposal2_payments. push ( payment) ;
468
+ proposal2_payment_pools. insert ( idx2, PoolType :: Transparent ) ;
469
+ }
470
+
471
+ // TODO: Calculate fee for transaction request 2 and add it to
472
+ // total_ephemeral_plus_fee.
473
+
474
+ let proposal1_transaction_request = TransactionRequest :: from_indexed (
475
+ transaction_request
476
+ . payments ( )
477
+ . iter ( )
478
+ . filter_map ( |( idx, payment) | {
479
+ // If this is the first ephemeral output in the original request,
480
+ // replace it with a transparent output to the legacy address.
481
+ // We use the legacy address here only as a dummy address for the
482
+ // ZIP-321 URI serialized in the proposal. It will be replaced by
483
+ // a unique ephemeral P2PKH address when the proposal is created.
484
+ if * idx == first_idx {
485
+ let legacy_address: TransparentAddress = ( || todo ! ( ) ) ( ) ;
486
+ transparent_outputs. push ( TxOut {
487
+ value : total_ephemeral_plus_fee,
488
+ script_pubkey : legacy_address. script ( ) ,
489
+ } ) ;
490
+ payment_pools. insert ( first_idx, PoolType :: Transparent ) ;
491
+ Some ( (
492
+ first_idx,
493
+ Payment {
494
+ recipient_address : Address :: Transparent ( legacy_address) ,
495
+ amount : total_ephemeral_plus_fee,
496
+ memo : None ,
497
+ label : None ,
498
+ message : None ,
499
+ // TODO: Add ZIP 320 ephemeral marker, or shift this
500
+ // into being a change output (which seems to need
501
+ // changes to the Proposal protobuf, as it doesn't
502
+ // explicitly store change, or have any way to mark
503
+ // the ephemeral output).
504
+ other_params : vec ! [ ] ,
505
+ } ,
506
+ ) )
507
+ } else if proposal1_excludes. contains ( & idx) {
508
+ None
509
+ } else {
510
+ Some ( ( * idx, payment. clone ( ) ) )
511
+ }
512
+ } )
513
+ . collect ( ) ,
514
+ )
515
+ . expect ( "correct by construction" ) ;
516
+ let proposal2_transaction_request =
517
+ TransactionRequest :: new ( proposal2_payments) . expect ( "correct by construction" ) ;
518
+
519
+ (
520
+ proposal1_transaction_request,
521
+ Some ( Zip320SecondStep {
522
+ transaction_request : proposal2_transaction_request,
523
+ payment_pools : proposal2_payment_pools,
524
+ // TODO: Update this depending on how the ephemeral output is represented.
525
+ prior_step_inputs : StepOutput :: new ( 0 , StepOutputIndex :: Payment ( first_idx) ) ,
526
+ balance : ( || todo ! ( ) ) ( ) ,
527
+ } ) ,
528
+ )
529
+ } ;
530
+
389
531
let mut shielded_inputs: Vec < ReceivedNote < DbT :: NoteRef , Note > > = vec ! [ ] ;
390
532
let mut prior_available = NonNegativeAmount :: ZERO ;
391
533
let mut amount_required = NonNegativeAmount :: ZERO ;
@@ -432,6 +574,16 @@ where
432
574
433
575
match balance {
434
576
Ok ( balance) => {
577
+ #[ cfg( feature = "transparent-inputs" ) ]
578
+ if let Some ( zip320_step) = second_step {
579
+ return Proposal :: multi_step (
580
+ ( * self . change_strategy . fee_rule ( ) ) . clone ( ) ,
581
+ target_height,
582
+ ( || todo ! ( ) ) ( ) ,
583
+ )
584
+ . map_err ( InputSelectorError :: Proposal ) ;
585
+ }
586
+
435
587
return Proposal :: single_step (
436
588
transaction_request,
437
589
payment_pools,
0 commit comments