Skip to content

Commit 060bf3c

Browse files
authored
Merge pull request #4379 from anoma/tomas/sa-tests
sa fixes tests
2 parents 0ba6dc9 + c29fd32 commit 060bf3c

File tree

9 files changed

+655
-2
lines changed

9 files changed

+655
-2
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,11 @@ jobs:
362362
run: sccache --stop-server || true
363363

364364
check-benchmarks:
365-
runs-on: [self-hosted, 8vcpu-16ram-ubuntu22-namada-x86]
365+
runs-on: [self-hosted, 16vcpu-32ram-ubuntu22-namada-x86]
366366
container:
367367
image: ghcr.io/heliaxdev/namada-ci:namada-main
368368
if: github.event.pull_request.draft == false || contains(github.head_ref, 'mergify/merge-queue') || contains(github.ref_name, 'mergify/merge-queue')
369-
timeout-minutes: 25
369+
timeout-minutes: 35
370370
needs: [build-wasm]
371371

372372
steps:

crates/benches/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ name = "wasm_opcodes"
3232
harness = false
3333
path = "wasm_opcodes.rs"
3434

35+
[[bench]]
36+
name = "mempool_validate"
37+
harness = false
38+
path = "mempool_validate.rs"
39+
3540
[features]
3641
namada-eth-bridge = ["namada_apps_lib/namada-eth-bridge"]
3742

crates/benches/mempool_validate.rs

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
#![allow(clippy::disallowed_methods)]
2+
3+
use std::collections::BTreeMap;
4+
5+
use criterion::{criterion_group, criterion_main, Criterion};
6+
use namada_apps_lib::collections::HashSet;
7+
use namada_apps_lib::key::{common, ed25519, RefTo, SigScheme};
8+
use namada_apps_lib::time::DateTimeUtc;
9+
use namada_apps_lib::tx::data::{Fee, GasLimit, TxType, WrapperTx};
10+
use namada_apps_lib::tx::{self, Signer, Tx};
11+
use namada_apps_lib::{address, token};
12+
use namada_node::bench_utils::BenchShell;
13+
use namada_node::shell::{MempoolTxType, ResultCode};
14+
use rand::rngs::StdRng;
15+
use rand::SeedableRng;
16+
17+
/// The value of namada-mainnet-genesis `max_tx_bytes` protocol parameter
18+
const MAX_TX_BYTES: usize = 1048576;
19+
20+
/// Benchmark mempool validation with a tx containing the max. number of bytes
21+
/// allowed by mainnet genesis parameters, filled with smallest data sections,
22+
/// but without an authorization section.
23+
fn max_tx_sections_validation(c: &mut Criterion) {
24+
let mut group = c.benchmark_group("non_uniq_sections_validation");
25+
26+
let bench_shell = BenchShell::default();
27+
let shell = bench_shell.read();
28+
29+
let chain_id = shell.chain_id.clone();
30+
let timestamp = DateTimeUtc::now();
31+
let gas_limit = GasLimit::from(u64::MAX);
32+
let mut rng = StdRng::from_seed([0; 32]);
33+
let secret_key = ed25519::SigScheme::generate(&mut rng);
34+
let pk = common::PublicKey::Ed25519(secret_key.ref_to());
35+
36+
let amount = token::Amount::from_u64(1);
37+
let amount_per_gas_unit =
38+
token::DenominatedAmount::new(amount, token::Denomination(0u8));
39+
let token = address::testing::nam();
40+
let fee = Fee {
41+
amount_per_gas_unit,
42+
token,
43+
};
44+
let wrapper = WrapperTx { fee, pk, gas_limit };
45+
let tx_type = TxType::Wrapper(Box::new(wrapper));
46+
47+
let batch = HashSet::new();
48+
let header = tx::Header {
49+
chain_id,
50+
expiration: None,
51+
timestamp,
52+
batch,
53+
atomic: false,
54+
tx_type,
55+
};
56+
57+
let (tx_1_section_len, section_additional_len) = {
58+
let tx = Tx {
59+
header: header.clone(),
60+
sections: vec![tx::Section::Data(tx::Data::new(vec![0_u8]))],
61+
};
62+
let tx_bytes = tx.try_to_bytes().unwrap();
63+
let tx_1_section_len = tx_bytes.len();
64+
65+
let tx = Tx {
66+
header: header.clone(),
67+
sections: vec![
68+
tx::Section::Data(tx::Data::new(vec![0_u8])),
69+
tx::Section::Data(tx::Data::new(vec![0_u8])),
70+
],
71+
};
72+
let tx_bytes = tx.try_to_bytes().unwrap();
73+
let tx_2_sections_len = tx_bytes.len();
74+
75+
(tx_1_section_len, tx_2_sections_len - tx_1_section_len)
76+
};
77+
78+
let sections_available_space = MAX_TX_BYTES - tx_1_section_len;
79+
let num_of_sections = sections_available_space / section_additional_len + 1;
80+
81+
let mut sections = Vec::with_capacity(num_of_sections);
82+
for _ in 0..num_of_sections {
83+
sections.push(tx::Section::Data(tx::Data::new(vec![0_u8])));
84+
}
85+
let tx = Tx {
86+
header: header.clone(),
87+
sections,
88+
};
89+
let tx_bytes = tx.try_to_bytes().unwrap();
90+
assert!(tx_bytes.len() <= MAX_TX_BYTES);
91+
92+
let res = shell.mempool_validate(&tx_bytes, MempoolTxType::NewTransaction);
93+
assert_eq!(res.code, ResultCode::InvalidSig.into());
94+
95+
group.bench_function("Shell::mempool_validate", |b| {
96+
b.iter(|| {
97+
shell.mempool_validate(&tx_bytes, MempoolTxType::NewTransaction);
98+
});
99+
});
100+
}
101+
102+
/// Benchmark mempool validation with a tx containing the max. number of bytes
103+
/// allowed by mainnet genesis parameters, filled with smallest data sections
104+
/// and an authorization section.
105+
fn max_tx_sections_with_auth_validation(c: &mut Criterion) {
106+
let mut group = c.benchmark_group("uniq_sections_auth_validation");
107+
108+
let bench_shell = BenchShell::default();
109+
let shell = bench_shell.read();
110+
111+
let chain_id = shell.chain_id.clone();
112+
let timestamp = DateTimeUtc::now();
113+
let gas_limit = GasLimit::from(u64::MAX);
114+
let mut rng = StdRng::from_seed([0; 32]);
115+
let secret_key = ed25519::SigScheme::generate(&mut rng);
116+
let pk = common::PublicKey::Ed25519(secret_key.ref_to());
117+
118+
let amount = token::Amount::from_u64(1);
119+
let amount_per_gas_unit =
120+
token::DenominatedAmount::new(amount, token::Denomination(0u8));
121+
let token = address::testing::nam();
122+
let fee = Fee {
123+
amount_per_gas_unit,
124+
token,
125+
};
126+
let wrapper = WrapperTx { fee, pk, gas_limit };
127+
let tx_type = TxType::Wrapper(Box::new(wrapper));
128+
129+
let batch = HashSet::new();
130+
let header = tx::Header {
131+
chain_id,
132+
expiration: None,
133+
timestamp,
134+
batch,
135+
atomic: false,
136+
tx_type,
137+
};
138+
139+
let (tx_1_section_len, section_additional_len) = {
140+
let data = tx::Section::Data(tx::Data::new(vec![0_u8]));
141+
let data_hash = data.get_hash();
142+
let targets = vec![
143+
tx::Section::Header(header.clone()).get_hash(),
144+
data_hash,
145+
data_hash,
146+
];
147+
let auth = tx::Authorization {
148+
targets,
149+
signer: Signer::PubKeys(vec![]),
150+
signatures: BTreeMap::default(),
151+
};
152+
let tx = Tx {
153+
header: header.clone(),
154+
sections: vec![data.clone(), tx::Section::Authorization(auth)],
155+
};
156+
let tx_bytes = tx.try_to_bytes().unwrap();
157+
let tx_1_section_len = tx_bytes.len();
158+
159+
let targets = vec![
160+
tx::Section::Header(header.clone()).get_hash(),
161+
data_hash,
162+
data_hash,
163+
data_hash,
164+
];
165+
let auth = tx::Authorization {
166+
targets,
167+
signer: Signer::PubKeys(vec![]),
168+
signatures: BTreeMap::default(),
169+
};
170+
let tx = Tx {
171+
header: header.clone(),
172+
sections: vec![
173+
data.clone(),
174+
data,
175+
tx::Section::Authorization(auth),
176+
],
177+
};
178+
let tx_bytes = tx.try_to_bytes().unwrap();
179+
let tx_2_sections_len = tx_bytes.len();
180+
181+
(tx_1_section_len, tx_2_sections_len - tx_1_section_len)
182+
};
183+
184+
let sections_available_space = MAX_TX_BYTES - tx_1_section_len;
185+
let num_of_sections = sections_available_space / section_additional_len + 1;
186+
187+
// Generate unique sections and add a target for each
188+
let mut sections = Vec::with_capacity(num_of_sections + 1);
189+
let mut targets = Vec::with_capacity(num_of_sections + 1);
190+
targets.push(tx::Section::Header(header.clone()).get_hash());
191+
for _ in 0..num_of_sections {
192+
let data = tx::Section::Data(tx::Data::new(vec![0_u8]));
193+
let data_hash = data.get_hash();
194+
sections.push(data);
195+
targets.push(data_hash);
196+
}
197+
198+
// Add auth raw hash target and section. Add the authorization section last
199+
// so that `Tx::verify_signatures` has to iterate through all sections
200+
let auth = tx::Authorization {
201+
targets: targets.clone(),
202+
signer: Signer::PubKeys(vec![]),
203+
signatures: BTreeMap::default(),
204+
};
205+
let raw_hash = auth.get_raw_hash();
206+
targets.push(raw_hash);
207+
let auth = tx::Authorization {
208+
targets,
209+
signer: Signer::PubKeys(vec![]),
210+
signatures: BTreeMap::default(),
211+
};
212+
sections.push(tx::Section::Authorization(auth));
213+
214+
let tx = Tx {
215+
header: header.clone(),
216+
sections,
217+
};
218+
let tx_bytes = tx.try_to_bytes().unwrap();
219+
assert!(tx_bytes.len() <= MAX_TX_BYTES);
220+
221+
let res = shell.mempool_validate(&tx_bytes, MempoolTxType::NewTransaction);
222+
assert_eq!(res.code, ResultCode::InvalidSig.into());
223+
224+
group.bench_function("Shell::mempool_validate", |b| {
225+
b.iter(|| {
226+
shell.mempool_validate(&tx_bytes, MempoolTxType::NewTransaction);
227+
});
228+
});
229+
}
230+
231+
criterion_group!(
232+
mempool_validate,
233+
max_tx_sections_validation,
234+
max_tx_sections_with_auth_validation,
235+
);
236+
criterion_main!(mempool_validate);

crates/node/src/shell/mod.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2967,6 +2967,54 @@ mod shell_tests {
29672967
assert_eq!(result.code, ResultCode::TooLarge.into());
29682968
}
29692969

2970+
/// Test max tx sections limit in CheckTx
2971+
#[test]
2972+
fn test_max_tx_sections_check_tx() {
2973+
let (shell, _recv, _, _) = test_utils::setup();
2974+
2975+
let new_tx = |num_of_sections: usize| {
2976+
let keypair = super::test_utils::gen_keypair();
2977+
let mut wrapper =
2978+
Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new(
2979+
Fee {
2980+
amount_per_gas_unit: DenominatedAmount::native(
2981+
100.into(),
2982+
),
2983+
token: shell.state.in_mem().native_token.clone(),
2984+
},
2985+
keypair.ref_to(),
2986+
(GAS_LIMIT * 10).into(),
2987+
))));
2988+
wrapper.header.chain_id = shell.chain_id.clone();
2989+
wrapper
2990+
.set_code(Code::new("wasm_code".as_bytes().to_owned(), None));
2991+
2992+
// Wrapper sig and header
2993+
const OTHER_SECTIONS: usize = 2;
2994+
for _ in 0..(num_of_sections - OTHER_SECTIONS) {
2995+
wrapper.set_data(Data::new(vec![0]));
2996+
}
2997+
wrapper.sign_wrapper(keypair);
2998+
2999+
assert_eq!(wrapper.sections.len(), num_of_sections);
3000+
wrapper
3001+
};
3002+
3003+
// test a tx on the limit of number of sections
3004+
let result = shell.mempool_validate(
3005+
new_tx(MAX_TX_SECTIONS_LEN).to_bytes().as_ref(),
3006+
MempoolTxType::NewTransaction,
3007+
);
3008+
assert!(result.code != ResultCode::InvalidTx.into());
3009+
3010+
// test a tx exceeding the limit of number of sections
3011+
let result = shell.mempool_validate(
3012+
new_tx(MAX_TX_SECTIONS_LEN + 1).to_bytes().as_ref(),
3013+
MempoolTxType::NewTransaction,
3014+
);
3015+
assert_eq!(result.code, ResultCode::InvalidTx.into());
3016+
}
3017+
29703018
/// Test the that the shell can restore it's state
29713019
/// from a snapshot if it is not syncing
29723020
#[test]

