mirror of
https://github.com/mimblewimble/grin.git
synced 2025-03-06 17:11:08 +03:00
Allow multiple Aggsig contexts (#685)
* update mean cuda miner to latest trompcode, and added tweakable parameters to grin configuration file * Added UUID for transactions, and store aggsig contexts indexed by transaction ID * updating test framework to allow checking of wallet contents during test
This commit is contained in:
parent
eb0ebab2d3
commit
92a23ec26d
11 changed files with 248 additions and 90 deletions
|
@ -288,6 +288,23 @@ impl LocalServerContainer {
|
||||||
self.wallet_is_running = true;
|
self.wallet_is_running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_wallet_seed(config: &WalletConfig) -> wallet::WalletSeed {
|
||||||
|
let _=fs::create_dir_all(config.clone().data_file_dir);
|
||||||
|
wallet::WalletSeed::init_file(config);
|
||||||
|
let wallet_seed =
|
||||||
|
wallet::WalletSeed::from_file(config).expect("Failed to read wallet seed file.");
|
||||||
|
wallet_seed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_wallet_info(config: &WalletConfig, wallet_seed: &wallet::WalletSeed) -> wallet::WalletInfo {
|
||||||
|
let keychain = wallet_seed.derive_keychain("grin_test").expect(
|
||||||
|
"Failed to derive keychain from seed file and passphrase.",
|
||||||
|
);
|
||||||
|
|
||||||
|
wallet::retrieve_info(config, &keychain)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn send_amount_to(config: &WalletConfig,
|
pub fn send_amount_to(config: &WalletConfig,
|
||||||
amount:&str,
|
amount:&str,
|
||||||
|
|
|
@ -58,6 +58,8 @@ fn basic_wallet_transactions() {
|
||||||
coinbase_wallet.lock().unwrap().wallet_config.clone()
|
coinbase_wallet.lock().unwrap().wallet_config.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let coinbase_seed = LocalServerContainer::get_wallet_seed(&coinbase_wallet_config);
|
||||||
|
|
||||||
let _ = thread::spawn(move || {
|
let _ = thread::spawn(move || {
|
||||||
let mut w = coinbase_wallet.lock().unwrap();
|
let mut w = coinbase_wallet.lock().unwrap();
|
||||||
w.run_wallet(0);
|
w.run_wallet(0);
|
||||||
|
@ -73,6 +75,7 @@ fn basic_wallet_transactions() {
|
||||||
target_wallet.lock().unwrap().wallet_config.clone()
|
target_wallet.lock().unwrap().wallet_config.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let recp_seed = LocalServerContainer::get_wallet_seed(&recp_wallet_config);
|
||||||
//Start up a second wallet, to receive
|
//Start up a second wallet, to receive
|
||||||
let _ = thread::spawn(move || {
|
let _ = thread::spawn(move || {
|
||||||
let mut w = target_wallet_cloned.lock().unwrap();
|
let mut w = target_wallet_cloned.lock().unwrap();
|
||||||
|
@ -96,15 +99,45 @@ fn basic_wallet_transactions() {
|
||||||
server_one.run_server(120);
|
server_one.run_server(120);
|
||||||
});
|
});
|
||||||
|
|
||||||
//Wait for chain to build
|
//Wait until we have some funds to send
|
||||||
thread::sleep(time::Duration::from_millis(5000));
|
let mut coinbase_info = LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed);
|
||||||
|
let mut slept_time = 0;
|
||||||
|
while coinbase_info.amount_currently_spendable < 100000000000{
|
||||||
|
thread::sleep(time::Duration::from_millis(500));
|
||||||
|
slept_time+=500;
|
||||||
|
if slept_time > 10000 {
|
||||||
|
panic!("Coinbase not confirming in time");
|
||||||
|
}
|
||||||
|
coinbase_info = LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed);
|
||||||
|
}
|
||||||
warn!(LOGGER, "Sending 50 Grins to recipient wallet");
|
warn!(LOGGER, "Sending 50 Grins to recipient wallet");
|
||||||
LocalServerContainer::send_amount_to(&coinbase_wallet_config, "50.00", 1, "all", "http://127.0.0.1:20002");
|
LocalServerContainer::send_amount_to(&coinbase_wallet_config, "50.00", 1, "not_all", "http://127.0.0.1:20002");
|
||||||
|
|
||||||
//let some more mining happen, make sure nothing pukes
|
//Wait for a confirmation
|
||||||
thread::sleep(time::Duration::from_millis(5000));
|
thread::sleep(time::Duration::from_millis(3000));
|
||||||
|
let coinbase_info = LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed);
|
||||||
|
println!("Coinbase wallet info: {:?}", coinbase_info);
|
||||||
|
|
||||||
|
let recipient_info = LocalServerContainer::get_wallet_info(&recp_wallet_config, &recp_seed);
|
||||||
|
println!("Recipient wallet info: {:?}", recipient_info);
|
||||||
|
|
||||||
|
assert!(recipient_info.data_confirmed && recipient_info.amount_currently_spendable==49992000000);
|
||||||
|
|
||||||
|
warn!(LOGGER, "Sending many small transactions to recipient wallet");
|
||||||
|
for _ in 0..10 {
|
||||||
|
LocalServerContainer::send_amount_to(&coinbase_wallet_config, "1.00", 1, "not_all", "http://127.0.0.1:20002");
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(time::Duration::from_millis(10000));
|
||||||
|
let recipient_info = LocalServerContainer::get_wallet_info(&recp_wallet_config, &recp_seed);
|
||||||
|
println!("Recipient wallet info post little sends: {:?}", recipient_info);
|
||||||
|
|
||||||
|
assert!(recipient_info.data_confirmed && recipient_info.amount_currently_spendable==59912000000);
|
||||||
//send some cash right back
|
//send some cash right back
|
||||||
LocalServerContainer::send_amount_to(&recp_wallet_config, "25.00", 1, "all", "http://127.0.0.1:10002");
|
LocalServerContainer::send_amount_to(&recp_wallet_config, "25.00", 1, "all", "http://127.0.0.1:10002");
|
||||||
|
|
||||||
thread::sleep(time::Duration::from_millis(5000));
|
thread::sleep(time::Duration::from_millis(5000));
|
||||||
|
|
||||||
|
let coinbase_info = LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed);
|
||||||
|
println!("Coinbase wallet info final: {:?}", coinbase_info);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ authors = ["Antioch Peverell"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "^1.0"
|
byteorder = "^1.0"
|
||||||
blake2-rfc = "~0.2.17"
|
blake2-rfc = "~0.2.17"
|
||||||
|
uuid = { version = "~0.5.1", features = ["serde", "v4"] }
|
||||||
rand = "^0.3"
|
rand = "^0.3"
|
||||||
slog = { version = "^2.0.12", features = ["max_level_trace", "release_max_level_trace"] }
|
slog = { version = "^2.0.12", features = ["max_level_trace", "release_max_level_trace"] }
|
||||||
serde = "~1.0.8"
|
serde = "~1.0.8"
|
||||||
|
|
|
@ -24,6 +24,7 @@ use util::secp::aggsig;
|
||||||
use util::logger::LOGGER;
|
use util::logger::LOGGER;
|
||||||
use util::kernel_sig_msg;
|
use util::kernel_sig_msg;
|
||||||
use blake2;
|
use blake2;
|
||||||
|
use uuid::Uuid;
|
||||||
use blind::{BlindSum, BlindingFactor};
|
use blind::{BlindSum, BlindingFactor};
|
||||||
use extkey::{self, Identifier};
|
use extkey::{self, Identifier};
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ pub enum Error {
|
||||||
ExtendedKey(extkey::Error),
|
ExtendedKey(extkey::Error),
|
||||||
Secp(secp::Error),
|
Secp(secp::Error),
|
||||||
KeyDerivation(String),
|
KeyDerivation(String),
|
||||||
|
Transaction(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<secp::Error> for Error {
|
impl From<secp::Error> for Error {
|
||||||
|
@ -62,7 +64,7 @@ pub struct AggSigTxContext {
|
||||||
pub struct Keychain {
|
pub struct Keychain {
|
||||||
secp: Secp256k1,
|
secp: Secp256k1,
|
||||||
extkey: extkey::ExtendedKey,
|
extkey: extkey::ExtendedKey,
|
||||||
pub aggsig_context: Arc<RwLock<Option<AggSigTxContext>>>,
|
pub aggsig_contexts: Arc<RwLock<Option<HashMap<Uuid,AggSigTxContext>>>>,
|
||||||
key_overrides: HashMap<Identifier, SecretKey>,
|
key_overrides: HashMap<Identifier, SecretKey>,
|
||||||
key_derivation_cache: Arc<RwLock<HashMap<Identifier, u32>>>,
|
key_derivation_cache: Arc<RwLock<HashMap<Identifier, u32>>>,
|
||||||
}
|
}
|
||||||
|
@ -91,7 +93,7 @@ impl Keychain {
|
||||||
let keychain = Keychain {
|
let keychain = Keychain {
|
||||||
secp: secp,
|
secp: secp,
|
||||||
extkey: extkey,
|
extkey: extkey,
|
||||||
aggsig_context: Arc::new(RwLock::new(None)),
|
aggsig_contexts: Arc::new(RwLock::new(None)),
|
||||||
key_overrides: HashMap::new(),
|
key_overrides: HashMap::new(),
|
||||||
key_derivation_cache: Arc::new(RwLock::new(HashMap::new())),
|
key_derivation_cache: Arc::new(RwLock::new(HashMap::new())),
|
||||||
};
|
};
|
||||||
|
@ -269,74 +271,109 @@ impl Keychain {
|
||||||
Ok(BlindingFactor::new(blinding))
|
Ok(BlindingFactor::new(blinding))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn aggsig_create_context(&self, sec_key:SecretKey) {
|
pub fn aggsig_create_context(&self, transaction_id: &Uuid, sec_key:SecretKey)
|
||||||
let mut context = self.aggsig_context.write().unwrap();
|
-> Result<(), Error>{
|
||||||
*context = Some(AggSigTxContext{
|
let mut contexts = self.aggsig_contexts.write().unwrap();
|
||||||
|
if contexts.is_none() {
|
||||||
|
*contexts = Some(HashMap::new())
|
||||||
|
}
|
||||||
|
if contexts.as_mut().unwrap().contains_key(transaction_id) {
|
||||||
|
return Err(Error::Transaction(String::from("Duplication transaction id")));
|
||||||
|
}
|
||||||
|
contexts.as_mut().unwrap().insert(transaction_id.clone(), AggSigTxContext{
|
||||||
sec_key: sec_key,
|
sec_key: sec_key,
|
||||||
sec_nonce: aggsig::export_secnonce_single(&self.secp).unwrap(),
|
sec_nonce: aggsig::export_secnonce_single(&self.secp).unwrap(),
|
||||||
output_ids: vec![],
|
output_ids: vec![],
|
||||||
});
|
});
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tracks an output contributing to my excess value (if it needs to
|
/// Tracks an output contributing to my excess value (if it needs to
|
||||||
/// be kept between invocations
|
/// be kept between invocations
|
||||||
pub fn aggsig_add_output(&self, id: &Identifier){
|
pub fn aggsig_add_output(&self, transaction_id: &Uuid, output_id:&Identifier){
|
||||||
let mut agg_context=self.aggsig_context.write().unwrap();
|
let mut agg_contexts = self.aggsig_contexts.write().unwrap();
|
||||||
let agg_context_write=agg_context.as_mut().unwrap();
|
let mut agg_contexts_local = agg_contexts.as_mut().unwrap().clone();
|
||||||
agg_context_write.output_ids.push(id.clone());
|
let mut agg_context = agg_contexts_local.get(transaction_id).unwrap().clone();
|
||||||
|
agg_context.output_ids.push(output_id.clone());
|
||||||
|
agg_contexts_local.insert(transaction_id.clone(), agg_context);
|
||||||
|
*agg_contexts = Some(agg_contexts_local);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all stored outputs
|
/// Returns all stored outputs
|
||||||
pub fn aggsig_get_outputs(&self) -> Vec<Identifier> {
|
pub fn aggsig_get_outputs(&self, transaction_id: &Uuid) -> Vec<Identifier> {
|
||||||
let context = self.aggsig_context.clone();
|
let contexts = self.aggsig_contexts.clone();
|
||||||
let context_read=context.read().unwrap();
|
let contexts_read = contexts.read().unwrap();
|
||||||
let agg_context=context_read.as_ref().unwrap();
|
let agg_context = contexts_read.as_ref().unwrap();
|
||||||
agg_context.output_ids.clone()
|
let agg_context_return = agg_context.get(transaction_id);
|
||||||
|
agg_context_return.unwrap().output_ids.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns private key, private nonce
|
/// Returns private key, private nonce
|
||||||
pub fn aggsig_get_private_keys(&self) -> (SecretKey, SecretKey) {
|
pub fn aggsig_get_private_keys(&self, transaction_id: &Uuid) -> (SecretKey, SecretKey) {
|
||||||
let context = self.aggsig_context.clone();
|
let contexts = self.aggsig_contexts.clone();
|
||||||
let context_read=context.read().unwrap();
|
let contexts_read=contexts.read().unwrap();
|
||||||
let agg_context=context_read.as_ref().unwrap();
|
let agg_context = contexts_read.as_ref().unwrap();
|
||||||
(agg_context.sec_key.clone(),
|
let agg_context_return = agg_context.get(transaction_id);
|
||||||
agg_context.sec_nonce.clone())
|
(agg_context_return.unwrap().sec_key.clone(),
|
||||||
|
agg_context_return.unwrap().sec_nonce.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns public key, public nonce
|
/// Returns public key, public nonce
|
||||||
pub fn aggsig_get_public_keys(&self) -> (PublicKey, PublicKey) {
|
pub fn aggsig_get_public_keys(&self, transaction_id: &Uuid) -> (PublicKey, PublicKey) {
|
||||||
let context = self.aggsig_context.clone();
|
let contexts = self.aggsig_contexts.clone();
|
||||||
let context_read=context.read().unwrap();
|
let contexts_read=contexts.read().unwrap();
|
||||||
let agg_context=context_read.as_ref().unwrap();
|
let agg_context = contexts_read.as_ref().unwrap();
|
||||||
(PublicKey::from_secret_key(&self.secp, &agg_context.sec_key).unwrap(),
|
let agg_context_return = agg_context.get(transaction_id);
|
||||||
PublicKey::from_secret_key(&self.secp, &agg_context.sec_nonce).unwrap())
|
(PublicKey::from_secret_key(&self.secp, &agg_context_return.unwrap().sec_key).unwrap(),
|
||||||
|
PublicKey::from_secret_key(&self.secp, &agg_context_return.unwrap().sec_nonce).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Note 'secnonce' here is used to perform the signature, while 'pubnonce' just allows you to
|
/// Note 'secnonce' here is used to perform the signature, while 'pubnonce' just allows you to
|
||||||
/// provide a custom public nonce to include while calculating e
|
/// provide a custom public nonce to include while calculating e
|
||||||
/// nonce_sum is the sum used to decide whether secnonce should be inverted during sig time
|
/// nonce_sum is the sum used to decide whether secnonce should be inverted during sig time
|
||||||
pub fn aggsig_sign_single(&self, msg: &Message, secnonce:Option<&SecretKey>, pubnonce: Option<&PublicKey>, nonce_sum: Option<&PublicKey>) -> Result<Signature, Error> {
|
pub fn aggsig_sign_single(&self,
|
||||||
let context = self.aggsig_context.clone();
|
transaction_id: &Uuid,
|
||||||
let context_read=context.read().unwrap();
|
msg: &Message,
|
||||||
let agg_context=context_read.as_ref().unwrap();
|
secnonce:Option<&SecretKey>,
|
||||||
let sig = aggsig::sign_single(&self.secp, msg, &agg_context.sec_key, secnonce, pubnonce, nonce_sum)?;
|
pubnonce: Option<&PublicKey>,
|
||||||
|
nonce_sum: Option<&PublicKey>) -> Result<Signature, Error> {
|
||||||
|
let contexts = self.aggsig_contexts.clone();
|
||||||
|
let contexts_read=contexts.read().unwrap();
|
||||||
|
let agg_context = contexts_read.as_ref().unwrap();
|
||||||
|
let agg_context_return = agg_context.get(transaction_id);
|
||||||
|
let sig = aggsig::sign_single(&self.secp, msg, &agg_context_return.unwrap().sec_key, secnonce, pubnonce, nonce_sum)?;
|
||||||
Ok(sig)
|
Ok(sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Verifies an aggsig signature
|
//Verifies an aggsig signature
|
||||||
pub fn aggsig_verify_single(&self, sig: &Signature, msg: &Message, pubnonce:Option<&PublicKey>, pubkey:&PublicKey, is_partial:bool) -> bool {
|
pub fn aggsig_verify_single(&self,
|
||||||
|
sig: &Signature,
|
||||||
|
msg: &Message,
|
||||||
|
pubnonce:Option<&PublicKey>,
|
||||||
|
pubkey:&PublicKey,
|
||||||
|
is_partial:bool) -> bool {
|
||||||
aggsig::verify_single(&self.secp, sig, msg, pubnonce, pubkey, is_partial)
|
aggsig::verify_single(&self.secp, sig, msg, pubnonce, pubkey, is_partial)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Verifies other final sig corresponds with what we're expecting
|
//Verifies other final sig corresponds with what we're expecting
|
||||||
pub fn aggsig_verify_final_sig_build_msg(&self, sig: &Signature, pubkey: &PublicKey, fee: u64, lock_height:u64) -> bool {
|
pub fn aggsig_verify_final_sig_build_msg(&self,
|
||||||
|
sig: &Signature,
|
||||||
|
pubkey: &PublicKey,
|
||||||
|
fee: u64,
|
||||||
|
lock_height:u64) -> bool {
|
||||||
let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap();
|
let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap();
|
||||||
self.aggsig_verify_single(sig, &msg, None, pubkey, true)
|
self.aggsig_verify_single(sig, &msg, None, pubkey, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Verifies other party's sig corresponds with what we're expecting
|
//Verifies other party's sig corresponds with what we're expecting
|
||||||
pub fn aggsig_verify_partial_sig(&self, sig: &Signature, other_pub_nonce:&PublicKey, pubkey:&PublicKey, fee: u64, lock_height:u64) -> bool {
|
pub fn aggsig_verify_partial_sig(&self,
|
||||||
let (_, sec_nonce) = self.aggsig_get_private_keys();
|
transaction_id: &Uuid,
|
||||||
|
sig: &Signature,
|
||||||
|
other_pub_nonce:&PublicKey,
|
||||||
|
pubkey:&PublicKey,
|
||||||
|
fee: u64,
|
||||||
|
lock_height:u64) -> bool {
|
||||||
|
let (_, sec_nonce) = self.aggsig_get_private_keys(transaction_id);
|
||||||
let mut nonce_sum = other_pub_nonce.clone();
|
let mut nonce_sum = other_pub_nonce.clone();
|
||||||
let _ = nonce_sum.add_exp_assign(&self.secp, &sec_nonce);
|
let _ = nonce_sum.add_exp_assign(&self.secp, &sec_nonce);
|
||||||
let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap();
|
let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap();
|
||||||
|
@ -344,21 +381,29 @@ impl Keychain {
|
||||||
self.aggsig_verify_single(sig, &msg, Some(&nonce_sum), pubkey, true)
|
self.aggsig_verify_single(sig, &msg, Some(&nonce_sum), pubkey, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn aggsig_calculate_partial_sig(&self, other_pub_nonce:&PublicKey, fee:u64, lock_height:u64) -> Result<Signature, Error>{
|
pub fn aggsig_calculate_partial_sig(&self,
|
||||||
|
transaction_id: &Uuid,
|
||||||
|
other_pub_nonce:&PublicKey,
|
||||||
|
fee:u64,
|
||||||
|
lock_height:u64) -> Result<Signature, Error>{
|
||||||
// Add public nonces kR*G + kS*G
|
// Add public nonces kR*G + kS*G
|
||||||
let (_, sec_nonce) = self.aggsig_get_private_keys();
|
let (_, sec_nonce) = self.aggsig_get_private_keys(transaction_id);
|
||||||
let mut nonce_sum = other_pub_nonce.clone();
|
let mut nonce_sum = other_pub_nonce.clone();
|
||||||
let _ = nonce_sum.add_exp_assign(&self.secp, &sec_nonce);
|
let _ = nonce_sum.add_exp_assign(&self.secp, &sec_nonce);
|
||||||
let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height))?;
|
let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height))?;
|
||||||
|
|
||||||
//Now calculate signature using message M=fee, nonce in e=nonce_sum
|
//Now calculate signature using message M=fee, nonce in e=nonce_sum
|
||||||
self.aggsig_sign_single(&msg, Some(&sec_nonce), Some(&nonce_sum), Some(&nonce_sum))
|
self.aggsig_sign_single(transaction_id, &msg, Some(&sec_nonce), Some(&nonce_sum), Some(&nonce_sum))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to calculate final singature
|
/// Helper function to calculate final singature
|
||||||
pub fn aggsig_calculate_final_sig(&self, their_sig: &Signature, our_sig: &Signature, their_pub_nonce: &PublicKey) -> Result<Signature, Error> {
|
pub fn aggsig_calculate_final_sig(&self,
|
||||||
|
transaction_id: &Uuid,
|
||||||
|
their_sig: &Signature,
|
||||||
|
our_sig: &Signature,
|
||||||
|
their_pub_nonce: &PublicKey) -> Result<Signature, Error> {
|
||||||
// Add public nonces kR*G + kS*G
|
// Add public nonces kR*G + kS*G
|
||||||
let (_, sec_nonce) = self.aggsig_get_private_keys();
|
let (_, sec_nonce) = self.aggsig_get_private_keys(transaction_id);
|
||||||
let mut nonce_sum = their_pub_nonce.clone();
|
let mut nonce_sum = their_pub_nonce.clone();
|
||||||
let _ = nonce_sum.add_exp_assign(&self.secp, &sec_nonce);
|
let _ = nonce_sum.add_exp_assign(&self.secp, &sec_nonce);
|
||||||
let sig = aggsig::add_signatures_single(&self.secp, their_sig, our_sig, &nonce_sum)?;
|
let sig = aggsig::add_signatures_single(&self.secp, their_sig, our_sig, &nonce_sum)?;
|
||||||
|
@ -368,9 +413,10 @@ impl Keychain {
|
||||||
/// Helper function to calculate final public key
|
/// Helper function to calculate final public key
|
||||||
pub fn aggsig_calculate_final_pubkey(
|
pub fn aggsig_calculate_final_pubkey(
|
||||||
&self,
|
&self,
|
||||||
|
transaction_id: &Uuid,
|
||||||
their_public_key: &PublicKey,
|
their_public_key: &PublicKey,
|
||||||
) -> Result<PublicKey, Error> {
|
) -> Result<PublicKey, Error> {
|
||||||
let (our_sec_key, _) = self.aggsig_get_private_keys();
|
let (our_sec_key, _) = self.aggsig_get_private_keys(transaction_id);
|
||||||
let mut pk_sum = their_public_key.clone();
|
let mut pk_sum = their_public_key.clone();
|
||||||
let _ = pk_sum.add_exp_assign(&self.secp, &our_sec_key);
|
let _ = pk_sum.add_exp_assign(&self.secp, &our_sec_key);
|
||||||
Ok(pk_sum)
|
Ok(pk_sum)
|
||||||
|
|
|
@ -18,6 +18,7 @@ extern crate blake2_rfc as blake2;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate grin_util as util;
|
extern crate grin_util as util;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
|
extern crate uuid;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
|
@ -25,6 +25,7 @@ tokio-retry="~0.1.0"
|
||||||
router = "~0.5.1"
|
router = "~0.5.1"
|
||||||
prettytable-rs = "^0.6"
|
prettytable-rs = "^0.6"
|
||||||
term = "~0.4.6"
|
term = "~0.4.6"
|
||||||
|
uuid = { version = "~0.5.1", features = ["serde", "v4"] }
|
||||||
grin_api = { path = "../api" }
|
grin_api = { path = "../api" }
|
||||||
grin_core = { path = "../core" }
|
grin_core = { path = "../core" }
|
||||||
grin_keychain = { path = "../keychain" }
|
grin_keychain = { path = "../keychain" }
|
||||||
|
|
|
@ -15,13 +15,40 @@
|
||||||
use checker;
|
use checker;
|
||||||
use keychain::Keychain;
|
use keychain::Keychain;
|
||||||
use core::core::amount_to_hr_string;
|
use core::core::amount_to_hr_string;
|
||||||
use types::{WalletConfig, WalletData, OutputStatus};
|
use types::{WalletConfig, WalletData, OutputStatus, WalletInfo};
|
||||||
use prettytable;
|
use prettytable;
|
||||||
|
|
||||||
pub fn show_info(config: &WalletConfig, keychain: &Keychain) {
|
pub fn show_info(config: &WalletConfig, keychain: &Keychain) {
|
||||||
|
let wallet_info = retrieve_info(config, keychain);
|
||||||
|
println!("\n____ Wallet Summary Info at {} ({}) ____\n",
|
||||||
|
wallet_info.current_height,
|
||||||
|
wallet_info.data_confirmed_from);
|
||||||
|
let mut table = table!(
|
||||||
|
[bFG->"Total", FG->amount_to_hr_string(wallet_info.total)],
|
||||||
|
[bFY->"Awaiting Confirmation", FY->amount_to_hr_string(wallet_info.amount_awaiting_confirmation)],
|
||||||
|
[bFY->"Confirmed but Still Locked", FY->amount_to_hr_string(wallet_info.amount_confirmed_but_locked)],
|
||||||
|
[bFG->"Currently Spendable", FG->amount_to_hr_string(wallet_info.amount_currently_spendable)],
|
||||||
|
[Fw->"---------", Fw->"---------"],
|
||||||
|
[Fr->"(Locked by previous transaction)", Fr->amount_to_hr_string(wallet_info.amount_locked)]
|
||||||
|
);
|
||||||
|
table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
|
||||||
|
table.printstd();
|
||||||
|
println!();
|
||||||
|
|
||||||
|
if !wallet_info.data_confirmed {
|
||||||
|
println!(
|
||||||
|
"\nWARNING: Failed to verify wallet contents with grin server. \
|
||||||
|
Above info is maybe not fully updated or invalid! \
|
||||||
|
Check that your `grin server` is OK, or see `wallet help restore`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn retrieve_info(config: &WalletConfig, keychain: &Keychain)
|
||||||
|
-> WalletInfo {
|
||||||
let result = checker::refresh_outputs(&config, &keychain);
|
let result = checker::refresh_outputs(&config, &keychain);
|
||||||
|
|
||||||
let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
let ret_val = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||||
let (current_height, from) = match checker::get_tip_from_node(config) {
|
let (current_height, from) = match checker::get_tip_from_node(config) {
|
||||||
Ok(tip) => (tip.height, "from server node"),
|
Ok(tip) => (tip.height, "from server node"),
|
||||||
Err(_) => match wallet_data.outputs.values().map(|out| out.height).max() {
|
Err(_) => match wallet_data.outputs.values().map(|out| out.height).max() {
|
||||||
|
@ -52,25 +79,20 @@ pub fn show_info(config: &WalletConfig, keychain: &Keychain) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("\n____ Wallet Summary Info at {} ({}) ____\n", current_height, from);
|
let mut data_confirmed = true;
|
||||||
let mut table = table!(
|
if let Err(_) = result {
|
||||||
[bFG->"Total", FG->amount_to_hr_string(unspent_total+unconfirmed_total)],
|
data_confirmed = false;
|
||||||
[bFY->"Awaiting Confirmation", FY->amount_to_hr_string(unconfirmed_total)],
|
}
|
||||||
[bFY->"Confirmed but Still Locked", FY->amount_to_hr_string(unspent_but_locked_total)],
|
WalletInfo {
|
||||||
[bFG->"Currently Spendable", FG->amount_to_hr_string(unspent_total-unspent_but_locked_total)],
|
current_height : current_height,
|
||||||
[Fw->"---------", Fw->"---------"],
|
total: unspent_total+unconfirmed_total,
|
||||||
[Fr->"(Locked by previous transaction)", Fr->amount_to_hr_string(locked_total)]
|
amount_awaiting_confirmation: unconfirmed_total,
|
||||||
);
|
amount_confirmed_but_locked: unspent_but_locked_total,
|
||||||
table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
|
amount_currently_spendable: unspent_total-unspent_but_locked_total,
|
||||||
table.printstd();
|
amount_locked: locked_total,
|
||||||
println!();
|
data_confirmed: data_confirmed,
|
||||||
|
data_confirmed_from: String::from(from),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
ret_val.unwrap()
|
||||||
if let Err(_) = result {
|
|
||||||
println!(
|
|
||||||
"\nWARNING: Failed to verify wallet contents with grin server. \
|
|
||||||
Above info is maybe not fully updated or invalid! \
|
|
||||||
Check that your `grin server` is OK, or see `wallet help restore`"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ extern crate blake2_rfc as blake2;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
extern crate uuid;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
@ -53,8 +54,8 @@ pub mod client;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
pub use outputs::show_outputs;
|
pub use outputs::show_outputs;
|
||||||
pub use info::show_info;
|
pub use info::{show_info, retrieve_info};
|
||||||
pub use receiver::{WalletReceiver};
|
pub use receiver::{WalletReceiver};
|
||||||
pub use sender::{issue_burn_tx, issue_send_tx};
|
pub use sender::{issue_burn_tx, issue_send_tx};
|
||||||
pub use types::{BlockFees, CbData, Error, WalletConfig, WalletReceiveRequest, WalletSeed};
|
pub use types::{BlockFees, CbData, Error, WalletConfig, WalletReceiveRequest, WalletInfo, WalletSeed};
|
||||||
pub use restore::restore;
|
pub use restore::restore;
|
||||||
|
|
|
@ -21,6 +21,7 @@ use iron::prelude::*;
|
||||||
use iron::Handler;
|
use iron::Handler;
|
||||||
use iron::status;
|
use iron::status;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use api;
|
use api;
|
||||||
use core::consensus::reward;
|
use core::consensus::reward;
|
||||||
|
@ -113,13 +114,16 @@ fn handle_sender_initiation(
|
||||||
warn!(LOGGER, "Creating new aggsig context");
|
warn!(LOGGER, "Creating new aggsig context");
|
||||||
// Create a new aggsig context
|
// Create a new aggsig context
|
||||||
// this will create a new blinding sum and nonce, and store them
|
// this will create a new blinding sum and nonce, and store them
|
||||||
keychain.aggsig_create_context(blind_sum.secret_key());
|
let result = keychain.aggsig_create_context(&partial_tx.id, blind_sum.secret_key());
|
||||||
keychain.aggsig_add_output(&key_id);
|
if let Err(_) = result {
|
||||||
|
return Err(Error::DuplicateTransactionId);
|
||||||
|
}
|
||||||
|
keychain.aggsig_add_output(&partial_tx.id, &key_id);
|
||||||
|
|
||||||
let sig_part=keychain.aggsig_calculate_partial_sig(&sender_pub_nonce, fee, tx.lock_height).unwrap();
|
let sig_part=keychain.aggsig_calculate_partial_sig(&partial_tx.id, &sender_pub_nonce, fee, tx.lock_height).unwrap();
|
||||||
|
|
||||||
// Build the response, which should contain sR, blinding excess xR * G, public nonce kR * G
|
// Build the response, which should contain sR, blinding excess xR * G, public nonce kR * G
|
||||||
let mut partial_tx = build_partial_tx(keychain, amount, Some(sig_part), tx);
|
let mut partial_tx = build_partial_tx(&partial_tx.id, keychain, amount, Some(sig_part), tx);
|
||||||
partial_tx.phase = PartialTxPhase::ReceiverInitiation;
|
partial_tx.phase = PartialTxPhase::ReceiverInitiation;
|
||||||
|
|
||||||
Ok(partial_tx)
|
Ok(partial_tx)
|
||||||
|
@ -144,7 +148,7 @@ fn handle_sender_confirmation(
|
||||||
) -> Result<PartialTx, Error> {
|
) -> Result<PartialTx, Error> {
|
||||||
let (amount, sender_pub_blinding, sender_pub_nonce, sender_sig_part, tx) = read_partial_tx(keychain, partial_tx)?;
|
let (amount, sender_pub_blinding, sender_pub_nonce, sender_sig_part, tx) = read_partial_tx(keychain, partial_tx)?;
|
||||||
let sender_sig_part=sender_sig_part.unwrap();
|
let sender_sig_part=sender_sig_part.unwrap();
|
||||||
let res = keychain.aggsig_verify_partial_sig(&sender_sig_part, &sender_pub_nonce, &sender_pub_blinding, tx.fee, tx.lock_height);
|
let res = keychain.aggsig_verify_partial_sig(&partial_tx.id, &sender_sig_part, &sender_pub_nonce, &sender_pub_blinding, tx.fee, tx.lock_height);
|
||||||
|
|
||||||
if !res {
|
if !res {
|
||||||
error!(LOGGER, "Partial Sig from sender invalid.");
|
error!(LOGGER, "Partial Sig from sender invalid.");
|
||||||
|
@ -152,13 +156,13 @@ fn handle_sender_confirmation(
|
||||||
}
|
}
|
||||||
|
|
||||||
//Just calculate our sig part again instead of storing
|
//Just calculate our sig part again instead of storing
|
||||||
let our_sig_part=keychain.aggsig_calculate_partial_sig(&sender_pub_nonce, tx.fee, tx.lock_height).unwrap();
|
let our_sig_part=keychain.aggsig_calculate_partial_sig(&partial_tx.id, &sender_pub_nonce, tx.fee, tx.lock_height).unwrap();
|
||||||
|
|
||||||
// And the final signature
|
// And the final signature
|
||||||
let final_sig=keychain.aggsig_calculate_final_sig(&sender_sig_part, &our_sig_part, &sender_pub_nonce).unwrap();
|
let final_sig=keychain.aggsig_calculate_final_sig(&partial_tx.id, &sender_sig_part, &our_sig_part, &sender_pub_nonce).unwrap();
|
||||||
|
|
||||||
// Calculate the final public key (for our own sanity check)
|
// Calculate the final public key (for our own sanity check)
|
||||||
let final_pubkey=keychain.aggsig_calculate_final_pubkey(&sender_pub_blinding).unwrap();
|
let final_pubkey=keychain.aggsig_calculate_final_pubkey(&partial_tx.id, &sender_pub_blinding).unwrap();
|
||||||
|
|
||||||
//Check our final sig verifies
|
//Check our final sig verifies
|
||||||
let res = keychain.aggsig_verify_final_sig_build_msg(&final_sig, &final_pubkey, tx.fee, tx.lock_height);
|
let res = keychain.aggsig_verify_final_sig_build_msg(&final_sig, &final_pubkey, tx.fee, tx.lock_height);
|
||||||
|
@ -168,8 +172,7 @@ fn handle_sender_confirmation(
|
||||||
return Err(Error::Signature(String::from("Final aggregated signature invalid.")));
|
return Err(Error::Signature(String::from("Final aggregated signature invalid.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let final_tx = build_final_transaction(&partial_tx.id, config, keychain, amount, &final_sig, tx.clone())?;
|
||||||
let final_tx = build_final_transaction(config, keychain, amount, &final_sig, tx.clone())?;
|
|
||||||
let tx_hex = to_hex(ser::ser_vec(&final_tx).unwrap());
|
let tx_hex = to_hex(ser::ser_vec(&final_tx).unwrap());
|
||||||
|
|
||||||
let url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str());
|
let url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str());
|
||||||
|
@ -177,7 +180,7 @@ fn handle_sender_confirmation(
|
||||||
.map_err(|e| Error::Node(e))?;
|
.map_err(|e| Error::Node(e))?;
|
||||||
|
|
||||||
// Return what we've actually posted
|
// Return what we've actually posted
|
||||||
let mut partial_tx = build_partial_tx(keychain, amount, Some(final_sig), tx);
|
let mut partial_tx = build_partial_tx(&partial_tx.id, keychain, amount, Some(final_sig), tx);
|
||||||
partial_tx.phase = PartialTxPhase::ReceiverConfirmation;
|
partial_tx.phase = PartialTxPhase::ReceiverConfirmation;
|
||||||
Ok(partial_tx)
|
Ok(partial_tx)
|
||||||
}
|
}
|
||||||
|
@ -310,6 +313,7 @@ pub fn receive_coinbase(
|
||||||
|
|
||||||
/// builds a final transaction after the aggregated sig exchange
|
/// builds a final transaction after the aggregated sig exchange
|
||||||
fn build_final_transaction(
|
fn build_final_transaction(
|
||||||
|
tx_id: &Uuid,
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
keychain: &Keychain,
|
keychain: &Keychain,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
|
@ -347,7 +351,7 @@ fn build_final_transaction(
|
||||||
|
|
||||||
// Get output we created in earlier step
|
// Get output we created in earlier step
|
||||||
// TODO: will just be one for now, support multiple later
|
// TODO: will just be one for now, support multiple later
|
||||||
let output_vec = keychain.aggsig_get_outputs();
|
let output_vec = keychain.aggsig_get_outputs(tx_id);
|
||||||
|
|
||||||
// operate within a lock on wallet data
|
// operate within a lock on wallet data
|
||||||
let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
use api;
|
use api;
|
||||||
use client;
|
use client;
|
||||||
use checker;
|
use checker;
|
||||||
|
@ -61,9 +62,9 @@ pub fn issue_send_tx(
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Create a new aggsig context
|
// Create a new aggsig context
|
||||||
keychain.aggsig_create_context(blind_sum.secret_key());
|
let tx_id = Uuid::new_v4();
|
||||||
|
let _ = keychain.aggsig_create_context(&tx_id, blind_sum.secret_key());
|
||||||
let partial_tx = build_partial_tx(keychain, amount, None, tx);
|
let partial_tx = build_partial_tx(&tx_id, keychain, amount, None, tx);
|
||||||
|
|
||||||
// Closure to acquire wallet lock and lock the coins being spent
|
// Closure to acquire wallet lock and lock the coins being spent
|
||||||
// so we avoid accidental double spend attempt.
|
// so we avoid accidental double spend attempt.
|
||||||
|
@ -117,16 +118,16 @@ pub fn issue_send_tx(
|
||||||
* -Sender posts sS to receiver
|
* -Sender posts sS to receiver
|
||||||
*/
|
*/
|
||||||
let (_amount, recp_pub_blinding, recp_pub_nonce, sig, tx) = read_partial_tx(keychain, &res.unwrap())?;
|
let (_amount, recp_pub_blinding, recp_pub_nonce, sig, tx) = read_partial_tx(keychain, &res.unwrap())?;
|
||||||
let res = keychain.aggsig_verify_partial_sig(&sig.unwrap(), &recp_pub_nonce, &recp_pub_blinding, tx.fee, lock_height);
|
let res = keychain.aggsig_verify_partial_sig(&tx_id, &sig.unwrap(), &recp_pub_nonce, &recp_pub_blinding, tx.fee, lock_height);
|
||||||
if !res {
|
if !res {
|
||||||
error!(LOGGER, "Partial Sig from recipient invalid.");
|
error!(LOGGER, "Partial Sig from recipient invalid.");
|
||||||
return Err(Error::Signature(String::from("Partial Sig from recipient invalid.")));
|
return Err(Error::Signature(String::from("Partial Sig from recipient invalid.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
let sig_part=keychain.aggsig_calculate_partial_sig(&recp_pub_nonce, tx.fee, tx.lock_height).unwrap();
|
let sig_part=keychain.aggsig_calculate_partial_sig(&tx_id, &recp_pub_nonce, tx.fee, tx.lock_height).unwrap();
|
||||||
|
|
||||||
// Build the next stage, containing sS (and our pubkeys again, for the recipient's convenience)
|
// Build the next stage, containing sS (and our pubkeys again, for the recipient's convenience)
|
||||||
let mut partial_tx = build_partial_tx(keychain, amount, Some(sig_part), tx);
|
let mut partial_tx = build_partial_tx(&tx_id, keychain, amount, Some(sig_part), tx);
|
||||||
partial_tx.phase = PartialTxPhase::SenderConfirmation;
|
partial_tx.phase = PartialTxPhase::SenderConfirmation;
|
||||||
|
|
||||||
// And send again
|
// And send again
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
use blake2;
|
use blake2;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use std::{error, fmt, num};
|
use std::{error, fmt, num};
|
||||||
|
use uuid::Uuid;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::fs::{self, File, OpenOptions};
|
use std::fs::{self, File, OpenOptions};
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
|
@ -85,6 +86,11 @@ pub enum Error {
|
||||||
Uri(hyper::error::UriError),
|
Uri(hyper::error::UriError),
|
||||||
/// Error with signatures during exchange
|
/// Error with signatures during exchange
|
||||||
Signature(String),
|
Signature(String),
|
||||||
|
/// Attempt to use duplicate transaction id in separate transactions
|
||||||
|
DuplicateTransactionId,
|
||||||
|
/// Wallet seed already exists
|
||||||
|
WalletSeedExists,
|
||||||
|
/// Other
|
||||||
GenericError(String,)
|
GenericError(String,)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,7 +399,7 @@ impl WalletSeed {
|
||||||
debug!(LOGGER, "Generating wallet seed file at: {}", seed_file_path,);
|
debug!(LOGGER, "Generating wallet seed file at: {}", seed_file_path,);
|
||||||
|
|
||||||
if Path::new(seed_file_path).exists() {
|
if Path::new(seed_file_path).exists() {
|
||||||
panic!("wallet seed file already exists");
|
Err(Error::WalletSeedExists)
|
||||||
} else {
|
} else {
|
||||||
let seed = WalletSeed::init_new();
|
let seed = WalletSeed::init_new();
|
||||||
let mut file = File::create(seed_file_path)?;
|
let mut file = File::create(seed_file_path)?;
|
||||||
|
@ -707,6 +713,7 @@ pub enum PartialTxPhase {
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct PartialTx {
|
pub struct PartialTx {
|
||||||
pub phase: PartialTxPhase,
|
pub phase: PartialTxPhase,
|
||||||
|
pub id: Uuid,
|
||||||
pub amount: u64,
|
pub amount: u64,
|
||||||
pub public_blind_excess: String,
|
pub public_blind_excess: String,
|
||||||
pub public_nonce: String,
|
pub public_nonce: String,
|
||||||
|
@ -718,13 +725,14 @@ pub struct PartialTx {
|
||||||
/// aggsig_tx_context should contain the private key/nonce pair
|
/// aggsig_tx_context should contain the private key/nonce pair
|
||||||
/// the resulting partial tx will contain the corresponding public keys
|
/// the resulting partial tx will contain the corresponding public keys
|
||||||
pub fn build_partial_tx(
|
pub fn build_partial_tx(
|
||||||
|
transaction_id : &Uuid,
|
||||||
keychain: &keychain::Keychain,
|
keychain: &keychain::Keychain,
|
||||||
receive_amount: u64,
|
receive_amount: u64,
|
||||||
part_sig: Option<secp::Signature>,
|
part_sig: Option<secp::Signature>,
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
) -> PartialTx {
|
) -> PartialTx {
|
||||||
|
|
||||||
let (pub_excess, pub_nonce) = keychain.aggsig_get_public_keys();
|
let (pub_excess, pub_nonce) = keychain.aggsig_get_public_keys(transaction_id);
|
||||||
let mut pub_excess = pub_excess.serialize_vec(keychain.secp(), true).clone();
|
let mut pub_excess = pub_excess.serialize_vec(keychain.secp(), true).clone();
|
||||||
let len = pub_excess.clone().len();
|
let len = pub_excess.clone().len();
|
||||||
let pub_excess: Vec<_> = pub_excess.drain(0..len).collect();
|
let pub_excess: Vec<_> = pub_excess.drain(0..len).collect();
|
||||||
|
@ -735,6 +743,7 @@ pub fn build_partial_tx(
|
||||||
|
|
||||||
PartialTx {
|
PartialTx {
|
||||||
phase: PartialTxPhase::SenderInitiation,
|
phase: PartialTxPhase::SenderInitiation,
|
||||||
|
id : transaction_id.clone(),
|
||||||
amount: receive_amount,
|
amount: receive_amount,
|
||||||
public_blind_excess: util::to_hex(pub_excess),
|
public_blind_excess: util::to_hex(pub_excess),
|
||||||
public_nonce: util::to_hex(pub_nonce),
|
public_nonce: util::to_hex(pub_nonce),
|
||||||
|
@ -797,3 +806,25 @@ pub struct CbData {
|
||||||
pub kernel: String,
|
pub kernel: String,
|
||||||
pub key_id: String,
|
pub key_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// a contained wallet info struct, so automated tests can parse wallet info
|
||||||
|
/// can add more fields here over time as needed
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct WalletInfo {
|
||||||
|
// height from which info was taken
|
||||||
|
pub current_height: u64,
|
||||||
|
// total amount in the wallet
|
||||||
|
pub total: u64,
|
||||||
|
// amount awaiting confirmation
|
||||||
|
pub amount_awaiting_confirmation: u64,
|
||||||
|
// confirmed but locked
|
||||||
|
pub amount_confirmed_but_locked: u64,
|
||||||
|
// amount currently spendable
|
||||||
|
pub amount_currently_spendable: u64,
|
||||||
|
// amount locked by previous transactions
|
||||||
|
pub amount_locked: u64,
|
||||||
|
// whether the data was confirmed against a live node
|
||||||
|
pub data_confirmed: bool,
|
||||||
|
// node confirming the data
|
||||||
|
pub data_confirmed_from: String,
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue