From 3a3057defb7780b26da8af63209580dd1cd0b117 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Wed, 1 May 2019 20:12:23 +0100 Subject: [PATCH] [WIP] Invoiced Transactions API Support (#90) * basic invoiced tx working * rustfmt * teardown * rustfmt * rename, new struct for invoice args, begin to add new functions to RPC apis * rustfmt * add fns to rpc api * rustfmt * owner api functions RPC documentation in place * rustfmt * doctests for new invoicing functions * rustfmt * test fixes * update documentation and doctests * rustfmt * invoice testing verification of tx log output * rustfmt --- api/src/foreign.rs | 59 ++++++- api/src/foreign_rpc.rs | 238 ++++++++++++++++++++++++-- api/src/owner.rs | 124 ++++++++++++-- api/src/owner_rpc.rs | 252 +++++++++++++++++++++++++++- api/tests/slate_versioning.rs | 4 +- controller/src/command.rs | 4 +- controller/src/controller.rs | 2 +- controller/tests/accounts.rs | 2 +- controller/tests/check.rs | 2 +- controller/tests/file.rs | 2 +- controller/tests/invoice.rs | 187 +++++++++++++++++++++ controller/tests/repost.rs | 4 +- controller/tests/restore.rs | 8 +- controller/tests/self_send.rs | 2 +- controller/tests/transaction.rs | 14 +- impls/src/test_framework/mod.rs | 2 +- libwallet/src/api_impl/foreign.rs | 21 +++ libwallet/src/api_impl/owner.rs | 135 ++++++++++++++- libwallet/src/api_impl/types.rs | 29 ++++ libwallet/src/internal/selection.rs | 5 +- libwallet/src/internal/tx.rs | 45 +++-- libwallet/src/lib.rs | 4 +- libwallet/src/slate.rs | 7 +- 23 files changed, 1072 insertions(+), 80 deletions(-) create mode 100644 controller/tests/invoice.rs diff --git a/api/src/foreign.rs b/api/src/foreign.rs index 56b63157..817eb069 100644 --- a/api/src/foreign.rs +++ b/api/src/foreign.rs @@ -311,6 +311,61 @@ where w.close()?; res } + + /// Finalizes an invoice transaction initiated by this wallet's Owner api. + /// This step assumes the paying party has completed round 1 and 2 of slate + /// creation, and added their partial signatures. The invoicer will verify + /// and add their partial sig, then create the finalized transaction, + /// ready to post to a node. + /// + /// Note that this function DOES NOT POST the transaction to a node + /// for validation. This is done in separately via the + /// [`post_tx`](struct.Owner.html#method.post_tx) function. + /// + /// This function also stores the final transaction in the user's wallet files for retrieval + /// via the [`get_stored_tx`](struct.Owner.html#method.get_stored_tx) function. + /// + /// # Arguments + /// * `slate` - The transaction [`Slate`](../grin_wallet_libwallet/slate/struct.Slate.html). The + /// payer should have filled in round 1 and 2. + /// + /// # Returns + /// * Ok([`slate`](../grin_wallet_libwallet/slate/struct.Slate.html)) if successful, + /// containing the new finalized slate. + /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. + /// + /// # Example + /// Set up as in [`new`](struct.Owner.html#method.new) method above. + /// ``` + /// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config); + /// + /// let mut api_owner = Owner::new(wallet.clone()); + /// let mut api_foreign = Foreign::new(wallet.clone()); + /// + /// // . . . + /// // Issue the invoice tx via the owner API + /// let args = IssueInvoiceTxArgs { + /// amount: 10_000_000_000, + /// ..Default::default() + /// }; + /// let result = api_owner.issue_invoice_tx(args); + /// + /// // If result okay, send to payer, who will apply the transaction via their + /// // owner API, then send back the slate + /// // ... + /// # let slate = Slate::blank(2); + /// + /// let slate = api_foreign.finalize_invoice_tx(&slate); + /// // if okay, then post via the owner API + /// ``` + + pub fn finalize_invoice_tx(&self, slate: &Slate) -> Result { + let mut w = self.wallet.lock(); + w.open_with_credentials()?; + let res = foreign::finalize_invoice_tx(&mut *w, slate); + w.close()?; + res + } } #[doc(hidden)] @@ -330,10 +385,10 @@ macro_rules! doctest_helper_setup_doc_env_foreign { use std::sync::Arc; use util::Mutex; - use api::Foreign; + use api::{Foreign, Owner}; use config::WalletConfig; use impls::{HTTPNodeClient, LMDBBackend, WalletSeed}; - use libwallet::{BlockFees, Slate, WalletBackend}; + use libwallet::{BlockFees, IssueInvoiceTxArgs, Slate, WalletBackend}; let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap(); let dir = dir diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index 62b758e5..634dfcab 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -16,8 +16,8 @@ use crate::keychain::Keychain; use crate::libwallet::{ - BlockFees, CbData, ErrorKind, InitTxArgs, NodeClient, Slate, VersionInfo, VersionedSlate, - WalletBackend, + BlockFees, CbData, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, Slate, VersionInfo, + VersionedSlate, WalletBackend, }; use crate::Foreign; use easy_jsonrpc; @@ -56,9 +56,8 @@ pub trait ForeignRpc { } } # "# - # , 0, false); + # , 0, false, false); ``` - */ fn check_version(&self) -> Result; @@ -107,9 +106,9 @@ pub trait ForeignRpc { } } # "# - # , 4, false); + # , 4, false, false); ``` - */ + */ fn build_coinbase(&self, block_fees: &BlockFees) -> Result; /** @@ -187,9 +186,9 @@ pub trait ForeignRpc { } } # "# - # ,1 ,false); + # ,1 ,false, false); ``` - */ + */ fn verify_slate_messages(&self, slate: &Slate) -> Result<(), ErrorKind>; /** @@ -340,15 +339,178 @@ pub trait ForeignRpc { } } # "# - # , 5, true); + # , 5, true, false); ``` - */ + */ fn receive_tx( &self, slate: VersionedSlate, dest_acct_name: Option, message: Option, ) -> Result; + + /** + + Networked version of [Foreign::finalize_invoice_tx](struct.Foreign.html#method.finalize_invoice_tx). + + # Json rpc example + + ``` + # grin_wallet_api::doctest_helper_json_rpc_foreign_assert_response!( + # r#" + { + "jsonrpc": "2.0", + "method": "finalize_invoice_tx", + "id": 1, + "params": [{ + "version_info": { + "version": 2, + "orig_version": 2, + "min_compat_version": 0 + }, + "num_participants": 2, + "id": "0436430c-2b02-624c-2032-570501212b00", + "tx": { + "offset": "d202964900000000d302964900000000d402964900000000d502964900000000", + "body": { + "inputs": [ + { + "features": "Coinbase", + "commit": "087df32304c5d4ae8b2af0bc31e700019d722910ef87dd4eec3197b80b207e3045" + }, + { + "features": "Coinbase", + "commit": "08e1da9e6dc4d6e808a718b2f110a991dd775d65ce5ae408a4e1f002a4961aa9e7" + } + ], + "outputs": [ + { + "features": "Plain", + "commit": "099b48cfb1f80a2347dc89818449e68e76a3c6817a532a8e9ef2b4a5ccf4363850", + "proof": "7ebcd2ed9bf5fb29854033ba3d0e720613bdf7dfacc586d2f6084c1cde0a2b72e955d4ce625916701dc7c347132f40d0f102a34e801d745ee54b49b765d08aae0bb801c60403e57cafade3b4b174e795b633ab9e402b5b1b6e1243fd10bbcf9368a75cb6a6c375c7bdf02da9e03b7f210df45d942e6fba2729cd512a372e6ed91a1b5c9c22831febea843e3f85adcf198f39ac9f7b73b70c60bfb474aa69878ea8d1d32fef30166b59caacaec3fd024de29a90f1587e08d2c36b3d5c560cabf658e212e0a40a4129b3e5c35557058def5551f4eb395759597ba808b3c34eac3bfb9716e4480d7931c5789c538463ec75be0eb807c894047fda6cbcd22682d3c6d3823cb330f090a2099e3510a3706b57d46c95224394d7f1c0a20d99cc314b8f1d9d02668e2e435f62e1194de0be6a1f50f72ed777ed51c8819f527a94918d1aa8df6461e98ed4c2b18210de50fbcf8c3df210bfe326d41f1dc0ad748cb0320ae28401c85ab4f7dcb99d88a052e95dc85b76d22b36cabd60e06ab84bb7e4ddfdab9c9730c8a986583237ed1ecbb323ee8e79b8cadca4b438b7c09531670b471dda6a2eb3e747916c88ce7d9d8e1b7f61660eeb9e5a13c60e4dfe89d1177d81d6f6570fda85158e646a15f1e8b9e977494dc19a339aab2e0e478670d80092d6ba37646e60714ef64eb4a3d37fe15f8f38b59114af34b235489eed3f69b7781c5fe496eb43ffe245c14bd740f745844a38cf0d904347aaa2b64f51add18822dac009d8b63fa3e4c9b1fa72187f9a4acba1ab315daa1b04c9a41f3be846ac420b37990e6c947a16cc9d5c0671b292bf77d7d8b8974d2ad3afae95ba7772c37432840f53a007f31e0195f3abdf100c4477723cc6c6d5da14894a73dfac342833731036487488fdade7b9d556c06f26173b6b67598d3769447ce2828d71dd45ac5af436c6b0" + }, + { + "features": "Plain", + "commit": "0812276cc788e6870612296d926cba9f0e7b9810670710b5a6e6f1ba006d395774", + "proof": "dcff6175390c602bfa92c2ffd1a9b2d84dcc9ea941f6f317bdd0f875244ef23e696fd17c71df79760ce5ce1a96aab1d15dd057358dc835e972febeb86d50ccec0dad7cfe0246d742eb753cf7b88c045d15bc7123f8cf7155647ccf663fca92a83c9a65d0ed756ea7ebffd2cac90c380a102ed9caaa355d175ed0bf58d3ac2f5e909d6c447dfc6b605e04925c2b17c33ebd1908c965a5541ea5d2ed45a0958e6402f89d7a56df1992e036d836e74017e73ccad5cb3a82b8e139e309792a31b15f3ffd72ed033253428c156c2b9799458a25c1da65b719780a22de7fe7f437ae2fccd22cf7ea357ab5aa66a5ef7d71fb0dc64aa0b5761f68278062bb39bb296c787e4cabc5e2a2933a416ce1c9a9696160386449c437e9120f7bb26e5b0e74d1f2e7d5bcd7aafb2a92b87d1548f1f911fb06af7bd6cc13cee29f7c9cb79021aed18186272af0e9d189ec107c81a8a3aeb4782b0d950e4881aa51b776bb6844b25bce97035b48a9bdb2aea3608687bcdd479d4fa998b5a839ff88558e4a29dff0ed13b55900abb5d439b70793d902ae9ad34587b18c919f6b875c91d14deeb1c373f5e76570d59a6549758f655f1128a54f162dfe8868e1587028e26ad91e528c5ae7ee9335fa58fb59022b5de29d80f0764a9917390d46db899acc6a5b416e25ecc9dccb7153646addcc81cadb5f0078febc7e05d7735aba494f39ef05697bbcc9b47b2ccc79595d75fc13c80678b5e237edce58d731f34c05b1ddcaa649acf2d865bbbc3ceda10508bcdd29d0496744644bf1c3516f6687dfeef5649c7dff90627d642739a59d91a8d1d0c4dc55d74a949e1074427664b467992c9e0f7d3af9d6ea79513e8946ddc0d356bac49878e64e6a95b0a30214214faf2ce317fa622ff3266b32a816e10a18e6d789a5da1f23e67b4f970a68a7bcd9e18825ee274b0483896a40" + } + ], + "kernels": [ + { + "features": "Plain", + "fee": "7000000", + "lock_height": "0", + "excess": "000000000000000000000000000000000000000000000000000000000000000000", + "excess_sig": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + ] + } + }, + "amount": "60000000000", + "fee": "7000000", + "height": "5", + "lock_height": "0", + "participant_data": [ + { + "id": "1", + "public_blind_excess": "033bbe2a419ea2e9d6810a8d66552e709d1783ca50759a44dbaf63fc79c0164c4c", + "public_nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "part_sig": null, + "message": null, + "message_sig": null + }, + { + "id": "0", + "public_blind_excess": "029f12f9f8c5489a18904de7cd46dc3384b79369d4cbc17cd74b299da8c2cf7445", + "public_nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "part_sig": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fe3cccc5ff1d832edb7e3cb3b1414b9e9d3d1d503294224b4c93bf3d59ed64018", + "message": null, + "message_sig": null + } + ] + }] + } + # "# + # , + # r#" + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "Ok": { + "amount": "60000000000", + "fee": "7000000", + "height": "5", + "id": "0436430c-2b02-624c-2032-570501212b00", + "lock_height": "0", + "num_participants": 2, + "participant_data": [ + { + "id": "1", + "message": null, + "message_sig": null, + "part_sig": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fc0e6f263f91010a6fea7099029c70eabdf75b48aefd977e14d1ed659b221eac9", + "public_blind_excess": "033bbe2a419ea2e9d6810a8d66552e709d1783ca50759a44dbaf63fc79c0164c4c", + "public_nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" + }, + { + "id": "0", + "message": null, + "message_sig": null, + "part_sig": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fe3cccc5ff1d832edb7e3cb3b1414b9e9d3d1d503294224b4c93bf3d59ed64018", + "public_blind_excess": "029f12f9f8c5489a18904de7cd46dc3384b79369d4cbc17cd74b299da8c2cf7445", + "public_nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" + } + ], + "tx": { + "body": { + "inputs": [ + { + "commit": "087df32304c5d4ae8b2af0bc31e700019d722910ef87dd4eec3197b80b207e3045", + "features": "Coinbase" + }, + { + "commit": "08e1da9e6dc4d6e808a718b2f110a991dd775d65ce5ae408a4e1f002a4961aa9e7", + "features": "Coinbase" + } + ], + "kernels": [ + { + "excess": "09bac6083b05a32a9d9b37710c70dd0a1ef9329fde0848558976b6f1b81d80ceed", + "excess_sig": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766a4b3bec3eae84394b68ad4cb3ddbc896f898aca769d2fc5a56886ba280c1e9a0", + "features": "Plain", + "fee": "7000000", + "lock_height": "0" + } + ], + "outputs": [ + { + "commit": "099b48cfb1f80a2347dc89818449e68e76a3c6817a532a8e9ef2b4a5ccf4363850", + "features": "Plain", + "proof": "7ebcd2ed9bf5fb29854033ba3d0e720613bdf7dfacc586d2f6084c1cde0a2b72e955d4ce625916701dc7c347132f40d0f102a34e801d745ee54b49b765d08aae0bb801c60403e57cafade3b4b174e795b633ab9e402b5b1b6e1243fd10bbcf9368a75cb6a6c375c7bdf02da9e03b7f210df45d942e6fba2729cd512a372e6ed91a1b5c9c22831febea843e3f85adcf198f39ac9f7b73b70c60bfb474aa69878ea8d1d32fef30166b59caacaec3fd024de29a90f1587e08d2c36b3d5c560cabf658e212e0a40a4129b3e5c35557058def5551f4eb395759597ba808b3c34eac3bfb9716e4480d7931c5789c538463ec75be0eb807c894047fda6cbcd22682d3c6d3823cb330f090a2099e3510a3706b57d46c95224394d7f1c0a20d99cc314b8f1d9d02668e2e435f62e1194de0be6a1f50f72ed777ed51c8819f527a94918d1aa8df6461e98ed4c2b18210de50fbcf8c3df210bfe326d41f1dc0ad748cb0320ae28401c85ab4f7dcb99d88a052e95dc85b76d22b36cabd60e06ab84bb7e4ddfdab9c9730c8a986583237ed1ecbb323ee8e79b8cadca4b438b7c09531670b471dda6a2eb3e747916c88ce7d9d8e1b7f61660eeb9e5a13c60e4dfe89d1177d81d6f6570fda85158e646a15f1e8b9e977494dc19a339aab2e0e478670d80092d6ba37646e60714ef64eb4a3d37fe15f8f38b59114af34b235489eed3f69b7781c5fe496eb43ffe245c14bd740f745844a38cf0d904347aaa2b64f51add18822dac009d8b63fa3e4c9b1fa72187f9a4acba1ab315daa1b04c9a41f3be846ac420b37990e6c947a16cc9d5c0671b292bf77d7d8b8974d2ad3afae95ba7772c37432840f53a007f31e0195f3abdf100c4477723cc6c6d5da14894a73dfac342833731036487488fdade7b9d556c06f26173b6b67598d3769447ce2828d71dd45ac5af436c6b0" + }, + { + "commit": "0812276cc788e6870612296d926cba9f0e7b9810670710b5a6e6f1ba006d395774", + "features": "Plain", + "proof": "dcff6175390c602bfa92c2ffd1a9b2d84dcc9ea941f6f317bdd0f875244ef23e696fd17c71df79760ce5ce1a96aab1d15dd057358dc835e972febeb86d50ccec0dad7cfe0246d742eb753cf7b88c045d15bc7123f8cf7155647ccf663fca92a83c9a65d0ed756ea7ebffd2cac90c380a102ed9caaa355d175ed0bf58d3ac2f5e909d6c447dfc6b605e04925c2b17c33ebd1908c965a5541ea5d2ed45a0958e6402f89d7a56df1992e036d836e74017e73ccad5cb3a82b8e139e309792a31b15f3ffd72ed033253428c156c2b9799458a25c1da65b719780a22de7fe7f437ae2fccd22cf7ea357ab5aa66a5ef7d71fb0dc64aa0b5761f68278062bb39bb296c787e4cabc5e2a2933a416ce1c9a9696160386449c437e9120f7bb26e5b0e74d1f2e7d5bcd7aafb2a92b87d1548f1f911fb06af7bd6cc13cee29f7c9cb79021aed18186272af0e9d189ec107c81a8a3aeb4782b0d950e4881aa51b776bb6844b25bce97035b48a9bdb2aea3608687bcdd479d4fa998b5a839ff88558e4a29dff0ed13b55900abb5d439b70793d902ae9ad34587b18c919f6b875c91d14deeb1c373f5e76570d59a6549758f655f1128a54f162dfe8868e1587028e26ad91e528c5ae7ee9335fa58fb59022b5de29d80f0764a9917390d46db899acc6a5b416e25ecc9dccb7153646addcc81cadb5f0078febc7e05d7735aba494f39ef05697bbcc9b47b2ccc79595d75fc13c80678b5e237edce58d731f34c05b1ddcaa649acf2d865bbbc3ceda10508bcdd29d0496744644bf1c3516f6687dfeef5649c7dff90627d642739a59d91a8d1d0c4dc55d74a949e1074427664b467992c9e0f7d3af9d6ea79513e8946ddc0d356bac49878e64e6a95b0a30214214faf2ce317fa622ff3266b32a816e10a18e6d789a5da1f23e67b4f970a68a7bcd9e18825ee274b0483896a40" + } + ] + }, + "offset": "d202964900000000d302964900000000d402964900000000d502964900000000" + }, + "version_info": { + "min_compat_version": 0, + "orig_version": 2, + "version": 2 + } + } + } + } + # "# + # , 5, false, true); + ``` + */ + fn finalize_invoice_tx(&self, slate: &Slate) -> Result; } impl ForeignRpc for Foreign @@ -387,6 +549,10 @@ where Ok(VersionedSlate::into_version(slate, version)) } + + fn finalize_invoice_tx(&self, slate: &Slate) -> Result { + Foreign::finalize_invoice_tx(self, slate).map_err(|e| e.kind()) + } } /// helper to set up a real environment to run integrated doctests @@ -395,6 +561,7 @@ pub fn run_doctest_foreign( test_dir: &str, blocks_to_mine: u64, init_tx: bool, + init_invoice_tx: bool, ) -> Result, String> { use crate::{Foreign, ForeignRpc}; use easy_jsonrpc::Handler; @@ -457,6 +624,36 @@ pub fn run_doctest_foreign( w.close().unwrap(); } + if init_invoice_tx { + let amount = 60_000_000_000; + let mut slate = { + let mut w = wallet2.lock(); + w.open_with_credentials().unwrap(); + let args = IssueInvoiceTxArgs { + amount, + ..Default::default() + }; + api_impl::owner::issue_invoice_tx(&mut *w, args, true).unwrap() + }; + slate = { + let mut w = wallet1.lock(); + w.open_with_credentials().unwrap(); + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + api_impl::owner::process_invoice_tx(&mut *w, &slate, args, true).unwrap() + }; + println!("INIT INVOICE SLATE"); + // Spit out slate for input to finalize_invoice_tx + println!("{}", serde_json::to_string_pretty(&slate).unwrap()); + } + if init_tx { let amount = 60_000_000_000; let mut w = wallet1.lock(); @@ -470,13 +667,16 @@ pub fn run_doctest_foreign( selection_strategy_is_use_all: true, ..Default::default() }; - let slate = api_impl::owner::initiate_tx(&mut *w, args, true).unwrap(); + let slate = api_impl::owner::init_send_tx(&mut *w, args, true).unwrap(); println!("INIT SLATE"); // Spit out slate for input to finalize_tx println!("{}", serde_json::to_string_pretty(&slate).unwrap()); } - let mut api_foreign = Foreign::new(wallet1.clone()); + let mut api_foreign = match init_invoice_tx { + false => Foreign::new(wallet1.clone()), + true => Foreign::new(wallet2.clone()), + }; api_foreign.doctest_mode = true; let foreign_api = &api_foreign as &dyn ForeignRpc; Ok(foreign_api.handle_request(request).as_option()) @@ -485,7 +685,7 @@ pub fn run_doctest_foreign( #[doc(hidden)] #[macro_export] macro_rules! doctest_helper_json_rpc_foreign_assert_response { - ($request:expr, $expected_response:expr, $blocks_to_mine:expr, $init_tx:expr) => { + ($request:expr, $expected_response:expr, $blocks_to_mine:expr, $init_tx:expr, $init_invoice_tx:expr) => { // create temporary wallet, run jsonrpc request on owner api of wallet, delete wallet, return // json response. // In order to prevent leaking tempdirs, This function should not panic. @@ -504,9 +704,15 @@ macro_rules! doctest_helper_json_rpc_foreign_assert_response { let request_val: Value = serde_json::from_str($request).unwrap(); let expected_response: Value = serde_json::from_str($expected_response).unwrap(); - let response = run_doctest_foreign(request_val, dir, $blocks_to_mine, $init_tx) - .unwrap() - .unwrap(); + let response = run_doctest_foreign( + request_val, + dir, + $blocks_to_mine, + $init_tx, + $init_invoice_tx, + ) + .unwrap() + .unwrap(); if response != expected_response { panic!( diff --git a/api/src/owner.rs b/api/src/owner.rs index ee706f1e..17868ec0 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -25,8 +25,8 @@ use crate::impls::{HTTPWalletCommAdapter, KeybaseWalletCommAdapter}; use crate::keychain::{Identifier, Keychain}; use crate::libwallet::api_impl::owner; use crate::libwallet::{ - AcctPathMapping, Error, ErrorKind, InitTxArgs, NodeClient, NodeHeightResult, - OutputCommitMapping, Slate, TxLogEntry, WalletBackend, WalletInfo, + AcctPathMapping, Error, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, + NodeHeightResult, OutputCommitMapping, Slate, TxLogEntry, WalletBackend, WalletInfo, }; /// Main interface into all wallet API functions. @@ -474,7 +474,7 @@ where /// message: Some("Have some Grins. Love, Yeastplume".to_owned()), /// ..Default::default() /// }; - /// let result = api_owner.initiate_tx( + /// let result = api_owner.init_send_tx( /// args, /// ); /// @@ -486,12 +486,12 @@ where /// } /// ``` - pub fn initiate_tx(&self, args: InitTxArgs) -> Result { + pub fn init_send_tx(&self, args: InitTxArgs) -> Result { let send_args = args.send_args.clone(); let mut slate = { let mut w = self.wallet.lock(); w.open_with_credentials()?; - let slate = owner::initiate_tx(&mut *w, args, self.doctest_mode)?; + let slate = owner::init_send_tx(&mut *w, args, self.doctest_mode)?; w.close()?; slate }; @@ -528,6 +528,108 @@ where } } + /// Issues a new invoice transaction slate, essentially a `request for payment`. + /// The slate created by this function will contain the amount, an output for the amount, + /// as well as round 1 of singature creation complete. The slate should then be send + /// to the payer, who should add their inputs and signature data and return the slate + /// via the [Foreign API's `finalize_invoice_tx`](struct.Foreign.html#method.finalize_invoice_tx) method. + /// + /// # Arguments + /// * `args` - [`IssueInvoiceTxArgs`](../grin_wallet_libwallet/types/struct.IssueInvoiceTxArgs.html), + /// invoice transaction initialization arguments. See struct documentation for further detail. + /// + /// # Returns + /// * ``Ok([`slate`](../grin_wallet_libwallet/slate/struct.Slate.html))` if successful, + /// containing the updated slate. + /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. + /// + /// # Example + /// Set up as in [`new`](struct.Owner.html#method.new) method above. + /// ``` + /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); + /// + /// let mut api_owner = Owner::new(wallet.clone()); + /// + /// let args = IssueInvoiceTxArgs { + /// amount: 60_000_000_000, + /// ..Default::default() + /// }; + /// let result = api_owner.issue_invoice_tx(args); + /// + /// if let Ok(slate) = result { + /// // if okay, send to the payer to add their inputs + /// // . . . + /// } + /// ``` + pub fn issue_invoice_tx(&self, args: IssueInvoiceTxArgs) -> Result { + let mut w = self.wallet.lock(); + w.open_with_credentials()?; + let slate = owner::issue_invoice_tx(&mut *w, args, self.doctest_mode)?; + w.close()?; + Ok(slate) + } + + /// Processes an invoice tranaction created by another party, essentially + /// a `request for payment`. The incoming slate should contain a requested + /// amount, an output created by the invoicer convering the amount, and + /// part 1 of signature creation completed. This function will add inputs + /// equalling the amount + fees, as well as perform round 1 and 2 of signature + /// creation. + /// + /// Callers should note that no prompting of the user will be done by this function + /// it is up to the caller to present the request for payment to the user + /// and verify that payment should go ahead. + /// + /// This function also stores the final transaction in the user's wallet files for retrieval + /// via the [`get_stored_tx`](struct.Owner.html#method.get_stored_tx) function. + /// + /// # Arguments + /// * `slate` - The transaction [`Slate`](../grin_wallet_libwallet/slate/struct.Slate.html). The + /// payer should have filled in round 1 and 2. + /// * `args` - [`InitTxArgs`](../grin_wallet_libwallet/types/struct.InitTxArgs.html), + /// transaction initialization arguments. See struct documentation for further detail. + /// + /// # Returns + /// * ``Ok([`slate`](../grin_wallet_libwallet/slate/struct.Slate.html))` if successful, + /// containing the updated slate. + /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. + /// + /// # Example + /// Set up as in [`new`](struct.Owner.html#method.new) method above. + /// ``` + /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); + /// + /// let mut api_owner = Owner::new(wallet.clone()); + /// + /// // . . . + /// // The slate has been recieved from the invoicer, somehow + /// # let slate = Slate::blank(2); + /// let args = InitTxArgs { + /// src_acct_name: None, + /// amount: slate.amount, + /// minimum_confirmations: 2, + /// max_outputs: 500, + /// num_change_outputs: 1, + /// selection_strategy_is_use_all: true, + /// ..Default::default() + /// }; + /// + /// let result = api_owner.process_invoice_tx(&slate, args); + /// + /// if let Ok(slate) = result { + /// // If result okay, send back to the invoicer + /// // . . . + /// } + /// ``` + + pub fn process_invoice_tx(&self, slate: &Slate, args: InitTxArgs) -> Result { + let mut w = self.wallet.lock(); + w.open_with_credentials()?; + let slate = owner::process_invoice_tx(&mut *w, slate, args, self.doctest_mode)?; + w.close()?; + Ok(slate) + } + /// Locks the outputs associated with the inputs to the transaction in the given /// [`Slate`](../grin_wallet_libwallet/slate/struct.Slate.html), /// making them unavailable for use in further transactions. This function is called @@ -566,7 +668,7 @@ where /// message: Some("Remember to lock this when we're happy this is sent".to_owned()), /// ..Default::default() /// }; - /// let result = api_owner.initiate_tx( + /// let result = api_owner.init_send_tx( /// args, /// ); /// @@ -624,7 +726,7 @@ where /// message: Some("Finalize this tx now".to_owned()), /// ..Default::default() /// }; - /// let result = api_owner.initiate_tx( + /// let result = api_owner.init_send_tx( /// args, /// ); /// @@ -681,7 +783,7 @@ where /// message: Some("Post this tx".to_owned()), /// ..Default::default() /// }; - /// let result = api_owner.initiate_tx( + /// let result = api_owner.init_send_tx( /// args, /// ); /// @@ -742,7 +844,7 @@ where /// message: Some("Cancel this tx".to_owned()), /// ..Default::default() /// }; - /// let result = api_owner.initiate_tx( + /// let result = api_owner.init_send_tx( /// args, /// ); /// @@ -835,7 +937,7 @@ where /// message: Some("Just verify messages".to_owned()), /// ..Default::default() /// }; - /// let result = api_owner.initiate_tx( + /// let result = api_owner.init_send_tx( /// args, /// ); /// @@ -1016,7 +1118,7 @@ macro_rules! doctest_helper_setup_doc_env { use api::Owner; use config::WalletConfig; use impls::{HTTPNodeClient, LMDBBackend, WalletSeed}; - use libwallet::{InitTxArgs, WalletBackend}; + use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate, WalletBackend}; let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap(); let dir = dir diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index 31f637b4..5e06c700 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -18,8 +18,8 @@ use uuid::Uuid; use crate::core::core::Transaction; use crate::keychain::{Identifier, Keychain}; use crate::libwallet::{ - AcctPathMapping, ErrorKind, InitTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping, - Slate, TxLogEntry, WalletBackend, WalletInfo, + AcctPathMapping, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult, + OutputCommitMapping, Slate, TxLogEntry, WalletBackend, WalletInfo, }; use crate::Owner; use easy_jsonrpc; @@ -314,14 +314,14 @@ pub trait OwnerRpc { ) -> Result<(bool, WalletInfo), ErrorKind>; /** - Networked version of [Owner::estimate_initiate_tx](struct.Owner.html#method.initiate_tx). + Networked version of [Owner::init_send_tx](struct.Owner.html#method.init_send_tx). ``` # grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!( # r#" { "jsonrpc": "2.0", - "method": "initiate_tx", + "method": "init_send_tx", "params": { "args": { "src_acct_name": null, @@ -401,7 +401,235 @@ pub trait OwnerRpc { ``` */ - fn initiate_tx(&self, args: InitTxArgs) -> Result; + fn init_send_tx(&self, args: InitTxArgs) -> Result; + + /** + Networked version of [Owner::issue_invoice_tx](struct.Owner.html#method.issue_invoice_tx). + + ``` + # grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!( + # r#" + { + "jsonrpc": "2.0", + "method": "issue_invoice_tx", + "params": { + "args": { + "amount": "6000000000", + "message": "Please give me your grins", + "dest_acct_name": null, + "target_slate_version": null + } + }, + "id": 1 + } + # "# + # , + # r#" + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "Ok": { + "amount": "6000000000", + "fee": "0", + "height": "4", + "id": "0436430c-2b02-624c-2032-570501212b00", + "lock_height": "0", + "num_participants": 2, + "participant_data": [ + { + "id": "1", + "message": "Please give me your grins", + "message_sig": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fd2599ab38942986602e943f684a85992893a6d34367dc7cc2b403a5dcfcdbcd9", + "part_sig": null, + "public_blind_excess": "028e95921cc0d5be5922362265d352c9bdabe51a9e1502a3f0d4a10387f1893f40", + "public_nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" + } + ], + "tx": { + "body": { + "inputs": [], + "kernels": [ + { + "excess": "000000000000000000000000000000000000000000000000000000000000000000", + "excess_sig": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "features": "Plain", + "fee": "0", + "lock_height": "0" + } + ], + "outputs": [ + { + "commit": "09cf47204446c326e361a1a92f34b174deff732daaedb80d7339fbe3db5ca2f6ba", + "features": "Plain", + "proof": "8f511614315626b5f39224482351d766f5a8ef136262befc050d839be8479b0a13470cd88f4436346d213d83847a4055c6e0ac63681556470349a1aab47034a3015eb64d8163955998e2dd4165dd24386b1e279974b05deb5d46ba2bc321f7000c0784f8f10690605ffe717119d045e02b141ed12d8fc6d20930483a8af889ef533495eb442fcff36d98ebc104f13fc645c28431b3296e4a11f7c991ff97f9abbc2f8886762d7f29fdacb31d52c6850e6ccf5386117d89e8ea4ca3071c56c218dd5d3bcd65f6c06ed9f51f848507ca1d594f41796d1cf99f68a5c3f0c5dd9873602284cff31269b102fcc6c68607565faaf0adb04ed4ff3ea5d41f3b5235ac6cb90e4046c808c9c48c27172c891b20085c56a99913ef47fd8b3dc4920cef50534b9319a7cefe0df10a0206a634ac837e11da92df83ff58b1a14de81313400988aa48b946fcbe1b81f0e79e13f7c6c639b1c10983b424bda08d0ce593a20f1f47e0aa01473e7144f116b76d9ebc60599053d8f1542d60747793d99064e51fce8f8866390325d48d6e8e3bbdbc1822c864303451525c6cb4c6902f105a70134186fb32110d8192fc2528a9483fc8a4001f4bdeab1dd7b3d1ccb9ae2e746a78013ef74043f0b2436f0ca49627af1768b7c791c669bd331fd18c16ef88ad0a29861db70f2f76f3e74fde5accb91b73573e31333333223693d6fbc786e740c085e4fc6e7bde0a3f54e9703f816c54f012d3b1f41ec4d253d9337af61e7f1f1383bd929421ac346e3d2771dfee0b60503b33938e7c83eb37af3b6bf66041a3519a2b4cb557b34e3b9afcf95524f9a011425a34d32e7b6e9f255291094930acae26e8f7a1e4e6bc405d0f88e919f354f3ba85356a34f1aba5f7da1fad88e2692f4129cc1fb80a2122b2d996c6ccf7f08d8248e511d92af9ce49039de728848a2dc74101f4e94a" + } + ] + }, + "offset": "d202964900000000d302964900000000d402964900000000d502964900000000" + }, + "version_info": { + "min_compat_version": 0, + "orig_version": 2, + "version": 2 + } + } + } + } + # "# + # ,4, false, false, false); + ``` + */ + + fn issue_invoice_tx(&self, args: IssueInvoiceTxArgs) -> Result; + + /** + Networked version of [Owner::process_invoice_tx](struct.Owner.html#method.process_invoice_tx). + + ``` + # grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!( + # r#" + { + "jsonrpc": "2.0", + "method": "process_invoice_tx", + "params": [ + { + "amount": "6000000000", + "fee": "0", + "height": "4", + "id": "0436430c-2b02-624c-2032-570501212b00", + "lock_height": "0", + "num_participants": 2, + "participant_data": [ + { + "id": "1", + "message": "Please give me your grins", + "message_sig": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fd2599ab38942986602e943f684a85992893a6d34367dc7cc2b403a5dcfcdbcd9", + "part_sig": null, + "public_blind_excess": "028e95921cc0d5be5922362265d352c9bdabe51a9e1502a3f0d4a10387f1893f40", + "public_nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" + } + ], + "tx": { + "body": { + "inputs": [], + "kernels": [ + { + "excess": "000000000000000000000000000000000000000000000000000000000000000000", + "excess_sig": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "features": "Plain", + "fee": "0", + "lock_height": "0" + } + ], + "outputs": [ + { + "commit": "09cf47204446c326e361a1a92f34b174deff732daaedb80d7339fbe3db5ca2f6ba", + "features": "Plain", + "proof": "8f511614315626b5f39224482351d766f5a8ef136262befc050d839be8479b0a13470cd88f4436346d213d83847a4055c6e0ac63681556470349a1aab47034a3015eb64d8163955998e2dd4165dd24386b1e279974b05deb5d46ba2bc321f7000c0784f8f10690605ffe717119d045e02b141ed12d8fc6d20930483a8af889ef533495eb442fcff36d98ebc104f13fc645c28431b3296e4a11f7c991ff97f9abbc2f8886762d7f29fdacb31d52c6850e6ccf5386117d89e8ea4ca3071c56c218dd5d3bcd65f6c06ed9f51f848507ca1d594f41796d1cf99f68a5c3f0c5dd9873602284cff31269b102fcc6c68607565faaf0adb04ed4ff3ea5d41f3b5235ac6cb90e4046c808c9c48c27172c891b20085c56a99913ef47fd8b3dc4920cef50534b9319a7cefe0df10a0206a634ac837e11da92df83ff58b1a14de81313400988aa48b946fcbe1b81f0e79e13f7c6c639b1c10983b424bda08d0ce593a20f1f47e0aa01473e7144f116b76d9ebc60599053d8f1542d60747793d99064e51fce8f8866390325d48d6e8e3bbdbc1822c864303451525c6cb4c6902f105a70134186fb32110d8192fc2528a9483fc8a4001f4bdeab1dd7b3d1ccb9ae2e746a78013ef74043f0b2436f0ca49627af1768b7c791c669bd331fd18c16ef88ad0a29861db70f2f76f3e74fde5accb91b73573e31333333223693d6fbc786e740c085e4fc6e7bde0a3f54e9703f816c54f012d3b1f41ec4d253d9337af61e7f1f1383bd929421ac346e3d2771dfee0b60503b33938e7c83eb37af3b6bf66041a3519a2b4cb557b34e3b9afcf95524f9a011425a34d32e7b6e9f255291094930acae26e8f7a1e4e6bc405d0f88e919f354f3ba85356a34f1aba5f7da1fad88e2692f4129cc1fb80a2122b2d996c6ccf7f08d8248e511d92af9ce49039de728848a2dc74101f4e94a" + } + ] + }, + "offset": "d202964900000000d302964900000000d402964900000000d502964900000000" + }, + "version_info": { + "min_compat_version": 0, + "orig_version": 2, + "version": 2 + } + }, + { + "src_acct_name": null, + "amount": "0", + "minimum_confirmations": 2, + "max_outputs": 500, + "num_change_outputs": 1, + "selection_strategy_is_use_all": true, + "message": "Ok, here are your grins", + "target_slate_version": null, + "send_args": null + } + ], + "id": 1 + } + # "# + # , + # r#" + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "Ok": { + "amount": "6000000000", + "fee": "8000000", + "height": "4", + "id": "0436430c-2b02-624c-2032-570501212b00", + "lock_height": "0", + "num_participants": 2, + "participant_data": [ + { + "id": "1", + "message": "Please give me your grins", + "message_sig": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fd2599ab38942986602e943f684a85992893a6d34367dc7cc2b403a5dcfcdbcd9", + "part_sig": null, + "public_blind_excess": "028e95921cc0d5be5922362265d352c9bdabe51a9e1502a3f0d4a10387f1893f40", + "public_nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" + }, + { + "id": "0", + "message": "Ok, here are your grins", + "message_sig": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f463643727bf45004637269e9afb5f5fbd8cdcc1881a2ef9ec3ab0fb5f6e01ae9", + "part_sig": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f19d37c475bc5cc495b732dfddb0fb5a5e782b7ae2797ef4904b66f6afb409d61", + "public_blind_excess": "0309e22f2adaa9b81f51414b775b86acd096e17794eb8159bfcfef27caa4bf5c90", + "public_nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" + } + ], + "tx": { + "body": { + "inputs": [ + { + "commit": "08e1da9e6dc4d6e808a718b2f110a991dd775d65ce5ae408a4e1f002a4961aa9e7", + "features": "Coinbase" + } + ], + "kernels": [ + { + "excess": "000000000000000000000000000000000000000000000000000000000000000000", + "excess_sig": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "features": "Plain", + "fee": "8000000", + "lock_height": "0" + } + ], + "outputs": [ + { + "commit": "09cf47204446c326e361a1a92f34b174deff732daaedb80d7339fbe3db5ca2f6ba", + "features": "Plain", + "proof": "8f511614315626b5f39224482351d766f5a8ef136262befc050d839be8479b0a13470cd88f4436346d213d83847a4055c6e0ac63681556470349a1aab47034a3015eb64d8163955998e2dd4165dd24386b1e279974b05deb5d46ba2bc321f7000c0784f8f10690605ffe717119d045e02b141ed12d8fc6d20930483a8af889ef533495eb442fcff36d98ebc104f13fc645c28431b3296e4a11f7c991ff97f9abbc2f8886762d7f29fdacb31d52c6850e6ccf5386117d89e8ea4ca3071c56c218dd5d3bcd65f6c06ed9f51f848507ca1d594f41796d1cf99f68a5c3f0c5dd9873602284cff31269b102fcc6c68607565faaf0adb04ed4ff3ea5d41f3b5235ac6cb90e4046c808c9c48c27172c891b20085c56a99913ef47fd8b3dc4920cef50534b9319a7cefe0df10a0206a634ac837e11da92df83ff58b1a14de81313400988aa48b946fcbe1b81f0e79e13f7c6c639b1c10983b424bda08d0ce593a20f1f47e0aa01473e7144f116b76d9ebc60599053d8f1542d60747793d99064e51fce8f8866390325d48d6e8e3bbdbc1822c864303451525c6cb4c6902f105a70134186fb32110d8192fc2528a9483fc8a4001f4bdeab1dd7b3d1ccb9ae2e746a78013ef74043f0b2436f0ca49627af1768b7c791c669bd331fd18c16ef88ad0a29861db70f2f76f3e74fde5accb91b73573e31333333223693d6fbc786e740c085e4fc6e7bde0a3f54e9703f816c54f012d3b1f41ec4d253d9337af61e7f1f1383bd929421ac346e3d2771dfee0b60503b33938e7c83eb37af3b6bf66041a3519a2b4cb557b34e3b9afcf95524f9a011425a34d32e7b6e9f255291094930acae26e8f7a1e4e6bc405d0f88e919f354f3ba85356a34f1aba5f7da1fad88e2692f4129cc1fb80a2122b2d996c6ccf7f08d8248e511d92af9ce49039de728848a2dc74101f4e94a" + }, + { + "commit": "094be57c91787fc2033d5d97fae099f1a6ddb37ea48370f1a138f09524c767fdd3", + "features": "Plain", + "proof": "2a42e9e902b70ce44e1fccb14de87ee0a97100bddf12c6bead1b9c5f4eb60300f29c13094fa12ffeee238fb4532b18f6b61cf51b23c1c7e1ad2e41560dc27edc0a2b9e647a0b3e4e806fced5b65e61d0f1f5197d3e2285c632d359e27b6b9206b2caffea4f67e0c7a2812e7a22c134b98cf89bd43d9f28b8bec25cce037a0ac5b1ae8f667e54e1250813a5263004486b4465ad4e641ab2b535736ea26535a11013564f08f483b7dab1c2bcc3ee38eadf2f7850eff7e3459a4bbabf9f0cf6c50d0c0a4120565cd4a2ce3e354c11721cd695760a24c70e0d5a0dfc3c5dcd51dfad6de2c237a682f36dc0b271f21bb3655e5333016aaa42c2efa1446e5f3c0a79ec417c4d30f77556951cb0f05dbfafb82d9f95951a9ea241fda2a6388f73ace036b98acce079f0e4feebccc96290a86dcc89118a901210b245f2d114cf94396e4dbb461e82aa26a0581389707957968c7cdc466213bb1cd417db207ef40c05842ab67a01a9b96eb1430ebc26e795bb491258d326d5174ad549401059e41782121e506744af8af9d8e493644a87d613600888541cbbe538c625883f3eb4aa3102c5cfcc25de8e97af8927619ce6a731b3b8462d51d993066b935b0648d2344ad72e4fd70f347fbd81041042e5ea31cc7b2e3156a920b80ecba487b950ca32ca95fae85b759c936246ecf441a9fdd95e8fee932d6782cdec686064018c857efc47fb4b2a122600d5fdd79af2486f44df7e629184e1c573bc0a9b3feb40b190ef2861a1ab45e2ac2201b9cd42e495deea247269820ed32389a2810ad6c0f9a296d2a2d9c54089fed50b7f5ecfcd33ab9954360e1d7f5598c32128cfcf2a1d8bf14616818da8a5343bfa88f0eedf392e9d4ab1ace1b60324129cd4852c2e27813a9cf71a6ae6229a4fcecc1a756b3e664c5f50af333082616815a3bec8fc0b75b8e4e767d719" + } + ] + }, + "offset": "d202964900000000d302964900000000d402964900000000d502964900000000" + }, + "version_info": { + "min_compat_version": 0, + "orig_version": 2, + "version": 2 + } + } + } + } + # "# + # ,4, false, false, false); + ``` + */ + + fn process_invoice_tx(&self, slate: &Slate, args: InitTxArgs) -> Result; /** Networked version of [Owner::tx_lock_outputs](struct.Owner.html#method.tx_lock_outputs). @@ -1049,8 +1277,16 @@ where .map_err(|e| e.kind()) } - fn initiate_tx(&self, args: InitTxArgs) -> Result { - Owner::initiate_tx(self, args).map_err(|e| e.kind()) + fn init_send_tx(&self, args: InitTxArgs) -> Result { + Owner::init_send_tx(self, args).map_err(|e| e.kind()) + } + + fn issue_invoice_tx(&self, args: IssueInvoiceTxArgs) -> Result { + Owner::issue_invoice_tx(self, args).map_err(|e| e.kind()) + } + + fn process_invoice_tx(&self, slate: &Slate, args: InitTxArgs) -> Result { + Owner::process_invoice_tx(self, slate, args).map_err(|e| e.kind()) } fn finalize_tx(&self, mut slate: Slate) -> Result { @@ -1173,7 +1409,7 @@ pub fn run_doctest_owner( selection_strategy_is_use_all: true, ..Default::default() }; - let mut slate = api_impl::owner::initiate_tx(&mut *w, args, true).unwrap(); + let mut slate = api_impl::owner::init_send_tx(&mut *w, args, true).unwrap(); { let mut w2 = wallet2.lock(); w2.open_with_credentials().unwrap(); diff --git a/api/tests/slate_versioning.rs b/api/tests/slate_versioning.rs index 38c2493f..51b99ac6 100644 --- a/api/tests/slate_versioning.rs +++ b/api/tests/slate_versioning.rs @@ -52,7 +52,7 @@ fn receive_versioned_slate() { let request_val: Value = serde_json::from_str(v1_req).unwrap(); let expected_response: Value = serde_json::from_str(v1_resp).unwrap(); - let response = run_doctest_foreign(request_val, dir, 5, true) + let response = run_doctest_foreign(request_val, dir, 5, true, false) .unwrap() .unwrap(); @@ -80,7 +80,7 @@ fn receive_tx(vs: VersionedSlate) -> VersionedSlate { ) .unwrap(); let (call, tracker) = bound_method.call(); - let json_response = run_doctest_foreign(call.as_request(), dir, 5, false) + let json_response = run_doctest_foreign(call.as_request(), dir, 5, false, false) .unwrap() .unwrap(); let mut response = easy_jsonrpc::Response::from_json_response(json_response).unwrap(); diff --git a/controller/src/command.rs b/controller/src/command.rs index ae5cba10..374e66c0 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -257,7 +257,7 @@ pub fn send( estimate_only: Some(true), ..Default::default() }; - let slate = api.initiate_tx(init_args).unwrap(); + let slate = api.init_send_tx(init_args).unwrap(); (strategy, slate.amount, slate.fee) }) .collect(); @@ -275,7 +275,7 @@ pub fn send( send_args: None, ..Default::default() }; - let result = api.initiate_tx(init_args); + let result = api.init_send_tx(init_args); let mut slate = match result { Ok(s) => { info!( diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 01f1601e..5a5f9215 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -359,7 +359,7 @@ where send_args: None, ..Default::default() }; - let result = api.initiate_tx(init_args); + let result = api.init_send_tx(init_args); let mut slate = match result { Ok(s) => { info!( diff --git a/controller/tests/accounts.rs b/controller/tests/accounts.rs index 85fe5915..1feb699d 100644 --- a/controller/tests/accounts.rs +++ b/controller/tests/accounts.rs @@ -189,7 +189,7 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let mut slate = api.initiate_tx(args)?; + let mut slate = api.init_send_tx(args)?; slate = client1.send_tx_slate_direct("wallet2", &slate)?; api.tx_lock_outputs(&slate)?; slate = api.finalize_tx(&slate)?; diff --git a/controller/tests/check.rs b/controller/tests/check.rs index 144e2902..cf8d2a0e 100644 --- a/controller/tests/check.rs +++ b/controller/tests/check.rs @@ -187,7 +187,7 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let mut slate = api.initiate_tx(args)?; + let mut slate = api.init_send_tx(args)?; // output tx file let file_adapter = FileWalletCommAdapter::new(); let send_file = format!("{}/part_tx_1.tx", test_dir); diff --git a/controller/tests/file.rs b/controller/tests/file.rs index 3816f8f8..a41989bd 100644 --- a/controller/tests/file.rs +++ b/controller/tests/file.rs @@ -117,7 +117,7 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { message: Some(message.to_owned()), ..Default::default() }; - let mut slate = api.initiate_tx(args)?; + let mut slate = api.init_send_tx(args)?; // output tx file let file_adapter = FileWalletCommAdapter::new(); file_adapter.send_tx_async(&send_file, &mut slate)?; diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs new file mode 100644 index 00000000..62482ba8 --- /dev/null +++ b/controller/tests/invoice.rs @@ -0,0 +1,187 @@ +// Copyright 2018 The Grin Developers +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test a wallet sending to self +#[macro_use] +extern crate log; +extern crate grin_wallet_controller as wallet; +extern crate grin_wallet_impls as impls; + +use grin_wallet_util::grin_core as core; +use grin_wallet_util::grin_keychain as keychain; +use grin_wallet_util::grin_util as util; + +use self::core::global; +use self::core::global::ChainTypes; +use self::keychain::ExtKeychain; +use grin_wallet_libwallet as libwallet; +use impls::test_framework::{self, LocalWalletClient, WalletProxy}; +use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate}; +use std::fs; +use std::thread; +use std::time::Duration; + +fn clean_output_dir(test_dir: &str) { + let _ = fs::remove_dir_all(test_dir); +} + +fn setup(test_dir: &str) { + util::init_test_logger(); + clean_output_dir(test_dir); + global::set_mining_mode(ChainTypes::AutomatedTesting); +} + +fn teardown(test_dir: &str) { + clean_output_dir(test_dir); +} + +/// self send impl +fn invoice_tx_impl(test_dir: &str) -> Result<(), libwallet::Error> { + { + setup(test_dir); + // Create a new proxy to simulate server and wallet responses + let mut wallet_proxy: WalletProxy = + WalletProxy::new(test_dir); + let chain = wallet_proxy.chain.clone(); + + // Create a new wallet test client, and set its queues to communicate with the proxy + let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); + let wallet1 = + test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None); + wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); + + // wallet 2, will be recipient + let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); + let wallet2 = + test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None); + wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); + + // Set the wallet proxy listener running + thread::spawn(move || { + if let Err(e) = wallet_proxy.run() { + error!("Wallet Proxy error: {}", e); + } + }); + + // few values to keep things shorter + let reward = core::consensus::REWARD; + + // add some accounts + wallet::controller::owner_single_use(wallet1.clone(), |api| { + api.create_account_path("mining")?; + api.create_account_path("listener")?; + Ok(()) + })?; + + // Get some mining done + { + let mut w = wallet1.lock(); + w.set_parent_key_id_by_name("mining")?; + } + let mut bh = 10u64; + let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize, false); + + // Sanity check wallet 1 contents + wallet::controller::owner_single_use(wallet1.clone(), |api| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + Ok(()) + })?; + + let mut slate = Slate::blank(2); + + wallet::controller::owner_single_use(wallet2.clone(), |api| { + // Wallet 2 inititates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward * 2, + ..Default::default() + }; + slate = api.issue_invoice_tx(args)?; + Ok(()) + })?; + + wallet::controller::owner_single_use(wallet1.clone(), |api| { + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + slate = api.process_invoice_tx(&slate, args)?; + Ok(()) + })?; + + // wallet 2 finalizes and posts + wallet::controller::foreign_single_use(wallet2.clone(), |api| { + // Wallet 2 receives the invoice transaction + slate = api.finalize_invoice_tx(&slate)?; + Ok(()) + })?; + + // wallet 1 posts so wallet 2 doesn't get the mined amount + wallet::controller::owner_single_use(wallet1.clone(), |api| { + api.post_tx(&slate.tx, false)?; + Ok(()) + })?; + bh += 1; + + let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 3, false); + bh += 3; + + // Check transaction log for wallet 2 + wallet::controller::owner_single_use(wallet2.clone(), |api| { + let (_, wallet2_info) = api.retrieve_summary_info(true, 1)?; + let (refreshed, txs) = api.retrieve_txs(true, None, None)?; + assert!(refreshed); + assert!(txs.len() == 1); + println!( + "last confirmed height: {}, bh: {}", + wallet2_info.last_confirmed_height, bh + ); + assert!(refreshed); + assert_eq!(wallet2_info.amount_currently_spendable, slate.amount); + Ok(()) + })?; + + // let logging finish + thread::sleep(Duration::from_millis(200)); + + // Check transaction log for wallet 1, ensure only 1 entry + // exists + wallet::controller::owner_single_use(wallet1.clone(), |api| { + let (_, wallet1_info) = api.retrieve_summary_info(true, 1)?; + let (refreshed, txs) = api.retrieve_txs(true, None, None)?; + assert!(refreshed); + assert_eq!(txs.len() as u64, bh + 1); + println!( + "Wallet 1: last confirmed height: {}, bh: {}", + wallet1_info.last_confirmed_height, bh + ); + Ok(()) + })?; + } + teardown(test_dir); + Ok(()) +} + +#[test] +fn wallet_invoice_tx() -> Result<(), libwallet::Error> { + let test_dir = "test_output/invoice_tx"; + invoice_tx_impl(test_dir) +} diff --git a/controller/tests/repost.rs b/controller/tests/repost.rs index 0377b543..0df02e9a 100644 --- a/controller/tests/repost.rs +++ b/controller/tests/repost.rs @@ -112,7 +112,7 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let mut slate = api.initiate_tx(args)?; + let mut slate = api.init_send_tx(args)?; // output tx file let file_adapter = FileWalletCommAdapter::new(); file_adapter.send_tx_async(&send_file, &mut slate)?; @@ -209,7 +209,7 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let slate_i = sender_api.initiate_tx(args)?; + let slate_i = sender_api.init_send_tx(args)?; slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&mut slate)?; diff --git a/controller/tests/restore.rs b/controller/tests/restore.rs index dd660b95..c2cc8241 100644 --- a/controller/tests/restore.rs +++ b/controller/tests/restore.rs @@ -245,7 +245,7 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let slate_i = sender_api.initiate_tx(args)?; + let slate_i = sender_api.init_send_tx(args)?; slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; @@ -268,7 +268,7 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let slate_i = sender_api.initiate_tx(args)?; + let slate_i = sender_api.init_send_tx(args)?; slate = client1.send_tx_slate_direct("wallet3", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; @@ -291,7 +291,7 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let slate_i = sender_api.initiate_tx(args)?; + let slate_i = sender_api.init_send_tx(args)?; slate = client3.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; @@ -320,7 +320,7 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let slate_i = sender_api.initiate_tx(args)?; + let slate_i = sender_api.init_send_tx(args)?; slate = client3.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; diff --git a/controller/tests/self_send.rs b/controller/tests/self_send.rs index 8053cfa8..20ab2280 100644 --- a/controller/tests/self_send.rs +++ b/controller/tests/self_send.rs @@ -96,7 +96,7 @@ fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let mut slate = api.initiate_tx(args)?; + let mut slate = api.init_send_tx(args)?; api.tx_lock_outputs(&slate)?; // Send directly to self wallet::controller::foreign_single_use(wallet1.clone(), |api| { diff --git a/controller/tests/transaction.rs b/controller/tests/transaction.rs index 57459863..bd121e2f 100644 --- a/controller/tests/transaction.rs +++ b/controller/tests/transaction.rs @@ -73,8 +73,8 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { // few values to keep things shorter let reward = core::consensus::REWARD; - let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height - // mine a few blocks + let cm = global::coinbase_maturity(); + // mine a few blocks let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 10, false); // Check wallet 1 contents are as expected @@ -108,7 +108,7 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let slate_i = sender_api.initiate_tx(args)?; + let slate_i = sender_api.init_send_tx(args)?; // Check we are creating a tx with the expected lock_height of 0. // We will check this produces a Plain kernel later. @@ -261,7 +261,7 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { estimate_only: Some(true), ..Default::default() }; - let est = sender_api.initiate_tx(init_args)?; + let est = sender_api.init_send_tx(init_args)?; assert_eq!(est.amount, 600_000_000_000); assert_eq!(est.fee, 4_000_000); @@ -275,7 +275,7 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { estimate_only: Some(true), ..Default::default() }; - let est = sender_api.initiate_tx(init_args)?; + let est = sender_api.init_send_tx(init_args)?; assert_eq!(est.amount, 180_000_000_000); assert_eq!(est.fee, 6_000_000); @@ -295,7 +295,7 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let slate_i = sender_api.initiate_tx(args)?; + let slate_i = sender_api.init_send_tx(args)?; slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; @@ -395,7 +395,7 @@ fn tx_rollback(test_dir: &str) -> Result<(), libwallet::Error> { ..Default::default() }; - let slate_i = sender_api.initiate_tx(args)?; + let slate_i = sender_api.init_send_tx(args)?; slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; diff --git a/impls/src/test_framework/mod.rs b/impls/src/test_framework/mod.rs index b748aa4a..576d61c8 100644 --- a/impls/src/test_framework/mod.rs +++ b/impls/src/test_framework/mod.rs @@ -205,7 +205,7 @@ where selection_strategy_is_use_all: true, ..Default::default() }; - let slate_i = owner::initiate_tx(&mut *w, args, test_mode)?; + let slate_i = owner::init_send_tx(&mut *w, args, test_mode)?; let slate = client.send_tx_slate_direct(dest, &slate_i)?; owner::tx_lock_outputs(&mut *w, &slate)?; let slate = owner::finalize_tx(&mut *w, &slate)?; diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index b0592a0d..713d4b2a 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -103,8 +103,29 @@ where &parent_key_id, 1, message, + false, use_test_rng, )?; tx::update_message(&mut *w, &mut ret_slate)?; Ok(ret_slate) } + +/// Receive an tx that this wallet has issued +pub fn finalize_invoice_tx(w: &mut T, slate: &Slate) -> Result +where + T: WalletBackend, + C: NodeClient, + K: Keychain, +{ + let mut sl = slate.clone(); + let context = w.get_private_context(sl.id.as_bytes())?; + tx::complete_tx(&mut *w, &mut sl, 1, &context)?; + tx::update_stored_tx(&mut *w, &mut sl, true)?; + tx::update_message(&mut *w, &mut sl)?; + { + let mut batch = w.batch()?; + batch.delete_private_context(sl.id.as_bytes())?; + batch.commit()?; + } + Ok(sl) +} diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index a381fbbb..12c7883d 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -26,7 +26,9 @@ use crate::internal::{keys, selection, tx, updater}; use crate::slate::Slate; use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, TxWrapper, WalletBackend, WalletInfo}; use crate::{Error, ErrorKind}; -use crate::{InitTxArgs, NodeHeightResult, OutputCommitMapping}; +use crate::{ + InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping, TxLogEntryType, +}; const USER_MESSAGE_MAX_LEN: usize = 256; @@ -133,7 +135,7 @@ where } /// Initiate tx as sender -pub fn initiate_tx( +pub fn init_send_tx( w: &mut T, args: InitTxArgs, use_test_rng: bool, @@ -191,6 +193,7 @@ where &parent_key_id, 0, message, + true, use_test_rng, )?; @@ -207,6 +210,132 @@ where Ok(slate) } +/// Initiate a transaction as the recipient (invoicing) +pub fn issue_invoice_tx( + w: &mut T, + args: IssueInvoiceTxArgs, + use_test_rng: bool, +) -> Result +where + T: WalletBackend, + C: NodeClient, + K: Keychain, +{ + let parent_key_id = match args.dest_acct_name { + Some(d) => { + let pm = w.get_acct_path(d)?; + match pm { + Some(p) => p.path, + None => w.parent_key_id(), + } + } + None => w.parent_key_id(), + }; + + let message = match args.message { + Some(mut m) => { + m.truncate(USER_MESSAGE_MAX_LEN); + Some(m) + } + None => None, + }; + + let mut slate = tx::new_tx_slate(&mut *w, args.amount, 2, use_test_rng)?; + let context = tx::add_output_to_slate( + &mut *w, + &mut slate, + &parent_key_id, + 1, + message, + true, + use_test_rng, + )?; + + // Save the aggsig context in our DB for when we + // recieve the transaction back + { + let mut batch = w.batch()?; + batch.save_private_context(slate.id.as_bytes(), &context)?; + batch.commit()?; + } + + Ok(slate) +} + +/// Receive an invoice tx, essentially adding inputs to whatever +/// output was specified +pub fn process_invoice_tx( + w: &mut T, + slate: &Slate, + args: InitTxArgs, + use_test_rng: bool, +) -> Result +where + T: WalletBackend, + C: NodeClient, + K: Keychain, +{ + let mut ret_slate = slate.clone(); + let parent_key_id = match args.src_acct_name { + Some(d) => { + let pm = w.get_acct_path(d.to_owned())?; + match pm { + Some(p) => p.path, + None => w.parent_key_id(), + } + } + None => w.parent_key_id(), + }; + // Don't do this multiple times + let tx = updater::retrieve_txs( + &mut *w, + None, + Some(ret_slate.id), + Some(&parent_key_id), + use_test_rng, + )?; + for t in &tx { + if t.tx_type == TxLogEntryType::TxSent { + return Err(ErrorKind::TransactionAlreadyReceived(ret_slate.id.to_string()).into()); + } + } + + let message = match args.message { + Some(mut m) => { + m.truncate(USER_MESSAGE_MAX_LEN); + Some(m) + } + None => None, + }; + + let context = tx::add_inputs_to_slate( + &mut *w, + &mut ret_slate, + args.minimum_confirmations, + args.max_outputs as usize, + args.num_change_outputs as usize, + args.selection_strategy_is_use_all, + &parent_key_id, + 0, + message, + false, + use_test_rng, + )?; + + // Save the aggsig context in our DB for when we + // recieve the transaction back + { + let mut batch = w.batch()?; + batch.save_private_context(slate.id.as_bytes(), &context)?; + batch.commit()?; + } + + // Always lock the context for now + selection::lock_tx_context(&mut *w, slate, &context)?; + tx::update_message(&mut *w, &mut ret_slate)?; + Ok(ret_slate) +} + /// Lock sender outputs pub fn tx_lock_outputs(w: &mut T, slate: &Slate) -> Result<(), Error> where @@ -228,7 +357,7 @@ where let mut sl = slate.clone(); let context = w.get_private_context(sl.id.as_bytes())?; tx::complete_tx(&mut *w, &mut sl, 0, &context)?; - tx::update_stored_tx(&mut *w, &mut sl)?; + tx::update_stored_tx(&mut *w, &mut sl, false)?; tx::update_message(&mut *w, &mut sl)?; { let mut batch = w.batch()?; diff --git a/libwallet/src/api_impl/types.rs b/libwallet/src/api_impl/types.rs index 473ddabb..965ec87d 100644 --- a/libwallet/src/api_impl/types.rs +++ b/libwallet/src/api_impl/types.rs @@ -129,6 +129,35 @@ impl Default for InitTxArgs { } } +/// V2 Issue Invoice Tx Args +#[derive(Clone, Serialize, Deserialize)] +pub struct IssueInvoiceTxArgs { + /// The human readable account name to which the received funds should be added + /// overriding whatever the active account is as set via the + /// [`set_active_account`](../grin_wallet_api/owner/struct.Owner.html#method.set_active_account) method. + pub dest_acct_name: Option, + /// The invoice amount in nanogrins. (`1 G = 1_000_000_000nG`) + #[serde(with = "secp_ser::string_or_u64")] + pub amount: u64, + /// Optional message, that will be signed + pub message: Option, + /// Optionally set the output target slate version (acceptable + /// down to the minimum slate version compatible with the current. If `None` the slate + /// is generated with the latest version. + pub target_slate_version: Option, +} + +impl Default for IssueInvoiceTxArgs { + fn default() -> IssueInvoiceTxArgs { + IssueInvoiceTxArgs { + dest_acct_name: None, + amount: 0, + message: None, + target_slate_version: None, + } + } +} + /// Fees in block to use for coinbase amount calculation #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BlockFees { diff --git a/libwallet/src/internal/selection.rs b/libwallet/src/internal/selection.rs index 38d33af7..927a04d7 100644 --- a/libwallet/src/internal/selection.rs +++ b/libwallet/src/internal/selection.rs @@ -166,9 +166,8 @@ where } /// Creates a new output in the wallet for the recipient, -/// returning the key of the fresh output and a closure -/// that actually performs the addition of the output to the -/// wallet +/// returning the key of the fresh output +/// Also creates a new transaction containing the output pub fn build_recipient_output( wallet: &mut T, slate: &mut Slate, diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index 52165a0f..d18f84ae 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -118,6 +118,7 @@ pub fn add_inputs_to_slate( parent_key_id: &Identifier, participant_id: usize, message: Option, + is_initator: bool, use_test_rng: bool, ) -> Result where @@ -158,16 +159,27 @@ where use_test_rng, )?; + if !is_initator { + // perform partial sig + let _ = slate.fill_round_2( + wallet.keychain(), + &context.sec_key, + &context.sec_nonce, + participant_id, + )?; + } + Ok(context) } -/// Add outputs to the slate, becoming the recipient +/// Add receiver output to the slate pub fn add_output_to_slate( wallet: &mut T, slate: &mut Slate, parent_key_id: &Identifier, participant_id: usize, message: Option, + is_initiator: bool, use_test_rng: bool, ) -> Result where @@ -189,18 +201,20 @@ where use_test_rng, )?; - // perform partial sig - let _ = slate.fill_round_2( - wallet.keychain(), - &context.sec_key, - &context.sec_nonce, - participant_id, - )?; + if !is_initiator { + // perform partial sig + let _ = slate.fill_round_2( + wallet.keychain(), + &context.sec_key, + &context.sec_nonce, + participant_id, + )?; + } Ok(context) } -/// Complete a transaction as the sender +/// Complete a transaction pub fn complete_tx( wallet: &mut T, slate: &mut Slate, @@ -218,6 +232,7 @@ where &context.sec_nonce, participant_id, )?; + // Final transaction can be built by anyone at this stage slate.finalize(wallet.keychain())?; Ok(()) @@ -260,7 +275,11 @@ where } /// Update the stored transaction (this update needs to happen when the TX is finalised) -pub fn update_stored_tx(wallet: &mut T, slate: &Slate) -> Result<(), Error> +pub fn update_stored_tx( + wallet: &mut T, + slate: &Slate, + is_invoiced: bool, +) -> Result<(), Error> where T: WalletBackend, C: NodeClient, @@ -271,7 +290,11 @@ where let mut tx = None; // don't want to assume this is the right tx, in case of self-sending for t in tx_vec { - if t.tx_type == TxLogEntryType::TxSent { + if t.tx_type == TxLogEntryType::TxSent && !is_invoiced { + tx = Some(t.clone()); + break; + } + if t.tx_type == TxLogEntryType::TxReceived && is_invoiced { tx = Some(t.clone()); break; } diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index fcf23cf6..90629abc 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -50,8 +50,8 @@ pub use crate::error::{Error, ErrorKind}; pub use crate::slate::{ParticipantData, ParticipantMessageData, Slate}; pub use crate::slate_versions::{SlateVersion, VersionedSlate}; pub use api_impl::types::{ - BlockFees, CbData, InitTxArgs, InitTxSendArgs, NodeHeightResult, OutputCommitMapping, - SendTXArgs, VersionInfo, + BlockFees, CbData, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, NodeHeightResult, + OutputCommitMapping, SendTXArgs, VersionInfo, }; pub use internal::restore::{check_repair, restore}; pub use types::{ diff --git a/libwallet/src/slate.rs b/libwallet/src/slate.rs index 9e158599..83283d57 100644 --- a/libwallet/src/slate.rs +++ b/libwallet/src/slate.rs @@ -333,7 +333,12 @@ impl Slate { Some(&self.pub_blind_sum(keychain.secp())?), &self.msg_to_sign()?, )?; - self.participant_data[participant_id].part_sig = Some(sig_part); + for i in 0..self.num_participants { + if self.participant_data[i].id == participant_id as u64 { + self.participant_data[i].part_sig = Some(sig_part); + break; + } + } Ok(()) }