Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'develop' into release/v1.10.0
Browse files Browse the repository at this point in the history
briancorbin committed Aug 11, 2022
2 parents 78585c8 + 6dede59 commit 7cedc22
Showing 6 changed files with 209 additions and 12 deletions.
21 changes: 12 additions & 9 deletions docs/v2/api-endpoints/build_unsigned_transaction.md
Original file line number Diff line number Diff line change
@@ -7,17 +7,20 @@ description: >-

## [Request](../../../full-service/src/json_rpc/v2/api/request.rs#L67-L74)

| Required Param | Purpose | Requirements |
| -------------- | ------------------------------------------- | -------------------------------- |
| `account_id` | The account on which to perform this action | Account must exist in the wallet |
| Required Param | Purpose | Requirements |
| :--- | :--- | :--- |
| `account_id` | The account on which to perform this action | Account must exist in the wallet |

| Optional Param | Purpose | Requirements|
| -------------------------- | -------------------------------- | ------------------------------ |
| Optional Param | Purpose | Requirements |
| :--- | :--- | :--- |
| `addresses_and_amounts` | An array of public addresses and [Amounts](../../../full-service/src/json_rpc/v2/models/amount.rs) as a tuple | addresses are b58-encoded public addresses |
| `recipient_public_address` | The recipient for this transaction | b58-encoded public address bytes |
| `amount`| The [Amount](../../../full-service/src/json_rpc/v2/models/amount.rs) to send in this transaction | |
| `fee_value` | The fee value to submit with this transaction | If not provided, uses `MINIMUM_FEE` of the appropriate token |
| `fee_token_id` | The fee token id | |
| `tombstone_block` | The block after which this transaction expires | If not provided, uses `cur_height` + 10 |
| `amount` | The [Amount](../../../full-service/src/json_rpc/v2/models/amount.rs) to send in this transaction | |
| `input_txo_ids` | Specific TXOs to use as inputs to this transaction | TXO IDs \(obtain from `get_txos_for_account`\) |
| `fee_value` | The fee value to submit with this transaction | If not provided, uses `MINIMUM_FEE` of the first outputs token_id, if available, or defaults to MOB |
| `fee_token_id` | The fee token_id to submit with this transaction | If not provided, uses token_id of first output, if available, or defaults to MOB |
| `tombstone_block` | The block after which this transaction expires | If not provided, uses `cur_height` + 10 |
| `max_spendable_value` | The maximum amount for an input TXO selected for this transaction | |

## [Response](../../../full-service/src/json_rpc/v2/api/response.rs#L52-L56)

3 changes: 3 additions & 0 deletions full-service/src/json_rpc/v2/api/request.rs
Original file line number Diff line number Diff line change
@@ -66,11 +66,14 @@ pub enum JsonCommandRequest {
},
build_unsigned_transaction {
account_id: String,
addresses_and_amounts: Option<Vec<(String, Amount)>>,
recipient_public_address: Option<String>,
amount: Option<Amount>,
input_txo_ids: Option<Vec<String>>,
fee_value: Option<String>,
fee_token_id: Option<String>,
tombstone_block: Option<String>,
max_spendable_value: Option<String>,
},
check_b58_type {
b58_code: String,
7 changes: 6 additions & 1 deletion full-service/src/json_rpc/v2/api/wallet.rs
Original file line number Diff line number Diff line change
@@ -198,18 +198,23 @@ where
fee_value,
fee_token_id,
tombstone_block,
addresses_and_amounts,
input_txo_ids,
max_spendable_value,
} => {
let mut addresses_and_amounts = Vec::new();
let mut addresses_and_amounts = addresses_and_amounts.unwrap_or_default();
if let (Some(address), Some(amount)) = (recipient_public_address, amount) {
addresses_and_amounts.push((address, amount));
}
let (unsigned_tx, fog_resolver) = service
.build_unsigned_transaction(
&account_id,
&addresses_and_amounts,
input_txo_ids.as_ref(),
fee_value,
fee_token_id,
tombstone_block,
max_spendable_value,
)
.map_err(format_error)?;
JsonCommandResponse::build_unsigned_transaction {
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright (c) 2020-2022 MobileCoin Inc.

//! End-to-end tests for the Full Service Wallet API.
#[cfg(test)]
mod e2e_transaction {
use crate::{
db::account::AccountID,
json_rpc::v2::api::test_utils::{dispatch, setup},
test_utils::{add_block_to_ledger_db, manually_sync_account, MOB},
unsigned_tx::UnsignedTx,
util::b58::b58_decode_public_address,
};

use mc_common::logger::{test_with_logger, Logger};
use mc_crypto_rand::rand_core::RngCore;
use mc_transaction_core::{ring_signature::KeyImage, tokens::Mob, Token};
use rand::{rngs::StdRng, SeedableRng};

#[test_with_logger]
fn test_build_unsigned_transaction(logger: Logger) {
let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]);
let (client, mut ledger_db, db_ctx, _network_state) = setup(&mut rng, logger.clone());
let wallet_db = db_ctx.get_db_instance(logger.clone());

// Create Account
let body = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "create_account",
"params": {
"name": "Alice Main Account",
},
});
let res = dispatch(&client, body, &logger);
assert_eq!(res.get("jsonrpc").unwrap(), "2.0");

