From a83f92dfd33183d311cdbd03c30e06e23876655f Mon Sep 17 00:00:00 2001 From: jaspervdm Date: Thu, 19 Nov 2020 12:54:49 +0100 Subject: [PATCH] Late locking (experimental) (#530) * Late locking (experimental) * Error on fee change * Improve comments * Fix typo in comment * Add late locking flag to CLI command --- api/src/foreign_rpc.rs | 24 ++-- api/src/owner_rpc.rs | 40 ++++--- controller/src/command.rs | 2 + controller/tests/late_lock.rs | 163 ++++++++++++++++++++++++++++ libwallet/src/api_impl/foreign.rs | 6 + libwallet/src/api_impl/owner.rs | 105 ++++++++++++++---- libwallet/src/api_impl/types.rs | 9 +- libwallet/src/internal/selection.rs | 28 ++--- libwallet/src/internal/tx.rs | 108 +++++++++--------- libwallet/src/slate.rs | 89 ++++++--------- libwallet/src/types.rs | 37 ++++++- libwallet/tests/libwallet.rs | 8 +- src/bin/grin-wallet.yml | 4 + src/cmd/wallet_args.rs | 3 + 14 files changed, 433 insertions(+), 193 deletions(-) create mode 100644 controller/tests/late_lock.rs diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index 7e1c07b6..c6cf150b 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -163,17 +163,17 @@ pub trait ForeignRpc { } ], "id": "0436430c-2b02-624c-2032-570501212b00", - "off": "a4052c9200000001a6052c9200000002ed564fab50b75fc5ea32ce052fc9bebf", + "off": "a4f88ac429dee1d453ae33ed9f944417a52c7310477936e484fd83f0f22db483", "proof": { "raddr": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3", - "rsig": "feb98c299e8328ea6b73b06e756eb3094180c3cc5ba01ed82dce75a5bfbe14f3ea235d9d9c2cee3e72cd162d9e5b5b77fcb8e34ad6a40551924bb010c9afdc0f", + "rsig": "02357a13b304ba8e22f4896d5664b72ad6d1b824e88782e2b716686ea14ec47281ef5ee14c03ead84c3260f5b0c1529ad3ddae57f28f6b8b1b66532bfcb2ee0f", "saddr": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3" }, "sigs": [ { "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b54735cb9ed2f59fb457144f7b1c8226d08b54cbdd0eb7e6492950751b0bb54f9", - "xs": "03b0d73a044f1f9ae06cf96ef91593f121864b66bf7f7e7ac481b0ce61e39847fe" + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841babbc82d2e200efe3a3b70cdfbed5b4e1d2a87641d0c4f6a3d7e73b80facb8507", + "xs": "02e3c128e436510500616fef3f9a22b15ca015f407c8c5cf96c9059163c873828f" } ], "sta": "S2", @@ -209,13 +209,13 @@ pub trait ForeignRpc { "ver": "4:2", "id": "0436430c-2b02-624c-2032-570501212b00", "sta": "I2", - "off": "750dbf4fd43b7f4cfd68d2698a522f3ff6e6a00ad9895b33f1ec46493b837b49", + "off": "dbd68b83e4d6f9ebaebf179fdde3efd4309734124937bcb5f5a7df49120eca7f", "fee": "7000000", "sigs": [ { - "xs": "030152d2d72e2dba7c6086ad49a219d9ff0dfe0fd993dcaea22e058c210033ce93", + "xs": "0384a71f13c434e79b70f9a0649e34887f1c6caf3021636cfced448a146ca23c7a", "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841bdad934daa17db7e477c4eed90afed40d1117896df8c4f5861b6309a949878074" + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b6485299a0be2e4306663dcc78f1bc9d029c7be30e423558fe3f4fcfc9ad79a8e" } ], "coms": [ @@ -262,17 +262,17 @@ pub trait ForeignRpc { ], "fee": "7000000", "id": "0436430c-2b02-624c-2032-570501212b00", - "off": "750dbf4fd43b7f4cfd68d2698a522f3ff6e6a00ad9895b33f1ec46493b837b49", + "off": "4940f497462b7140383738c1665fa3989e6fff5d9f411a33aa04438f75d6de2c", "sigs": [ { "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841bdad934daa17db7e477c4eed90afed40d1117896df8c4f5861b6309a949878074", - "xs": "030152d2d72e2dba7c6086ad49a219d9ff0dfe0fd993dcaea22e058c210033ce93" + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b6485299a0be2e4306663dcc78f1bc9d029c7be30e423558fe3f4fcfc9ad79a8e", + "xs": "0384a71f13c434e79b70f9a0649e34887f1c6caf3021636cfced448a146ca23c7a" }, { "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b35fdfe55271f2ae73d75f58c70d1efb69b3384c7bc507d57e99e56de77e20874", - "xs": "033bbe2a419ea2e9d6810a8d66552e709d1783ca50759a44dbaf63fc79c0164c4c" + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b32617ea149fa6faf220f4212530517809e9ed2193f5e558b1de9d339414fda91", + "xs": "02e89cce4499ac1e9bb498dab9e3fab93cc40cd3d26c04a0292e00f4bf272499ec" } ], "sta": "I3", diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index d93dee65..da5e835b 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -395,7 +395,6 @@ pub trait OwnerRpc { "amt": "6000000000", "fee": "8000000", "id": "0436430c-2b02-624c-2032-570501212b00", - "off": "d202964900000000d302964900000000d402964900000000d502964900000000", "proof": { "raddr": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3", "saddr": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3" @@ -403,7 +402,7 @@ pub trait OwnerRpc { "sigs": [ { "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "xs": "023878ce845727f3a4ec76ca3f3db4b38a2d05d636b8c3632108b857fed63c96de" + "xs": "02e89cce4499ac1e9bb498dab9e3fab93cc40cd3d26c04a0292e00f4bf272499ec" } ], "sta": "S1", @@ -447,11 +446,10 @@ pub trait OwnerRpc { "Ok": { "amt": "6000000000", "id": "0436430c-2b02-624c-2032-570501212b00", - "off": "d202964900000000d302964900000000d402964900000000d502964900000000", "sigs": [ { "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "xs": "028e95921cc0d5be5922362265d352c9bdabe51a9e1502a3f0d4a10387f1893f40" + "xs": "02e89cce4499ac1e9bb498dab9e3fab93cc40cd3d26c04a0292e00f4bf272499ec" } ], "sta": "I1", @@ -529,12 +527,12 @@ pub trait OwnerRpc { ], "fee": "8000000", "id": "0436430c-2b02-624c-2032-570501212b00", - "off": "9b6e26e78b49c7136ce70334dd83acb89c78f6c54cfab64ba62e598837241d36", + "off": "bca108f36955448dacfc0464d75d010641f9e1a81709c27bc4404eea895c4f91", "sigs": [ { "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841bf1804f6fe8e55f5556bbd807fefbcd72b30d90cf708f8c56447acc63228274e6", - "xs": "023878ce845727f3a4ec76ca3f3db4b38a2d05d636b8c3632108b857fed63c96de" + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b20b8e09af72b6b7212c1bf6a4c17d56ce0048e05bb5309c1394a3d763a102a7e", + "xs": "02e3c128e436510500616fef3f9a22b15ca015f407c8c5cf96c9059163c873828f" } ], "sta": "I2", @@ -616,12 +614,12 @@ pub trait OwnerRpc { "ver": "4:2", "id": "0436430c-2b02-624c-2032-570501212b00", "sta": "S2", - "off": "a4052c9200000001a6052c9200000002ed564fab50b75fc5ea32ce052fc9bebf", + "off": "696a69136154775485782121887bb3c32487a8320551fdb9702ec2d333fe54ee", "sigs": [ { - "xs": "033bbe2a419ea2e9d6810a8d66552e709d1783ca50759a44dbaf63fc79c0164c4c", + "xs": "0384a71f13c434e79b70f9a0649e34887f1c6caf3021636cfced448a146ca23c7a", "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b35fdfe55271f2ae73d75f58c70d1efb69b3384c7bc507d57e99e56de77e20874" + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b6485299a0be2e4306663dcc78f1bc9d029c7be30e423558fe3f4fcfc9ad79a8e" } ], "coms": [ @@ -661,17 +659,17 @@ pub trait OwnerRpc { ], "fee": "7000000", "id": "0436430c-2b02-624c-2032-570501212b00", - "off": "750dbf4fd43b7f4cfd68d2698a522f3ff6e6a00ad9895b33f1ec46493b837b49", + "off": "4940f497462b7140383738c1665fa3989e6fff5d9f411a33aa04438f75d6de2c", "sigs": [ { "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b35fdfe55271f2ae73d75f58c70d1efb69b3384c7bc507d57e99e56de77e20874", - "xs": "033bbe2a419ea2e9d6810a8d66552e709d1783ca50759a44dbaf63fc79c0164c4c" + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b6485299a0be2e4306663dcc78f1bc9d029c7be30e423558fe3f4fcfc9ad79a8e", + "xs": "0384a71f13c434e79b70f9a0649e34887f1c6caf3021636cfced448a146ca23c7a" }, { "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841bdad934daa17db7e477c4eed90afed40d1117896df8c4f5861b6309a949878074", - "xs": "030152d2d72e2dba7c6086ad49a219d9ff0dfe0fd993dcaea22e058c210033ce93" + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b32617ea149fa6faf220f4212530517809e9ed2193f5e558b1de9d339414fda91", + "xs": "02e89cce4499ac1e9bb498dab9e3fab93cc40cd3d26c04a0292e00f4bf272499ec" } ], "sta": "S3", @@ -1626,11 +1624,11 @@ pub trait OwnerRpc { "result": { "Ok": { "amount": "60000000000", - "excess": "091f151170bfac881479bfb56c7012c52cd4ce4198ad661586374dd499925922fb", + "excess": "09eac5f5872fa5e08e0c29fd900f1b8f77ff3ad1d0d1c46aeb202cbf92363fe0af", "recipient_address": "tgrin10qlk22rxjap2ny8qltc2tl996kenxr3hhwuu6hrzs6tdq08yaqgqq6t83r", - "recipient_sig": "b9b1885a3f33297df32e1aa4db23220bd305da8ed92ff6873faf3ab2c116fea25e9d0e34bd4f567f022b88a37400821ffbcaec71c9a8c3a327c4626611886d0d", + "recipient_sig": "02868f2d2b983981f8f98043701687a8531ed2de564ea3df48e9e7e0229ccbe8359efe506896df2efbe3528e977252c50e4a41ca3cc9896e7c5a30bbb1d33604", "sender_address": "tgrin1xtxavwfgs48ckf3gk8wwgcndmn0nt4tvkl8a7ltyejjcy2mc6nfs9gm2lp", - "sender_sig": "611b92331e395c3d29871ac35b1fce78ec595e28ccbe8cc55452da40775e8e46d35a2e84eaffd986935da3275e34d46a8d777d02dabcf4339704c2a621da9700" + "sender_sig": "c511764f3f61ed3d1cbca9514df8bc6811fad5662b1cb0e0587b9c9e49db9f33183cce71af6cb24b507fabf525a2bc405c6e84e63a60334edff0b451ae5e6102" } } } @@ -1659,11 +1657,11 @@ pub trait OwnerRpc { "token": "d202964900000000d302964900000000d402964900000000d502964900000000", "proof": { "amount": "60000000000", - "excess": "091f151170bfac881479bfb56c7012c52cd4ce4198ad661586374dd499925922fb", + "excess": "09eac5f5872fa5e08e0c29fd900f1b8f77ff3ad1d0d1c46aeb202cbf92363fe0af", "recipient_address": "slatepack10qlk22rxjap2ny8qltc2tl996kenxr3hhwuu6hrzs6tdq08yaqgqnlumr7", - "recipient_sig": "b9b1885a3f33297df32e1aa4db23220bd305da8ed92ff6873faf3ab2c116fea25e9d0e34bd4f567f022b88a37400821ffbcaec71c9a8c3a327c4626611886d0d", + "recipient_sig": "02868f2d2b983981f8f98043701687a8531ed2de564ea3df48e9e7e0229ccbe8359efe506896df2efbe3528e977252c50e4a41ca3cc9896e7c5a30bbb1d33604", "sender_address": "slatepack1xtxavwfgs48ckf3gk8wwgcndmn0nt4tvkl8a7ltyejjcy2mc6nfskdvkdu", - "sender_sig": "611b92331e395c3d29871ac35b1fce78ec595e28ccbe8cc55452da40775e8e46d35a2e84eaffd986935da3275e34d46a8d777d02dabcf4339704c2a621da9700" + "sender_sig": "c511764f3f61ed3d1cbca9514df8bc6811fad5662b1cb0e0587b9c9e49db9f33183cce71af6cb24b507fabf525a2bc405c6e84e63a60334edff0b451ae5e6102" } }, "id": 1 diff --git a/controller/src/command.rs b/controller/src/command.rs index 198414c3..77cd9c6d 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -249,6 +249,7 @@ pub struct SendArgs { pub minimum_confirmations: u64, pub selection_strategy: String, pub estimate_selection_strategies: bool, + pub late_lock: bool, pub dest: String, pub change_outputs: usize, pub fluff: bool, @@ -307,6 +308,7 @@ where payment_proof_recipient_address: args.payment_proof_address.clone(), ttl_blocks: args.ttl_blocks, send_args: None, + late_lock: Some(args.late_lock), ..Default::default() }; let result = api.init_send_tx(m, init_args); diff --git a/controller/tests/late_lock.rs b/controller/tests/late_lock.rs new file mode 100644 index 00000000..0d02255d --- /dev/null +++ b/controller/tests/late_lock.rs @@ -0,0 +1,163 @@ +// Copyright 2020 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. + +//! Tests and experimentations with late locking +#[macro_use] +extern crate log; +extern crate grin_wallet_controller as wallet; +extern crate grin_wallet_impls as impls; +extern crate grin_wallet_libwallet as libwallet; + +use self::libwallet::{InitTxArgs, Slate}; +use impls::test_framework::{self, LocalWalletClient}; +use std::sync::atomic::Ordering; +use std::thread; +use std::time::Duration; + +#[macro_use] +mod common; +use common::{clean_output_dir, create_wallet_proxy, setup}; + +/// self send impl +fn late_lock_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { + // Create a new proxy to simulate server and wallet responses + let mut wallet_proxy = create_wallet_proxy(test_dir); + let chain = wallet_proxy.chain.clone(); + let stopper = wallet_proxy.running.clone(); + + // Create a new wallet test client, and set its queues to communicate with the + // proxy + create_wallet_and_add!( + client1, + wallet1, + mask1_i, + test_dir, + "wallet1", + None, + &mut wallet_proxy, + false + ); + let mask1 = (&mask1_i).as_ref(); + create_wallet_and_add!( + client2, + wallet2, + mask2_i, + test_dir, + "wallet2", + None, + &mut wallet_proxy, + false + ); + let mask2 = (&mask2_i).as_ref(); + + // Set the wallet proxy listener running + thread::spawn(move || { + if let Err(e) = wallet_proxy.run() { + error!("Wallet Proxy error: {}", e); + } + }); + + // add some accounts + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + api.create_account_path(m, "mining")?; + api.create_account_path(m, "listener")?; + Ok(()) + })?; + + // add some accounts + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { + api.create_account_path(m, "account1")?; + api.create_account_path(m, "account2")?; + Ok(()) + })?; + + // Get some mining done + { + wallet_inst!(wallet1, w); + w.set_parent_key_id_by_name("mining")?; + } + { + wallet_inst!(wallet2, w); + w.set_parent_key_id_by_name("account1")?; + } + + let bh = 10u64; + let _ = + test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); + + let mut slate = Slate::blank(2, false); + let amount = 60_000_000_000; + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + late_lock: Some(true), + ..Default::default() + }; + let slate_i = sender_api.init_send_tx(m, args)?; + println!("S1 SLATE: {}", slate_i); + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + println!("S2 SLATE: {}", slate); + + // Note we don't call `tx_lock_outputs` on the sender side here, + // as the outputs will only be locked during finalization + + slate = sender_api.finalize_tx(m, &slate)?; + println!("S3 SLATE: {}", slate); + + Ok(()) + })?; + + let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); + + // update/test contents of both accounts + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + let (wallet1_refreshed, wallet_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + print!( + "Wallet 1 amount: {}", + wallet_info.amount_currently_spendable + ); + Ok(()) + })?; + + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { + let (wallet2_refreshed, wallet_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet2_refreshed); + println!( + "Wallet 2 amount: {}", + wallet_info.amount_currently_spendable + ); + Ok(()) + })?; + + // let logging finish + stopper.store(false, Ordering::Relaxed); + thread::sleep(Duration::from_millis(200)); + Ok(()) +} + +#[test] +fn late_lock() { + let test_dir = "test_output/late_lock"; + setup(test_dir); + if let Err(e) = late_lock_test_impl(test_dir) { + panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); + } + clean_output_dir(test_dir); +} diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index c707a74a..99f17ba4 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -105,6 +105,9 @@ where use_test_rng, )?; + // Add our contribution to the offset + ret_slate.adjust_offset(&keychain, &context)?; + let excess = ret_slate.calc_excess(keychain.secp())?; if let Some(ref mut p) = ret_slate.payment_proof { @@ -143,6 +146,9 @@ where if sl.state == SlateState::Invoice2 { check_ttl(w, &sl)?; + // Add our contribution to the offset + sl.adjust_offset(&w.keychain(keychain_mask)?, &context)?; + let mut temp_ctx = context.clone(); temp_ctx.sec_key = context.initial_sec_key.clone(); temp_ctx.sec_nonce = context.initial_sec_nonce.clone(); diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 904a3221..7edeef7c 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -457,9 +457,9 @@ where C: NodeClient + 'a, K: Keychain + 'a, { - let parent_key_id = match args.src_acct_name { + let parent_key_id = match &args.src_acct_name { Some(d) => { - let pm = w.get_acct_path(d)?; + let pm = w.get_acct_path(d.clone())?; match pm { Some(p) => p.path, None => w.parent_key_id(), @@ -500,19 +500,31 @@ where } let height = w.w2n_client().get_chain_tip()?.0; - let mut context = tx::add_inputs_to_slate( - &mut *w, - keychain_mask, - &mut slate, - height, - args.minimum_confirmations, - args.max_outputs as usize, - args.num_change_outputs as usize, - args.selection_strategy_is_use_all, - &parent_key_id, - true, - use_test_rng, - )?; + let mut context = if args.late_lock.unwrap_or(false) { + tx::create_late_lock_context( + &mut *w, + keychain_mask, + &mut slate, + height, + &args, + &parent_key_id, + use_test_rng, + )? + } else { + tx::add_inputs_to_slate( + &mut *w, + keychain_mask, + &mut slate, + height, + args.minimum_confirmations, + args.max_outputs as usize, + args.num_change_outputs as usize, + args.selection_strategy_is_use_all, + &parent_key_id, + true, + use_test_rng, + )? + }; // Payment Proof, add addresses to slate and save address // TODO: Note we only use single derivation path for now, @@ -667,6 +679,19 @@ where )?; let keychain = w.keychain(keychain_mask)?; + + // Add our contribution to the offset + if context_res.is_ok() { + // Self sending: don't correct for inputs and outputs + // here, as we will do it during finalization. + let mut tmp_context = context.clone(); + tmp_context.input_ids.clear(); + tmp_context.output_ids.clear(); + ret_slate.adjust_offset(&keychain, &tmp_context)?; + } else { + ret_slate.adjust_offset(&keychain, &context)?; + } + // needs to be stored as we're removing sig data for return trip. this needs to be present // when locking transaction context and updating tx log with excess later context.calculated_excess = Some(ret_slate.calc_excess(keychain.secp())?); @@ -685,7 +710,6 @@ where } } - tx::sub_inputs_from_offset(&mut *w, keychain_mask, &context, &mut ret_slate)?; selection::repopulate_tx(&mut *w, keychain_mask, &mut ret_slate, &context, false)?; // Save the aggsig context in our DB for when we @@ -754,16 +778,51 @@ where { let mut sl = slate.clone(); check_ttl(w, &sl)?; - let context = w.get_private_context(keychain_mask, sl.id.as_bytes())?; + let mut context = w.get_private_context(keychain_mask, sl.id.as_bytes())?; + let keychain = w.keychain(keychain_mask)?; let parent_key_id = w.parent_key_id(); - // since we're now actually inserting our inputs, pick an offset and adjust - // our contribution to the excess by offset amount - // TODO: Post HF3, this should allow for inputs to be picked at this stage - // as opposed to locking them prior to this stage, as the excess to this point - // will just be the change output + if let Some(args) = context.late_lock_args.take() { + // Transaction was late locked, select inputs+change now + // and insert into original context + + let current_height = w.w2n_client().get_chain_tip()?.0; + let mut temp_sl = + tx::new_tx_slate(&mut *w, context.amount, false, 2, false, args.ttl_blocks)?; + let temp_context = selection::build_send_tx( + w, + &keychain, + keychain_mask, + &mut temp_sl, + current_height, + args.minimum_confirmations, + args.max_outputs as usize, + args.num_change_outputs as usize, + args.selection_strategy_is_use_all, + Some(context.fee), + parent_key_id.clone(), + false, + true, + )?; + + // Add inputs and outputs to original context + context.input_ids = temp_context.input_ids; + context.output_ids = temp_context.output_ids; + + // Store the updated context + { + let mut batch = w.batch(keychain_mask)?; + batch.save_private_context(sl.id.as_bytes(), &context)?; + batch.commit()?; + } + + // Now do the actual locking + tx_lock_outputs(w, keychain_mask, &sl)?; + } + + // Add our contribution to the offset + sl.adjust_offset(&keychain, &context)?; - tx::sub_inputs_from_offset(&mut *w, keychain_mask, &context, &mut sl)?; selection::repopulate_tx(&mut *w, keychain_mask, &mut sl, &context, true)?; tx::complete_tx(&mut *w, keychain_mask, &mut sl, &context)?; diff --git a/libwallet/src/api_impl/types.rs b/libwallet/src/api_impl/types.rs index 33bee5b9..bcd8409c 100644 --- a/libwallet/src/api_impl/types.rs +++ b/libwallet/src/api_impl/types.rs @@ -25,7 +25,7 @@ use crate::SlatepackAddress; use ed25519_dalek::Signature as DalekSignature; /// V2 Init / Send TX API Args -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct InitTxArgs { /// The human readable account name from which to draw outputs /// for the transaction, overriding whatever the active account is as set via the @@ -71,6 +71,10 @@ pub struct InitTxArgs { /// 'true', the amount field in the slate will contain the total amount locked, not the provided /// transaction amount pub estimate_only: Option, + /// EXPERIMENTAL: if flagged, create the transaction as late-locked, i.e. don't select actual + /// inputs until just before finalization + #[serde(default)] + pub late_lock: Option, /// Sender arguments. If present, the underlying function will also attempt to send the /// transaction to a destination and optionally finalize the result pub send_args: Option, @@ -78,7 +82,7 @@ pub struct InitTxArgs { /// Send TX API Args, for convenience functionality that inits the transaction and sends /// in one go -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct InitTxSendArgs { /// The destination, contents will depend on the particular method pub dest: String, @@ -103,6 +107,7 @@ impl Default for InitTxArgs { ttl_blocks: None, estimate_only: Some(false), payment_proof_recipient_address: None, + late_lock: Some(false), send_args: None, } } diff --git a/libwallet/src/internal/selection.rs b/libwallet/src/internal/selection.rs index 95aab663..8fa95c1f 100644 --- a/libwallet/src/internal/selection.rs +++ b/libwallet/src/internal/selection.rs @@ -46,8 +46,10 @@ pub fn build_send_tx<'a, T: ?Sized, C, K>( max_outputs: usize, change_outputs: usize, selection_strategy_is_use_all: bool, + fixed_fee: Option, parent_key_id: Identifier, use_test_nonce: bool, + is_initiator: bool, ) -> Result where T: WalletBackend<'a, C, K>, @@ -67,17 +69,20 @@ where false, )?; + if fixed_fee.map(|f| fee != f).unwrap_or(false) { + return Err(ErrorKind::Fee("The initially selected fee is not sufficient".into()).into()); + } + // Update the fee on the slate so we account for this when building the tx. slate.fee = fee; - - let blinding = slate.add_transaction_elements(keychain, &ProofBuilder::new(keychain), elems)?; + slate.add_transaction_elements(keychain, &ProofBuilder::new(keychain), elems)?; // Create our own private context let mut context = Context::new( keychain.secp(), - blinding.secret_key(&keychain.secp()).unwrap(), &parent_key_id, use_test_nonce, + is_initiator, ); context.fee = fee; @@ -237,6 +242,7 @@ pub fn build_recipient_output<'a, T: ?Sized, C, K>( current_height: u64, parent_key_id: Identifier, use_test_rng: bool, + is_initiator: bool, ) -> Result<(Identifier, Context, TxLogEntry), Error> where T: WalletBackend<'a, C, K>, @@ -251,21 +257,14 @@ where let height = current_height; let slate_id = slate.id; - let blinding = slate.add_transaction_elements( + slate.add_transaction_elements( &keychain, &ProofBuilder::new(&keychain), vec![build::output(amount, key_id.clone())], )?; // Add blinding sum to our context - let mut context = Context::new( - keychain.secp(), - blinding - .secret_key(wallet.keychain(keychain_mask)?.secp()) - .unwrap(), - &parent_key_id, - use_test_rng, - ); + let mut context = Context::new(keychain.secp(), &parent_key_id, use_test_rng, is_initiator); context.add_output(&key_id, &None, amount); context.amount = amount; @@ -398,9 +397,6 @@ where // recipient should double check the fee calculation and not blindly trust the // sender - // TODO - Is it safe to spend without a change output? (1 input -> 1 output) - // TODO - Does this not potentially reveal the senders private key? - // // First attempt to spend without change let mut fee = tx_fee(coins.len(), 1, 1, None); let mut total: u64 = coins.iter().map(|c| c.value).sum(); @@ -665,7 +661,7 @@ where let keychain = wallet.keychain(keychain_mask)?; // restore my signature data - slate.add_participant_info(&keychain, &context.sec_key, &context.sec_nonce, None)?; + slate.add_participant_info(&keychain, &context, None)?; let mut parts = vec![]; for (id, _, value) in &context.get_inputs() { diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index 7f18c5b7..f4cc37a5 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -20,7 +20,7 @@ use uuid::Uuid; use crate::grin_core::consensus::valid_header_version; use crate::grin_core::core::HeaderVersion; -use crate::grin_keychain::{BlindSum, BlindingFactor, Identifier, Keychain, SwitchCommitmentType}; +use crate::grin_keychain::{Identifier, Keychain}; use crate::grin_util::secp::key::SecretKey; use crate::grin_util::secp::pedersen; use crate::grin_util::Mutex; @@ -28,6 +28,7 @@ use crate::internal::{selection, updater}; use crate::slate::Slate; use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType, WalletBackend}; use crate::util::OnionV3Address; +use crate::InitTxArgs; use crate::{address, Error, ErrorKind}; use ed25519_dalek::Keypair as DalekKeypair; use ed25519_dalek::PublicKey as DalekPublicKey; @@ -175,19 +176,16 @@ where max_outputs, num_change_outputs, selection_strategy_is_use_all, + None, parent_key_id.clone(), use_test_rng, + is_initiator, )?; // Generate a kernel offset and subtract from our context's secret key. Store // the offset in the slate's transaction kernel, and adds our public key // information to the slate - slate.fill_round_1( - &wallet.keychain(keychain_mask)?, - &mut context.sec_key, - &context.sec_nonce, - use_test_rng, - )?; + slate.fill_round_1(&wallet.keychain(keychain_mask)?, &mut context)?; context.initial_sec_key = context.sec_key.clone(); @@ -227,15 +225,11 @@ where current_height, parent_key_id.clone(), use_test_rng, + is_initiator, )?; // fill public keys - slate.fill_round_1( - &keychain, - &mut context.sec_key, - &context.sec_nonce, - use_test_rng, - )?; + slate.fill_round_1(&keychain, &mut context)?; context.initial_sec_key = context.sec_key.clone(); @@ -252,6 +246,53 @@ where Ok(context) } +/// Create context, without adding inputs to slate +pub fn create_late_lock_context<'a, T: ?Sized, C, K>( + wallet: &mut T, + keychain_mask: Option<&SecretKey>, + slate: &mut Slate, + current_height: u64, + init_tx_args: &InitTxArgs, + parent_key_id: &Identifier, + use_test_rng: bool, +) -> Result +where + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ + // sender should always refresh outputs + updater::refresh_outputs(wallet, keychain_mask, parent_key_id, false)?; + + // we're just going to run a selection to get the potential fee, + // but this won't be locked + let (_coins, _total, _amount, fee) = selection::select_coins_and_fee( + wallet, + init_tx_args.amount, + current_height, + init_tx_args.minimum_confirmations, + init_tx_args.max_outputs as usize, + init_tx_args.num_change_outputs as usize, + init_tx_args.selection_strategy_is_use_all, + &parent_key_id, + )?; + slate.fee = fee; + + let keychain = wallet.keychain(keychain_mask)?; + + // Create our own private context + let mut context = Context::new(keychain.secp(), &parent_key_id, use_test_rng, true); + context.fee = slate.fee; + context.amount = slate.amount; + context.late_lock_args = Some(init_tx_args.clone()); + + // Generate a blinding factor for the tx and add + // public key info to the slate + slate.fill_round_1(&wallet.keychain(keychain_mask)?, &mut context)?; + + Ok(context) +} + /// Complete a transaction pub fn complete_tx<'a, T: ?Sized, C, K>( wallet: &mut T, @@ -396,47 +437,6 @@ where Ok(()) } -/// Update the transaction's offset by subtracting the inputs -/// stored in the context -pub fn sub_inputs_from_offset<'a, T: ?Sized, C, K>( - wallet: &mut T, - keychain_mask: Option<&SecretKey>, - context: &Context, - slate: &mut Slate, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - let k = wallet.keychain(keychain_mask)?; - // Offset has been created and adjusted - // Now subtract sum total of all my inputs from the offset - let new_offset = k.blind_sum( - &context - .get_inputs() - .iter() - .map( - |i| match k.derive_key(i.2, &i.0, SwitchCommitmentType::Regular) { - Ok(k) => BlindingFactor::from_secret_key(k), - Err(e) => { - error!("Error deriving key for offset: {}", e); - BlindingFactor::zero() - } - }, - ) - .fold( - BlindSum::new().add_blinding_factor(slate.offset.clone()), - |acc, x| acc.sub_blinding_factor(x.clone()), - ), - )?; - - slate.offset = new_offset.clone(); - slate.tx_or_err_mut()?.offset = new_offset; - - Ok(()) -} - pub fn payment_proof_message( amount: u64, kernel_commitment: &pedersen::Commitment, diff --git a/libwallet/src/slate.rs b/libwallet/src/slate.rs index 2fe485ba..5cc51a37 100644 --- a/libwallet/src/slate.rs +++ b/libwallet/src/slate.rs @@ -24,15 +24,13 @@ use crate::grin_core::core::transaction::{ use crate::grin_core::core::verifier_cache::LruVerifierCache; use crate::grin_core::libtx::{aggsig, build, proof::ProofBuild, tx_fee}; use crate::grin_core::map_vec; -use crate::grin_keychain::{BlindSum, BlindingFactor, Keychain}; +use crate::grin_keychain::{BlindSum, BlindingFactor, Keychain, SwitchCommitmentType}; use crate::grin_util::secp::key::{PublicKey, SecretKey}; use crate::grin_util::secp::pedersen::Commitment; use crate::grin_util::secp::Signature; use crate::grin_util::{secp, static_secp_instance, RwLock}; use ed25519_dalek::PublicKey as DalekPublicKey; use ed25519_dalek::Signature as DalekSignature; -use rand::rngs::mock::StepRng; -use rand::thread_rng; use serde::ser::{Serialize, Serializer}; use serde_json; use std::fmt; @@ -45,6 +43,7 @@ use crate::slate_versions::v4::{ }; use crate::slate_versions::VersionedSlate; use crate::slate_versions::{CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION}; +use crate::Context; #[derive(Debug, Clone)] pub struct PaymentInfo { @@ -337,20 +336,11 @@ impl Slate { /// Completes callers part of round 1, adding public key info /// to the slate - pub fn fill_round_1( - &mut self, - keychain: &K, - sec_key: &mut SecretKey, - sec_nonce: &SecretKey, - use_test_rng: bool, - ) -> Result<(), Error> + pub fn fill_round_1(&mut self, keychain: &K, context: &mut Context) -> Result<(), Error> where K: Keychain, { - // Always choose my part of the offset, and subtract from my excess - self.generate_offset(keychain, sec_key, use_test_rng)?; - self.add_participant_info(keychain, &sec_key, &sec_nonce, None)?; - Ok(()) + self.add_participant_info(keychain, context, None) } // Build kernel features based on variant and associated data. @@ -455,7 +445,7 @@ impl Slate { } /// Return the sum of public blinding factors - fn pub_blind_sum(&self, secp: &secp::Secp256k1) -> Result { + pub fn pub_blind_sum(&self, secp: &secp::Secp256k1) -> Result { let pub_blinds: Vec<&PublicKey> = self .participant_data .iter() @@ -488,16 +478,15 @@ impl Slate { pub fn add_participant_info( &mut self, keychain: &K, - sec_key: &SecretKey, - sec_nonce: &SecretKey, + context: &Context, part_sig: Option, ) -> Result<(), Error> where K: Keychain, { // Add our public key and nonce to the slate - let pub_key = PublicKey::from_secret_key(keychain.secp(), &sec_key)?; - let pub_nonce = PublicKey::from_secret_key(keychain.secp(), &sec_nonce)?; + let pub_key = PublicKey::from_secret_key(keychain.secp(), &context.sec_key)?; + let pub_nonce = PublicKey::from_secret_key(keychain.secp(), &context.sec_nonce)?; let mut part_sig = part_sig; // Remove if already here and replace @@ -524,46 +513,32 @@ impl Slate { Ok(()) } - /// Somebody involved needs to generate an offset with their private key - /// For now, we'll have the transaction initiator be responsible for it - /// Return offset private key for the participant to use later in the - /// transaction - pub fn generate_offset( + /// Add our contribution to the offset based on the excess, inputs and outputs + pub fn adjust_offset( &mut self, keychain: &K, - sec_key: &mut SecretKey, - use_test_rng: bool, - ) -> Result<(), Error> - where - K: Keychain, - { - // Generate a random kernel offset here - // and subtract it from the blind_sum so we create - // the aggsig context with the "split" key - let my_offset = match use_test_rng { - false => { - BlindingFactor::from_secret_key(SecretKey::new(&keychain.secp(), &mut thread_rng())) - } - true => { - // allow for consistent test results - let mut test_rng = StepRng::new(1_234_567_890_u64, 1); - BlindingFactor::from_secret_key(SecretKey::new(&keychain.secp(), &mut test_rng)) - } - }; - - let total_offset = keychain.blind_sum( - &BlindSum::new() - .add_blinding_factor(self.offset.clone()) - .add_blinding_factor(my_offset.clone()), - )?; - self.offset = total_offset; - - let adjusted_offset = keychain.blind_sum( - &BlindSum::new() - .add_blinding_factor(BlindingFactor::from_secret_key(sec_key.clone())) - .sub_blinding_factor(my_offset), - )?; - *sec_key = adjusted_offset.secret_key(&keychain.secp())?; + context: &Context, + ) -> Result<(), Error> { + let mut sum = BlindSum::new() + .add_blinding_factor(self.offset.clone()) + .sub_blinding_factor(BlindingFactor::from_secret_key( + context.initial_sec_key.clone(), + )); + for (id, _, amount) in &context.input_ids { + sum = sum.sub_blinding_factor(BlindingFactor::from_secret_key(keychain.derive_key( + *amount, + id, + SwitchCommitmentType::Regular, + )?)); + } + for (id, _, amount) in &context.output_ids { + sum = sum.add_blinding_factor(BlindingFactor::from_secret_key(keychain.derive_key( + *amount, + id, + SwitchCommitmentType::Regular, + )?)); + } + self.offset = keychain.blind_sum(&sum)?; Ok(()) } diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index df8b59b7..27fa7126 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -27,10 +27,13 @@ use crate::grin_util::secp::key::{PublicKey, SecretKey}; use crate::grin_util::secp::{self, pedersen, Secp256k1}; use crate::grin_util::{ToHex, ZeroingString}; use crate::slate_versions::ser as dalek_ser; +use crate::InitTxArgs; use chrono::prelude::*; use ed25519_dalek::PublicKey as DalekPublicKey; use ed25519_dalek::Signature as DalekSignature; use failure::ResultExt; +use rand::rngs::mock::StepRng; +use rand::thread_rng; use serde; use serde_json; use std::collections::HashMap; @@ -554,6 +557,9 @@ pub struct Context { pub fee: u64, /// Payment proof sender address derivation path, if needed pub payment_proof_derivation_index: Option, + /// If late-locking, store my tranasction creation prefs + /// for later + pub late_lock_args: Option, /// for invoice I2 Only, store the tx excess so we can /// remove it from the slate on return pub calculated_excess: Option, @@ -562,26 +568,49 @@ pub struct Context { impl Context { /// Create a new context with defaults pub fn new( + secp: &secp::Secp256k1, + parent_key_id: &Identifier, + use_test_rng: bool, + is_initiator: bool, + ) -> Self { + let sec_key = match use_test_rng { + false => SecretKey::new(secp, &mut thread_rng()), + true => { + // allow for consistent test results + let mut test_rng = if is_initiator { + StepRng::new(1_234_567_890_u64, 1) + } else { + StepRng::new(1_234_567_891_u64, 1) + }; + SecretKey::new(secp, &mut test_rng) + } + }; + Self::with_excess(secp, sec_key, parent_key_id, use_test_rng) + } + + /// Create a new context with a specific excess + pub fn with_excess( secp: &secp::Secp256k1, sec_key: SecretKey, parent_key_id: &Identifier, use_test_rng: bool, - ) -> Context { + ) -> Self { let sec_nonce = match use_test_rng { false => aggsig::create_secnonce(secp).unwrap(), true => SecretKey::from_slice(secp, &[1; 32]).unwrap(), }; - Context { + Self { parent_key_id: parent_key_id.clone(), sec_key: sec_key.clone(), sec_nonce: sec_nonce.clone(), - initial_sec_key: sec_key.clone(), - initial_sec_nonce: sec_nonce.clone(), + initial_sec_key: sec_key, + initial_sec_nonce: sec_nonce, input_ids: vec![], output_ids: vec![], amount: 0, fee: 0, payment_proof_derivation_index: None, + late_lock_args: None, calculated_excess: None, } } diff --git a/libwallet/tests/libwallet.rs b/libwallet/tests/libwallet.rs index 8a692920..761c01bf 100644 --- a/libwallet/tests/libwallet.rs +++ b/libwallet/tests/libwallet.rs @@ -74,7 +74,7 @@ fn aggsig_sender_receiver_interaction() { let blind = blinding_factor.secret_key(&keychain.secp()).unwrap(); - s_cx = Context::new(&keychain.secp(), blind, &parent, false); + s_cx = Context::with_excess(&keychain.secp(), blind, &parent, false); s_cx.get_public_keys(&keychain.secp()) }; @@ -88,7 +88,7 @@ fn aggsig_sender_receiver_interaction() { // let blind = blind_sum.secret_key(&keychain.secp())?; let blind = keychain.derive_key(0, &key_id, switch).unwrap(); - rx_cx = Context::new(&keychain.secp(), blind, &parent, false); + rx_cx = Context::with_excess(&keychain.secp(), blind, &parent, false); let (pub_excess, pub_nonce) = rx_cx.get_public_keys(&keychain.secp()); rx_cx.add_output(&key_id, &None, 0); @@ -293,7 +293,7 @@ fn aggsig_sender_receiver_interaction_offset() { let blind = blinding_factor.secret_key(&keychain.secp()).unwrap(); - s_cx = Context::new(&keychain.secp(), blind, &parent, false); + s_cx = Context::with_excess(&keychain.secp(), blind, &parent, false); s_cx.get_public_keys(&keychain.secp()) }; @@ -306,7 +306,7 @@ fn aggsig_sender_receiver_interaction_offset() { let blind = keychain.derive_key(0, &key_id, switch).unwrap(); - rx_cx = Context::new(&keychain.secp(), blind, &parent, false); + rx_cx = Context::with_excess(&keychain.secp(), blind, &parent, false); let (pub_excess, pub_nonce) = rx_cx.get_public_keys(&keychain.secp()); rx_cx.add_output(&key_id, &None, 0); diff --git a/src/bin/grin-wallet.yml b/src/bin/grin-wallet.yml index 775a6370..9187e859 100644 --- a/src/bin/grin-wallet.yml +++ b/src/bin/grin-wallet.yml @@ -112,6 +112,10 @@ subcommands: help: Estimates all possible Coin/Output selection strategies. short: e long: estimate-selection + - late_lock: + help: EXPERIMENTAL - Do not lock the coins immediately, instead only lock them during finalization. + short: l + long: late-lock - change_outputs: help: Number of change outputs to generate (mainly for testing) short: o diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index f2f58dd5..f0eb300e 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -479,6 +479,8 @@ pub fn parse_send_args(args: &ArgMatches) -> Result d, @@ -533,6 +535,7 @@ pub fn parse_send_args(args: &ArgMatches) -> Result