Skip to content

Commit 1fe7363

Browse files
committed
Merge branch 'tiago/rebased-validate-tx-bytes-len' (#2187)
* tiago/rebased-validate-tx-bytes-len: Changelog Update masp proofs New unit tests Validate tx bytes in CheckTx and ProcessProposal Add too large tx error Validate tx sizes storage api Add max tx bytes to genesis template Fix docstr Add max tx bytes protocol param Tune block and mempool CometBFT params
2 parents d2efffe + 1dd91c9 commit 1fe7363

27 files changed

+253
-25
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Tighten security around potential P2P issues
2+
([\#2131](https://github.com/anoma/namada/pull/2131))

apps/src/lib/config/genesis.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ pub struct ImplicitAccount {
153153
BorshDeserialize,
154154
)]
155155
pub struct Parameters {
156-
// Max payload size, in bytes, for a tx batch proposal.
156+
/// Max payload size, in bytes, for a tx batch proposal.
157157
pub max_proposal_bytes: ProposalBytes,
158158
/// Max block gas
159159
pub max_block_gas: u64,

apps/src/lib/config/genesis/chain.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ impl Finalized {
262262
fee_unshielding_descriptions_limit,
263263
max_block_gas,
264264
minimum_gas_price,
265+
max_tx_bytes,
265266
..
266267
} = self.parameters.parameters.clone();
267268

@@ -291,6 +292,7 @@ impl Finalized {
291292
let pos_inflation_amount = 0;
292293

293294
namada::ledger::parameters::Parameters {
295+
max_tx_bytes,
294296
epoch_duration,
295297
max_expected_time_per_block,
296298
vp_whitelist,

apps/src/lib/config/genesis/templates.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,9 @@ pub struct Parameters<T: TemplateValidation> {
242242
Eq,
243243
)]
244244
pub struct ChainParams<T: TemplateValidation> {
245+
/// Max payload size, in bytes, for a tx decided through
246+
/// the consensus protocol.
247+
pub max_tx_bytes: u32,
245248
/// Name of the native token - this must one of the tokens from
246249
/// `tokens.toml` file
247250
pub native_token: Alias,
@@ -296,6 +299,7 @@ impl ChainParams<Unvalidated> {
296299
tokens: &Tokens,
297300
) -> eyre::Result<ChainParams<Validated>> {
298301
let ChainParams {
302+
max_tx_bytes,
299303
native_token,
300304
min_num_of_blocks,
301305
max_expected_time_per_block,
@@ -342,6 +346,7 @@ impl ChainParams<Unvalidated> {
342346
}
343347

344348
Ok(ChainParams {
349+
max_tx_bytes,
345350
native_token,
346351
min_num_of_blocks,
347352
max_expected_time_per_block,

apps/src/lib/node/ledger/shell/mod.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use namada::ledger::storage::{
4848
DBIter, Sha256Hasher, Storage, StorageHasher, TempWlStorage, WlStorage, DB,
4949
EPOCH_SWITCH_BLOCKS_DELAY,
5050
};
51+
use namada::ledger::storage_api::tx::validate_tx_bytes;
5152
use namada::ledger::storage_api::{self, StorageRead};
5253
use namada::ledger::{parameters, pos, protocol};
5354
use namada::proof_of_stake::{self, process_slashes, read_pos_params, slash};
@@ -154,6 +155,7 @@ pub enum ErrorCodes {
154155
TxGasLimit = 11,
155156
FeeError = 12,
156157
InvalidVoteExtension = 13,
158+
TooLarge = 14,
157159
}
158160

159161
impl ErrorCodes {
@@ -167,7 +169,8 @@ impl ErrorCodes {
167169
Ok | WasmRuntimeError => true,
168170
InvalidTx | InvalidSig | InvalidOrder | ExtraTxs
169171
| Undecryptable | AllocationError | ReplayTx | InvalidChainId
170-
| ExpiredTx | TxGasLimit | FeeError | InvalidVoteExtension => false,
172+
| ExpiredTx | TxGasLimit | FeeError | InvalidVoteExtension
173+
| TooLarge => false,
171174
}
172175
}
173176
}
@@ -1073,6 +1076,18 @@ where
10731076
const VALID_MSG: &str = "Mempool validation passed";
10741077
const INVALID_MSG: &str = "Mempool validation failed";
10751078

1079+
// check tx bytes
1080+
//
1081+
// NB: always keep this as the first tx check,
1082+
// as it is a pretty cheap one
1083+
if !validate_tx_bytes(&self.wl_storage, tx_bytes.len())
1084+
.expect("Failed to get max tx bytes param from storage")
1085+
{
1086+
response.code = ErrorCodes::TooLarge.into();
1087+
response.log = format!("{INVALID_MSG}: Tx too large");
1088+
return response;
1089+
}
1090+
10761091
// Tx format check
10771092
let tx = match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) {
10781093
Ok(t) => t,
@@ -2091,6 +2106,7 @@ mod test_utils {
20912106
.new_epoch(BlockHeight(1));
20922107
// initialize parameter storage
20932108
let params = Parameters {
2109+
max_tx_bytes: 1024 * 1024,
20942110
epoch_duration: EpochDuration {
20952111
min_num_of_blocks: 1,
20962112
min_duration: DurationSecs(3600),
@@ -2945,4 +2961,57 @@ mod shell_tests {
29452961
);
29462962
assert_eq!(result.code, ErrorCodes::FeeError.into());
29472963
}
2964+
2965+
/// Test max tx bytes parameter in CheckTx
2966+
#[test]
2967+
fn test_max_tx_bytes_check_tx() {
2968+
let (shell, _recv, _, _) = test_utils::setup();
2969+
2970+
let max_tx_bytes: u32 = {
2971+
let key = parameters::storage::get_max_tx_bytes_key();
2972+
shell
2973+
.wl_storage
2974+
.read(&key)
2975+
.expect("Failed to read from storage")
2976+
.expect("Max tx bytes should have been written to storage")
2977+
};
2978+
2979+
let new_tx = |size: u32| {
2980+
let keypair = super::test_utils::gen_keypair();
2981+
let mut wrapper =
2982+
Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new(
2983+
Fee {
2984+
amount_per_gas_unit: 100.into(),
2985+
token: shell.wl_storage.storage.native_token.clone(),
2986+
},
2987+
keypair.ref_to(),
2988+
Epoch(0),
2989+
GAS_LIMIT_MULTIPLIER.into(),
2990+
None,
2991+
))));
2992+
wrapper.header.chain_id = shell.chain_id.clone();
2993+
wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned()));
2994+
wrapper.set_data(Data::new(vec![0; size as usize]));
2995+
wrapper.add_section(Section::Signature(Signature::new(
2996+
wrapper.sechashes(),
2997+
[(0, keypair)].into_iter().collect(),
2998+
None,
2999+
)));
3000+
wrapper
3001+
};
3002+
3003+
// test a small tx
3004+
let result = shell.mempool_validate(
3005+
new_tx(50).to_bytes().as_ref(),
3006+
MempoolTxType::NewTransaction,
3007+
);
3008+
assert!(result.code != ErrorCodes::TooLarge.into());
3009+
3010+
// max tx bytes + 1, on the other hand, is not
3011+
let result = shell.mempool_validate(
3012+
new_tx(max_tx_bytes + 1).to_bytes().as_ref(),
3013+
MempoolTxType::NewTransaction,
3014+
);
3015+
assert_eq!(result.code, ErrorCodes::TooLarge.into());
3016+
}
29483017
}

apps/src/lib/node/ledger/shell/process_proposal.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use namada::core::ledger::storage::WlStorage;
77
use namada::ledger::pos::PosQueries;
88
use namada::ledger::protocol::get_fee_unshielding_transaction;
99
use namada::ledger::storage::TempWlStorage;
10+
use namada::ledger::storage_api::tx::validate_tx_bytes;
1011
use namada::proof_of_stake::find_validator_by_raw_hash;
1112
use namada::types::internal::TxInQueue;
1213
use namada::types::transaction::protocol::{
@@ -266,6 +267,19 @@ where
266267
where
267268
CA: 'static + WasmCacheAccess + Sync,
268269
{
270+
// check tx bytes
271+
//
272+
// NB: always keep this as the first tx check,
273+
// as it is a pretty cheap one
274+
if !validate_tx_bytes(&self.wl_storage, tx_bytes.len())
275+
.expect("Failed to get max tx bytes param from storage")
276+
{
277+
return TxResult {
278+
code: ErrorCodes::TooLarge.into(),
279+
info: "Tx too large".into(),
280+
};
281+
}
282+
269283
// try to allocate space for this tx
270284
if let Err(e) = metadata.txs_bin.try_dump(tx_bytes) {
271285
return TxResult {
@@ -2039,4 +2053,69 @@ mod test_process_proposal {
20392053
);
20402054
}
20412055
}
2056+
2057+
/// Test max tx bytes parameter in ProcessProposal
2058+
#[test]
2059+
fn test_max_tx_bytes_process_proposal() {
2060+
use namada::ledger::parameters::storage::get_max_tx_bytes_key;
2061+
let (shell, _recv, _, _) = test_utils::setup_at_height(3u64);
2062+
2063+
let max_tx_bytes: u32 = {
2064+
let key = get_max_tx_bytes_key();
2065+
shell
2066+
.wl_storage
2067+
.read(&key)
2068+
.expect("Failed to read from storage")
2069+
.expect("Max tx bytes should have been written to storage")
2070+
};
2071+
2072+
let new_tx = |size: u32| {
2073+
let keypair = super::test_utils::gen_keypair();
2074+
let mut wrapper =
2075+
Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new(
2076+
Fee {
2077+
amount_per_gas_unit: 100.into(),
2078+
token: shell.wl_storage.storage.native_token.clone(),
2079+
},
2080+
keypair.ref_to(),
2081+
Epoch(0),
2082+
GAS_LIMIT_MULTIPLIER.into(),
2083+
None,
2084+
))));
2085+
wrapper.header.chain_id = shell.chain_id.clone();
2086+
wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned()));
2087+
wrapper.set_data(Data::new(vec![0; size as usize]));
2088+
wrapper.add_section(Section::Signature(Signature::new(
2089+
wrapper.sechashes(),
2090+
[(0, keypair)].into_iter().collect(),
2091+
None,
2092+
)));
2093+
wrapper
2094+
};
2095+
2096+
let request = ProcessProposal {
2097+
txs: vec![new_tx(max_tx_bytes + 1).to_bytes()],
2098+
};
2099+
match shell.process_proposal(request) {
2100+
Ok(_) => panic!("Test failed"),
2101+
Err(TestError::RejectProposal(response)) => {
2102+
assert_eq!(
2103+
response[0].result.code,
2104+
u32::from(ErrorCodes::TooLarge)
2105+
);
2106+
}
2107+
}
2108+
2109+
let request = ProcessProposal {
2110+
txs: vec![new_tx(0).to_bytes()],
2111+
};
2112+
match shell.process_proposal(request) {
2113+
Ok(_) => panic!("Test failed"),
2114+
Err(TestError::RejectProposal(response)) => {
2115+
assert!(
2116+
response[0].result.code != u32::from(ErrorCodes::TooLarge)
2117+
);
2118+
}
2119+
}
2120+
}
20422121
}