let result = res.get("result").unwrap();
let account_obj = result.get("account").unwrap();
assert!(account_obj.get("id").is_some());
assert_eq!(account_obj.get("name").unwrap(), "Alice Main Account");
let account_id = account_obj.get("id").unwrap();
let main_address = account_obj.get("main_address").unwrap().as_str().unwrap();
let main_account_address = b58_decode_public_address(main_address).unwrap();

// add some funds to that account
add_block_to_ledger_db(
&mut ledger_db,
&vec![main_account_address],
100 * MOB,
&vec![KeyImage::from(rng.next_u64())],
&mut rng,
);
manually_sync_account(
&ledger_db,
&db_ctx.get_db_instance(logger.clone()),
&AccountID(account_id.as_str().unwrap().to_string()),
&logger,
);

// confirm that the regular account has the correct balance
let body = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "get_account_status",
"params": {
"account_id": account_id,
},
});
let res = dispatch(&client, body, &logger);
let result = res.get("result").unwrap();
let balance_per_token = result.get("balance_per_token").unwrap();
let balance_mob = balance_per_token.get(Mob::ID.to_string()).unwrap();
let unspent = balance_mob["unspent"].as_str().unwrap();
assert_eq!(unspent, "100000000000000");

// export view only import package
let body = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "create_view_only_account_import_request",
"params": {
"account_id": account_id,
},
});
let res = dispatch(&client, body, &logger);
assert_eq!(res.get("jsonrpc").unwrap(), "2.0");
let result = res.get("result").unwrap();
let request = result.get("json_rpc_request").unwrap();

let body = json!({
"jsonrpc": "2.0",
"id": 2,
"method": "remove_account",
"params": {
"account_id": account_id,
}
});
let res = dispatch(&client, body, &logger);
let result = res.get("result").unwrap();
assert_eq!(result["removed"].as_bool().unwrap(), true);

// import vo account
let body = json!(request);
let res = dispatch(&client, body, &logger);
let result = res.get("result").unwrap();
let account = result.get("account").unwrap();
let vo_account_id = account.get("id").unwrap();
assert_eq!(vo_account_id, account_id);

// sync the view only account
manually_sync_account(
&ledger_db,
&wallet_db,
&AccountID(vo_account_id.as_str().unwrap().to_string()),
&logger,
);

// confirm that the view only account has the correct balance
let body = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "get_account_status",
"params": {
"account_id": vo_account_id,
},
});
let res = dispatch(&client, body, &logger);
let result = res.get("result").unwrap();
let balance_per_token = result.get("balance_per_token").unwrap();
let balance_mob = balance_per_token.get(Mob::ID.to_string()).unwrap();
let unverified = balance_mob["unverified"].as_str().unwrap();
let unspent = balance_mob["unspent"].as_str().unwrap();
assert_eq!(unverified, "100000000000000");
assert_eq!(unspent, "0");

