Add exceptions handling to the interpreter (#1393)

* Add exceptions handling to the interpreter

* Apply comments

* Fix comments
This commit is contained in:
Linda Guiga 2023-12-18 19:11:16 +01:00 committed by GitHub
parent 536cd1c89c
commit f67ee258a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 859 additions and 358 deletions

File diff suppressed because it is too large Load Diff

View File

@ -115,9 +115,15 @@ fn prepare_interpreter(
trie_data.push(account.code_hash.into_uint());
let trie_data_len = trie_data.len().into();
interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len);
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(value_ptr.into()); // value_ptr
interpreter.push(k.try_into_u256().unwrap()); // key
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(value_ptr.into())
.expect("The stack should not overflow"); // value_ptr
interpreter
.push(k.try_into_u256().unwrap())
.expect("The stack should not overflow"); // key
interpreter.run()?;
assert_eq!(
@ -129,8 +135,12 @@ fn prepare_interpreter(
// Now, execute mpt_hash_state_trie.
interpreter.generation_state.registers.program_counter = mpt_hash_state_trie;
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(1.into()); // Initial length of the trie data segment, unused.
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(1.into()) // Initial length of the trie data segment, unused.
.expect("The stack should not overflow");
interpreter.run()?;
assert_eq!(
@ -162,11 +172,15 @@ fn test_extcodesize() -> Result<()> {
// Test `extcodesize`
interpreter.generation_state.registers.program_counter = extcodesize;
interpreter.pop();
interpreter.pop();
interpreter.pop().expect("The stack should not be empty");
interpreter.pop().expect("The stack should not be empty");
assert!(interpreter.stack().is_empty());
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(U256::from_big_endian(address.as_bytes()));
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(U256::from_big_endian(address.as_bytes()))
.expect("The stack should not overflow");
interpreter.generation_state.inputs.contract_code =
HashMap::from([(keccak(&code), code.clone())]);
interpreter.run()?;
@ -211,14 +225,24 @@ fn test_extcodecopy() -> Result<()> {
// Test `extcodecopy`
interpreter.generation_state.registers.program_counter = extcodecopy;
interpreter.pop();
interpreter.pop();
interpreter.pop().expect("The stack should not be empty");
interpreter.pop().expect("The stack should not be empty");
assert!(interpreter.stack().is_empty());
interpreter.push(size.into());
interpreter.push(offset.into());
interpreter.push(dest_offset.into());
interpreter.push(U256::from_big_endian(address.as_bytes()));
interpreter.push((0xDEADBEEFu64 + (1 << 32)).into()); // kexit_info
interpreter
.push(size.into())
.expect("The stack should not overflow");
interpreter
.push(offset.into())
.expect("The stack should not overflow");
interpreter
.push(dest_offset.into())
.expect("The stack should not overflow");
interpreter
.push(U256::from_big_endian(address.as_bytes()))
.expect("The stack should not overflow");
interpreter
.push((0xDEADBEEFu64 + (1 << 32)).into())
.expect("The stack should not overflow"); // kexit_info
interpreter.generation_state.inputs.contract_code =
HashMap::from([(keccak(&code), code.clone())]);
interpreter.run()?;
@ -342,8 +366,12 @@ fn sstore() -> Result<()> {
interpreter.generation_state.registers.program_counter = mpt_hash_state_trie;
interpreter.set_is_kernel(true);
interpreter.set_context(0);
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(1.into()); // Initia length of the trie data segment, unused.
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(1.into()) // Initial length of the trie data segment, unused.
.expect("The stack should not overflow");
interpreter.run()?;
assert_eq!(
@ -402,27 +430,38 @@ fn sload() -> Result<()> {
// Prepare the interpreter by inserting the account in the state trie.
prepare_interpreter_all_accounts(&mut interpreter, trie_inputs, addr, &code)?;
interpreter.run()?;
// The first two elements in the stack are `success` and `leftover_gas`,
// returned by the `sys_stop` opcode.
interpreter.pop();
interpreter.pop();
interpreter
.pop()
.expect("The stack length should not be empty.");
interpreter
.pop()
.expect("The stack length should not be empty.");
// The SLOAD in the provided code should return 0, since
// the storage trie is empty. The last step in the code
// pushes the value 3.
assert_eq!(interpreter.stack(), vec![0x0.into(), 0x3.into()]);
interpreter.pop();
interpreter.pop();
interpreter
.pop()
.expect("The stack length should not be empty.");
interpreter
.pop()
.expect("The stack length should not be empty.");
// Now, execute mpt_hash_state_trie. We check that the state trie has not changed.
let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"];
interpreter.generation_state.registers.program_counter = mpt_hash_state_trie;
interpreter.set_is_kernel(true);
interpreter.set_context(0);
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(1.into()); // Initia length of the trie data segment, unused.
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow.");
interpreter
.push(1.into()) // Initial length of the trie data segment, unused.
.expect("The stack should not overflow.");
interpreter.run()?;
assert_eq!(

View File

@ -204,3 +204,137 @@ fn test_add11_yml() {
interpreter.set_is_kernel(true);
interpreter.run().expect("Proving add11 failed.");
}
#[test]
fn test_add11_yml_with_exception() {
// In this test, we make sure that the user code throws a stack underflow exception.
let beneficiary = hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba");
let sender = hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b");
let to = hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87");
let beneficiary_state_key = keccak(beneficiary);
let sender_state_key = keccak(sender);
let to_hashed = keccak(to);
let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap();
let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap();
let to_nibbles = Nibbles::from_bytes_be(to_hashed.as_bytes()).unwrap();
let code = [0x60, 0x01, 0x60, 0x01, 0x01, 0x8e, 0x00];
let code_hash = keccak(code);
let mut contract_code = HashMap::new();
contract_code.insert(keccak(vec![]), vec![]);
contract_code.insert(code_hash, code.to_vec());
let beneficiary_account_before = AccountRlp {
nonce: 1.into(),
..AccountRlp::default()
};
let sender_account_before = AccountRlp {
balance: 0x0de0b6b3a7640000u64.into(),
..AccountRlp::default()
};
let to_account_before = AccountRlp {
balance: 0x0de0b6b3a7640000u64.into(),
code_hash,
..AccountRlp::default()
};
let mut state_trie_before = HashedPartialTrie::from(Node::Empty);
state_trie_before.insert(
beneficiary_nibbles,
rlp::encode(&beneficiary_account_before).to_vec(),
);
state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec());
state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec());
let tries_before = TrieInputs {
state_trie: state_trie_before,
transactions_trie: Node::Empty.into(),
receipts_trie: Node::Empty.into(),
storage_tries: vec![(to_hashed, Node::Empty.into())],
};
let txn = hex!("f863800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d87830186a0801ba0ffb600e63115a7362e7811894a91d8ba4330e526f22121c994c4692035dfdfd5a06198379fcac8de3dbfac48b165df4bf88e2088f294b61efb9a65fe2281c76e16");
let txn_gas_limit = 400_000;
let gas_price = 10;
let initial_stack = vec![];
let mut interpreter = Interpreter::new_with_kernel(0, initial_stack);
prepare_interpreter(&mut interpreter, tries_before.clone(), &txn, contract_code);
// Here, since the transaction fails, it consumes its gas limit, and does nothing else.
let expected_state_trie_after = {
let beneficiary_account_after = beneficiary_account_before;
// This is the only account that changes: the nonce and the balance are updated.
let sender_account_after = AccountRlp {
balance: sender_account_before.balance - txn_gas_limit * gas_price,
nonce: 1.into(),
..AccountRlp::default()
};
let to_account_after = to_account_before;
let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty);
expected_state_trie_after.insert(
beneficiary_nibbles,
rlp::encode(&beneficiary_account_after).to_vec(),
);
expected_state_trie_after
.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec());
expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec());
expected_state_trie_after
};
let receipt_0 = LegacyReceiptRlp {
status: false,
cum_gas_used: txn_gas_limit.into(),
bloom: vec![0; 256].into(),
logs: vec![],
};
let mut receipts_trie = HashedPartialTrie::from(Node::Empty);
receipts_trie.insert(
Nibbles::from_str("0x80").unwrap(),
rlp::encode(&receipt_0).to_vec(),
);
let transactions_trie: HashedPartialTrie = Node::Leaf {
nibbles: Nibbles::from_str("0x80").unwrap(),
value: txn.to_vec(),
}
.into();
let trie_roots_after = TrieRoots {
state_root: expected_state_trie_after.hash(),
transactions_root: transactions_trie.hash(),
receipts_root: receipts_trie.hash(),
};
// Set trie roots after the transaction was executed.
let metadata_to_set = [
(
GlobalMetadata::StateTrieRootDigestAfter,
h2u(trie_roots_after.state_root),
),
(
GlobalMetadata::TransactionTrieRootDigestAfter,
h2u(trie_roots_after.transactions_root),
),
(
GlobalMetadata::ReceiptTrieRootDigestAfter,
h2u(trie_roots_after.receipts_root),
),
// The gas used in this case is the transaction gas limit
(GlobalMetadata::BlockGasUsedAfter, txn_gas_limit.into()),
];
interpreter.set_global_metadata_multi_fields(&metadata_to_set);
let route_txn_label = KERNEL.global_labels["hash_initial_tries"];
// Switch context and initialize memory with the data we need for the tests.
interpreter.generation_state.registers.program_counter = route_txn_label;
interpreter.generation_state.memory.contexts[0].segments[Segment::ContextMetadata as usize]
.set(ContextMetadata::GasLimit as usize, 1_000_000.into());
interpreter.set_is_kernel(true);
interpreter
.run()
.expect("Proving add11 with exception failed.");
}

View File

@ -58,9 +58,15 @@ fn prepare_interpreter(
trie_data.push(account.code_hash.into_uint());
let trie_data_len = trie_data.len().into();
interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len);
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(value_ptr.into()); // value_ptr
interpreter.push(k.try_into_u256().unwrap()); // key
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(value_ptr.into())
.expect("The stack should not overflow"); // value_ptr
interpreter
.push(k.try_into_u256().unwrap())
.expect("The stack should not overflow"); // key
interpreter.run()?;
assert_eq!(
@ -72,8 +78,12 @@ fn prepare_interpreter(
// Now, execute mpt_hash_state_trie.
interpreter.generation_state.registers.program_counter = mpt_hash_state_trie;
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(1.into()); // Initial trie data segment size, unused.
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(1.into()) // Initial trie data segment size, unused.
.expect("The stack should not overflow");
interpreter.run()?;
assert_eq!(
@ -104,11 +114,15 @@ fn test_balance() -> Result<()> {
// Test `balance`
interpreter.generation_state.registers.program_counter = KERNEL.global_labels["balance"];
interpreter.pop();
interpreter.pop();
interpreter.pop().expect("The stack should not be empty");
interpreter.pop().expect("The stack should not be empty");
assert!(interpreter.stack().is_empty());
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(U256::from_big_endian(address.as_bytes()));
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(U256::from_big_endian(address.as_bytes()))
.expect("The stack should not overflow");
interpreter.run()?;
assert_eq!(interpreter.stack(), vec![balance]);

View File

@ -94,9 +94,15 @@ fn test_state_trie(
trie_data.push(account.code_hash.into_uint());
let trie_data_len = trie_data.len().into();
interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len);
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(value_ptr.into()); // value_ptr
interpreter.push(k.try_into_u256().unwrap()); // key
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(value_ptr.into())
.expect("The stack should not overflow"); // value_ptr
interpreter
.push(k.try_into_u256().unwrap())
.expect("The stack should not overflow"); // key
interpreter.run()?;
assert_eq!(
interpreter.stack().len(),
@ -108,21 +114,34 @@ fn test_state_trie(
// Next, execute mpt_delete, deleting the account we just inserted.
let state_trie_ptr = interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot);
interpreter.generation_state.registers.program_counter = mpt_delete;
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(k.try_into_u256().unwrap());
interpreter.push(64.into());
interpreter.push(state_trie_ptr);
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(k.try_into_u256().unwrap())
.expect("The stack should not overflow");
interpreter
.push(64.into())
.expect("The stack should not overflow");
interpreter
.push(state_trie_ptr)
.expect("The stack should not overflow");
interpreter.run()?;
let state_trie_ptr = interpreter.pop();
let state_trie_ptr = interpreter.pop().expect("The stack should not be empty");
interpreter.set_global_metadata_field(GlobalMetadata::StateTrieRoot, state_trie_ptr);
// Now, execute mpt_hash_state_trie.
interpreter.generation_state.registers.program_counter = mpt_hash_state_trie;
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(1.into()); // Initial length of the trie data segment, unused.
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(1.into()) // Initial length of the trie data segment, unused.
.expect("The stack should not overflow");
interpreter.run()?;
let state_trie_hash = H256::from_uint(&interpreter.pop());
let state_trie_hash =
H256::from_uint(&interpreter.pop().expect("The stack should not be empty"));
let expected_state_trie_hash = state_trie.hash();
assert_eq!(state_trie_hash, expected_state_trie_hash);

View File

@ -118,8 +118,12 @@ fn test_state_trie(trie_inputs: TrieInputs) -> Result<()> {
// Now, execute mpt_hash_state_trie.
interpreter.generation_state.registers.program_counter = mpt_hash_state_trie;
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(1.into()); // Initial length of the trie data segment, unused.
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(1.into()) // Initial length of the trie data segment, unused.
.expect("The stack should not overflow");
interpreter.run()?;
assert_eq!(

View File

@ -196,9 +196,15 @@ fn test_state_trie(
trie_data.push(account.code_hash.into_uint());
let trie_data_len = trie_data.len().into();
interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len);
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(value_ptr.into()); // value_ptr
interpreter.push(k.try_into_u256().unwrap()); // key
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(value_ptr.into())
.expect("The stack should not overflow"); // value_ptr
interpreter
.push(k.try_into_u256().unwrap())
.expect("The stack should not overflow"); // key
interpreter.run()?;
assert_eq!(
@ -210,8 +216,12 @@ fn test_state_trie(
// Now, execute mpt_hash_state_trie.
interpreter.generation_state.registers.program_counter = mpt_hash_state_trie;
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(1.into()); // Initial length of the trie data segment, unused.
interpreter
.push(0xDEADBEEFu32.into())
.expect("The stack should not overflow");
interpreter
.push(1.into()) // Initial length of the trie data segment, unused.
.expect("The stack should not overflow");
interpreter.run()?;
assert_eq!(

View File

@ -26,10 +26,18 @@ fn mpt_read() -> Result<()> {
// Now, execute mpt_read on the state trie.
interpreter.generation_state.registers.program_counter = mpt_read;
interpreter.push(0xdeadbeefu32.into());
interpreter.push(0xABCDEFu64.into());
interpreter.push(6.into());
interpreter.push(interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot));
interpreter
.push(0xdeadbeefu32.into())
.expect("The stack should not overflow");
interpreter
.push(0xABCDEFu64.into())
.expect("The stack should not overflow");
interpreter
.push(6.into())
.expect("The stack should not overflow");
interpreter
.push(interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot))
.expect("The stack should not overflow");
interpreter.run()?;
assert_eq!(interpreter.stack().len(), 1);

View File

@ -194,7 +194,7 @@ fn test_receipt_encoding() -> Result<()> {
interpreter.set_memory_segment(Segment::TrieData, receipt);
interpreter.run()?;
let rlp_pos = interpreter.pop();
let rlp_pos = interpreter.pop().expect("The stack should not be empty");
let rlp_read: Vec<u8> = interpreter.get_rlp_memory();
@ -295,7 +295,9 @@ fn test_receipt_bloom_filter() -> Result<()> {
.map(U256::from);
logs2.extend(cur_data);
interpreter.push(retdest);
interpreter
.push(retdest)
.expect("The stack should not overflow");
interpreter.generation_state.registers.program_counter = logs_bloom;
interpreter.set_memory_segment(Segment::TxnBloom, vec![0.into(); 256]); // Initialize transaction Bloom filter.
interpreter.set_memory_segment(Segment::LogsData, logs2);
@ -427,7 +429,9 @@ fn test_mpt_insert_receipt() -> Result<()> {
num_nibbles.into(),
];
for i in 0..initial_stack.len() {
interpreter.push(initial_stack[i]);
interpreter
.push(initial_stack[i])
.expect("The stack should not overflow");
}
interpreter.generation_state.registers.program_counter = mpt_insert;
@ -497,7 +501,9 @@ fn test_mpt_insert_receipt() -> Result<()> {
num_nibbles.into(),
];
for i in 0..initial_stack2.len() {
interpreter.push(initial_stack2[i]);
interpreter
.push(initial_stack2[i])
.expect("The stack should not overflow");
}
cur_trie_data.extend(receipt_1);
@ -510,8 +516,12 @@ fn test_mpt_insert_receipt() -> Result<()> {
// Finally, check that the hashes correspond.
let mpt_hash_receipt = KERNEL.global_labels["mpt_hash_receipt_trie"];
interpreter.generation_state.registers.program_counter = mpt_hash_receipt;
interpreter.push(retdest);
interpreter.push(1.into()); // Initial length of the trie data segment, unused.
interpreter
.push(retdest)
.expect("The stack should not overflow");
interpreter
.push(1.into()) // Initial length of the trie data segment, unused.; // Initial length of the trie data segment, unused.
.expect("The stack should not overflow");
interpreter.run()?;
assert_eq!(
interpreter.stack()[1],