crates/node/src/shell/process_proposal.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1922,4 +1922,57 @@ mod test_process_proposal {
19221922
assert!(rsp.is_ok());
19231923
}
19241924
}
1925+
1926+
/// Process Proposal should reject a block containing a tx with a number of
1927+
/// sections exceeding the limit
1928+
#[test]
1929+
fn test_max_sections_exceeded_tx_rejected() {
1930+
let (shell, _recv, _, _) = test_utils::setup_at_height(3u64);
1931+
1932+
let keypair = namada_apps_lib::wallet::defaults::daewon_keypair();
1933+
1934+
// Make a transaction that exceeds the number of max number of allowed
1935+
// sections
1936+
let mut tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new(
1937+
Fee {
1938+
amount_per_gas_unit: DenominatedAmount::native(100.into()),
1939+
token: shell.state.in_mem().native_token.clone(),
1940+
},
1941+
keypair.ref_to(),
1942+
(GAS_LIMIT * 10).into(),
1943+
))));
1944+
tx.header.chain_id = shell.chain_id.clone();
1945+
tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None));
1946+
1947+
// Wrapper sig and header
1948+
const OTHER_SECTIONS: usize = 2;
1949+
for _ in 0..(MAX_TX_SECTIONS_LEN - OTHER_SECTIONS + 1) {
1950+
tx.set_data(Data::new(vec![0]));
1951+
}
1952+
tx.sign_wrapper(keypair);
1953+
1954+
assert_eq!(tx.sections.len(), MAX_TX_SECTIONS_LEN + 1);
1955+
1956+
let response = {
1957+
let request = ProcessProposal {
1958+
txs: vec![tx.to_bytes()],
1959+
};
1960+
if let Err(TestError::RejectProposal(resp)) =
1961+
shell.process_proposal(request)
1962+
{
1963+
if let [resp] = resp.as_slice() {
1964+
resp.clone()
1965+
} else {
1966+
panic!("Test failed")
1967+
}
1968+
} else {
1969+
panic!("Test failed")
1970+
}
1971+
};
1972+
assert_eq!(response.result.code, u32::from(ResultCode::InvalidTx));
1973+
assert_eq!(
1974+
response.result.info,
1975+
format!("Tx contains more than {MAX_TX_SECTIONS_LEN} sections."),
1976+
);
1977+
}
19251978
}

0 commit comments

Comments
 (0)