From 557e90b3e5d5aa4dc76232bdd62da226d9bed97c Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Sat, 25 Feb 2023 15:09:40 -0700 Subject: [PATCH 1/9] add proto api for scis to mobilecoind, tests for swap proposal still to come: incorporating scis into transactions i also tweaked mob tool because it seemed to hang when i pulled a new docker image, this tweak makes it display the output. --- mob | 6 +- mobilecoind/api/proto/mobilecoind_api.proto | 57 ++++ mobilecoind/src/payments.rs | 286 ++++++++++++++++++- mobilecoind/src/service.rs | 288 +++++++++++++++++++- 4 files changed, 624 insertions(+), 13 deletions(-) diff --git a/mob b/mob index 2e08750a55..6ae2bda471 100755 --- a/mob +++ b/mob @@ -394,11 +394,7 @@ if args.name: image_and_tag = get_image(conf='.mobconf', image=args.image, tag=args.tag) if not args.no_pull: pull_image_command = ["docker", "image", "pull", image_and_tag] - - if args.verbose: - maybe_run(pull_image_command) - else: - maybe_run_quiet(pull_image_command) + maybe_run(pull_image_command) docker_run.append(image_and_tag) diff --git a/mobilecoind/api/proto/mobilecoind_api.proto b/mobilecoind/api/proto/mobilecoind_api.proto index c70ce7f4b6..161c46f9e5 100644 --- a/mobilecoind/api/proto/mobilecoind_api.proto +++ b/mobilecoind/api/proto/mobilecoind_api.proto @@ -48,6 +48,9 @@ service MobilecoindAPI { rpc GenerateBurnRedemptionTx (GenerateBurnRedemptionTxRequest) returns (GenerateBurnRedemptionTxResponse) {} rpc SubmitTx (SubmitTxRequest) returns (SubmitTxResponse) {} + // Swaps + rpc GenerateSwap (GenerateSwapRequest) returns (GenerateSwapResponse) {} + // Databases rpc GetLedgerInfo (google.protobuf.Empty) returns (GetLedgerInfoResponse) {} rpc GetBlockInfo (GetBlockInfoRequest) returns (GetBlockInfoResponse) {} @@ -139,6 +142,16 @@ message UnspentTxOut { bytes monitor_id = 10; } +// Structure used to refer to an SCI that we want to add to a transaction. +// The structure has additional information -- if it's a partial fill SCI, we need to know the partial fill amount. +message SciForTx { + /// The signed input we want to add + external.SignedContingentInput sci = 1; + + /// If it's a partial fill SCI, the value we wish to fill it for + uint64 partial_fill_value = 2; +} + // Structure used to refer to a prepared transaction message TxProposal { // List of inputs being spent. @@ -490,7 +503,11 @@ message GenerateTxRequest { // Token id to use for the transaction. uint64 token_id = 7; + + // List of SCIs to be added to the transaction + repeated SciForTx scis = 8; } + message GenerateTxResponse { TxProposal tx_proposal = 1; } @@ -611,6 +628,46 @@ message GenerateBurnRedemptionTxResponse { TxProposal tx_proposal = 1; } +// Generate a simple swap proposal. The result is a signed contingent input +// which trades one currency for another and is suitable for use with the deqs. +// (This API is restrictive and doesn't let you build more complex SCIs.) +message GenerateSwapRequest { + // Monitor id sending the funds. + bytes sender_monitor_id = 1; + + // Subaddress to return change to. + uint64 change_subaddress = 2; + + // A specific input, whose value will be offered in full by the swap. + // + // You may need to conduct a self-spend first to find an input of exactly + // the correct value before using this API if none of your inputs match + // the offer you want to make. + UnspentTxOut input = 3; + + // The u64 value we are asking for in exchange for our input + uint64 ask_value = 4; + + // The token_id we are asking for in exchange for our input + uint64 ask_token_id = 5; + + // If set to true, the offer is "all or nothing", the entire ask must be supplied. + // Otherwise, it is a "partial-fill" SCI + bool all_or_nothing = 6; + + // The smallest u64 value that we will accept to conduct the swap. + // This can be set to avoid receiving "dust" amounts. + // This is ignored if all_or_nothing is true. + uint64 minimum_fill_value = 7; + + // Tombstone block (setting to 0 means this offer does not expire). + uint64 tombstone = 8; +} + +message GenerateSwapResponse { + external.SignedContingentInput sci = 1; +} + // Submits a transaction to the network. message SubmitTxRequest { TxProposal tx_proposal = 1; diff --git a/mobilecoind/src/payments.rs b/mobilecoind/src/payments.rs index fdcec50dbd..9aeecb0c12 100644 --- a/mobilecoind/src/payments.rs +++ b/mobilecoind/src/payments.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation +// Copyright (c) 2018-2023 The MobileCoin Foundation //! Construct and submit transactions to the validator network. @@ -18,8 +18,8 @@ use mc_crypto_ring_signature_signer::NoKeysRingSigner; use mc_fog_report_validation::FogPubkeyResolver; use mc_ledger_db::{Error as LedgerError, Ledger, LedgerDB}; use mc_transaction_builder::{ - EmptyMemoBuilder, InputCredentials, MemoBuilder, ReservedSubaddresses, TransactionBuilder, - TxOutContext, + EmptyMemoBuilder, InputCredentials, MemoBuilder, ReservedSubaddresses, + SignedContingentInputBuilder, TransactionBuilder, TxOutContext, }; use mc_transaction_core::{ constants::{MAX_INPUTS, MILLIMOB_TO_PICOMOB, RING_SIZE}, @@ -28,7 +28,7 @@ use mc_transaction_core::{ tx::{Tx, TxOut, TxOutMembershipProof}, Amount, FeeMap, TokenId, }; -use mc_transaction_extra::TxOutConfirmationNumber; +use mc_transaction_extra::{SignedContingentInput, TxOutConfirmationNumber}; use mc_util_uri::FogUri; use rand::Rng; use std::{ @@ -347,6 +347,89 @@ impl>, + ) -> Result { + let logger = self.logger.new(o!("sender_monitor_id" => sender_monitor_id.to_string(), "ask" => format!("{ask_amount:?}"))); + log::trace!(logger, "Building swap proposal..."); + + // Get sender monitor data. + let sender_monitor_data = self.mobilecoind_db.get_monitor_data(sender_monitor_id)?; + + // Get the subaddress. + let change_subaddress = sender_monitor_data + .account_key + .subaddress(change_subaddress_index); + + // Figure out the block version, fee and minimum fee map. + let (_fee, _fee_map, block_version) = + self.get_fee_info_and_block_version(last_block_infos, 0.into(), 0)?; + + // Get global index of the utxo + let global_index = self + .ledger_db + .get_tx_out_index_by_hash(&utxo.tx_out.hash())?; + log::trace!(logger, "Got global index: {}", global_index); + + // Get a ring of mixins and their proofs + let ring = self.get_rings(DEFAULT_RING_SIZE, 1, &[global_index])?; + // Convert to a ring of mixins and their indices + let ring: Vec<(TxOut, u64)> = ring[0] + .clone() + .into_iter() + .map(|(tx_out, _proof)| { + let index = self.ledger_db.get_tx_out_index_by_hash(&tx_out.hash())?; + Ok((tx_out, index)) + }) + .collect::>()?; + log::trace!(logger, "Got ring"); + + let mut required_outputs = Vec::default(); + let mut fractional_outputs = Vec::default(); + + // Add the ask either as a required or fractional output + if is_partial_fill { + fractional_outputs.push((ask_amount, change_subaddress)); + } else { + required_outputs.push((ask_amount, change_subaddress)); + } + + // Build and return the TxProposal object + let mut rng = rand::thread_rng(); + let sci = Self::build_sci( + utxo, + global_index, + ring, + block_version, + &sender_monitor_data.account_key, + change_subaddress_index, + None, // custom change_amount + &required_outputs, + &fractional_outputs, + min_fill_value, + opt_tombstone, + &self.fog_resolver_factory, + opt_memo_builder, + &mut rng, + &self.logger, + )?; + log::trace!(logger, "Sci constructed"); + + Ok(sci) + } + /// Create a TxProposal that attempts to merge multiple UTXOs into a single /// larger UTXO. /// @@ -1082,6 +1165,201 @@ impl, + block_version: BlockVersion, + from_account_key: &AccountKey, + change_subaddress_index: u64, + change_amount: Option, + required_outputs: &[(Amount, PublicAddress)], + fractional_outputs: &[(Amount, PublicAddress)], + min_fill_value: u64, + tombstone_block: BlockIndex, + fog_resolver_factory: &Arc Result + Send + Sync>, + opt_memo_builder: Option>, + rng: &mut (impl RngCore + CryptoRng), + _logger: &Logger, + ) -> Result { + // Check that we have at least one destination. + if required_outputs.is_empty() && fractional_outputs.is_empty() { + return Err(Error::TxBuild("Must have at least one destination".into())); + } + + // Collect all required FogUris from public addresses, then pass to resolver + // factory + let fog_resolver = { + let change_address = from_account_key.subaddress(change_subaddress_index); + let fog_uris = core::slice::from_ref(&change_address) + .iter() + .chain(required_outputs.iter().map(|x| &x.1)) + .chain(fractional_outputs.iter().map(|x| &x.1)) + .filter_map(|x| extract_fog_uri(x).transpose()) + .collect::, _>>()?; + fog_resolver_factory(&fog_uris).map_err(Error::Fog)? + }; + + let (mut ring, mut global_indices): (Vec, Vec) = ring.into_iter().unzip(); + + // Add the input to the ring. + let position_opt = ring.iter().position(|tx_out| *tx_out == utxo.tx_out); + let real_key_index = match position_opt { + Some(position) => { + // The input is already present in the ring. + // This could happen if ring elements are sampled randomly from the ledger. + position + } + None => { + // The input is not already in the ring. + if ring.is_empty() { + // Append the input and its proof of membership. + ring.push(utxo.tx_out.clone()); + global_indices.push(global_index); + } else { + // Replace the first element of the ring. + ring[0] = utxo.tx_out.clone(); + global_indices[0] = global_index; + } + // The real input is always the first element. This is safe because + // TransactionBuilder sorts each ring. + 0 + } + }; + + let membership_proofs: Vec = vec![Default::default(); ring.len()]; + + // Create input credentials + let input_credentials = { + assert_eq!( + ring.len(), + membership_proofs.len(), + "Each ring element must have a corresponding membership proof." + ); + + let public_key = RistrettoPublic::try_from(&utxo.tx_out.public_key).unwrap(); + let onetime_private_key = recover_onetime_private_key( + &public_key, + from_account_key.view_private_key(), + &from_account_key.subaddress_spend_private(utxo.subaddress_index), + ); + + InputCredentials::new( + ring, + membership_proofs, + real_key_index, + onetime_private_key, + *from_account_key.view_private_key(), + ) + .map_err(|_| Error::TxBuild("failed creating InputCredentials".into()))? + }; + + // Create sci_builder. + // TODO (GH #1522): Use RTH memo builder, optionally? + let memo_builder: Box = opt_memo_builder + .unwrap_or_else(|| Box::::default()); + + let mut sci_builder = SignedContingentInputBuilder::new_with_box( + block_version, + input_credentials, + fog_resolver, + memo_builder, + ) + .map_err(|err| { + Error::TxBuild(format!( + "Error creating signed contingent input builder: {err}" + )) + })?; + + // Add outputs to our destinations. + for (amount, recipient) in required_outputs.iter() { + sci_builder + .add_required_output(*amount, recipient, rng) + .map_err(|err| Error::TxBuild(format!("failed adding required output: {err}")))?; + } + for (amount, recipient) in fractional_outputs.iter() { + sci_builder + .add_partial_fill_output(*amount, recipient, rng) + .map_err(|err| Error::TxBuild(format!("failed adding fractional output: {err}")))?; + } + + // Figure out about the change output + if let Some(change_amount) = change_amount.as_ref() { + if change_amount.token_id != utxo.token_id { + return Err(Error::TxBuild("Incorrect change Token Id".to_string())); + } + if change_amount.value > utxo.value { + return Err(Error::InsufficientFunds); + } + } + + let change_dest = ReservedSubaddresses::from_subaddress_index( + from_account_key, + Some(change_subaddress_index), + None, + ); + + if fractional_outputs.is_empty() { + // When we have an all-or-nothing swap and no change value is given, + // we don't have to add a change output. (Maybe we should add a zero-value + // change?) If one is specified, add it. + if let Some(change_amount) = change_amount.as_ref() { + sci_builder + .add_required_change_output(*change_amount, &change_dest, rng) + .map_err(|err| { + Error::TxBuild(format!("failed adding output (change): {err}")) + })?; + } + } else { + // When we have a partial fill swap and no custom change value is given, + // add a fractional change output equal in value to the input. + let change_amount = + change_amount.unwrap_or_else(|| Amount::new(utxo.value, utxo.token_id.into())); + + sci_builder + .add_partial_fill_change_output(change_amount, &change_dest, rng) + .map_err(|err| Error::TxBuild(format!("failed adding output (change): {err}")))?; + + sci_builder.set_min_partial_fill_value(min_fill_value); + } + + // Set tombstone block. + if tombstone_block != 0 { + sci_builder.set_tombstone_block(tombstone_block); + } + + // Build sci. + Ok(sci_builder + .build(&NoKeysRingSigner {}, rng) + .map_err(|err| Error::TxBuild(format!("build tx failed: {err}")))?) + } } // Helper which extracts FogUri from PublicAddress or returns None, or returns diff --git a/mobilecoind/src/service.rs b/mobilecoind/src/service.rs index 2b08674800..6e87b199f7 100644 --- a/mobilecoind/src/service.rs +++ b/mobilecoind/src/service.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation +// Copyright (c) 2018-2023 The MobileCoin Foundation //! The mobilecoind Service //! * provides a GRPC server @@ -40,7 +40,7 @@ use mc_transaction_core::{ onetime_keys::recover_onetime_private_key, ring_signature::KeyImage, tx::{TxOut, TxOutMembershipProof}, - TokenId, + Amount, TokenId, }; use mc_transaction_extra::{BurnRedemptionMemo, TxOutConfirmationNumber}; use mc_util_from_random::FromRandom; @@ -908,7 +908,7 @@ impl Result { + // Get sender monitor id from request. + let sender_monitor_id = MonitorId::try_from(&request.sender_monitor_id) + .map_err(|err| rpc_internal_error("monitor_id.try_from.bytes", err, &self.logger))?; + + // Get monitor data for this monitor. + let sender_monitor_data = self + .mobilecoind_db + .get_monitor_data(&sender_monitor_id) + .map_err(|err| { + rpc_internal_error("mobilecoind_db.get_monitor_data", err, &self.logger) + })?; + + // Check that change_subaddress is covered by this monitor. + if !sender_monitor_data + .subaddress_indexes() + .contains(&request.change_subaddress) + { + return Err(RpcStatus::with_message( + RpcStatusCode::INVALID_ARGUMENT, + "change_subaddress".into(), + )); + } + + // Get the utxo we are signing for + let proto_utxo = request.input.as_ref().ok_or_else(|| { + RpcStatus::with_message(RpcStatusCode::INVALID_ARGUMENT, "input".into()) + })?; + + let utxo = UnspentTxOut::try_from(proto_utxo) + .map_err(|err| rpc_internal_error("unspent_tx_out.try_from", err, &self.logger))?; + + // Verify this output belongs to the monitor. + let subaddress_id = self + .mobilecoind_db + .get_subaddress_id_by_utxo_id(&UtxoId::from(&utxo)) + .map_err(|err| { + rpc_internal_error( + "mobilecoind_db.get_subaddress_id_by_utxo_id", + err, + &self.logger, + ) + })?; + + if subaddress_id.monitor_id != sender_monitor_id { + return Err(RpcStatus::with_message( + RpcStatusCode::INVALID_ARGUMENT, + format!("input.monitor_id"), + )); + } + + if request.ask_value == 0 { + return Err(RpcStatus::with_message( + RpcStatusCode::INVALID_ARGUMENT, + format!("ask_value"), + )); + } + + let ask_amount = Amount::new(request.ask_value, request.ask_token_id.into()); + + // Attempt to construct an sci + let sci = self + .transactions_manager + .build_swap_proposal( + &sender_monitor_id, + request.change_subaddress, + &utxo, + ask_amount, + !request.all_or_nothing, + request.minimum_fill_value, + &self.get_last_block_infos(), + request.tombstone, + None, // opt_memo_builder + ) + .map_err(|err| { + rpc_internal_error("transactions_manager.generate_swap", err, &self.logger) + })?; + + let mut response = api::GenerateSwapResponse::new(); + response.set_sci((&sci).into()); + + Ok(response) + } + fn submit_tx_impl( &mut self, request: api::SubmitTxRequest, @@ -2084,6 +2171,9 @@ build_api! { generate_burn_redemption_tx GenerateBurnRedemptionTxRequest GenerateBurnRedemptionTxResponse generate_burn_redemption_tx_impl, submit_tx SubmitTxRequest SubmitTxResponse submit_tx_impl, + // Signed contingent inputs + generate_swap GenerateSwapRequest GenerateSwapResponse generate_swap_impl, + // Databases get_ledger_info Empty GetLedgerInfoResponse get_ledger_info_impl, get_block_info GetBlockInfoRequest GetBlockInfoResponse get_block_info_impl, @@ -2143,7 +2233,7 @@ mod test { tx::{Tx, TxOut}, Amount, Token, }; - use mc_transaction_extra::MemoType; + use mc_transaction_extra::{MemoType, SignedContingentInput}; use mc_util_repr_bytes::{typenum::U32, GenericArray, ReprBytes}; use mc_util_uri::FogUri; use rand::{rngs::StdRng, SeedableRng}; @@ -3586,6 +3676,196 @@ mod test { } } + #[test_with_logger] + fn test_generate_swap(logger: Logger) { + let mut rng: StdRng = SeedableRng::from_seed([23u8; 32]); + + let sender = AccountKey::random(&mut rng); + let data = MonitorData::new( + sender.clone(), + 0, // first_subaddress + 20, // num_subaddresses + 0, // first_block + "", // name + ) + .unwrap(); + + // 1 known recipient, 3 random recipients and no monitors. + let (mut ledger_db, mobilecoind_db, client, _server, _server_conn_manager) = + get_testing_environment( + BLOCK_VERSION, + 3, + &[sender.default_subaddress()], + &[], + logger.clone(), + &mut rng, + ); + + // Add a block with a non-MOB token ID. + add_block_to_ledger( + &mut ledger_db, + BlockVersion::MAX, + &[ + AccountKey::random(&mut rng).default_subaddress(), + AccountKey::random(&mut rng).default_subaddress(), + AccountKey::random(&mut rng).default_subaddress(), + sender.default_subaddress(), + ], + Amount::new(1_000_000_000_000, TokenId::from(1)), + &[KeyImage::from(101)], + &mut rng, + ) + .unwrap(); + + // Insert into database. + let monitor_id = mobilecoind_db.add_monitor(&data).unwrap(); + + // Allow the new monitor to process the ledger. + wait_for_monitors(&mobilecoind_db, &ledger_db, &logger); + + // Get list of unspent tx outs + let utxos = mobilecoind_db + .get_utxos_for_subaddress(&monitor_id, 0) + .unwrap(); + assert!(!utxos.is_empty()); + + // Call generate swap. + let mut request = api::GenerateSwapRequest::new(); + request.set_sender_monitor_id(monitor_id.to_vec()); + request.set_change_subaddress(0); + request.set_input( + utxos + .iter() + .filter(|utxo| utxo.token_id == *Mob::ID) + .map(api::UnspentTxOut::from) + .next() + .unwrap(), + ); + request.set_ask_value(123); + request.set_ask_token_id(1); + request.set_minimum_fill_value(10); + + // Test the happy flow for MOB -> eUSD, partial fill swap + { + let response = client.generate_swap(&request).unwrap(); + + // Sanity test the response. + let sci = response.get_sci(); + + assert_eq!(sci.tx_out_global_indices.len(), 11); + assert_eq!(sci.required_output_amounts.len(), 0); + + let tx_in = sci.get_tx_in(); + assert_eq!(tx_in.ring.len(), 11); + + let rules = tx_in.get_input_rules(); + assert_eq!(rules.required_outputs.len(), 0); + assert_eq!(rules.partial_fill_outputs.len(), 1); + assert!(rules.partial_fill_change.as_ref().is_some()); + assert_eq!(rules.max_tombstone_block, 0); + assert_eq!(rules.min_partial_fill_value, 10); + + let sci = SignedContingentInput::try_from(sci).unwrap(); + + sci.validate().unwrap(); + + let (amount, _scalar) = sci.tx_in.input_rules.as_ref().unwrap().partial_fill_outputs[0] + .reveal_amount() + .unwrap(); + assert_eq!(amount.value, 123); + assert_eq!(amount.token_id, TokenId::from(1)); + } + + // Test the happy flow for eUSD -> MOB, non partial fill swap + { + let mut request = api::GenerateSwapRequest::new(); + request.set_sender_monitor_id(monitor_id.to_vec()); + request.set_change_subaddress(0); + request.set_input( + utxos + .iter() + .filter(|utxo| utxo.token_id == TokenId::from(1)) + .map(api::UnspentTxOut::from) + .next() + .unwrap(), + ); + request.set_ask_value(999_999); + request.set_ask_token_id(0); + request.set_all_or_nothing(true); + request.set_tombstone(1000); + + let response = client.generate_swap(&request).unwrap(); + + // Sanity test the response. + let sci = response.get_sci(); + assert_eq!(sci.tx_out_global_indices.len(), 11); + assert_eq!(sci.required_output_amounts.len(), 1); + + let tx_in = sci.get_tx_in(); + assert_eq!(tx_in.ring.len(), 11); + + let rules = tx_in.get_input_rules(); + assert_eq!(rules.required_outputs.len(), 1); + assert_eq!(rules.partial_fill_outputs.len(), 0); + assert!(rules.partial_fill_change.as_ref().is_none()); + assert_eq!(rules.max_tombstone_block, 1000); + assert_eq!(rules.min_partial_fill_value, 0); + + let sci = SignedContingentInput::try_from(sci).unwrap(); + + sci.validate().unwrap(); + + let unmasked_amount = sci.required_output_amounts[0].clone(); + assert_eq!(unmasked_amount.value, 999_999); + assert_eq!(unmasked_amount.token_id, *Mob::ID); + } + + // Invalid input scenarios should result in an error. + { + // No monitor id + let mut request = request.clone(); + request.set_sender_monitor_id(vec![]); + assert!(client.generate_swap(&request).is_err()); + } + + { + // Unrecognized monitor id + let sender = AccountKey::random(&mut rng); + let data = MonitorData::new( + sender, 0, // first_subaddress + 20, // num_subaddresses + 0, // first_block + "", // name + ) + .unwrap(); + + let mut request = request.clone(); + request.set_sender_monitor_id(MonitorId::from(&data).to_vec()); + assert!(client.generate_swap(&request).is_err()); + } + + { + // Subaddress index out of range + let mut request = request.clone(); + request.set_change_subaddress(data.first_subaddress + data.num_subaddresses + 1); + assert!(client.generate_swap(&request).is_err()); + } + + { + // Junk input + let mut request = request.clone(); + request.set_input(api::UnspentTxOut::default()); + assert!(client.generate_swap(&request).is_err()); + } + + { + // Ask value of zero is an error + let mut request = request.clone(); + request.set_ask_value(0); + assert!(client.generate_swap(&request).is_err()); + } + } + #[test_with_logger] fn test_generate_tx(logger: Logger) { let mut rng: StdRng = SeedableRng::from_seed([23u8; 32]); From f806c8238a3aab950e9273b1a185673bc45f7012 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Wed, 8 Mar 2023 20:59:25 -0700 Subject: [PATCH 2/9] clippy --- mobilecoind/src/payments.rs | 6 +++--- mobilecoind/src/service.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mobilecoind/src/payments.rs b/mobilecoind/src/payments.rs index 9aeecb0c12..b99e6c746d 100644 --- a/mobilecoind/src/payments.rs +++ b/mobilecoind/src/payments.rs @@ -1284,7 +1284,7 @@ impl = opt_memo_builder - .unwrap_or_else(|| Box::::default()); + .unwrap_or_else(Box::::default); let mut sci_builder = SignedContingentInputBuilder::new_with_box( block_version, @@ -1356,9 +1356,9 @@ impl Date: Thu, 9 Mar 2023 12:48:34 -0700 Subject: [PATCH 3/9] fixup --- mobilecoind/api/proto/mobilecoind_api.proto | 13 ------------- mobilecoind/src/payments.rs | 5 ++++- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/mobilecoind/api/proto/mobilecoind_api.proto b/mobilecoind/api/proto/mobilecoind_api.proto index 161c46f9e5..4bdfcc1d21 100644 --- a/mobilecoind/api/proto/mobilecoind_api.proto +++ b/mobilecoind/api/proto/mobilecoind_api.proto @@ -142,16 +142,6 @@ message UnspentTxOut { bytes monitor_id = 10; } -// Structure used to refer to an SCI that we want to add to a transaction. -// The structure has additional information -- if it's a partial fill SCI, we need to know the partial fill amount. -message SciForTx { - /// The signed input we want to add - external.SignedContingentInput sci = 1; - - /// If it's a partial fill SCI, the value we wish to fill it for - uint64 partial_fill_value = 2; -} - // Structure used to refer to a prepared transaction message TxProposal { // List of inputs being spent. @@ -503,9 +493,6 @@ message GenerateTxRequest { // Token id to use for the transaction. uint64 token_id = 7; - - // List of SCIs to be added to the transaction - repeated SciForTx scis = 8; } message GenerateTxResponse { diff --git a/mobilecoind/src/payments.rs b/mobilecoind/src/payments.rs index b99e6c746d..20cd7ee959 100644 --- a/mobilecoind/src/payments.rs +++ b/mobilecoind/src/payments.rs @@ -1283,8 +1283,11 @@ impl = opt_memo_builder - .unwrap_or_else(Box::::default); + .unwrap_or_else(|| Box::::default()); let mut sci_builder = SignedContingentInputBuilder::new_with_box( block_version, From 01b524018a5cf8daefdf487a392fcc92c1085273 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Thu, 9 Mar 2023 16:34:19 -0700 Subject: [PATCH 4/9] review comments --- mobilecoind/src/payments.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mobilecoind/src/payments.rs b/mobilecoind/src/payments.rs index 20cd7ee959..edaabe7a49 100644 --- a/mobilecoind/src/payments.rs +++ b/mobilecoind/src/payments.rs @@ -1013,6 +1013,7 @@ impl { // The input is not already in the ring. if ring.is_empty() { + // The ring is probably not empty, but ring[0] will panic if it is. // Append the input and its proof of membership. ring.push(utxo.tx_out.clone()); membership_proofs.push(proof.clone()); @@ -1033,7 +1034,7 @@ impl { // The input is not already in the ring. if ring.is_empty() { + // The ring is probably not empty, but ring[0] will panic if it is. // Append the input and its proof of membership. ring.push(utxo.tx_out.clone()); global_indices.push(global_index); @@ -1264,7 +1266,7 @@ impl Date: Thu, 9 Mar 2023 16:34:42 -0700 Subject: [PATCH 5/9] Update mobilecoind/src/payments.rs --- mobilecoind/src/payments.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobilecoind/src/payments.rs b/mobilecoind/src/payments.rs index edaabe7a49..123536cecb 100644 --- a/mobilecoind/src/payments.rs +++ b/mobilecoind/src/payments.rs @@ -1315,7 +1315,7 @@ impl Date: Thu, 9 Mar 2023 17:07:49 -0700 Subject: [PATCH 6/9] fix naming of things, polish --- mobilecoind/api/proto/mobilecoind_api.proto | 26 ++++++++++++++------- mobilecoind/src/payments.rs | 25 ++++++++++++++++---- mobilecoind/src/service.rs | 25 ++++++++++---------- 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/mobilecoind/api/proto/mobilecoind_api.proto b/mobilecoind/api/proto/mobilecoind_api.proto index 4bdfcc1d21..c7fa175799 100644 --- a/mobilecoind/api/proto/mobilecoind_api.proto +++ b/mobilecoind/api/proto/mobilecoind_api.proto @@ -627,24 +627,32 @@ message GenerateSwapRequest { // A specific input, whose value will be offered in full by the swap. // - // You may need to conduct a self-spend first to find an input of exactly + // This becomes the "base token" amount from the point of view of a quoting service. + // + // You may need to conduct a self-spend first to make an input of exactly // the correct value before using this API if none of your inputs match - // the offer you want to make. + // the volume of the quote you want to make. UnspentTxOut input = 3; // The u64 value we are asking for in exchange for our input - uint64 ask_value = 4; + // + // This becomes the "counter token" value from the point of view of a quoting service. + uint64 counter_value = 4; // The token_id we are asking for in exchange for our input - uint64 ask_token_id = 5; + // + // This becomes the "counter token" token id from the point of view of a quoting service. + uint64 counter_token_id = 5; - // If set to true, the offer is "all or nothing", the entire ask must be supplied. - // Otherwise, it is a "partial-fill" SCI - bool all_or_nothing = 6; + // If set to false, the offer is "all or nothing", the entire counter token value must be supplied, + // in exchange for the entire value of the input we are signing over. + // Otherwise, it is a "partial-fill" SCI, and can be filled at less than the maximum volume for + // proportionally more of the input value. + bool allow_partial_fill = 6; // The smallest u64 value that we will accept to conduct the swap. - // This can be set to avoid receiving "dust" amounts. - // This is ignored if all_or_nothing is true. + // This can be set to avoid receiving "dust" amounts when allow_partial_fill is true. + // This is ignored if allow_partial_fill is false. uint64 minimum_fill_value = 7; // Tombstone block (setting to 0 means this offer does not expire). diff --git a/mobilecoind/src/payments.rs b/mobilecoind/src/payments.rs index 123536cecb..5d041bb1b3 100644 --- a/mobilecoind/src/payments.rs +++ b/mobilecoind/src/payments.rs @@ -350,19 +350,36 @@ impl>, ) -> Result { - let logger = self.logger.new(o!("sender_monitor_id" => sender_monitor_id.to_string(), "ask" => format!("{ask_amount:?}"))); + let logger = self.logger.new(o!("sender_monitor_id" => sender_monitor_id.to_string(), "counter_amount" => format!("{counter_amount:?}"))); log::trace!(logger, "Building swap proposal..."); // Get sender monitor data. @@ -401,9 +418,9 @@ impl eUSD, partial fill swap @@ -3789,9 +3790,9 @@ mod test { .next() .unwrap(), ); - request.set_ask_value(999_999); - request.set_ask_token_id(0); - request.set_all_or_nothing(true); + request.set_counter_value(999_999); + request.set_counter_token_id(0); + request.set_allow_partial_fill(false); request.set_tombstone(1000); let response = client.generate_swap(&request).unwrap(); @@ -3859,9 +3860,9 @@ mod test { } { - // Ask value of zero is an error + // Counter value of zero is an error let mut request = request.clone(); - request.set_ask_value(0); + request.set_counter_value(0); assert!(client.generate_swap(&request).is_err()); } } From 82c63253b96819b2e3bddc3a1056aedb41a91514 Mon Sep 17 00:00:00 2001 From: Chris Beck <5683852+cbeck88@users.noreply.github.com> Date: Fri, 10 Mar 2023 14:35:51 -0700 Subject: [PATCH 7/9] Update mobilecoind/src/payments.rs Co-authored-by: wjuan-mob --- mobilecoind/src/payments.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobilecoind/src/payments.rs b/mobilecoind/src/payments.rs index 5d041bb1b3..f5ecd1cd08 100644 --- a/mobilecoind/src/payments.rs +++ b/mobilecoind/src/payments.rs @@ -352,7 +352,7 @@ impl Date: Fri, 10 Mar 2023 14:36:10 -0700 Subject: [PATCH 8/9] Update mobilecoind/src/payments.rs Co-authored-by: Eran Rundstein --- mobilecoind/src/payments.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobilecoind/src/payments.rs b/mobilecoind/src/payments.rs index f5ecd1cd08..a24c86035b 100644 --- a/mobilecoind/src/payments.rs +++ b/mobilecoind/src/payments.rs @@ -1187,7 +1187,7 @@ impl Date: Fri, 10 Mar 2023 15:09:41 -0700 Subject: [PATCH 9/9] update documentation --- mobilecoind/src/payments.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mobilecoind/src/payments.rs b/mobilecoind/src/payments.rs index a24c86035b..773ecfd802 100644 --- a/mobilecoind/src/payments.rs +++ b/mobilecoind/src/payments.rs @@ -352,9 +352,8 @@ impl