let account = result.get("account").unwrap();
let vo_account_id = account.get("id").unwrap();
assert_eq!(vo_account_id, account_id);

// test creating unsigned tx with recipient public address and amount
let body = json!({
"jsonrpc": "2.0",
"id": 2,
"method": "build_unsigned_transaction",
"params": {
"account_id": account_id,
"recipient_public_address": main_address,
"amount": { "value": "50000000000000", "token_id": "0"},
}
});
let res = dispatch(&client, body, &logger);
let result = res.get("result").unwrap();
let _tx: UnsignedTx =
serde_json::from_value(result.get("unsigned_tx").unwrap().clone()).unwrap();

// test creating unsigned tx with addresses_and_amounts
let body = json!({
"jsonrpc": "2.0",
"id": 2,
"method": "build_unsigned_transaction",
"params": {
"account_id": account_id,
"addresses_and_amounts": [[main_address, { "value": "50000000000000", "token_id": "0"}]]
}
});
let res = dispatch(&client, body, &logger);
let result = res.get("result").unwrap();
let _tx: UnsignedTx =
serde_json::from_value(result.get("unsigned_tx").unwrap().clone()).unwrap();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod build_and_submit;
mod build_then_submit;
mod build_unsigned;
mod large_transaction;
mod multiple_outlay;
17 changes: 15 additions & 2 deletions full-service/src/service/transaction.rs
Original file line number Diff line number Diff line change
@@ -147,13 +147,16 @@ impl From<mc_ledger_db::Error> for TransactionServiceError {
/// Trait defining the ways in which the wallet can interact with and manage
/// transactions.
pub trait TransactionService {
#[allow(clippy::too_many_arguments)]
fn build_unsigned_transaction(
&self,
account_id_hex: &str,
addresses_and_amounts: &[(String, AmountJSON)],
input_txo_ids: Option<&Vec<String>>,
fee_value: Option<String>,
fee_token_id: Option<String>,
tombstone_block: Option<String>,
max_spendable_value: Option<String>,
) -> Result<(UnsignedTx, FullServiceFogResolver), TransactionServiceError>;

/// Builds a transaction from the given account to the specified recipients.
@@ -202,9 +205,11 @@ where
&self,
account_id_hex: &str,
addresses_and_amounts: &[(String, AmountJSON)],
input_txo_ids: Option<&Vec<String>>,
fee_value: Option<String>,
fee_token_id: Option<String>,
tombstone_block: Option<String>,
max_spendable_value: Option<String>,
) -> Result<(UnsignedTx, FullServiceFogResolver), TransactionServiceError> {
validate_number_outputs(addresses_and_amounts.len() as u64)?;

@@ -228,7 +233,6 @@ where
let recipient = b58_decode_public_address(recipient_public_address)?;
let amount =
Amount::try_from(amount).map_err(TransactionServiceError::InvalidAmount)?;
// let token_id = TokenId::from(token_id.parse::<u64>()?);
builder.add_recipient(recipient, amount.value, amount.token_id)?;
default_fee_token_id = amount.token_id;
}
@@ -253,7 +257,16 @@ where

builder.set_block_version(self.get_network_block_version());

builder.select_txos(&conn, None)?;
if let Some(inputs) = input_txo_ids {
builder.set_txos(&conn, inputs)?;
} else {
let max_spendable = if let Some(msv) = max_spendable_value {
Some(msv.parse::<u64>()?)
} else {
None
};
builder.select_txos(&conn, max_spendable)?;
}

let unsigned_tx = builder.build_unsigned()?;
let fog_resolver = builder.get_fs_fog_resolver(&conn)?;

0 comments on commit 7cedc22

Please sign in to comment.