apps/src/lib/node/ledger/storage/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ mod tests {
146146
let mut wl_storage = WlStorage::new(WriteLog::default(), storage);
147147
// initialize parameter storage
148148
let params = Parameters {
149+
max_tx_bytes: 1024 * 1024,
149150
epoch_duration: EpochDuration {
150151
min_num_of_blocks: 1,
151152
min_duration: DurationSecs(3600),

apps/src/lib/node/ledger/tendermint_node.rs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -347,22 +347,45 @@ pub fn id_from_pk(pk: &common::PublicKey) -> TendermintNodeId {
347347

348348
async fn update_tendermint_config(
349349
home_dir: impl AsRef<Path>,
350-
config: TendermintConfig,
350+
mut config: TendermintConfig,
351351
) -> Result<()> {
352352
let home_dir = home_dir.as_ref();
353353
let path = home_dir.join("config").join("config.toml");
354-
let mut config = config.clone();
355354

356355
config.moniker =
357356
Moniker::from_str(&format!("{}-{}", config.moniker, namada_version()))
358357
.expect("Invalid moniker");
359358

360359
config.consensus.create_empty_blocks = true;
361360

362-
// We set this to true as we don't want any invalid tx be re-applied. This
363-
// also implies that it's not possible for an invalid tx to become valid
364-
// again in the future.
365-
config.mempool.keep_invalid_txs_in_cache = false;
361+
// mempool config
362+
// https://forum.cosmos.network/t/our-understanding-of-the-cosmos-hub-mempool-issues/12040
363+
{
364+
// We set this to true as we don't want any invalid tx be re-applied.
365+
// This also implies that it's not possible for an invalid tx to
366+
// become valid again in the future.
367+
config.mempool.keep_invalid_txs_in_cache = false;
368+
369+
// Drop txs from the mempool that are larger than 1 MiB
370+
//
371+
// The application (Namada) can assign arbitrary max tx sizes,
372+
// which are subject to consensus. Either way, nodes are able to
373+
// configure their local mempool however they please.
374+
//
375+
// 1 MiB is a reasonable value that allows governance proposal txs
376+
// containing wasm code to be proposed by a leading validator
377+
// during some round's start
378+
config.mempool.max_tx_bytes = 1024 * 1024;
379+
380+
// Hold 50x the max amount of txs in a block
381+
//
382+
// 6 MiB is the default Namada max proposal size governance
383+
// parameter -> 50 * 6 MiB
384+
config.mempool.max_txs_bytes = 50 * 6 * 1024 * 1024;
385+
386+
// Hold up to 4k txs in the mempool
387+
config.mempool.size = 4000;
388+
}
366389

367390
// Bumped from the default `1_000_000`, because some WASMs can be
368391
// quite large
@@ -408,11 +431,11 @@ async fn write_tm_genesis(
408431
.try_into()
409432
.expect("Couldn't convert DateTimeUtc to Tendermint Time");
410433
let size = block::Size {
411-
// maximum size of a serialized Tendermint block
412-
// cannot go over 100 MiB
413-
max_bytes: (100 << 20) - 1, /* unsure if we are dealing with an open
414-
* range, so it's better to subtract one,
415-
* here */
434+
// maximum size of a serialized Tendermint block.
435+
// on Namada, we have a hard-cap of 16 MiB (6 MiB max
436+
// txs in a block + 10 MiB reserved for evidence data,
437+
// block headers and protobuf serialization overhead)
438+
max_bytes: 16 * 1024 * 1024,
416439
// gas is metered app-side, so we disable it
417440
// at the Tendermint level
418441
max_gas: -1,

core/src/ledger/parameters/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ pub const ADDRESS: Address = Address::Internal(InternalAddress::Parameters);
3535
BorshSchema,
3636
)]
3737
pub struct Parameters {
38+
/// Max payload size, in bytes, for a mempool tx.
39+
pub max_tx_bytes: u32,
3840
/// Epoch duration (read only)
3941
pub epoch_duration: EpochDuration,
4042
/// Maximum expected time per block (read only)
@@ -117,6 +119,7 @@ impl Parameters {
117119
S: StorageRead + StorageWrite,
118120
{
119121
let Self {
122+
max_tx_bytes,
120123
epoch_duration,
121124
max_expected_time_per_block,
122125
max_proposal_bytes,
@@ -135,6 +138,10 @@ impl Parameters {
135138
fee_unshielding_descriptions_limit,
136139
} = self;
137140

141+
// write max tx bytes parameter
142+
let max_tx_bytes_key = storage::get_max_tx_bytes_key();
143+
storage.write(&max_tx_bytes_key, max_tx_bytes)?;
144+
138145
// write max proposal bytes parameter
139146
let max_proposal_bytes_key = storage::get_max_proposal_bytes_key();
140147
storage.write(&max_proposal_bytes_key, max_proposal_bytes)?;
@@ -542,7 +549,15 @@ where
542549
.ok_or(ReadError::ParametersMissing)
543550
.into_storage_result()?;
544551

552+
// read max tx bytes
553+
let max_tx_bytes_key = storage::get_max_tx_bytes_key();
554+
let value = storage.read(&max_tx_bytes_key)?;
555+
let max_tx_bytes = value
556+
.ok_or(ReadError::ParametersMissing)
557+
.into_storage_result()?;
558+
545559
Ok(Parameters {
560+
max_tx_bytes,
546561
epoch_duration,
547562
max_expected_time_per_block,
548563
max_proposal_bytes,

0 commit comments

Comments
 (0)