diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index 0f97a20..9aeff2b 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -1239,7 +1239,7 @@ pub trait OwnerRpc { "id": "0436430c-2b02-624c-2032-570501212b00", "sigs": [], "sta": "S3", - "ver": "4:3" + "ver": "4:5" } } } @@ -3140,7 +3140,7 @@ macro_rules! doctest_helper_json_rpc_owner_assert_response { #[cfg(test)] mod tests { use super::*; - use crate::libwallet::{SlatepackArmor, VersionedBinSlate}; + use crate::libwallet::{SlatepackArmor, VersionedBinSlate, GRIN_BLOCK_HEADER_VERSION}; use grin_wallet_util::byte_ser; use tempfile::tempdir; @@ -3186,7 +3186,10 @@ mod tests { .unwrap() .unwrap(); - assert_eq!(response["result"]["Ok"]["ver"], "5:3"); + assert_eq!( + response["result"]["Ok"]["ver"], + format!("5:{}", GRIN_BLOCK_HEADER_VERSION) + ); } #[test] diff --git a/controller/src/command.rs b/controller/src/command.rs index 054e792..e6f1a89 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -842,6 +842,25 @@ where args.input_file, args.input_slatepack_message, )?; + match slate.state { + SlateState::Invoice1 => { + return Err(Error::GenericError( + "Use the 'pay' command to process this invoice transaction".to_string(), + )) + } + SlateState::Standard2 | SlateState::Invoice2 => { + return Err(Error::GenericError( + "Use the 'finalize' command to complete this transaction".to_string(), + )) + } + SlateState::Standard3 | SlateState::Invoice3 => { + return Err(Error::GenericError( + "Use the 'post' command to post this finalized transaction to the chain" + .to_string(), + )) + } + _ => {} + } let km = match keychain_mask.as_ref() { None => None, @@ -1005,6 +1024,25 @@ where args.input_file.clone(), args.input_slatepack_message.clone(), )?; + match slate.state { + SlateState::Standard1 => { + return Err(Error::GenericError( + "Use the 'receive' command on the recipient's wallet first".to_string(), + )) + } + SlateState::Invoice1 => { + return Err(Error::GenericError( + "Use the 'pay' command on the payer's wallet first".to_string(), + )) + } + SlateState::Standard3 | SlateState::Invoice3 => { + return Err(Error::GenericError( + "Use the 'post' command to post this finalized transaction to the chain" + .to_string(), + )) + } + _ => {} + } // Rather than duplicating the entire command, we'll just // try to determine what kind of finalization this is diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 3df3bd3..ea941da 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -66,22 +66,18 @@ lazy_static! { fn check_middleware( name: ForeignCheckMiddlewareFn, - node_version_info: Option, + _node_version_info: Option, slate: Option<&Slate>, ) -> Result<(), Error> { match name { // allow coinbases to be built regardless ForeignCheckMiddlewareFn::BuildCoinbase => Ok(()), _ => { - let mut bhv = 3; - if let Some(n) = node_version_info { - bhv = n.block_header_version; - } if let Some(s) = slate { - if bhv > 4 && s.version_info.block_header_version < GRIN_BLOCK_HEADER_VERSION { + if s.version_info.block_header_version > GRIN_BLOCK_HEADER_VERSION { Err(Error::Compatibility( "Incoming Slate is not compatible with this wallet. \ - Please upgrade the node or use a different one." + Please upgrade the wallet." .into(), ))?; } diff --git a/controller/src/display.rs b/controller/src/display.rs index 9c03823..2f8dd4f 100644 --- a/controller/src/display.rs +++ b/controller/src/display.rs @@ -17,12 +17,11 @@ use crate::core::core::{self, amount_to_hr_string, Inputs, OutputFeatures}; use crate::core::global; use crate::libwallet::contract::types::{ContractView, OwnCommitmentStatus}; use crate::libwallet::{ - AcctPathMapping, Error, OutputCommitMapping, OutputStatus, Slate, TxLogEntry, ViewWallet, - WalletInfo, + AcctPathMapping, Error, OutputCommitMapping, OutputStatus, Slate, SlatepackAddress, TxLogEntry, + ViewWallet, WalletInfo, }; use crate::util::secp::pedersen::Commitment; use crate::util::ToHex; -use grin_wallet_util::OnionV3Address; use prettytable; use std::io::prelude::Write; use term; @@ -615,7 +614,7 @@ pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> { writeln!( t, "Receiver Address: {}", - OnionV3Address::from_bytes(pp.receiver_address.to_bytes()) + SlatepackAddress::new(&pp.receiver_address) ) .unwrap(); writeln!(t, "Receiver Signature: {}", receiver_signature).unwrap(); @@ -624,7 +623,7 @@ pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> { writeln!( t, "Sender Address: {}", - OnionV3Address::from_bytes(pp.sender_address.to_bytes()) + SlatepackAddress::new(&pp.sender_address) ) .unwrap(); writeln!(t, "Sender Signature: {}", sender_signature).unwrap(); diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs index 7acd8bf..3713d5e 100644 --- a/controller/tests/invoice.rs +++ b/controller/tests/invoice.rs @@ -124,6 +124,44 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { )?; assert_eq!(slate.state, SlateState::Invoice1); + let receive_result = wallet::controller::foreign_single_use( + wallet1.clone(), + PathBuf::from(test_dir), + mask1_i.clone(), + |api| api.receive_tx(&slate, None, None).map(|_| ()), + ); + assert_eq!(receive_result, Err(libwallet::Error::SlateState)); + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, txs) = api.retrieve_txs(m, false, None, Some(slate.id), None)?; + assert!(txs.is_empty()); + Ok(()) + }, + )?; + + // receive must reject every slate state except S1 + for state in [ + SlateState::Unknown, + SlateState::Standard2, + SlateState::Standard3, + SlateState::Invoice1, + SlateState::Invoice2, + SlateState::Invoice3, + ] { + let mut bad_slate = slate.clone(); + bad_slate.state = state.clone(); + let res = wallet::controller::foreign_single_use( + wallet1.clone(), + PathBuf::from(test_dir), + mask1_i.clone(), + |api| api.receive_tx(&bad_slate, None, None).map(|_| ()), + ); + assert_eq!(res, Err(libwallet::Error::SlateState), "state {}", state); + } + wallet::controller::owner_single_use( wallet1.clone(), mask1, diff --git a/controller/tests/transaction.rs b/controller/tests/transaction.rs index 6c3d8e5..5425ef8 100644 --- a/controller/tests/transaction.rs +++ b/controller/tests/transaction.rs @@ -23,7 +23,7 @@ use grin_core as core; use self::core::core::transaction; use self::core::core::Transaction; use self::core::global; -use self::libwallet::{InitTxArgs, OutputStatus, Slate, SlateState}; +use self::libwallet::{InitTxArgs, OutputStatus, Slate, SlateState, GRIN_BLOCK_HEADER_VERSION}; use impls::test_framework::{self, LocalWalletClient}; use std::convert::TryInto; use std::path::PathBuf; @@ -105,6 +105,7 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> // assert wallet contents // and a single use api for a send command let amount = 60_000_000_000; + let header_version = core::consensus::header_version(chain.head().unwrap().height).0; let mut slate = Slate::blank(1, false); wallet::controller::owner_single_use( wallet1.clone(), @@ -124,6 +125,7 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> let slate_i = sender_api.init_send_tx(m, args)?; assert_eq!(slate_i.state, SlateState::Standard1); + assert_eq!(slate_i.version_info.block_header_version, header_version); // Check we are creating a tx with the expected lock_height of 0. // We will check this produces a Plain kernel later. @@ -131,6 +133,26 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; assert_eq!(slate.state, SlateState::Standard2); + + // wallet2 already has this slate, so accepted versions fail on the duplicate check + for bhv in 1..=GRIN_BLOCK_HEADER_VERSION + 1 { + let mut s = slate_i.clone(); + s.version_info.block_header_version = bhv; + let res = wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| api.receive_tx(&s, None, None).map(|_| ()), + ); + if bhv <= GRIN_BLOCK_HEADER_VERSION { + assert!(matches!( + res, + Err(libwallet::Error::TransactionAlreadyReceived(_)) + )); + } else { + assert!(matches!(res, Err(libwallet::Error::Compatibility(_)))); + } + } sender_api.tx_lock_outputs(m, &slate)?; slate = sender_api.finalize_tx(m, &slate)?; assert_eq!(slate.state, SlateState::Standard3); diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index e629b1a..ca30339 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -72,6 +72,9 @@ where K: Keychain, { let mut ret_slate = slate.clone(); + if ret_slate.state != SlateState::Standard1 { + return Err(Error::SlateState); + } check_ttl(w, &ret_slate)?; let parent_key_id = match dest_acct_name { Some(d) => { diff --git a/libwallet/src/contract/actions/new.rs b/libwallet/src/contract/actions/new.rs index 60f80fa..0df0fc8 100644 --- a/libwallet/src/contract/actions/new.rs +++ b/libwallet/src/contract/actions/new.rs @@ -21,6 +21,7 @@ use crate::contract::types::ContractSetupArgsAPI; use crate::error::Error; use crate::grin_keychain::Keychain; use crate::grin_util::secp::key::SecretKey; +use crate::internal::tx; use crate::slate::Slate; use crate::types::{Context, NodeClient}; use uuid::Uuid; @@ -65,11 +66,15 @@ where let net_change = setup_args.required_net_change()?; debug!("contract::new => net_change passed: {}", net_change); - // Initialize a new contract (if net_change is positive, I'm the receiver meaning this is invoice flow) - let num_participants = setup_args.num_participants; - let mut slate = Slate::blank(num_participants, net_change > 0); - // Contracts start with V4 and move to V5 when their proof data requires it - slate.version_info.version = 4; + // invoice flow if net_change is positive + let mut slate = tx::new_tx_slate( + w, + net_change.unsigned_abs(), + net_change > 0, + setup_args.num_participants, + false, + None, + )?; // Use a caller-supplied id when given, so a retried creation reuses the same context. let mut existing_context = None; if let Some(id) = slate_id { @@ -96,9 +101,6 @@ where })?; } } - // We set slate.amount to contain the _positive_ net_change for the other party so they can derive expectations. - // unsigned_abs avoids the i64::MIN overflow panic of abs(). - slate.amount = net_change.unsigned_abs(); debug!("contract::new => slate amount: {}", slate.amount); // Perform setup for the slate diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index ca06c76..4c7c8ba 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -18,8 +18,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use std::io::Cursor; use uuid::Uuid; -use crate::grin_core::consensus::valid_header_version; -use crate::grin_core::core::HeaderVersion; +use crate::grin_core::consensus::header_version; use crate::grin_keychain::{Identifier, Keychain}; use crate::grin_util::secp::key::SecretKey; use crate::grin_util::secp::pedersen; @@ -72,17 +71,7 @@ where } slate.amount = amount; - if valid_header_version(current_height, HeaderVersion(1)) { - slate.version_info.block_header_version = 1; - } - - if valid_header_version(current_height, HeaderVersion(2)) { - slate.version_info.block_header_version = 2; - } - - if valid_header_version(current_height, HeaderVersion(3)) { - slate.version_info.block_header_version = 3; - } + slate.version_info.block_header_version = header_version(current_height).0; // Set the features explicitly to 0 here. // This will generate a Plain kernel (rather than a HeightLocked kernel). diff --git a/libwallet/src/slate_versions/mod.rs b/libwallet/src/slate_versions/mod.rs index b7759e9..fc2f4e1 100644 --- a/libwallet/src/slate_versions/mod.rs +++ b/libwallet/src/slate_versions/mod.rs @@ -41,8 +41,8 @@ pub mod v5_bin; /// The most recent version of the slate pub const CURRENT_SLATE_VERSION: u16 = 5; -/// The grin block header this slate is intended to be compatible with -pub const GRIN_BLOCK_HEADER_VERSION: u16 = 3; +/// The latest grin block header version this wallet supports +pub const GRIN_BLOCK_HEADER_VERSION: u16 = 5; const HEIGHT_LOCKED_FEATURE: u8 = 2; const NRD_FEATURE: u8 = 3; diff --git a/src/cmd/wallet.rs b/src/cmd/wallet.rs index b21a66b..b30ed81 100644 --- a/src/cmd/wallet.rs +++ b/src/cmd/wallet.rs @@ -21,7 +21,7 @@ use semver::Version; use std::thread; use std::time::Duration; -const MIN_COMPAT_NODE_VERSION: &str = "4.0.0-alpha.1"; +const MIN_COMPAT_NODE_VERSION: &str = "5.0.0-beta.1"; pub fn wallet_command( wallet_args: &ArgMatches<'_>, diff --git a/tests/cmd_line_basic.rs b/tests/cmd_line_basic.rs index 019e6cf..86f7675 100644 --- a/tests/cmd_line_basic.rs +++ b/tests/cmd_line_basic.rs @@ -567,6 +567,22 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: test_dir ); + // receive and finalize should point to pay + for cmd in ["receive", "finalize"] { + let arg_vec = vec![ + "grin-wallet", + "-a", + "mining", + "-p", + "password1", + cmd, + "-i", + &file_name, + ]; + let e = execute_command(&app, test_dir, "wallet1", &client1, arg_vec).unwrap_err(); + assert!(e.to_string().contains("'pay'"), "{}", e); + } + // now pay the invoice tx, wallet 1 let arg_vec = vec![ "grin-wallet",