diff --git a/.changelog/unreleased/features/1123-tx-lifetime.md b/.changelog/unreleased/features/1123-tx-lifetime.md new file mode 100644 index 0000000000..44b51be3f0 --- /dev/null +++ b/.changelog/unreleased/features/1123-tx-lifetime.md @@ -0,0 +1,2 @@ +- Adds expiration field to transactions + ([#1123](https://github.com/anoma/namada/pull/1123)) \ No newline at end of file diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index a4cd3c1e79..f6688a8a2d 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1620,6 +1620,7 @@ pub mod args { const DRY_RUN_TX: ArgFlag = flag("dry-run"); const DUMP_TX: ArgFlag = flag("dump-tx"); const EPOCH: ArgOpt = arg_opt("epoch"); + const EXPIRATION_OPT: ArgOpt = arg_opt("expiration"); const FORCE: ArgFlag = flag("force"); const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); const GAS_AMOUNT: ArgDefault = @@ -2897,6 +2898,8 @@ pub mod args { pub fee_token: WalletAddress, /// The max amount of gas used to process tx pub gas_limit: GasLimit, + /// The optional expiration of the transaction + pub expiration: Option, /// Sign the tx with the key for the given alias from your wallet pub signing_key: Option, /// Sign the tx with the keypair of the public key of the given address @@ -2963,6 +2966,12 @@ pub mod args { "The maximum amount of gas needed to run transaction", ), ) + .arg(EXPIRATION_OPT.def().about( + "The expiration datetime of the transaction, after which the \ + tx won't be accepted anymore. All of these examples are \ + equivalent:\n2012-12-12T12:12:12Z\n2012-12-12 \ + 12:12:12Z\n2012- 12-12T12: 12:12Z", + )) .arg( SIGNING_KEY_OPT .def() @@ -2994,6 +3003,7 @@ pub mod args { let fee_amount = GAS_AMOUNT.parse(matches); let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).into(); + let expiration = EXPIRATION_OPT.parse(matches); let signing_key = SIGNING_KEY_OPT.parse(matches); let signer = SIGNER.parse(matches); @@ -3007,6 +3017,7 @@ pub mod args { fee_amount, fee_token, gas_limit, + expiration, signing_key, signer, } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 39d85bd07b..6bcef3595b 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -226,7 +226,7 @@ pub async fn query_tx_deltas( let mut transfer = None; extract_payload(tx, &mut wrapper, &mut transfer); // Epoch data is not needed for transparent transactions - let epoch = wrapper.map(|x| x.epoch).unwrap_or_default(); + let epoch = Epoch::default(); if let Some(transfer) = transfer { // Skip MASP addresses as they are already handled by // ShieldedContext diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 5fb6a2410b..cb5f28aee7 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -3,11 +3,11 @@ use borsh::BorshSerialize; use namada::ledger::parameters::storage as parameter_storage; +use namada::proof_of_stake::Epoch; use namada::proto::Tx; use namada::types::address::{Address, ImplicitAddress}; use namada::types::hash::Hash; use namada::types::key::*; -use namada::types::storage::Epoch; use namada::types::token; use namada::types::token::Amount; use namada::types::transaction::{hash_tx, Fee, WrapperTx, MIN_FEE}; @@ -310,7 +310,7 @@ pub async fn sign_wrapper( let decrypted_hash = tx.tx_hash.to_string(); TxBroadcastData::Wrapper { tx: tx - .sign(keypair, ctx.config.ledger.chain_id.clone()) + .sign(keypair, ctx.config.ledger.chain_id.clone(), args.expiration) .expect("Wrapper tx signing keypair should be correct"), wrapper_hash, decrypted_hash, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index d3ae6d168a..ca88a6f916 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -107,7 +107,12 @@ pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let data = args.data_path.map(|data_path| { std::fs::read(data_path).expect("Expected a file at given data path") }); - let tx = Tx::new(tx_code, data, ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + data, + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let (ctx, initialized_accounts) = process_tx( ctx, &args.tx, @@ -170,7 +175,12 @@ pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { let data = UpdateVp { addr, vp_code }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); process_tx( ctx, &args.tx, @@ -203,7 +213,12 @@ pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let (ctx, initialized_accounts) = process_tx( ctx, &args.tx, @@ -336,7 +351,12 @@ pub async fn submit_init_validator( validator_vp_code, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + tx_args.expiration, + ); let (mut ctx, initialized_accounts) = process_tx( ctx, &tx_args, @@ -1678,7 +1698,12 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { .try_to_vec() .expect("Encoding tx data shouldn't fail"); let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let signing_address = TxSigningKey::WalletAddress(args.source.to_address()); process_tx( @@ -1798,7 +1823,12 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { prost::Message::encode(&any_msg, &mut data) .expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); process_tx( ctx, &args.tx, @@ -1943,8 +1973,12 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .try_to_vec() .expect("Encoding proposal data shouldn't fail"); let tx_code = ctx.read_wasm(TX_INIT_PROPOSAL); - let tx = - Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); process_tx( ctx, @@ -2199,6 +2233,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { tx_code, Some(data), ctx.config.ledger.chain_id.clone(), + args.tx.expiration, ); process_tx( @@ -2272,7 +2307,7 @@ pub async fn submit_reveal_pk_aux( .expect("Encoding a public key shouldn't fail"); let tx_code = ctx.read_wasm(TX_REVEAL_PK); let chain_id = ctx.config.ledger.chain_id.clone(); - let tx = Tx::new(tx_code, Some(tx_data), chain_id); + let tx = Tx::new(tx_code, Some(tx_data), chain_id, args.expiration); // submit_tx without signing the inner tx let keypair = if let Some(signing_key) = &args.signing_key { @@ -2475,7 +2510,12 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { }; let data = bond.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.source.unwrap_or(args.validator); process_tx( ctx, @@ -2530,7 +2570,12 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx_code = ctx.read_wasm(TX_UNBOND_WASM); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.source.unwrap_or(args.validator); let (_ctx, _) = process_tx( ctx, @@ -2595,7 +2640,12 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx_code = ctx.read_wasm(TX_WITHDRAW_WASM); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.source.unwrap_or(args.validator); process_tx( ctx, @@ -2681,7 +2731,12 @@ pub async fn submit_validator_commission_change( }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.validator; process_tx( ctx, diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e112dec1a6..abd3fa91e2 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -941,6 +941,7 @@ mod test_finalize_block { "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -956,7 +957,7 @@ mod test_finalize_block { None, ); let tx = wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); if i > 1 { processed_txs.push(ProcessedTx { @@ -1017,6 +1018,7 @@ mod test_finalize_block { "wasm_code".as_bytes().to_owned(), Some(String::from("transaction data").as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1152,6 +1154,7 @@ mod test_finalize_block { .to_owned(), ), shell.chain_id.clone(), + None, ); let wrapper_tx = WrapperTx::new( Fee { @@ -1190,6 +1193,7 @@ mod test_finalize_block { .to_owned(), ), shell.chain_id.clone(), + None, ); let wrapper_tx = WrapperTx::new( Fee { @@ -1205,7 +1209,7 @@ mod test_finalize_block { None, ); let wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); valid_txs.push(wrapper_tx); processed_txs.push(ProcessedTx { @@ -1698,6 +1702,7 @@ mod test_finalize_block { tx_code, Some("Encrypted transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper_tx = WrapperTx::new( Fee { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 9d68261432..dfdae4d04e 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -151,6 +151,7 @@ where proposal_code, Some(encode(&id)), shell.chain_id.clone(), + None, ); let tx_type = TxType::Decrypted(DecryptedTx::Decrypted { tx, diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9f4fe4de2d..67d651be2c 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -43,9 +43,11 @@ use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token::{self}; +#[cfg(not(feature = "mainnet"))] +use namada::types::transaction::MIN_FEE; use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, - EllipticCurve, PairingEngine, TxType, MIN_FEE, + EllipticCurve, PairingEngine, TxType, }; use namada::types::{address, hash}; use namada::vm::wasm::{TxCache, VpCache}; @@ -62,6 +64,7 @@ use crate::facade::tendermint_proto::abci::{ Misbehavior as Evidence, MisbehaviorType as EvidenceType, ValidatorUpdate, }; use crate::facade::tendermint_proto::crypto::public_key; +use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::facade::tower_abci::{request, response}; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; @@ -124,16 +127,18 @@ impl From for TxResult { #[derive(Debug, Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] pub enum ErrorCodes { Ok = 0, - InvalidTx = 1, - InvalidSig = 2, + InvalidDecryptedChainId = 1, + ExpiredDecryptedTx = 2, WasmRuntimeError = 3, - InvalidOrder = 4, - ExtraTxs = 5, - Undecryptable = 6, - AllocationError = 7, - ReplayTx = 8, - InvalidChainId = 9, - InvalidDecryptedChainId = 10, + InvalidTx = 4, + InvalidSig = 5, + InvalidOrder = 6, + ExtraTxs = 7, + Undecryptable = 8, + AllocationError = 9, + ReplayTx = 10, + InvalidChainId = 11, + ExpiredTx = 12, } impl ErrorCodes { @@ -144,10 +149,13 @@ impl ErrorCodes { // NOTE: pattern match on all `ErrorCodes` variants, in order // to catch potential bugs when adding new codes match self { - Ok | InvalidDecryptedChainId => true, - InvalidTx | InvalidSig | WasmRuntimeError | InvalidOrder - | ExtraTxs | Undecryptable | AllocationError | ReplayTx - | InvalidChainId => false, + Ok + | InvalidDecryptedChainId + | ExpiredDecryptedTx + | WasmRuntimeError => true, + InvalidTx | InvalidSig | InvalidOrder | ExtraTxs + | Undecryptable | AllocationError | ReplayTx | InvalidChainId + | ExpiredTx => false, } } } @@ -414,6 +422,25 @@ where response } + /// Takes the optional tendermint timestamp of the block: if it's Some than + /// converts it to a [`DateTimeUtc`], otherwise retrieve from self the + /// time of the last block committed + pub fn get_block_timestamp( + &self, + tendermint_block_time: Option, + ) -> DateTimeUtc { + if let Some(t) = tendermint_block_time { + if let Ok(t) = t.try_into() { + return t; + } + } + // Default to last committed block time + self.wl_storage + .storage + .get_last_block_timestamp() + .expect("Failed to retrieve last block timestamp") + } + /// Read the value for a storage key dropping any error pub fn read_storage_key(&self, key: &Key) -> Option where @@ -638,6 +665,20 @@ where return response; } + // Tx expiration + if let Some(exp) = tx.expiration { + let last_block_timestamp = self.get_block_timestamp(None); + + if last_block_timestamp > exp { + response.code = ErrorCodes::ExpiredTx.into(); + response.log = format!( + "Tx expired at {:#?}, last committed block time: {:#?}", + exp, last_block_timestamp + ); + return response; + } + } + // Tx signature check let tx_type = match process_tx(tx) { Ok(ty) => ty, @@ -1115,6 +1156,7 @@ mod test_utils { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1166,8 +1208,8 @@ mod test_utils { /// Test the failure cases of [`mempool_validate`] #[cfg(test)] mod test_mempool_validate { + use namada::proof_of_stake::Epoch; use namada::proto::SignedTxData; - use namada::types::storage::Epoch; use namada::types::transaction::{Fee, WrapperTx}; use super::test_utils::TestShell; @@ -1184,6 +1226,7 @@ mod test_mempool_validate { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let mut wrapper = WrapperTx::new( @@ -1199,7 +1242,7 @@ mod test_mempool_validate { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Wrapper signing failed"); let unsigned_wrapper = if let Some(Ok(SignedTxData { @@ -1210,7 +1253,7 @@ mod test_mempool_validate { .take() .map(|data| SignedTxData::try_from_slice(&data[..])) { - Tx::new(vec![], Some(data), shell.chain_id.clone()) + Tx::new(vec![], Some(data), shell.chain_id.clone(), None) } else { panic!("Test failed") }; @@ -1238,6 +1281,7 @@ mod test_mempool_validate { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let mut wrapper = WrapperTx::new( @@ -1253,7 +1297,7 @@ mod test_mempool_validate { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Wrapper signing failed"); let invalid_wrapper = if let Some(Ok(SignedTxData { @@ -1289,6 +1333,7 @@ mod test_mempool_validate { .expect("Test failed"), ), shell.chain_id.clone(), + None, ) } else { panic!("Test failed"); @@ -1316,6 +1361,7 @@ mod test_mempool_validate { "wasm_code".as_bytes().to_owned(), None, shell.chain_id.clone(), + None, ); let result = shell.mempool_validate( @@ -1338,6 +1384,7 @@ mod test_mempool_validate { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( @@ -1353,7 +1400,7 @@ mod test_mempool_validate { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Wrapper signing failed"); let tx_type = match process_tx(wrapper.clone()).expect("Test failed") { @@ -1449,6 +1496,7 @@ mod test_mempool_validate { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), wrong_chain_id.clone(), + None, ) .sign(&keypair); @@ -1465,4 +1513,26 @@ mod test_mempool_validate { ) ) } + + /// Check that an expired transaction gets rejected + #[test] + fn test_expired_tx() { + let (shell, _) = TestShell::new(); + + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + Some(DateTimeUtc::now()), + ) + .sign(&keypair); + + let result = shell.mempool_validate( + tx.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::ExpiredTx)); + } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5f51afee65..ecae01e3e9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -5,6 +5,7 @@ use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::proof_of_stake::pos_queries::PosQueries; use namada::proto::Tx; use namada::types::internal::WrapperTxInQueue; +use namada::types::time::DateTimeUtc; use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; @@ -20,6 +21,7 @@ use super::block_space_alloc::{AllocFailure, BlockSpaceAllocator}; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::ExtendedCommitInfo; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; +use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::{response, TxBytes}; @@ -46,7 +48,7 @@ where // add encrypted txs let (encrypted_txs, alloc) = - self.build_encrypted_txs(alloc, &req.txs); + self.build_encrypted_txs(alloc, &req.txs, &req.time); let mut txs = encrypted_txs; // decrypt the wrapper txs included in the previous block @@ -118,18 +120,29 @@ where &self, mut alloc: EncryptedTxBatchAllocator, txs: &[TxBytes], + block_time: &Option, ) -> (Vec, BlockSpaceAllocator) { let pos_queries = self.wl_storage.pos_queries(); + let block_time = block_time.clone().and_then(|block_time| { + // If error in conversion, default to last block datetime, it's + // valid because of mempool check + TryInto::::try_into(block_time).ok() + }); let txs = txs .iter() .filter_map(|tx_bytes| { - if let Ok(Ok(TxType::Wrapper(_))) = - Tx::try_from(tx_bytes.as_slice()).map(process_tx) - { - Some(tx_bytes.clone()) - } else { - None + if let Ok(tx) = Tx::try_from(tx_bytes.as_slice()) { + // If tx doesn't have an expiration it is valid. If time cannot be + // retrieved from block default to last block datetime which has + // already been checked by mempool_validate, so it's valid + if let (Some(block_time), Some(exp)) = (block_time.as_ref(), &tx.expiration) { + if block_time > exp { return None } + } + if let Ok(TxType::Wrapper(_)) = process_tx(tx) { + return Some(tx_bytes.clone()); + } } + None }) .take_while(|tx_bytes| { alloc.try_alloc(&tx_bytes[..]) @@ -256,8 +269,9 @@ where #[cfg(test)] mod test_prepare_proposal { + use borsh::BorshSerialize; - use namada::types::storage::Epoch; + use namada::proof_of_stake::Epoch; use namada::types::transaction::{Fee, WrapperTx}; use super::*; @@ -273,6 +287,7 @@ mod test_prepare_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let req = RequestPrepareProposal { txs: vec![tx.to_bytes()], @@ -292,6 +307,7 @@ mod test_prepare_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); // an unsigned wrapper will cause an error in processing let wrapper = Tx::new( @@ -314,6 +330,7 @@ mod test_prepare_proposal { .expect("Test failed"), ), shell.chain_id.clone(), + None, ) .to_bytes(); #[allow(clippy::redundant_clone)] @@ -345,6 +362,7 @@ mod test_prepare_proposal { "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), shell.chain_id.clone(), + None, ); expected_decrypted.push(Tx::from(DecryptedTx::Decrypted { tx: tx.clone(), @@ -365,7 +383,7 @@ mod test_prepare_proposal { None, ); let wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); shell.enqueue_tx(wrapper_tx); expected_wrapper.push(wrapper.clone()); @@ -393,4 +411,58 @@ mod test_prepare_proposal { // check that the order of the txs is correct assert_eq!(received, expected_txs); } + + /// Test that expired wrapper transactions are not included in the block + #[test] + fn test_expired_wrapper_tx() { + let (shell, _) = TestShell::new(); + let keypair = gen_keypair(); + let tx_time = DateTimeUtc::now(); + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + let wrapper_tx = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let wrapper = wrapper_tx + .sign(&keypair, shell.chain_id.clone(), Some(tx_time)) + .expect("Test failed"); + + let time = DateTimeUtc::now(); + let block_time = + namada::core::tendermint_proto::google::protobuf::Timestamp { + seconds: time.0.timestamp(), + nanos: time.0.timestamp_subsec_nanos() as i32, + }; + let req = RequestPrepareProposal { + txs: vec![wrapper.to_bytes()], + max_tx_bytes: 0, + time: Some(block_time), + ..Default::default() + }; + #[cfg(feature = "abcipp")] + assert_eq!( + shell.prepare_proposal(req).tx_records, + vec![record::remove(wrapper.to_bytes())] + ); + #[cfg(not(feature = "abcipp"))] + { + let result = shell.prepare_proposal(req); + eprintln!("Proposal: {:?}", result.txs); + assert!(result.txs.is_empty()); + } + } } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 60f3f68d7d..ecdbcd719c 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -82,7 +82,8 @@ where &self, req: RequestProcessProposal, ) -> ProcessProposal { - let (tx_results, metadata) = self.process_txs(&req.txs); + let (tx_results, metadata) = + self.process_txs(&req.txs, self.get_block_timestamp(req.time)); // Erroneous transactions were detected when processing // the leader's proposal. We allow txs that do not @@ -133,6 +134,7 @@ where pub fn process_txs( &self, txs: &[TxBytes], + block_time: DateTimeUtc, ) -> (Vec, ValidationMeta) { let mut tx_queue_iter = self.wl_storage.storage.tx_queue.iter(); let mut temp_wl_storage = TempWlStorage::new(&self.wl_storage.storage); @@ -145,6 +147,7 @@ where &mut tx_queue_iter, &mut metadata, &mut temp_wl_storage, + block_time, ); if let ErrorCodes::Ok = ErrorCodes::from_u32(result.code).unwrap() @@ -189,6 +192,7 @@ where tx_queue_iter: &mut impl Iterator, metadata: &mut ValidationMeta, temp_wl_storage: &mut TempWlStorage, + block_time: DateTimeUtc, ) -> TxResult { // try to allocate space for this tx if let Err(e) = metadata.txs_bin.try_dump(tx_bytes) { @@ -222,6 +226,7 @@ where }, |tx| { let tx_chain_id = tx.chain_id.clone(); + let tx_expiration = tx.expiration; let tx_type = process_tx(tx).map_err(|err| { // This occurs if the wrapper / protocol tx signature is // invalid @@ -230,10 +235,10 @@ where info: err.to_string(), } })?; - Ok((tx_chain_id, tx_type)) + Ok((tx_chain_id, tx_expiration, tx_type)) }, ); - let (tx_chain_id, tx) = match maybe_tx { + let (tx_chain_id, tx_expiration, tx) = match maybe_tx { Ok(tx) => tx, Err(tx_result) => return tx_result, }; @@ -250,6 +255,7 @@ where .into(), }, TxType::Protocol(_) => { + // Tx chain id if tx_chain_id != self.chain_id { return TxResult { code: ErrorCodes::InvalidChainId.into(), @@ -260,6 +266,19 @@ where ), }; } + + // Tx expiration + if let Some(exp) = tx_expiration { + if block_time > exp { + return TxResult { + code: ErrorCodes::ExpiredTx.into(), + info: format!( + "Tx expired at {:#?}, block time: {:#?}", + exp, block_time + ), + }; + } + } TxResult { code: ErrorCodes::InvalidTx.into(), info: "Protocol transactions are a fun new feature that \ @@ -285,6 +304,7 @@ where has_valid_pow: _, } = tx { + // Tx chain id if tx.chain_id != self.chain_id { return TxResult { code: @@ -297,6 +317,22 @@ where ), }; } + + // Tx expiration + if let Some(exp) = tx.expiration { + if block_time > exp { + return TxResult { + code: + ErrorCodes::ExpiredDecryptedTx + .into(), + info: format!( + "Decrypted tx expired at \ + {:#?}, block time: {:#?}", + exp, block_time + ), + }; + } + } } TxResult { code: ErrorCodes::Ok.into(), @@ -307,7 +343,7 @@ where } else { // Wrong inner tx commitment TxResult { - code: ErrorCodes::Undecryptable.into(), + code: ErrorCodes::InvalidTx.into(), info: "The encrypted payload of tx was \ incorrectly marked as un-decryptable" .into(), @@ -369,6 +405,19 @@ where }; } + // Tx expiration + if let Some(exp) = tx_expiration { + if block_time > exp { + return TxResult { + code: ErrorCodes::ExpiredTx.into(), + info: format!( + "Tx expired at {:#?}, block time: {:#?}", + exp, block_time + ), + }; + } + } + // validate the ciphertext via Ferveo if !wrapper.validate_ciphertext() { TxResult { @@ -514,6 +563,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -532,6 +582,7 @@ mod test_process_proposal { vec![], Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), shell.chain_id.clone(), + None, ) .to_bytes(); #[allow(clippy::redundant_clone)] @@ -564,6 +615,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let timestamp = tx.timestamp; let mut wrapper = WrapperTx::new( @@ -579,7 +631,7 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); let new_tx = if let Some(Ok(SignedTxData { data: Some(data), @@ -615,6 +667,7 @@ mod test_process_proposal { ), timestamp, chain_id: shell.chain_id.clone(), + expiration: None, } } else { panic!("Test failed"); @@ -660,6 +713,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -674,7 +728,7 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); let request = ProcessProposal { txs: vec![wrapper.to_bytes()], @@ -727,6 +781,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -741,7 +796,7 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); let request = ProcessProposal { @@ -778,6 +833,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -829,7 +885,7 @@ mod test_process_proposal { ); } - /// Test that a tx incorrectly labelled as undecryptable + /// Test that a block containing a tx incorrectly labelled as undecryptable /// is rejected by [`process_proposal`] #[test] fn test_incorrectly_labelled_as_undecryptable() { @@ -840,6 +896,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -865,11 +922,11 @@ mod test_process_proposal { }; match shell.process_proposal(request) { - Ok(_) => panic!("Test failes"), + Ok(_) => panic!("Test failed"), Err(TestError::RejectProposal(response)) => { assert_eq!( response[0].result.code, - u32::from(ErrorCodes::Undecryptable) + u32::from(ErrorCodes::InvalidTx) ); assert_eq!( response[0].result.info, @@ -894,6 +951,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let mut wrapper = WrapperTx::new( Fee { @@ -989,6 +1047,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { @@ -1028,6 +1087,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let mut tx = Tx::from(TxType::Raw(tx)); tx.chain_id = shell.chain_id.clone(); @@ -1065,6 +1125,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1080,7 +1141,7 @@ mod test_process_proposal { None, ); let signed = wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); // Write wrapper hash to storage @@ -1138,6 +1199,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1153,7 +1215,7 @@ mod test_process_proposal { None, ); let signed = wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); // Run validation @@ -1195,6 +1257,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1211,7 +1274,7 @@ mod test_process_proposal { ); let inner_unsigned_hash = wrapper.tx_hash.clone(); let signed = wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); // Write inner hash to storage @@ -1280,6 +1343,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1296,7 +1360,7 @@ mod test_process_proposal { ); let inner_unsigned_hash = wrapper.tx_hash.clone(); let signed = wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); let new_wrapper = WrapperTx::new( @@ -1313,7 +1377,7 @@ mod test_process_proposal { None, ); let new_signed = new_wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); // Run validation @@ -1351,6 +1415,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1367,7 +1432,7 @@ mod test_process_proposal { ); let wrong_chain_id = ChainId("Wrong chain id".to_string()); let signed = wrapper - .sign(&keypair, wrong_chain_id.clone()) + .sign(&keypair, wrong_chain_id.clone(), None) .expect("Test failed"); let protocol_tx = ProtocolTxType::EthereumStateUpdate(tx).sign( @@ -1413,6 +1478,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("new transaction data".as_bytes().to_owned()), wrong_chain_id.clone(), + None, ); let decrypted: Tx = DecryptedTx::Decrypted { tx: tx.clone(), @@ -1462,4 +1528,101 @@ mod test_process_proposal { Err(_) => panic!("Test failed"), } } + + /// Test that an expired wrapper transaction causes a block rejection + #[test] + fn test_expired_wrapper() { + let (mut shell, _) = TestShell::new(); + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let signed = wrapper + .sign(&keypair, shell.chain_id.clone(), Some(DateTimeUtc::now())) + .expect("Test failed"); + + // Run validation + let request = ProcessProposal { + txs: vec![signed.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::ExpiredTx) + ); + } + } + } + + /// Test that an expired decrypted transaction is correctlye marked as so + /// without rejecting the entire block + #[test] + fn test_expired_decrypted() { + let (mut shell, _) = TestShell::new(); + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("new transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + Some(DateTimeUtc::now()), + ); + let decrypted: Tx = DecryptedTx::Decrypted { + tx: tx.clone(), + has_valid_pow: false, + } + .into(); + let signed_decrypted = decrypted.sign(&keypair); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let wrapper_in_queue = WrapperTxInQueue { + tx: wrapper, + has_valid_pow: false, + }; + shell.wl_storage.storage.tx_queue.push(wrapper_in_queue); + + // Run validation + let request = ProcessProposal { + txs: vec![signed_decrypted.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(response) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::ExpiredDecryptedTx) + ); + } + Err(_) => panic!("Test failed"), + } + } } diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 970607f655..8bc45ce977 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -102,9 +102,11 @@ impl AbcippShim { }), #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { + let block_time = + self.service.get_block_timestamp(block.time.clone()); let unprocessed_txs = block.txs.clone(); let (processing_results, _) = - self.service.process_txs(&block.txs); + self.service.process_txs(&block.txs, block_time); let mut txs = Vec::with_capacity(unprocessed_txs.len()); for (result, tx) in processing_results .into_iter() @@ -137,8 +139,18 @@ impl AbcippShim { } #[cfg(not(feature = "abcipp"))] Req::EndBlock(_) => { - let (processing_results, _) = - self.service.process_txs(&self.delivered_txs); + let begin_block_request = + self.begin_block_request.take().unwrap(); + let block_time = self.service.get_block_timestamp( + begin_block_request + .header + .as_ref() + .and_then(|header| header.time.to_owned()), + ); + + let (processing_results, _) = self + .service + .process_txs(&self.delivered_txs, block_time); let mut txs = Vec::with_capacity(self.delivered_txs.len()); let mut delivered = vec![]; std::mem::swap(&mut self.delivered_txs, &mut delivered); @@ -149,7 +161,7 @@ impl AbcippShim { txs.push(ProcessedTx { tx, result }); } let mut end_block_request: FinalizeBlock = - self.begin_block_request.take().unwrap().into(); + begin_block_request.into(); let hash = self.get_hash(); end_block_request.hash = BlockHash::from(hash.clone()); end_block_request.txs = txs; diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 18edcac30e..57f7a68d49 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -806,6 +806,17 @@ where } } + /// Get the timestamp of the last committed block, or the current timestamp + /// if no blocks have been produced yet + pub fn get_last_block_timestamp(&self) -> Result { + let last_block_height = self.get_block_height().0; + + Ok(self + .db + .read_block_header(last_block_height)? + .map_or_else(DateTimeUtc::now, |header| header.time)) + } + /// Get the current conversions pub fn get_conversion_state(&self) -> &ConversionState { &self.conversion_state diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index c945d229b9..39e18f23b7 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -7,6 +7,8 @@ pub use types::{Dkg, Error, Signed, SignedTxData, Tx}; #[cfg(test)] mod tests { + use std::time::SystemTime; + use data_encoding::HEXLOWER; use generated::types::Tx; use prost::Message; @@ -19,8 +21,9 @@ mod tests { let tx = Tx { code: "wasm code".as_bytes().to_owned(), data: Some("arbitrary data".as_bytes().to_owned()), - timestamp: Some(std::time::SystemTime::now().into()), + timestamp: Some(SystemTime::now().into()), chain_id: ChainId::default().0, + expiration: Some(SystemTime::now().into()), }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 657083b924..18d3ccbd1f 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -138,17 +138,20 @@ pub struct SigningTx { pub data: Option>, pub timestamp: DateTimeUtc, pub chain_id: ChainId, + pub expiration: Option, } impl SigningTx { pub fn hash(&self) -> [u8; 32] { let timestamp = Some(self.timestamp.into()); + let expiration = self.expiration.map(|e| e.into()); let mut bytes = vec![]; types::Tx { code: self.code_hash.to_vec(), data: self.data.clone(), timestamp, chain_id: self.chain_id.as_str().to_owned(), + expiration, } .encode(&mut bytes) .expect("encoding a transaction failed"); @@ -170,6 +173,7 @@ impl SigningTx { data: Some(signed), timestamp: self.timestamp, chain_id: self.chain_id, + expiration: self.expiration, } } @@ -190,6 +194,7 @@ impl SigningTx { data, timestamp: self.timestamp, chain_id: self.chain_id.clone(), + expiration: self.expiration, }; let signed_data = tx.hash(); common::SigScheme::verify_signature_raw(pk, &signed_data, sig) @@ -204,6 +209,7 @@ impl SigningTx { data: self.data, timestamp: self.timestamp, chain_id: self.chain_id, + expiration: self.expiration, }) } else { None @@ -218,6 +224,7 @@ impl From for SigningTx { data: tx.data, timestamp: tx.timestamp, chain_id: tx.chain_id, + expiration: tx.expiration, } } } @@ -233,6 +240,7 @@ pub struct Tx { pub data: Option>, pub timestamp: DateTimeUtc, pub chain_id: ChainId, + pub expiration: Option, } impl TryFrom<&[u8]> for Tx { @@ -245,12 +253,17 @@ impl TryFrom<&[u8]> for Tx { None => return Err(Error::NoTimestampError), }; let chain_id = ChainId(tx.chain_id); + let expiration = match tx.expiration { + Some(e) => Some(e.try_into().map_err(Error::InvalidTimestamp)?), + None => None, + }; Ok(Tx { code: tx.code, data: tx.data, timestamp, chain_id, + expiration, }) } } @@ -258,11 +271,14 @@ impl TryFrom<&[u8]> for Tx { impl From for types::Tx { fn from(tx: Tx) -> Self { let timestamp = Some(tx.timestamp.into()); + let expiration = tx.expiration.map(|e| e.into()); + types::Tx { code: tx.code, data: tx.data, timestamp, chain_id: tx.chain_id.as_str().to_owned(), + expiration, } } } @@ -358,12 +374,14 @@ impl Tx { code: Vec, data: Option>, chain_id: ChainId, + expiration: Option, ) -> Self { Tx { code, data, timestamp: DateTimeUtc::now(), chain_id, + expiration, } } @@ -390,6 +408,7 @@ impl Tx { data: signed_data.data, timestamp: self.timestamp, chain_id: self.chain_id.clone(), + expiration: self.expiration, }; unsigned_tx.hash() } @@ -513,7 +532,8 @@ mod tests { let code = "wasm code".as_bytes().to_owned(); let data = "arbitrary data".as_bytes().to_owned(); let chain_id = ChainId::default(); - let tx = Tx::new(code.clone(), Some(data.clone()), chain_id.clone()); + let tx = + Tx::new(code.clone(), Some(data.clone()), chain_id.clone(), None); let bytes = tx.to_bytes(); let tx_from_bytes = @@ -525,6 +545,7 @@ mod tests { data: Some(data), timestamp: None, chain_id: chain_id.0, + expiration: None, }; let mut bytes = vec![]; types_tx.encode(&mut bytes).expect("encoding failed"); diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 285113c36a..3407179168 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -93,12 +93,14 @@ pub mod decrypted_tx { .try_to_vec() .expect("Encrypting transaction should not fail"), ), - // If undecrytable we cannot extract the ChainId. - // If instead the tx gets decrypted successfully, the correct - // chain id is serialized inside the data field - // of the Tx, while the one available - // in the chain_id field is just a placeholder + // If undecrytable we cannot extract the ChainId and + // expiration. If instead the tx gets decrypted + // successfully, the correct chain id and + // expiration are serialized inside the data field + // of the Tx, while the ones available + // in the chain_id and expiration field are just placeholders ChainId(String::new()), + None, ) } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 18f4475701..dc53125617 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -246,8 +246,10 @@ pub mod tx_types { vec![], Some(ty.try_to_vec().unwrap()), ChainId(String::new()), /* No need to provide a valid - * ChainId when casting back from + * ChainId or expiration when + * casting back from * TxType */ + None, ) } } @@ -304,6 +306,7 @@ pub mod tx_types { data: Some(data.clone()), timestamp: tx.timestamp, chain_id: tx.chain_id.clone(), + expiration: tx.expiration, } .hash(); match TxType::try_from(Tx { @@ -311,6 +314,7 @@ pub mod tx_types { data: Some(data), timestamp: tx.timestamp, chain_id: tx.chain_id, + expiration: tx.expiration, }) .map_err(|err| TxError::Deserialization(err.to_string()))? { @@ -351,6 +355,7 @@ pub mod tx_types { use super::*; use crate::types::address::nam; use crate::types::storage::Epoch; + use crate::types::time::DateTimeUtc; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; @@ -368,6 +373,7 @@ pub mod tx_types { "wasm code".as_bytes().to_owned(), None, ChainId::default(), + None, ); match process_tx(tx.clone()).expect("Test failed") { @@ -385,6 +391,7 @@ pub mod tx_types { "code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); let tx = Tx::new( "wasm code".as_bytes().to_owned(), @@ -394,6 +401,7 @@ pub mod tx_types { .expect("Test failed"), ), inner.chain_id.clone(), + None, ); match process_tx(tx).expect("Test failed") { @@ -410,6 +418,7 @@ pub mod tx_types { "code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); let tx = Tx::new( "wasm code".as_bytes().to_owned(), @@ -419,6 +428,7 @@ pub mod tx_types { .expect("Test failed"), ), inner.chain_id.clone(), + None, ) .sign(&gen_keypair()); @@ -437,6 +447,7 @@ pub mod tx_types { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); // the signed tx let wrapper = WrapperTx::new( @@ -452,7 +463,7 @@ pub mod tx_types { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, tx.chain_id.clone()) + .sign(&keypair, tx.chain_id.clone(), Some(DateTimeUtc::now())) .expect("Test failed"); match process_tx(wrapper).expect("Test failed") { @@ -475,6 +486,7 @@ pub mod tx_types { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); // the signed tx let wrapper = WrapperTx::new( @@ -497,6 +509,7 @@ pub mod tx_types { TxType::Wrapper(wrapper).try_to_vec().expect("Test failed"), ), ChainId::default(), + None, ); let result = process_tx(tx).expect_err("Test failed"); assert_matches!(result, TxError::Unsigned(_)); @@ -511,6 +524,7 @@ pub mod tx_types { "transaction data".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); let decrypted = DecryptedTx::Decrypted { tx: payload.clone(), @@ -539,6 +553,7 @@ pub mod tx_types { "transaction data".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); let decrypted = DecryptedTx::Decrypted { tx: payload.clone(), @@ -561,6 +576,7 @@ pub mod tx_types { vec![], Some(signed.try_to_vec().expect("Test failed")), ChainId::default(), + None, ); match process_tx(tx).expect("Test failed") { TxType::Decrypted(DecryptedTx::Decrypted { diff --git a/core/src/types/transaction/protocol.rs b/core/src/types/transaction/protocol.rs index 3139ee598e..a0aee560ed 100644 --- a/core/src/types/transaction/protocol.rs +++ b/core/src/types/transaction/protocol.rs @@ -101,6 +101,7 @@ mod protocol_txs { .expect("Could not serialize ProtocolTx"), ), chain_id, + None, ) .sign(signing_key) } @@ -130,6 +131,7 @@ mod protocol_txs { .expect("Serializing request should not fail"), ), chain_id, + None, ) .sign(signing_key), ) diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 555186f221..5de138bacd 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -16,6 +16,7 @@ pub mod wrapper_tx { use crate::types::chain::ChainId; use crate::types::key::*; use crate::types::storage::Epoch; + use crate::types::time::DateTimeUtc; use crate::types::token::Amount; use crate::types::transaction::encrypted::EncryptedTx; use crate::types::transaction::{EncryptionKey, Hash, TxError, TxType}; @@ -251,6 +252,7 @@ pub mod wrapper_tx { &self, keypair: &common::SecretKey, chain_id: ChainId, + expiration: Option, ) -> Result { if self.pk != keypair.ref_to() { return Err(WrapperTxErr::InvalidKeyPair); @@ -263,6 +265,7 @@ pub mod wrapper_tx { .expect("Could not serialize WrapperTx"), ), chain_id, + expiration, ) .sign(keypair)) } @@ -368,6 +371,7 @@ pub mod wrapper_tx { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + Some(DateTimeUtc::now()), ); let wrapper = WrapperTx::new( @@ -397,6 +401,7 @@ pub mod wrapper_tx { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + Some(DateTimeUtc::now()), ); let mut wrapper = WrapperTx::new( @@ -432,6 +437,7 @@ pub mod wrapper_tx { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + Some(DateTimeUtc::now()), ); // the signed tx let mut tx = WrapperTx::new( @@ -447,7 +453,7 @@ pub mod wrapper_tx { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, ChainId::default()) + .sign(&keypair, ChainId::default(), None) .expect("Test failed"); // we now try to alter the inner tx maliciously @@ -469,6 +475,7 @@ pub mod wrapper_tx { "Give me all the money".as_bytes().to_owned(), None, ChainId::default(), + None, ); // We replace the inner tx with a malicious one diff --git a/documentation/specs/src/base-ledger/replay-protection.md b/documentation/specs/src/base-ledger/replay-protection.md index 85001729a5..483bf37db1 100644 --- a/documentation/specs/src/base-ledger/replay-protection.md +++ b/documentation/specs/src/base-ledger/replay-protection.md @@ -296,11 +296,11 @@ a series of external factors (ledger state, etc.) might change the mind of the submitter who's now not interested in the execution of the transaction anymore. We have to introduce the concept of a lifetime (or timeout) for the -transactions: basically, the `Tx` struct will hold an extra field called -`expiration` stating the maximum `DateTimeUtc` up until which the submitter is -willing to see the transaction executed. After the specified time, the -transaction will be considered invalid and discarded regardless of all the other -checks. +transactions: basically, the `Tx` struct will hold an optional extra field +called `expiration` stating the maximum `DateTimeUtc` up until which the +submitter is willing to see the transaction executed. After the specified time, +the transaction will be considered invalid and discarded regardless of all the +other checks. By introducing this new field we are setting a new constraint in the transaction's contract, where the ledger will make sure to prevent the execution @@ -322,60 +322,22 @@ transaction submitter commits himself to one of these three conditions: The first condition satisfied will invalidate further executions of the same tx. -In anticipation of DKG implementation, the current struct `WrapperTx` holds a -field `epoch` stating the epoch in which the tx should be executed. This is -because Ferveo will produce a new public key each epoch, effectively limiting -the lifetime of the transaction (see section 2.2.2 of the -[documentation](https://eprint.iacr.org/2022/898.pdf)). Unfortunately, for -replay protection, a resolution of 1 epoch (~ 1 day) is too low for the possible -needs of the submitters, therefore we need the `expiration` field to hold a -maximum `DateTimeUtc` to increase resolution down to a single block (~ 10 -seconds). - ```rust pub struct Tx { pub code: Vec, pub data: Option>, pub timestamp: DateTimeUtc, pub chain_id: ChainId, - /// Lifetime of the transaction, also determines which decryption key will be used - pub expiration: DateTimeUtc, -} - -pub struct WrapperTx { - /// The fee to be payed for including the tx - pub fee: Fee, - /// Used to determine an implicit account of the fee payer - pub pk: common::PublicKey, - /// Max amount of gas that can be used when executing the inner tx - pub gas_limit: GasLimit, - /// the encrypted payload - pub inner_tx: EncryptedTx, - /// sha-2 hash of the inner transaction acting as a commitment - /// the contents of the encrypted payload - pub tx_hash: Hash, + /// Optional lifetime of the transaction + pub expiration: Option, } ``` -Since we now have more detailed information about the desired lifetime of the -transaction, we can remove the `epoch` field and rely solely on `expiration`. -Now, the producer of the inner transaction should make sure to set a sensible -value for this field, in the sense that it should not span more than one epoch. -If this happens, then the transaction will be correctly decrypted only in a -subset of the desired lifetime (the one expecting the actual key used for the -encryption), while, in the following epochs, the transaction will fail -decryption and won't be executed. In essence, the `expiration` parameter can -only restrict the implicit lifetime within the current epoch, it can not surpass -it as that would make the transaction fail in the decryption phase. - -The subject encrypting the inner transaction will also be responsible for using -the appropriate public key for encryption relative to the targeted time. - -The wrapper transaction will match the `expiration` of the inner for correct -execution. Note that we need this field also for the wrapper to anticipate the -check at mempool/proposal evaluation time, but also to prevent someone from -inserting a wrapper transaction after the corresponding inner has expired -forcing the wrapper signer to pay for the fees. +The wrapper transaction will match the `expiration` of the inner (if any) for a +correct execution. Note that we need this field also for the wrapper to +anticipate the check at mempool/proposal evaluation time, but also to prevent +someone from inserting a wrapper transaction after the corresponding inner has +expired forcing the wrapper signer to pay for the fees. ### Wrapper checks diff --git a/proto/types.proto b/proto/types.proto index 371416cff7..0414da45ef 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -10,6 +10,7 @@ message Tx { optional bytes data = 2; google.protobuf.Timestamp timestamp = 3; string chain_id = 4; + optional google.protobuf.Timestamp expiration = 5; } message Dkg { string data = 1; } diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index a195ff5ca6..faf6a316a1 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -609,8 +609,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -649,8 +650,9 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -741,6 +743,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -810,6 +813,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -867,8 +871,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -962,6 +967,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -1066,6 +1072,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -1154,6 +1161,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -1228,6 +1236,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -1321,6 +1330,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -1425,6 +1435,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -1526,6 +1537,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -1571,6 +1583,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -1617,6 +1630,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -1713,6 +1727,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -1814,6 +1829,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -1923,6 +1939,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -2023,6 +2040,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -2134,6 +2152,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); @@ -2192,6 +2211,7 @@ mod tests { tx_code, Some(tx_data), wl_storage.storage.chain_id.clone(), + None, ) .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 94d09253f4..d5caa856f9 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -386,8 +386,12 @@ mod test { // Request dry run tx let tx_no_op = TestWasms::TxNoOp.read_bytes(); - let tx = - Tx::new(tx_no_op, None, client.wl_storage.storage.chain_id.clone()); + let tx = Tx::new( + tx_no_op, + None, + client.wl_storage.storage.chain_id.clone(), + None, + ); let tx_bytes = tx.to_bytes(); let result = RPC .shell() diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index d468f11fa6..f1232f1545 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -540,7 +540,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); // When the `eval`ed VP doesn't run out of memory, it should return // `true` @@ -569,7 +569,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); // When the `eval`ed VP runs out of memory, its result should be // `false`, hence we should also get back `false` from the VP that // called `eval`. @@ -613,7 +613,7 @@ mod tests { // Allocating `2^23` (8 MiB) should be below the memory limit and // shouldn't fail let tx_data = 2_usize.pow(23).try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( vp_code.clone(), @@ -634,7 +634,7 @@ mod tests { // Allocating `2^24` (16 MiB) should be above the memory limit and // should fail let tx_data = 2_usize.pow(24).try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let error = vp( vp_code, &tx, @@ -726,7 +726,7 @@ mod tests { // limit and should fail let len = 2_usize.pow(24); let tx_data: Vec = vec![6_u8; len]; - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( vp_code, @@ -833,7 +833,7 @@ mod tests { // Borsh. storage.write(&key, value.try_to_vec().unwrap()).unwrap(); let tx_data = key.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let error = vp( vp_read_key, @@ -890,7 +890,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let passed = vp( vp_eval, @@ -995,7 +995,7 @@ mod tests { ) .expect("unexpected error converting wat2wasm").into_owned(); - let tx = Tx::new(vec![], None, ChainId::default()); + let tx = Tx::new(vec![], None, ChainId::default(), None); let tx_index = TxIndex::default(); let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 4d6300ad1c..7dc7acd67d 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1706,7 +1706,7 @@ fn invalid_transactions() -> Result<()> { client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; client.exp_string("Transaction is invalid")?; - client.exp_string(r#""code": "1"#)?; + client.exp_string(r#""code": "4"#)?; client.assert_success(); let mut ledger = bg_ledger.foreground(); diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 0c281e542c..83cc9eebf0 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -442,6 +442,7 @@ mod tests { // Use some arbitrary bytes for tx code let code = vec![4, 3, 2, 1, 0]; + let expiration = Some(DateTimeUtc::now()); for data in &[ // Tx with some arbitrary data Some(vec![1, 2, 3, 4].repeat(10)), @@ -453,6 +454,7 @@ mod tests { code.clone(), data.clone(), env.wl_storage.storage.chain_id.clone(), + expiration, ) .sign(&keypair); let tx_data = env.tx.data.as_ref().expect("data should exist"); @@ -562,6 +564,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -600,6 +603,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); @@ -638,6 +642,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // get and update the client without a header @@ -684,6 +689,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // update the client with the message @@ -718,6 +724,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // upgrade the client with the message @@ -760,6 +767,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -798,6 +806,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // init a connection with the message @@ -828,6 +837,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open the connection with the message @@ -868,6 +878,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open try a connection with the message @@ -899,6 +910,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open the connection with the mssage @@ -944,6 +956,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // not bind a port @@ -986,6 +999,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // bind a port @@ -1031,6 +1045,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // init a channel with the message @@ -1056,6 +1071,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open the channle with the message @@ -1098,6 +1114,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // try open a channel with the message @@ -1124,6 +1141,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open a channel with the message @@ -1168,6 +1186,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // close the channel with the message @@ -1212,6 +1231,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); @@ -1261,6 +1281,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1302,6 +1323,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1355,6 +1377,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1424,6 +1447,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1508,6 +1532,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1559,6 +1584,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // send a packet with the message @@ -1589,6 +1615,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1644,6 +1671,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1710,6 +1738,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); @@ -1786,6 +1815,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index d3feb299a9..c384d06e38 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -78,7 +78,7 @@ impl Default for TestTxEnv { vp_cache_dir, tx_wasm_cache, tx_cache_dir, - tx: Tx::new(vec![], None, chain_id), + tx: Tx::new(vec![], None, chain_id, None), } } } diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 77f6240651..bc9e1f2225 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -74,7 +74,7 @@ impl Default for TestVpEnv { wl_storage, iterators: PrefixIterators::default(), gas_meter: VpGasMeter::default(), - tx: Tx::new(vec![], None, chain_id), + tx: Tx::new(vec![], None, chain_id, None), tx_index: TxIndex::default(), keys_changed: BTreeSet::default(), verifiers: BTreeSet::default(), diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 649dccaa2d..8215045d5c 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -100,7 +100,7 @@ mod tests { let tx_code = vec![]; let tx_data = bond.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index ef81fcb1b5..30ae263269 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -79,7 +79,7 @@ mod tests { let tx_code = vec![]; let tx_data = commission_change.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 2cdc11789e..33cf9f56ea 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -122,7 +122,7 @@ mod tests { let tx_code = vec![]; let tx_data = unbond.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index cafdca8f6b..b00661261e 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -155,7 +155,7 @@ mod tests { let tx_code = vec![]; let tx_data = withdraw.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap();