mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-02-01 08:51:09 +03:00
Update transactions via kernel where necessary (#220)
* add test for no change output scenario * rustfmt * add kernel lookup functionality to transaction retrievals * rustfmt * updates and fixes for no-change invoice workflow, test implementations * rustfmt
This commit is contained in:
parent
26ad378686
commit
07758f55d3
14 changed files with 424 additions and 28 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -904,6 +904,7 @@ dependencies = [
|
|||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -232,6 +232,8 @@ pub trait OwnerRpc: Sync + Send {
|
|||
"creation_ts": "2019-01-15T16:01:26Z",
|
||||
"fee": null,
|
||||
"id": 0,
|
||||
"kernel_excess": null,
|
||||
"kernel_lookup_min_height": null,
|
||||
"messages": null,
|
||||
"num_inputs": 0,
|
||||
"num_outputs": 1,
|
||||
|
@ -248,6 +250,8 @@ pub trait OwnerRpc: Sync + Send {
|
|||
"creation_ts": "2019-01-15T16:01:26Z",
|
||||
"fee": null,
|
||||
"id": 1,
|
||||
"kernel_excess": null,
|
||||
"kernel_lookup_min_height": null,
|
||||
"messages": null,
|
||||
"num_inputs": 0,
|
||||
"num_outputs": 1,
|
||||
|
|
|
@ -255,6 +255,8 @@ pub trait OwnerRpcS {
|
|||
"creation_ts": "2019-01-15T16:01:26Z",
|
||||
"fee": null,
|
||||
"id": 0,
|
||||
"kernel_excess": null,
|
||||
"kernel_lookup_min_height": null,
|
||||
"messages": null,
|
||||
"num_inputs": 0,
|
||||
"num_outputs": 1,
|
||||
|
@ -271,6 +273,8 @@ pub trait OwnerRpcS {
|
|||
"creation_ts": "2019-01-15T16:01:26Z",
|
||||
"fee": null,
|
||||
"id": 1,
|
||||
"kernel_excess": null,
|
||||
"kernel_lookup_min_height": null,
|
||||
"messages": null,
|
||||
"num_inputs": 0,
|
||||
"num_outputs": 1,
|
||||
|
|
168
controller/tests/no_change.rs
Normal file
168
controller/tests/no_change.rs
Normal file
|
@ -0,0 +1,168 @@
|
|||
// 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 sender transaction with no change output
|
||||
#[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_libwallet as libwallet;
|
||||
use impls::test_framework::{self, LocalWalletClient};
|
||||
use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallet_proxy, setup};
|
||||
|
||||
fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
let mut wallet_proxy = create_wallet_proxy(test_dir);
|
||||
let chain = wallet_proxy.chain.clone();
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
// few values to keep things shorter
|
||||
let reward = core::consensus::REWARD;
|
||||
|
||||
// Mine into wallet 1
|
||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 4, false);
|
||||
let fee = core::libtx::tx_fee(1, 1, 1, None);
|
||||
|
||||
// send a single block's worth of transactions with minimal strategy
|
||||
let mut slate = Slate::blank(2);
|
||||
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: None,
|
||||
amount: reward - fee,
|
||||
minimum_confirmations: 2,
|
||||
max_outputs: 500,
|
||||
num_change_outputs: 1,
|
||||
selection_strategy_is_use_all: false,
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.init_send_tx(m, args)?;
|
||||
slate = client1.send_tx_slate_direct("wallet2", &slate)?;
|
||||
api.tx_lock_outputs(m, &slate, 0)?;
|
||||
slate = api.finalize_tx(m, &slate)?;
|
||||
api.post_tx(m, &slate.tx, false)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Refresh and check transaction log for wallet 1
|
||||
wallet::controller::owner_single_use(wallet1.clone(), mask2, |api, m| {
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?;
|
||||
assert!(refreshed);
|
||||
let tx = txs[0].clone();
|
||||
println!("{:?}", tx);
|
||||
assert!(tx.confirmed);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// ensure invoice TX works as well with no change
|
||||
wallet::controller::owner_single_use(wallet2.clone(), mask2, |api, m| {
|
||||
// Wallet 2 inititates an invoice transaction, requesting payment
|
||||
let args = IssueInvoiceTxArgs {
|
||||
amount: reward - fee,
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.issue_invoice_tx(m, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
||||
// 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: false,
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.process_invoice_tx(m, &slate, args)?;
|
||||
api.tx_lock_outputs(m, &slate, 0)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// wallet 2 finalizes and posts
|
||||
wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| {
|
||||
// Wallet 2 receives the invoice transaction
|
||||
slate = api.finalize_invoice_tx(&slate)?;
|
||||
Ok(())
|
||||
})?;
|
||||
wallet::controller::owner_single_use(wallet2.clone(), mask1, |api, m| {
|
||||
api.post_tx(m, &slate.tx, false)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Refresh and check transaction log for wallet 1
|
||||
wallet::controller::owner_single_use(wallet1.clone(), mask2, |api, m| {
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?;
|
||||
assert!(refreshed);
|
||||
for tx in txs {
|
||||
println!("{:?}", tx);
|
||||
assert!(tx.confirmed);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_change() {
|
||||
let test_dir = "test_output/no_change";
|
||||
setup(test_dir);
|
||||
if let Err(e) = no_change_test_impl(test_dir) {
|
||||
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
|
||||
}
|
||||
clean_output_dir(test_dir);
|
||||
}
|
|
@ -15,6 +15,7 @@ failure = "0.1"
|
|||
failure_derive = "0.1"
|
||||
futures = "0.1"
|
||||
rand = "0.5"
|
||||
semver = "0.9"
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
serde_json = "1"
|
||||
|
|
|
@ -17,14 +17,17 @@
|
|||
|
||||
use futures::{stream, Stream};
|
||||
|
||||
use crate::api::LocatedTxKernel;
|
||||
use crate::core::core::TxKernel;
|
||||
use crate::libwallet::{NodeClient, NodeVersionInfo, TxWrapper};
|
||||
use semver::Version;
|
||||
use std::collections::HashMap;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::api;
|
||||
use crate::libwallet;
|
||||
use crate::util;
|
||||
use crate::util::secp::pedersen;
|
||||
use crate::util::{self, to_hex};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HTTPNodeClient {
|
||||
|
@ -127,6 +130,55 @@ impl NodeClient for HTTPNodeClient {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get kernel implementation
|
||||
fn get_kernel(
|
||||
&mut self,
|
||||
excess: &pedersen::Commitment,
|
||||
min_height: Option<u64>,
|
||||
max_height: Option<u64>,
|
||||
) -> Result<Option<(TxKernel, u64, u64)>, libwallet::Error> {
|
||||
let version = self
|
||||
.get_version_info()
|
||||
.ok_or(libwallet::ErrorKind::ClientCallback(
|
||||
"Unable to get version".into(),
|
||||
))?;
|
||||
let version = Version::parse(&version.node_version)
|
||||
.map_err(|_| libwallet::ErrorKind::ClientCallback("Unable to parse version".into()))?;
|
||||
if version <= Version::new(2, 0, 0) {
|
||||
return Err(libwallet::ErrorKind::ClientCallback(
|
||||
"Kernel lookup not supported by node, please upgrade it".into(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let mut query = String::new();
|
||||
if let Some(h) = min_height {
|
||||
query += &format!("min_height={}", h);
|
||||
}
|
||||
if let Some(h) = max_height {
|
||||
if query.len() > 0 {
|
||||
query += "&";
|
||||
}
|
||||
query += &format!("max_height={}", h);
|
||||
}
|
||||
if query.len() > 0 {
|
||||
query.insert_str(0, "?");
|
||||
}
|
||||
|
||||
let url = format!(
|
||||
"{}/v1/chain/kernels/{}{}",
|
||||
self.node_url(),
|
||||
to_hex(excess.0.to_vec()),
|
||||
query
|
||||
);
|
||||
let res: Option<LocatedTxKernel> = api::client::get(url.as_str(), self.node_api_secret())
|
||||
.map_err(|e| {
|
||||
libwallet::ErrorKind::ClientCallback(format!("Kernel lookup: {}", e))
|
||||
})?;
|
||||
|
||||
Ok(res.map(|k| (k.tx_kernel, k.height, k.mmr_index)))
|
||||
}
|
||||
|
||||
/// Retrieve outputs from node
|
||||
fn get_outputs_from_node(
|
||||
&self,
|
||||
|
|
|
@ -52,6 +52,23 @@ fn get_output_local(chain: &chain::Chain, commit: &pedersen::Commitment) -> Opti
|
|||
None
|
||||
}
|
||||
|
||||
/// Get a kernel from the chain locally
|
||||
fn get_kernel_local(
|
||||
chain: Arc<chain::Chain>,
|
||||
excess: &pedersen::Commitment,
|
||||
min_height: Option<u64>,
|
||||
max_height: Option<u64>,
|
||||
) -> Option<api::LocatedTxKernel> {
|
||||
chain
|
||||
.get_kernel_height(&excess, min_height, max_height)
|
||||
.unwrap()
|
||||
.map(|(tx_kernel, height, mmr_index)| api::LocatedTxKernel {
|
||||
tx_kernel,
|
||||
height,
|
||||
mmr_index,
|
||||
})
|
||||
}
|
||||
|
||||
/// get output listing traversing pmmr from local
|
||||
fn get_outputs_by_pmmr_index_local(
|
||||
chain: Arc<chain::Chain>,
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
//! so that wallet API can be fully exercised
|
||||
//! Operates directly on a chain instance
|
||||
|
||||
use crate::api;
|
||||
use crate::api::{self, LocatedTxKernel};
|
||||
use crate::chain::types::NoopAdapter;
|
||||
use crate::chain::Chain;
|
||||
use crate::core::core::verifier_cache::LruVerifierCache;
|
||||
use crate::core::core::Transaction;
|
||||
use crate::core::core::{Transaction, TxKernel};
|
||||
use crate::core::global::{set_mining_mode, ChainTypes};
|
||||
use crate::core::{pow, ser};
|
||||
use crate::keychain::Keychain;
|
||||
|
@ -151,6 +151,7 @@ where
|
|||
"get_outputs_by_pmmr_index" => self.get_outputs_by_pmmr_index(m)?,
|
||||
"send_tx_slate" => self.send_tx_slate(m)?,
|
||||
"post_tx" => self.post_tx(m)?,
|
||||
"get_kernel" => self.get_kernel(m)?,
|
||||
_ => panic!("Unknown Wallet Proxy Message"),
|
||||
};
|
||||
|
||||
|
@ -299,6 +300,26 @@ where
|
|||
body: serde_json::to_string(&ol).unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
/// get kernel
|
||||
fn get_kernel(
|
||||
&mut self,
|
||||
m: WalletProxyMessage,
|
||||
) -> Result<WalletProxyMessage, libwallet::Error> {
|
||||
let split = m.body.split(",").collect::<Vec<&str>>();
|
||||
let excess = split[0].parse::<String>().unwrap();
|
||||
let min = split[1].parse::<u64>().unwrap();
|
||||
let max = split[2].parse::<u64>().unwrap();
|
||||
let commit_bytes = util::from_hex(excess).unwrap();
|
||||
let commit = pedersen::Commitment::from_vec(commit_bytes);
|
||||
let k = super::get_kernel_local(self.chain.clone(), &commit, Some(min), Some(max));
|
||||
Ok(WalletProxyMessage {
|
||||
sender_id: "node".to_owned(),
|
||||
dest: m.sender_id,
|
||||
method: m.method,
|
||||
body: serde_json::to_string(&k).unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -450,6 +471,47 @@ impl NodeClient for LocalWalletClient {
|
|||
Ok(api_outputs)
|
||||
}
|
||||
|
||||
fn get_kernel(
|
||||
&mut self,
|
||||
excess: &pedersen::Commitment,
|
||||
min_height: Option<u64>,
|
||||
max_height: Option<u64>,
|
||||
) -> Result<Option<(TxKernel, u64, u64)>, libwallet::Error> {
|
||||
let mut query = format!("{},", util::to_hex(excess.0.to_vec()));
|
||||
if let Some(h) = min_height {
|
||||
query += &format!("{},", h);
|
||||
} else {
|
||||
query += "0,"
|
||||
}
|
||||
if let Some(h) = max_height {
|
||||
query += &format!("{}", h);
|
||||
} else {
|
||||
query += "0"
|
||||
}
|
||||
|
||||
let m = WalletProxyMessage {
|
||||
sender_id: self.id.clone(),
|
||||
dest: self.node_url().to_owned(),
|
||||
method: "get_kernel".to_owned(),
|
||||
body: query,
|
||||
};
|
||||
{
|
||||
let p = self.proxy_tx.lock();
|
||||
p.send(m).context(libwallet::ErrorKind::ClientCallback(
|
||||
"Get outputs from node by PMMR index send".to_owned(),
|
||||
))?;
|
||||
}
|
||||
let r = self.rx.lock();
|
||||
let m = r.recv().unwrap();
|
||||
let res: Option<LocatedTxKernel> = serde_json::from_str(&m.body).context(
|
||||
libwallet::ErrorKind::ClientCallback("Get transaction kernels send".to_owned()),
|
||||
)?;
|
||||
match res {
|
||||
Some(k) => Ok(Some((k.tx_kernel, k.height, k.mmr_index))),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_outputs_by_pmmr_index(
|
||||
&self,
|
||||
start_height: u64,
|
||||
|
|
|
@ -130,7 +130,7 @@ where
|
|||
let mut sl = slate.clone();
|
||||
let context = w.get_private_context(keychain_mask, sl.id.as_bytes(), 1)?;
|
||||
tx::complete_tx(&mut *w, keychain_mask, &mut sl, 1, &context)?;
|
||||
tx::update_stored_tx(&mut *w, &mut sl, true)?;
|
||||
tx::update_stored_tx(&mut *w, keychain_mask, &mut sl, true)?;
|
||||
tx::update_message(&mut *w, keychain_mask, &mut sl)?;
|
||||
{
|
||||
let mut batch = w.batch(keychain_mask)?;
|
||||
|
|
|
@ -119,10 +119,13 @@ where
|
|||
validated = update_outputs(w, keychain_mask, false)?;
|
||||
}
|
||||
|
||||
Ok((
|
||||
validated,
|
||||
updater::retrieve_txs(&mut *w, tx_id, tx_slate_id, Some(&parent_key_id), false)?,
|
||||
))
|
||||
let mut txs = updater::retrieve_txs(&mut *w, tx_id, tx_slate_id, Some(&parent_key_id), false)?;
|
||||
|
||||
if refresh_from_node {
|
||||
validated = update_txs_via_kernel(w, keychain_mask, &mut txs)?;
|
||||
}
|
||||
|
||||
Ok((validated, txs))
|
||||
}
|
||||
|
||||
/// Retrieve summary info
|
||||
|
@ -274,7 +277,6 @@ where
|
|||
// recieve the transaction back
|
||||
{
|
||||
let mut batch = w.batch(keychain_mask)?;
|
||||
println!("Saving private context: {:?}", slate.id.as_bytes());
|
||||
batch.save_private_context(slate.id.as_bytes(), 1, &context)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
|
@ -396,7 +398,7 @@ where
|
|||
let mut sl = slate.clone();
|
||||
let context = w.get_private_context(keychain_mask, sl.id.as_bytes(), 0)?;
|
||||
tx::complete_tx(&mut *w, keychain_mask, &mut sl, 0, &context)?;
|
||||
tx::update_stored_tx(&mut *w, &mut sl, false)?;
|
||||
tx::update_stored_tx(&mut *w, keychain_mask, &mut sl, false)?;
|
||||
tx::update_message(&mut *w, keychain_mask, &mut sl)?;
|
||||
{
|
||||
let mut batch = w.batch(keychain_mask)?;
|
||||
|
@ -546,3 +548,44 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update transactions that need to be validated via kernel lookup
|
||||
fn update_txs_via_kernel<'a, T: ?Sized, C, K>(
|
||||
w: &mut T,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
txs: &mut Vec<TxLogEntry>,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
T: WalletBackend<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
let parent_key_id = w.parent_key_id();
|
||||
let height = match w.w2n_client().get_chain_height() {
|
||||
Ok(h) => h,
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
for tx in txs.iter_mut() {
|
||||
if tx.confirmed {
|
||||
continue;
|
||||
}
|
||||
if let Some(e) = tx.kernel_excess {
|
||||
let res = w
|
||||
.w2n_client()
|
||||
.get_kernel(&e, tx.kernel_lookup_min_height, Some(height));
|
||||
let kernel = match res {
|
||||
Ok(k) => k,
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
if let Some(k) = kernel {
|
||||
debug!("Kernel Retrieved: {:?}", k);
|
||||
let mut batch = w.batch(keychain_mask)?;
|
||||
tx.confirmed = true;
|
||||
tx.update_confirmation_ts();
|
||||
batch.save_tx_log_entry(tx.clone(), &parent_key_id)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@ where
|
|||
{
|
||||
let mut output_commits: HashMap<Identifier, (Option<String>, u64)> = HashMap::new();
|
||||
// Store cached commits before locking wallet
|
||||
let mut total_change = 0;
|
||||
for (id, _, change_amount) in &context.get_outputs() {
|
||||
output_commits.insert(
|
||||
id.clone(),
|
||||
|
@ -119,8 +120,13 @@ where
|
|||
*change_amount,
|
||||
),
|
||||
);
|
||||
total_change += change_amount;
|
||||
}
|
||||
|
||||
debug!("Change amount is: {}", total_change);
|
||||
|
||||
let keychain = wallet.keychain(keychain_mask)?;
|
||||
|
||||
let tx_entry = {
|
||||
let lock_inputs = context.get_inputs().clone();
|
||||
let messages = Some(slate.participant_messages());
|
||||
|
@ -134,6 +140,11 @@ where
|
|||
let filename = format!("{}.grintx", slate_id);
|
||||
t.stored_tx = Some(filename);
|
||||
t.fee = Some(slate.fee);
|
||||
// TODO: Future multi-kernel considerations
|
||||
if total_change == 0 {
|
||||
t.kernel_excess = Some(slate.calc_excess(&keychain)?);
|
||||
t.kernel_lookup_min_height = Some(slate.height);
|
||||
}
|
||||
let mut amount_debited = 0;
|
||||
t.num_inputs = lock_inputs.len();
|
||||
for id in lock_inputs {
|
||||
|
|
|
@ -302,6 +302,7 @@ where
|
|||
/// Update the stored transaction (this update needs to happen when the TX is finalised)
|
||||
pub fn update_stored_tx<'a, T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
slate: &Slate,
|
||||
is_invoiced: bool,
|
||||
) -> Result<(), Error>
|
||||
|
@ -324,11 +325,20 @@ where
|
|||
break;
|
||||
}
|
||||
}
|
||||
let tx = match tx {
|
||||
let mut tx = match tx {
|
||||
Some(t) => t,
|
||||
None => return Err(ErrorKind::TransactionDoesntExist(slate.id.to_string()))?,
|
||||
};
|
||||
wallet.store_tx(&format!("{}", tx.tx_slate_id.unwrap()), &slate.tx)?;
|
||||
// If kernel excess is needed in the case of a no change transaction, update
|
||||
// tx log info with final excess
|
||||
if let Some(_) = tx.kernel_excess {
|
||||
tx.kernel_excess = Some(slate.tx.body.kernels[0].excess);
|
||||
let parent_key = wallet.parent_key_id();
|
||||
let mut batch = wallet.batch(keychain_mask)?;
|
||||
batch.save_tx_log_entry(tx, &parent_key)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ use crate::grin_core::libtx::{aggsig, build, proof::ProofBuild, secp_ser, tx_fee
|
|||
use crate::grin_core::map_vec;
|
||||
use crate::grin_keychain::{BlindSum, BlindingFactor, Keychain};
|
||||
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::{self, secp, RwLock};
|
||||
use failure::ResultExt;
|
||||
|
@ -622,6 +623,25 @@ impl Slate {
|
|||
Ok(final_sig)
|
||||
}
|
||||
|
||||
/// return the final excess
|
||||
pub fn calc_excess<K>(&self, keychain: &K) -> Result<Commitment, Error>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
let kernel_offset = &self.tx.offset;
|
||||
let tx = self.tx.clone();
|
||||
let overage = tx.fee() as i64;
|
||||
let tx_excess = tx.sum_commitments(overage)?;
|
||||
|
||||
// subtract the kernel_excess (built from kernel_offset)
|
||||
let offset_excess = keychain
|
||||
.secp()
|
||||
.commit(0, kernel_offset.secret_key(&keychain.secp())?)?;
|
||||
Ok(keychain
|
||||
.secp()
|
||||
.commit_sum(vec![tx_excess], vec![offset_excess])?)
|
||||
}
|
||||
|
||||
/// builds a final transaction after the aggregated sig exchange
|
||||
fn finalize_transaction<K>(
|
||||
&mut self,
|
||||
|
@ -631,27 +651,14 @@ impl Slate {
|
|||
where
|
||||
K: Keychain,
|
||||
{
|
||||
let kernel_offset = &self.tx.offset;
|
||||
|
||||
self.check_fees()?;
|
||||
// build the final excess based on final tx and offset
|
||||
let final_excess = self.calc_excess(keychain)?;
|
||||
|
||||
debug!("Final Tx excess: {:?}", final_excess);
|
||||
|
||||
let mut final_tx = self.tx.clone();
|
||||
|
||||
// build the final excess based on final tx and offset
|
||||
let final_excess = {
|
||||
// sum the input/output commitments on the final tx
|
||||
let overage = final_tx.fee() as i64;
|
||||
let tx_excess = final_tx.sum_commitments(overage)?;
|
||||
|
||||
// subtract the kernel_excess (built from kernel_offset)
|
||||
let offset_excess = keychain
|
||||
.secp()
|
||||
.commit(0, kernel_offset.secret_key(&keychain.secp())?)?;
|
||||
keychain
|
||||
.secp()
|
||||
.commit_sum(vec![tx_excess], vec![offset_excess])?
|
||||
};
|
||||
|
||||
// update the tx kernel to reflect the offset excess and sig
|
||||
assert_eq!(final_tx.kernels().len(), 1);
|
||||
final_tx.kernels_mut()[0].excess = final_excess.clone();
|
||||
|
|
|
@ -325,6 +325,15 @@ pub trait NodeClient: Send + Sync + Clone {
|
|||
/// retrieves the current tip from the specified grin node
|
||||
fn get_chain_height(&self) -> Result<u64, Error>;
|
||||
|
||||
/// Get a kernel and the height of the block it's included in. Returns
|
||||
/// (tx_kernel, height, mmr_index)
|
||||
fn get_kernel(
|
||||
&mut self,
|
||||
excess: &pedersen::Commitment,
|
||||
min_height: Option<u64>,
|
||||
max_height: Option<u64>,
|
||||
) -> Result<Option<(TxKernel, u64, u64)>, Error>;
|
||||
|
||||
/// retrieve a list of outputs from the specified grin node
|
||||
/// need "by_height" and "by_id" variants
|
||||
fn get_outputs_from_node(
|
||||
|
@ -748,6 +757,11 @@ pub struct TxLogEntry {
|
|||
pub messages: Option<ParticipantMessages>,
|
||||
/// Location of the store transaction, (reference or resending)
|
||||
pub stored_tx: Option<String>,
|
||||
/// Associated kernel excess, for later lookup if necessary
|
||||
pub kernel_excess: Option<pedersen::Commitment>,
|
||||
/// Height reported when transaction was created, if lookup
|
||||
/// of kernel is necessary
|
||||
pub kernel_lookup_min_height: Option<u64>,
|
||||
}
|
||||
|
||||
impl ser::Writeable for TxLogEntry {
|
||||
|
@ -781,6 +795,8 @@ impl TxLogEntry {
|
|||
fee: None,
|
||||
messages: None,
|
||||
stored_tx: None,
|
||||
kernel_excess: None,
|
||||
kernel_lookup_min_height: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue