Compact slate merge (#404)

* Add support for sending compact slates (#366)

* WIP add support for sending compact slates

* add repopulate_tx function to internal API

* first pass at compacted slate working

* move slate compaction to separate function

* test fixes

* support compact slate inits in invoice workflow

* add compress flags to send and invoice

* attempting to remove is_compact and assume all V4 slates begin as compact

* attempting to calculate offsets when full tx data isn't available

* update calc_commit to use participant blind data

* update doctests for compact slates

* start to remove unneeded fields from serialization

* make num_participants optional

* remove other_version from slate

* use grin master branch

* remove message field

* lock height assumed to be 0 if it doesn't exist

* don't serialise receiver signature when null

* don't serialize payment_info if not needed

* remove participant id from participant info

* add note on id field

* fix finalize and receive doctests

* finalize_tx tests, init_send_tx tests

* doctests for process_invoice_tx, retrieve_tx, tx_lock_outputs

* finished test changes

* update from grin master

* rebuild PR from diff (#380)

* recreate PR from diff (#381)

* serialize tx struct into top level coms object (#382)

* remove height (#383)

* Add State Slate (#384)

* add state field to slate and SlateV4

* set slate state at each transaction stage, add check to tests

* serialize slate status properly

* V4 Slate field tweaks (#386)

* various tweaks to V4 slate

* field renaming

* serialize slate v4 ID as base64 (#387)

* remove amount and fee where not needed (#388)

* Final Changes for compact Slate (#389)

* add tests for all types of file output, remove message args

* default range proof serialization

* shorten output features serialization

* rename payment proof fields in slate v4

* v4 payment proof serialization

* Binary Slates (#385)

* start test implementation

* add experimental binary serialization to slate

* serialize id

* serialize fields that can be skipped as a separate struct

* factor out sigs serialization

* clean up sigs and coms serialization

* completed v4 bin serialization

* add manual de/ser traits for V4 bin slate

* add simple byte array serializer

* complete wiring in of bin slate serialization

* clarify comment

* clarify comment

* update version

* test output dir name fix

* update slate v4 change description

* add binary output to command line

* Remove unneeded signature data during S2 and I2 stages (#390)

* remove unneeded return signature data during S2

* remove unneeded sig data from I2

* Doctest Fixes for compact slate branch (#392)

* begin to fix doctests

* more doctest fixes

* fix receive_tx

* update get_stored_tx to accept an UUID instead of a tx object, and operate on a raw Transaction object (#394)

* Fixes to async transaction posting (#395)

* unstash post_tx changes

* add offset during S3 and I3

* Revert slate id serialization to hex-string uuid (#396)

* update from master (#397)

* v3.x.x - v4.0.0 wallet compatibility fixes (#398)

* changes to support http sending to v3 wallets

* sending via http/tor TO 3.0.0 wallet works

* receiving FROM 3.0.0 wallets works over http/tor

* output converted V3 slate when needed

* paying invoices from 3.0.0 wallets working

* handle all participant info in slate states

* sending and receiving standard file transactions between v3 and 4 wallets confirmed working

* all file-based workflows working

* fixes resulting from tests

* remove reminder warnings

* remove lock_height, add kernel_features + arguments (#399)

* grin-wallet master now building against grin master (#402) (#403)

Co-authored-by: Antioch Peverell <apeverell@protonmail.com>

* Enhanced offset creation (#407)

* initial tests reworking offset creation

* invoice flow fixing + tests

* further test fixes

* change offset name in v4 slate, base64 serialize

* logic optimisation

* changes based on review feedback

Co-authored-by: Antioch Peverell <apeverell@protonmail.com>
This commit is contained in:
Yeastplume
2020-05-19 11:19:03 +01:00
committed by GitHub
co-authored by Antioch Peverell
parent 19da1e4aec
commit 9450d2a3b8
49 changed files with 5124 additions and 3800 deletions
+117 -62
View File
@@ -20,10 +20,11 @@ use crate::config::{TorConfig, WalletConfig, WALLET_CONFIG_FILE_NAME};
use crate::core::{core, global};
use crate::error::{Error, ErrorKind};
use crate::impls::{create_sender, KeybaseAllChannels, SlateGetter as _, SlateReceiver as _};
use crate::impls::{PathToSlate, SlatePutter};
use crate::impls::{HttpSlateSender, PathToSlate, SlatePutter};
use crate::keychain;
use crate::libwallet::{
self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, WalletLCProvider,
self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateVersion,
WalletLCProvider,
};
use crate::util::secp::key::SecretKey;
use crate::util::{Mutex, ZeroingString};
@@ -266,7 +267,6 @@ where
/// Arguments for the send command
pub struct SendArgs {
pub amount: u64,
pub message: Option<String>,
pub minimum_confirmations: u64,
pub selection_strategy: String,
pub estimate_selection_strategies: bool,
@@ -278,6 +278,8 @@ pub struct SendArgs {
pub target_slate_version: Option<u16>,
pub payment_proof_address: Option<OnionV3Address>,
pub ttl_blocks: Option<u64>,
//TODO: Remove HF3
pub output_v4_slate: bool,
}
pub fn send<L, C, K>(
@@ -293,6 +295,60 @@ where
K: keychain::Keychain + 'static,
{
let wallet_inst = owner_api.wallet_inst.clone();
// Check other version, and if it only supports 3 set the target slate
// version to 3 to avoid removing the transaction object
// TODO: This block is temporary, for the period between the release of v4.0.0 and HF3,
// after which this should be removable
let mut args = args;
{
let invalid = || {
ErrorKind::GenericError(format!(
"Invalid wallet comm type and destination. method: {}, dest: {}",
args.method, args.dest
))
};
let trailing = match args.dest.ends_with('/') {
true => "",
false => "/",
};
let url_str = format!("{}{}v2/foreign", args.dest, trailing);
match args.method.as_ref() {
"http" => {
let v_sender = HttpSlateSender::new(&args.dest).map_err(|_| invalid())?;
let other_version = v_sender.check_other_version(&url_str)?;
if other_version == SlateVersion::V3 {
args.target_slate_version = Some(3);
}
}
"tor" => {
let v_sender = HttpSlateSender::with_socks_proxy(
&args.dest,
&tor_config.as_ref().unwrap().socks_proxy_addr,
&tor_config.as_ref().unwrap().send_config_dir,
)
.map_err(|_| invalid())?;
let other_version = v_sender.check_other_version(&url_str)?;
if other_version == SlateVersion::V3 {
args.target_slate_version = Some(3);
}
}
"file" => {
// For files spit out a V3 Slate if we're before HF3,
// Or V4 slate otherwise
let cur_height = {
libwallet::wallet_lock!(wallet_inst, w);
w.w2n_client().get_chain_tip()?.0
};
// TODO: Floonet HF4
if cur_height < 786240 && !args.output_v4_slate {
args.target_slate_version = Some(3);
}
}
_ => {}
}
}
// end block to delete post HF3
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
if args.estimate_selection_strategies {
let strategies = vec!["smallest", "all"]
@@ -321,7 +377,6 @@ where
max_outputs: args.max_outputs as u32,
num_change_outputs: args.change_outputs as u32,
selection_strategy_is_use_all: args.selection_strategy == "all",
message: args.message.clone(),
target_slate_version: args.target_slate_version,
payment_proof_recipient_address: args.payment_proof_address.clone(),
ttl_blocks: args.ttl_blocks,
@@ -347,34 +402,35 @@ where
match args.method.as_str() {
"file" => {
PathToSlate((&args.dest).into()).put_tx(&slate)?;
api.tx_lock_outputs(m, &slate, 0)?;
PathToSlate((&args.dest).into()).put_tx(&slate, false)?;
api.tx_lock_outputs(m, &slate)?;
return Ok(());
}
"binfile" => {
PathToSlate((&args.dest).into()).put_tx(&slate, true)?;
api.tx_lock_outputs(m, &slate)?;
return Ok(());
}
"self" => {
api.tx_lock_outputs(m, &slate, 0)?;
api.tx_lock_outputs(m, &slate)?;
let km = match keychain_mask.as_ref() {
None => None,
Some(&m) => Some(m.to_owned()),
};
controller::foreign_single_use(wallet_inst, km, |api| {
slate = api.receive_tx(&slate, Some(&args.dest), None)?;
slate = api.receive_tx(&slate, Some(&args.dest))?;
Ok(())
})?;
}
method => {
let sender = create_sender(method, &args.dest, tor_config)?;
slate = sender.send_tx(&slate)?;
api.tx_lock_outputs(m, &slate, 0)?;
api.tx_lock_outputs(m, &slate)?;
}
}
api.verify_slate_messages(m, &slate).map_err(|e| {
error!("Error validating participant messages: {}", e);
e
})?;
slate = api.finalize_tx(m, &slate)?;
let result = api.post_tx(m, slate.tx_or_err()?, args.fluff);
let result = api.post_tx(m, &slate, args.fluff);
match result {
Ok(_) => {
info!("Tx sent ok",);
@@ -394,7 +450,6 @@ where
/// Receive command argument
pub struct ReceiveArgs {
pub input: String,
pub message: Option<String>,
}
pub fn receive<L, C, K>(
@@ -408,20 +463,16 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let mut slate = PathToSlate((&args.input).into()).get_tx()?;
let (mut slate, was_bin) = PathToSlate((&args.input).into()).get_tx()?;
let km = match keychain_mask.as_ref() {
None => None,
Some(&m) => Some(m.to_owned()),
};
controller::foreign_single_use(owner_api.wallet_inst.clone(), km, |api| {
if let Err(e) = api.verify_slate_messages(&slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
}
slate = api.receive_tx(&slate, Some(&g_args.account), args.message.clone())?;
slate = api.receive_tx(&slate, Some(&g_args.account))?;
Ok(())
})?;
PathToSlate(format!("{}.response", args.input).into()).put_tx(&slate)?;
PathToSlate(format!("{}.response", args.input).into()).put_tx(&slate, was_bin)?;
info!(
"Response file {}.response generated, and can be sent back to the transaction originator.",
args.input
@@ -447,25 +498,18 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let mut slate = PathToSlate((&args.input).into()).get_tx()?;
let (mut slate, was_bin) = PathToSlate((&args.input).into()).get_tx()?;
// Rather than duplicating the entire command, we'll just
// try to determine what kind of finalization this is
// based on the slate contents
// for now, we can tell this is an invoice transaction
// if the receipient (participant 1) hasn't completed sigs
let part_data = slate.participant_with_id(1);
let is_invoice = {
match part_data {
None => {
error!("Expected slate participant data missing");
return Err(ErrorKind::ArgumentError(
"Expected Slate participant data missing".into(),
))?;
}
Some(p) => !p.is_complete(),
}
};
let mut is_invoice = false;
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
is_invoice = api.context_is_invoice(m, &slate)?;
Ok(())
})?;
if is_invoice {
let km = match keychain_mask.as_ref() {
@@ -473,19 +517,11 @@ where
Some(&m) => Some(m.to_owned()),
};
controller::foreign_single_use(owner_api.wallet_inst.clone(), km, |api| {
if let Err(e) = api.verify_slate_messages(&slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
}
slate = api.finalize_invoice_tx(&slate)?;
Ok(())
})?;
} else {
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
if let Err(e) = api.verify_slate_messages(m, &slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
}
slate = api.finalize_tx(m, &slate)?;
Ok(())
})?;
@@ -493,7 +529,7 @@ where
if !args.nopost {
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
let result = api.post_tx(m, slate.tx_or_err()?, args.fluff);
let result = api.post_tx(m, &slate, args.fluff);
match result {
Ok(_) => {
info!(
@@ -510,7 +546,7 @@ where
}
if args.dest.is_some() {
PathToSlate((&args.dest.unwrap()).into()).put_tx(&slate)?;
PathToSlate((&args.dest.unwrap()).into()).put_tx(&slate, was_bin)?;
}
Ok(())
@@ -522,6 +558,11 @@ pub struct IssueInvoiceArgs {
pub dest: String,
/// issue invoice tx args
pub issue_args: IssueInvoiceTxArgs,
/// whether to output as bin
pub bin: bool,
// TODO: Remove HF3
/// whether to output a V4 slate
pub output_v4_slate: bool,
}
pub fn issue_invoice_tx<L, C, K>(
@@ -534,9 +575,23 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
//TODO: Remove block HF3
let args = {
let mut a = args;
let wallet_inst = owner_api.wallet_inst.clone();
let cur_height = {
libwallet::wallet_lock!(wallet_inst, w);
w.w2n_client().get_chain_tip()?.0
};
// TODO: Floonet HF4
if cur_height < 786240 && !a.output_v4_slate && !a.bin {
a.issue_args.target_slate_version = Some(3);
}
a
};
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
let slate = api.issue_invoice_tx(m, args.issue_args)?;
PathToSlate((&args.dest).into()).put_tx(&slate)?;
PathToSlate((&args.dest).into()).put_tx(&slate, args.bin)?;
Ok(())
})?;
Ok(())
@@ -544,7 +599,6 @@ where
/// Arguments for the process_invoice command
pub struct ProcessInvoiceArgs {
pub message: Option<String>,
pub minimum_confirmations: u64,
pub selection_strategy: String,
pub method: String,
@@ -568,7 +622,7 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let slate = PathToSlate((&args.input).into()).get_tx()?;
let (slate, _) = PathToSlate((&args.input).into()).get_tx()?;
let wallet_inst = owner_api.wallet_inst.clone();
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
if args.estimate_selection_strategies {
@@ -598,15 +652,10 @@ where
max_outputs: args.max_outputs as u32,
num_change_outputs: 1u32,
selection_strategy_is_use_all: args.selection_strategy == "all",
message: args.message.clone(),
ttl_blocks: args.ttl_blocks,
send_args: None,
..Default::default()
};
if let Err(e) = api.verify_slate_messages(m, &slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
}
let result = api.process_invoice_tx(m, &slate, init_args);
let mut slate = match result {
Ok(s) => {
@@ -627,11 +676,16 @@ where
match args.method.as_str() {
"file" => {
let slate_putter = PathToSlate((&args.dest).into());
slate_putter.put_tx(&slate)?;
api.tx_lock_outputs(m, &slate, 0)?;
slate_putter.put_tx(&slate, false)?;
api.tx_lock_outputs(m, &slate)?;
}
"filebin" => {
let slate_putter = PathToSlate((&args.dest).into());
slate_putter.put_tx(&slate, true)?;
api.tx_lock_outputs(m, &slate)?;
}
"self" => {
api.tx_lock_outputs(m, &slate, 0)?;
api.tx_lock_outputs(m, &slate)?;
let km = match keychain_mask.as_ref() {
None => None,
Some(&m) => Some(m.to_owned()),
@@ -644,7 +698,7 @@ where
method => {
let sender = create_sender(method, &args.dest, tor_config)?;
slate = sender.send_tx(&slate)?;
api.tx_lock_outputs(m, &slate, 0)?;
api.tx_lock_outputs(m, &slate)?;
}
}
}
@@ -769,7 +823,6 @@ where
)?;
// should only be one here, but just in case
for tx in txs {
display::tx_messages(&tx, dark_scheme)?;
display::payment_proof(&tx)?;
}
}
@@ -795,10 +848,10 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let slate = PathToSlate((&args.input).into()).get_tx()?;
let slate = PathToSlate((&args.input).into()).get_tx()?.0;
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
api.post_tx(m, slate.tx_or_err()?, args.fluff)?;
api.post_tx(m, &slate, args.fluff)?;
info!("Posted transaction");
return Ok(());
})?;
@@ -824,7 +877,7 @@ where
{
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
let (_, txs) = api.retrieve_txs(m, true, Some(args.id), None)?;
let stored_tx = api.get_stored_tx(m, &txs[0])?;
let stored_tx = api.get_stored_tx(m, txs[0].tx_slate_id.unwrap())?;
if stored_tx.is_none() {
error!(
"Transaction with id {} does not have transaction data. Not reposting.",
@@ -841,7 +894,9 @@ where
);
return Ok(());
}
api.post_tx(m, &stored_tx.unwrap(), args.fluff)?;
let mut slate = Slate::blank(2, false);
slate.tx = Some(stored_tx.unwrap());
api.post_tx(m, &slate, args.fluff)?;
info!("Reposted transaction at {}", args.id);
return Ok(());
}