mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
Save slate participant messages in database (#2441)
* Save and display optional slate messages * rustfmt * fixes and test updates * rustfmt * output json * rustfmt * add better serialisation of slate message, rename to secp_ser, add tests for serialization functions * rustfmt * max length for message enforced by API * rustfmt
This commit is contained in:
parent
422db82667
commit
0c851c5140
13 changed files with 288 additions and 11 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -788,6 +788,7 @@ dependencies = [
|
||||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
|
@ -30,3 +30,6 @@ chrono = "0.4.4"
|
||||||
|
|
||||||
grin_keychain = { path = "../keychain", version = "1.0.0" }
|
grin_keychain = { path = "../keychain", version = "1.0.0" }
|
||||||
grin_util = { path = "../util", version = "1.0.0" }
|
grin_util = { path = "../util", version = "1.0.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_json = "1"
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub mod build;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod proof;
|
pub mod proof;
|
||||||
pub mod reward;
|
pub mod reward;
|
||||||
pub mod serialization;
|
pub mod secp_ser;
|
||||||
pub mod slate;
|
pub mod slate;
|
||||||
|
|
||||||
use crate::consensus;
|
use crate::consensus;
|
||||||
|
|
|
@ -173,3 +173,58 @@ where
|
||||||
{
|
{
|
||||||
serializer.serialize_str(&to_hex(bytes.as_ref().to_vec()))
|
serializer.serialize_str(&to_hex(bytes.as_ref().to_vec()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test serialization methods of components that are being used
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::libtx::aggsig;
|
||||||
|
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||||
|
use crate::util::secp::{Message, Signature};
|
||||||
|
use crate::util::static_secp_instance;
|
||||||
|
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
|
||||||
|
struct SerTest {
|
||||||
|
#[serde(with = "pubkey_serde")]
|
||||||
|
pub pub_key: PublicKey,
|
||||||
|
#[serde(with = "option_sig_serde")]
|
||||||
|
pub opt_sig: Option<Signature>,
|
||||||
|
#[serde(with = "sig_serde")]
|
||||||
|
pub sig: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerTest {
|
||||||
|
pub fn random() -> SerTest {
|
||||||
|
let static_secp = static_secp_instance();
|
||||||
|
let secp = static_secp.lock();
|
||||||
|
let sk = SecretKey::new(&secp, &mut thread_rng());
|
||||||
|
let mut msg = [0u8; 32];
|
||||||
|
thread_rng().fill(&mut msg);
|
||||||
|
let msg = Message::from_slice(&msg).unwrap();
|
||||||
|
let sig = aggsig::sign_single(&secp, &msg, &sk, None).unwrap();
|
||||||
|
SerTest {
|
||||||
|
pub_key: PublicKey::from_secret_key(&secp, &sk).unwrap(),
|
||||||
|
opt_sig: Some(sig.clone()),
|
||||||
|
sig: sig.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ser_secp_primitives() {
|
||||||
|
for _ in 0..10 {
|
||||||
|
let s = SerTest::random();
|
||||||
|
println!("Before Serialization: {:?}", s);
|
||||||
|
let serialized = serde_json::to_string_pretty(&s).unwrap();
|
||||||
|
println!("JSON: {}", serialized);
|
||||||
|
let deserialized: SerTest = serde_json::from_str(&serialized).unwrap();
|
||||||
|
println!("After Serialization: {:?}", deserialized);
|
||||||
|
println!();
|
||||||
|
assert_eq!(s, deserialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ use crate::core::transaction::{kernel_features, kernel_sig_msg, Transaction, Wei
|
||||||
use crate::core::verifier_cache::LruVerifierCache;
|
use crate::core::verifier_cache::LruVerifierCache;
|
||||||
use crate::keychain::{BlindSum, BlindingFactor, Keychain};
|
use crate::keychain::{BlindSum, BlindingFactor, Keychain};
|
||||||
use crate::libtx::error::{Error, ErrorKind};
|
use crate::libtx::error::{Error, ErrorKind};
|
||||||
use crate::libtx::{aggsig, build, tx_fee};
|
use crate::libtx::{aggsig, build, secp_ser, tx_fee};
|
||||||
use crate::util::secp;
|
use crate::util::secp;
|
||||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||||
use crate::util::secp::Signature;
|
use crate::util::secp::Signature;
|
||||||
|
@ -66,6 +66,33 @@ impl ParticipantData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Public message data (for serialising and storage)
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct ParticipantMessageData {
|
||||||
|
/// id of the particpant in the tx
|
||||||
|
pub id: u64,
|
||||||
|
/// Public key
|
||||||
|
#[serde(with = "secp_ser::pubkey_serde")]
|
||||||
|
pub public_key: PublicKey,
|
||||||
|
/// Message,
|
||||||
|
pub message: Option<String>,
|
||||||
|
/// Signature
|
||||||
|
#[serde(with = "secp_ser::option_sig_serde")]
|
||||||
|
pub message_sig: Option<Signature>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParticipantMessageData {
|
||||||
|
/// extract relevant message data from participant data
|
||||||
|
pub fn from_participant_data(p: &ParticipantData) -> ParticipantMessageData {
|
||||||
|
ParticipantMessageData {
|
||||||
|
id: p.id,
|
||||||
|
public_key: p.public_blind_excess,
|
||||||
|
message: p.message.clone(),
|
||||||
|
message_sig: p.message_sig.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A 'Slate' is passed around to all parties to build up all of the public
|
/// A 'Slate' is passed around to all parties to build up all of the public
|
||||||
/// transaction data needed to create a finalized transaction. Callers can pass
|
/// transaction data needed to create a finalized transaction. Callers can pass
|
||||||
/// the slate around by whatever means they choose, (but we can provide some
|
/// the slate around by whatever means they choose, (but we can provide some
|
||||||
|
@ -94,6 +121,13 @@ pub struct Slate {
|
||||||
pub participant_data: Vec<ParticipantData>,
|
pub participant_data: Vec<ParticipantData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper just to facilitate serialization
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct ParticipantMessages {
|
||||||
|
/// included messages
|
||||||
|
pub messages: Vec<ParticipantMessageData>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Slate {
|
impl Slate {
|
||||||
/// Create a new slate
|
/// Create a new slate
|
||||||
pub fn blank(num_participants: usize) -> Slate {
|
pub fn blank(num_participants: usize) -> Slate {
|
||||||
|
@ -274,10 +308,19 @@ impl Slate {
|
||||||
message: message,
|
message: message,
|
||||||
message_sig: message_sig,
|
message_sig: message_sig,
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// helper to return all participant messages
|
||||||
|
pub fn participant_messages(&self) -> ParticipantMessages {
|
||||||
|
let mut ret = ParticipantMessages { messages: vec![] };
|
||||||
|
for ref m in self.participant_data.iter() {
|
||||||
|
ret.messages
|
||||||
|
.push(ParticipantMessageData::from_participant_data(m));
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
/// Somebody involved needs to generate an offset with their private key
|
/// Somebody involved needs to generate an offset with their private key
|
||||||
/// For now, we'll have the transaction initiator be responsible for it
|
/// For now, we'll have the transaction initiator be responsible for it
|
||||||
/// Return offset private key for the participant to use later in the
|
/// Return offset private key for the participant to use later in the
|
||||||
|
|
|
@ -253,6 +253,16 @@ mod wallet_tests {
|
||||||
let mut bh = 10u64;
|
let mut bh = 10u64;
|
||||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize);
|
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize);
|
||||||
|
|
||||||
|
let very_long_message = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
This part should all be truncated";
|
||||||
|
|
||||||
// Update info and check
|
// Update info and check
|
||||||
let arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "info"];
|
let arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "info"];
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
@ -273,7 +283,7 @@ mod wallet_tests {
|
||||||
"-d",
|
"-d",
|
||||||
&file_name,
|
&file_name,
|
||||||
"-g",
|
"-g",
|
||||||
"Love, Yeast",
|
very_long_message,
|
||||||
"10",
|
"10",
|
||||||
];
|
];
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
@ -491,6 +501,12 @@ mod wallet_tests {
|
||||||
let arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "txs"];
|
let arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "txs"];
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
// message output (mostly spit out for a visual in test logs)
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin", "wallet", "-p", "password", "-a", "mining", "txs", "-i", "10",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
// txs and outputs (mostly spit out for a visual in test logs)
|
// txs and outputs (mostly spit out for a visual in test logs)
|
||||||
let arg_vec = vec![
|
let arg_vec = vec![
|
||||||
"grin", "wallet", "-p", "password", "-a", "mining", "outputs",
|
"grin", "wallet", "-p", "password", "-a", "mining", "outputs",
|
||||||
|
|
|
@ -396,15 +396,19 @@ pub fn txs(
|
||||||
&g_args.account,
|
&g_args.account,
|
||||||
height,
|
height,
|
||||||
validated,
|
validated,
|
||||||
txs,
|
&txs,
|
||||||
include_status,
|
include_status,
|
||||||
dark_scheme,
|
dark_scheme,
|
||||||
)?;
|
)?;
|
||||||
// if given a particular transaction id, also get and display associated
|
// if given a particular transaction id, also get and display associated
|
||||||
// inputs/outputs
|
// inputs/outputs and messages
|
||||||
if args.id.is_some() {
|
if args.id.is_some() {
|
||||||
let (_, outputs) = api.retrieve_outputs(true, false, args.id)?;
|
let (_, outputs) = api.retrieve_outputs(true, false, args.id)?;
|
||||||
display::outputs(&g_args.account, height, validated, outputs, dark_scheme)?;
|
display::outputs(&g_args.account, height, validated, outputs, dark_scheme)?;
|
||||||
|
// should only be one here, but just in case
|
||||||
|
for tx in txs {
|
||||||
|
display::tx_messages(&tx, dark_scheme)?;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -123,7 +123,7 @@ pub fn txs(
|
||||||
account: &str,
|
account: &str,
|
||||||
cur_height: u64,
|
cur_height: u64,
|
||||||
validated: bool,
|
validated: bool,
|
||||||
txs: Vec<TxLogEntry>,
|
txs: &Vec<TxLogEntry>,
|
||||||
include_status: bool,
|
include_status: bool,
|
||||||
dark_background_color_scheme: bool,
|
dark_background_color_scheme: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
@ -357,3 +357,77 @@ pub fn accounts(acct_mappings: Vec<AcctPathMapping>) {
|
||||||
table.printstd();
|
table.printstd();
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Display transaction log messages
|
||||||
|
pub fn tx_messages(tx: &TxLogEntry, dark_background_color_scheme: bool) -> Result<(), Error> {
|
||||||
|
let title = format!("Transaction Messages - Transaction '{}'", tx.id,);
|
||||||
|
println!();
|
||||||
|
let mut t = term::stdout().unwrap();
|
||||||
|
t.fg(term::color::MAGENTA).unwrap();
|
||||||
|
writeln!(t, "{}", title).unwrap();
|
||||||
|
t.reset().unwrap();
|
||||||
|
|
||||||
|
let msgs = match tx.messages.clone() {
|
||||||
|
None => {
|
||||||
|
writeln!(t, "{}", "None").unwrap();
|
||||||
|
t.reset().unwrap();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Some(m) => m.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if msgs.messages.is_empty() {
|
||||||
|
writeln!(t, "{}", "None").unwrap();
|
||||||
|
t.reset().unwrap();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut table = table!();
|
||||||
|
|
||||||
|
table.set_titles(row![
|
||||||
|
bMG->"Participant Id",
|
||||||
|
bMG->"Message",
|
||||||
|
bMG->"Public Key",
|
||||||
|
bMG->"Signature",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let secp = util::static_secp_instance();
|
||||||
|
let secp_lock = secp.lock();
|
||||||
|
|
||||||
|
for m in msgs.messages {
|
||||||
|
let id = format!("{}", m.id);
|
||||||
|
let public_key = format!(
|
||||||
|
"{}",
|
||||||
|
util::to_hex(m.public_key.serialize_vec(&secp_lock, true).to_vec())
|
||||||
|
);
|
||||||
|
let message = match m.message {
|
||||||
|
Some(m) => format!("{}", m),
|
||||||
|
None => "None".to_owned(),
|
||||||
|
};
|
||||||
|
let message_sig = match m.message_sig {
|
||||||
|
Some(s) => format!("{}", util::to_hex(s.serialize_der(&secp_lock))),
|
||||||
|
None => "None".to_owned(),
|
||||||
|
};
|
||||||
|
if dark_background_color_scheme {
|
||||||
|
table.add_row(row![
|
||||||
|
bFC->id,
|
||||||
|
bFC->message,
|
||||||
|
bFC->public_key,
|
||||||
|
bFB->message_sig,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
table.add_row(row![
|
||||||
|
bFD->id,
|
||||||
|
bFb->message,
|
||||||
|
bFD->public_key,
|
||||||
|
bFB->message_sig,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
|
||||||
|
table.printstd();
|
||||||
|
println!();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -48,6 +48,8 @@ use crate::libwallet::{Error, ErrorKind};
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use crate::util::secp::{pedersen, ContextFlag, Secp256k1};
|
use crate::util::secp::{pedersen, ContextFlag, Secp256k1};
|
||||||
|
|
||||||
|
const USER_MESSAGE_MAX_LEN: usize = 256;
|
||||||
|
|
||||||
/// Functions intended for use by the owner (e.g. master seed holder) of the wallet.
|
/// Functions intended for use by the owner (e.g. master seed holder) of the wallet.
|
||||||
pub struct APIOwner<W: ?Sized, C, K>
|
pub struct APIOwner<W: ?Sized, C, K>
|
||||||
where
|
where
|
||||||
|
@ -551,7 +553,8 @@ where
|
||||||
/// ParticipantData within the slate. This message will include a signature created with the
|
/// ParticipantData within the slate. This message will include a signature created with the
|
||||||
/// sender's private keys, and will be publically verifiable. Note this message is for
|
/// sender's private keys, and will be publically verifiable. Note this message is for
|
||||||
/// the convenience of the participants during the exchange; it is not included in the final
|
/// the convenience of the participants during the exchange; it is not included in the final
|
||||||
/// transaction sent to the chain. Validation of this message is optional.
|
/// transaction sent to the chain. The message will be truncated to 256 characters.
|
||||||
|
/// Validation of this message is optional.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * a result containing:
|
/// * a result containing:
|
||||||
|
@ -640,6 +643,14 @@ where
|
||||||
None => w.parent_key_id(),
|
None => w.parent_key_id(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let message = match message {
|
||||||
|
Some(mut m) => {
|
||||||
|
m.truncate(USER_MESSAGE_MAX_LEN);
|
||||||
|
Some(m)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
let (slate, context, lock_fn) = tx::create_send_tx(
|
let (slate, context, lock_fn) = tx::create_send_tx(
|
||||||
&mut *w,
|
&mut *w,
|
||||||
amount,
|
amount,
|
||||||
|
@ -685,12 +696,12 @@ where
|
||||||
let context = w.get_private_context(slate.id.as_bytes())?;
|
let context = w.get_private_context(slate.id.as_bytes())?;
|
||||||
tx::complete_tx(&mut *w, slate, &context)?;
|
tx::complete_tx(&mut *w, slate, &context)?;
|
||||||
tx::update_stored_tx(&mut *w, slate)?;
|
tx::update_stored_tx(&mut *w, slate)?;
|
||||||
|
tx::update_message(&mut *w, slate)?;
|
||||||
{
|
{
|
||||||
let mut batch = w.batch()?;
|
let mut batch = w.batch()?;
|
||||||
batch.delete_private_context(slate.id.as_bytes())?;
|
batch.delete_private_context(slate.id.as_bytes())?;
|
||||||
batch.commit()?;
|
batch.commit()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
w.close()?;
|
w.close()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -873,6 +884,15 @@ where
|
||||||
return Err(ErrorKind::TransactionAlreadyReceived(slate.id.to_string()).into());
|
return Err(ErrorKind::TransactionAlreadyReceived(slate.id.to_string()).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let message = match message {
|
||||||
|
Some(mut m) => {
|
||||||
|
m.truncate(USER_MESSAGE_MAX_LEN);
|
||||||
|
Some(m)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
let res = tx::receive_tx(&mut *w, slate, &parent_key_id, message);
|
let res = tx::receive_tx(&mut *w, slate, &parent_key_id, message);
|
||||||
w.close()?;
|
w.close()?;
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,7 @@ where
|
||||||
|
|
||||||
let lock_inputs = context.get_inputs().clone();
|
let lock_inputs = context.get_inputs().clone();
|
||||||
let _lock_outputs = context.get_outputs().clone();
|
let _lock_outputs = context.get_outputs().clone();
|
||||||
|
let messages = Some(slate.participant_messages());
|
||||||
|
|
||||||
// Return a closure to acquire wallet lock and lock the coins being spent
|
// Return a 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.
|
||||||
|
@ -122,6 +123,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
t.amount_debited = amount_debited;
|
t.amount_debited = amount_debited;
|
||||||
|
t.messages = messages;
|
||||||
|
|
||||||
// write the output representing our change
|
// write the output representing our change
|
||||||
for (change_amount, id, _) in &change_amounts_derivations {
|
for (change_amount, id, _) in &change_amounts_derivations {
|
||||||
|
@ -195,6 +197,7 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
context.add_output(&key_id, &None);
|
context.add_output(&key_id, &None);
|
||||||
|
let messages = Some(slate.participant_messages());
|
||||||
|
|
||||||
// Create closure that adds the output to recipient's wallet
|
// Create closure that adds the output to recipient's wallet
|
||||||
// (up to the caller to decide when to do)
|
// (up to the caller to decide when to do)
|
||||||
|
@ -206,6 +209,7 @@ where
|
||||||
t.tx_slate_id = Some(slate_id);
|
t.tx_slate_id = Some(slate_id);
|
||||||
t.amount_credited = amount;
|
t.amount_credited = amount;
|
||||||
t.num_outputs = 1;
|
t.num_outputs = 1;
|
||||||
|
t.messages = messages;
|
||||||
batch.save(OutputData {
|
batch.save(OutputData {
|
||||||
root_key_id: parent_key_id.clone(),
|
root_key_id: parent_key_id.clone(),
|
||||||
key_id: key_id_inner.clone(),
|
key_id: key_id_inner.clone(),
|
||||||
|
|
|
@ -39,7 +39,6 @@ where
|
||||||
// create an output using the amount in the slate
|
// create an output using the amount in the slate
|
||||||
let (_, mut context, receiver_create_fn) =
|
let (_, mut context, receiver_create_fn) =
|
||||||
selection::build_recipient_output_with_slate(wallet, slate, parent_key_id.clone())?;
|
selection::build_recipient_output_with_slate(wallet, slate, parent_key_id.clone())?;
|
||||||
|
|
||||||
// fill public keys
|
// fill public keys
|
||||||
let _ = slate.fill_round_1(
|
let _ = slate.fill_round_1(
|
||||||
wallet.keychain(),
|
wallet.keychain(),
|
||||||
|
@ -55,6 +54,8 @@ where
|
||||||
// Save output in wallet
|
// Save output in wallet
|
||||||
let _ = receiver_create_fn(wallet);
|
let _ = receiver_create_fn(wallet);
|
||||||
|
|
||||||
|
update_message(wallet, slate)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +225,26 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the transaction participant messages
|
||||||
|
pub fn update_message<T: ?Sized, C, K>(wallet: &mut T, slate: &Slate) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: WalletBackend<C, K>,
|
||||||
|
C: NodeClient,
|
||||||
|
K: Keychain,
|
||||||
|
{
|
||||||
|
let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, false)?;
|
||||||
|
if tx_vec.is_empty() {
|
||||||
|
return Err(ErrorKind::TransactionDoesntExist(slate.id.to_string()))?;
|
||||||
|
}
|
||||||
|
let mut batch = wallet.batch()?;
|
||||||
|
for mut tx in tx_vec.into_iter() {
|
||||||
|
tx.messages = Some(slate.participant_messages());
|
||||||
|
let parent_key = tx.parent_key_id.clone();
|
||||||
|
batch.save_tx_log_entry(tx, &parent_key)?;
|
||||||
|
}
|
||||||
|
batch.commit()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::core::libtx::build;
|
use crate::core::libtx::build;
|
||||||
|
|
|
@ -25,6 +25,7 @@ use crate::util::secp::key::{PublicKey, SecretKey};
|
||||||
use crate::util::secp::{self, pedersen, Secp256k1};
|
use crate::util::secp::{self, pedersen, Secp256k1};
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
|
use grin_core::libtx::slate::ParticipantMessages;
|
||||||
use serde;
|
use serde;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -610,6 +611,8 @@ pub struct TxLogEntry {
|
||||||
pub amount_debited: u64,
|
pub amount_debited: u64,
|
||||||
/// Fee
|
/// Fee
|
||||||
pub fee: Option<u64>,
|
pub fee: Option<u64>,
|
||||||
|
/// Message data, stored as json
|
||||||
|
pub messages: Option<ParticipantMessages>,
|
||||||
/// Location of the store transaction, (reference or resending)
|
/// Location of the store transaction, (reference or resending)
|
||||||
pub stored_tx: Option<String>,
|
pub stored_tx: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -643,6 +646,7 @@ impl TxLogEntry {
|
||||||
num_inputs: 0,
|
num_inputs: 0,
|
||||||
num_outputs: 0,
|
num_outputs: 0,
|
||||||
fee: None,
|
fee: None,
|
||||||
|
messages: None,
|
||||||
stored_tx: None,
|
stored_tx: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ use std::fs;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
fn clean_output_dir(test_dir: &str) {
|
fn clean_output_dir(test_dir: &str) {
|
||||||
let _ = fs::remove_dir_all(test_dir);
|
let _ = fs::remove_dir_all(test_dir);
|
||||||
}
|
}
|
||||||
|
@ -137,7 +139,7 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
|
|
||||||
// wallet 2 receives file, completes, sends file back
|
// wallet 2 receives file, completes, sends file back
|
||||||
wallet::controller::foreign_single_use(wallet2.clone(), |api| {
|
wallet::controller::foreign_single_use(wallet2.clone(), |api| {
|
||||||
api.receive_tx(&mut slate, None, Some(sender2_message))?;
|
api.receive_tx(&mut slate, None, Some(sender2_message.clone()))?;
|
||||||
adapter.send_tx_async(&receive_file, &mut slate)?;
|
adapter.send_tx_async(&receive_file, &mut slate)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
@ -174,6 +176,36 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Check messages, all participants should have both
|
||||||
|
wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||||
|
let (_, tx) = api.retrieve_txs(true, None, Some(slate.id))?;
|
||||||
|
assert_eq!(
|
||||||
|
tx[0].clone().messages.unwrap().messages[0].message,
|
||||||
|
Some(message.to_owned())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tx[0].clone().messages.unwrap().messages[1].message,
|
||||||
|
Some(sender2_message.to_owned())
|
||||||
|
);
|
||||||
|
|
||||||
|
let msg_json = serde_json::to_string_pretty(&tx[0].clone().messages.unwrap()).unwrap();
|
||||||
|
println!("{}", msg_json);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
wallet::controller::owner_single_use(wallet2.clone(), |api| {
|
||||||
|
let (_, tx) = api.retrieve_txs(true, None, Some(slate.id))?;
|
||||||
|
assert_eq!(
|
||||||
|
tx[0].clone().messages.unwrap().messages[0].message,
|
||||||
|
Some(message.to_owned())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tx[0].clone().messages.unwrap().messages[1].message,
|
||||||
|
Some(sender2_message)
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
// let logging finish
|
// let logging finish
|
||||||
thread::sleep(Duration::from_millis(200));
|
thread::sleep(Duration::from_millis(200));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in a new issue