mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-02 22:03:07 +00:00
* Start * Scale TxnFields * Speed-up * Misc fixes * Other fixes * Fix * Fix offset * One more fix * And one more fix * Fix * Fix * Fix init * More interpreter fixes * Final fixes * Add helper methods * Clippy * Apply suggestions * Comments * Update documentation * Regenerate pdf * minor * Rename some macros for consistency * Add utility method for unscaling segments and scaled metadata * Address comments
470 lines
16 KiB
Rust
470 lines
16 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use anyhow::{anyhow, Result};
|
|
use eth_trie_utils::nibbles::Nibbles;
|
|
use eth_trie_utils::partial_trie::{HashedPartialTrie, PartialTrie};
|
|
use ethereum_types::{Address, BigEndianHash, H256, U256};
|
|
use hex_literal::hex;
|
|
use keccak_hash::keccak;
|
|
use rand::{thread_rng, Rng};
|
|
|
|
use crate::cpu::kernel::aggregator::KERNEL;
|
|
use crate::cpu::kernel::constants::context_metadata::ContextMetadata::{self, GasLimit};
|
|
use crate::cpu::kernel::constants::global_metadata::GlobalMetadata;
|
|
use crate::cpu::kernel::interpreter::Interpreter;
|
|
use crate::cpu::kernel::tests::mpt::nibbles_64;
|
|
use crate::generation::mpt::{load_all_mpts, AccountRlp};
|
|
use crate::generation::TrieInputs;
|
|
use crate::memory::segments::Segment;
|
|
use crate::witness::memory::MemoryAddress;
|
|
use crate::witness::operation::CONTEXT_SCALING_FACTOR;
|
|
use crate::Node;
|
|
|
|
pub(crate) fn initialize_mpts(interpreter: &mut Interpreter, trie_inputs: &TrieInputs) {
|
|
// Load all MPTs.
|
|
let (trie_root_ptrs, trie_data) =
|
|
load_all_mpts(trie_inputs).expect("Invalid MPT data for preinitialization");
|
|
|
|
let state_addr =
|
|
MemoryAddress::new_bundle((GlobalMetadata::StateTrieRoot as usize).into()).unwrap();
|
|
let txn_addr =
|
|
MemoryAddress::new_bundle((GlobalMetadata::TransactionTrieRoot as usize).into()).unwrap();
|
|
let receipts_addr =
|
|
MemoryAddress::new_bundle((GlobalMetadata::ReceiptTrieRoot as usize).into()).unwrap();
|
|
let len_addr =
|
|
MemoryAddress::new_bundle((GlobalMetadata::TrieDataSize as usize).into()).unwrap();
|
|
|
|
let to_set = [
|
|
(state_addr, trie_root_ptrs.state_root_ptr.into()),
|
|
(txn_addr, trie_root_ptrs.txn_root_ptr.into()),
|
|
(receipts_addr, trie_root_ptrs.receipt_root_ptr.into()),
|
|
(len_addr, trie_data.len().into()),
|
|
];
|
|
|
|
interpreter.set_memory_multi_addresses(&to_set);
|
|
|
|
for (i, data) in trie_data.iter().enumerate() {
|
|
let trie_addr = MemoryAddress::new(0, Segment::TrieData, i);
|
|
interpreter
|
|
.generation_state
|
|
.memory
|
|
.set(trie_addr, data.into());
|
|
}
|
|
}
|
|
|
|
// Test account with a given code hash.
|
|
fn test_account(code: &[u8]) -> AccountRlp {
|
|
AccountRlp {
|
|
nonce: U256::from(1111),
|
|
balance: U256::from(2222),
|
|
storage_root: HashedPartialTrie::from(Node::Empty).hash(),
|
|
code_hash: keccak(code),
|
|
}
|
|
}
|
|
|
|
fn random_code() -> Vec<u8> {
|
|
let mut rng = thread_rng();
|
|
let num_bytes = rng.gen_range(0..1000);
|
|
(0..num_bytes).map(|_| rng.gen()).collect()
|
|
}
|
|
|
|
// Stolen from `tests/mpt/insert.rs`
|
|
// Prepare the interpreter by inserting the account in the state trie.
|
|
fn prepare_interpreter(
|
|
interpreter: &mut Interpreter,
|
|
address: Address,
|
|
account: &AccountRlp,
|
|
) -> Result<()> {
|
|
let mpt_insert_state_trie = KERNEL.global_labels["mpt_insert_state_trie"];
|
|
let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"];
|
|
let mut state_trie: HashedPartialTrie = Default::default();
|
|
let trie_inputs = Default::default();
|
|
|
|
initialize_mpts(interpreter, &trie_inputs);
|
|
|
|
let k = nibbles_64(U256::from_big_endian(
|
|
keccak(address.to_fixed_bytes()).as_bytes(),
|
|
));
|
|
// Next, execute mpt_insert_state_trie.
|
|
interpreter.generation_state.registers.program_counter = mpt_insert_state_trie;
|
|
let trie_data = interpreter.get_trie_data_mut();
|
|
if trie_data.is_empty() {
|
|
// In the assembly we skip over 0, knowing trie_data[0] = 0 by default.
|
|
// Since we don't explicitly set it to 0, we need to do so here.
|
|
trie_data.push(0.into());
|
|
}
|
|
let value_ptr = trie_data.len();
|
|
trie_data.push(account.nonce);
|
|
trie_data.push(account.balance);
|
|
// In memory, storage_root gets interpreted as a pointer to a storage trie,
|
|
// so we have to ensure the pointer is valid. It's easiest to set it to 0,
|
|
// which works as an empty node, since trie_data[0] = 0 = MPT_TYPE_EMPTY.
|
|
trie_data.push(H256::zero().into_uint());
|
|
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())
|
|
.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(),
|
|
0,
|
|
"Expected empty stack after insert, found {:?}",
|
|
interpreter.stack()
|
|
);
|
|
|
|
// Now, execute mpt_hash_state_trie.
|
|
interpreter.generation_state.registers.program_counter = mpt_hash_state_trie;
|
|
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!(
|
|
interpreter.stack().len(),
|
|
2,
|
|
"Expected 2 items on stack after hashing, found {:?}",
|
|
interpreter.stack()
|
|
);
|
|
let hash = H256::from_uint(&interpreter.stack()[1]);
|
|
|
|
state_trie.insert(k, rlp::encode(account).to_vec());
|
|
let expected_state_trie_hash = state_trie.hash();
|
|
assert_eq!(hash, expected_state_trie_hash);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_extcodesize() -> Result<()> {
|
|
let code = random_code();
|
|
let account = test_account(&code);
|
|
|
|
let mut interpreter = Interpreter::new_with_kernel(0, vec![]);
|
|
let address: Address = thread_rng().gen();
|
|
// Prepare the interpreter by inserting the account in the state trie.
|
|
prepare_interpreter(&mut interpreter, address, &account)?;
|
|
|
|
let extcodesize = KERNEL.global_labels["extcodesize"];
|
|
|
|
// Test `extcodesize`
|
|
interpreter.generation_state.registers.program_counter = extcodesize;
|
|
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())
|
|
.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()?;
|
|
|
|
assert_eq!(interpreter.stack(), vec![code.len().into()]);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_extcodecopy() -> Result<()> {
|
|
let code = random_code();
|
|
let account = test_account(&code);
|
|
|
|
let mut interpreter = Interpreter::new_with_kernel(0, vec![]);
|
|
let address: Address = thread_rng().gen();
|
|
// Prepare the interpreter by inserting the account in the state trie.
|
|
prepare_interpreter(&mut interpreter, address, &account)?;
|
|
|
|
let context = interpreter.context();
|
|
interpreter.generation_state.memory.contexts[context].segments
|
|
[Segment::ContextMetadata.unscale()]
|
|
.set(GasLimit.unscale(), U256::from(1000000000000u64));
|
|
|
|
let extcodecopy = KERNEL.global_labels["sys_extcodecopy"];
|
|
|
|
// Put random data in main memory and the `KernelAccountCode` segment for realism.
|
|
let mut rng = thread_rng();
|
|
for i in 0..2000 {
|
|
interpreter.generation_state.memory.contexts[context].segments
|
|
[Segment::MainMemory.unscale()]
|
|
.set(i, U256::from(rng.gen::<u8>()));
|
|
interpreter.generation_state.memory.contexts[context].segments
|
|
[Segment::KernelAccountCode.unscale()]
|
|
.set(i, U256::from(rng.gen::<u8>()));
|
|
}
|
|
|
|
// Random inputs
|
|
let dest_offset = rng.gen_range(0..3000);
|
|
let offset = rng.gen_range(0..1500);
|
|
let size = rng.gen_range(0..1500);
|
|
|
|
// Test `extcodecopy`
|
|
interpreter.generation_state.registers.program_counter = extcodecopy;
|
|
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())
|
|
.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()?;
|
|
|
|
assert!(interpreter.stack().is_empty());
|
|
// Check that the code was correctly copied to memory.
|
|
for i in 0..size {
|
|
let memory = interpreter.generation_state.memory.contexts[context].segments
|
|
[Segment::MainMemory.unscale()]
|
|
.get(dest_offset + i);
|
|
assert_eq!(
|
|
memory,
|
|
code.get(offset + i).copied().unwrap_or_default().into()
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Prepare the interpreter for storage tests by inserting all necessary accounts
|
|
/// in the state trie, adding the code we want to context 1 and switching the context.
|
|
fn prepare_interpreter_all_accounts(
|
|
interpreter: &mut Interpreter,
|
|
trie_inputs: TrieInputs,
|
|
addr: [u8; 20],
|
|
code: &[u8],
|
|
) -> Result<()> {
|
|
// Load all MPTs.
|
|
initialize_mpts(interpreter, &trie_inputs);
|
|
assert_eq!(interpreter.stack(), vec![]);
|
|
|
|
// Switch context and initialize memory with the data we need for the tests.
|
|
interpreter.generation_state.registers.program_counter = 0;
|
|
interpreter.set_code(1, code.to_vec());
|
|
interpreter.set_context_metadata_field(
|
|
1,
|
|
ContextMetadata::Address,
|
|
U256::from_big_endian(&addr),
|
|
);
|
|
interpreter.set_context_metadata_field(1, ContextMetadata::GasLimit, 100_000.into());
|
|
interpreter.set_context(1);
|
|
interpreter.set_is_kernel(false);
|
|
interpreter.set_context_metadata_field(
|
|
1,
|
|
ContextMetadata::ParentProgramCounter,
|
|
0xdeadbeefu32.into(),
|
|
);
|
|
interpreter.set_context_metadata_field(
|
|
1,
|
|
ContextMetadata::ParentContext,
|
|
U256::one() << CONTEXT_SCALING_FACTOR, // ctx = 1
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Tests an SSTORE within a code similar to the contract code in add11_yml.
|
|
#[test]
|
|
fn sstore() -> Result<()> {
|
|
// We take the same `to` account as in add11_yml.
|
|
let addr = hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87");
|
|
|
|
let addr_hashed = keccak(addr);
|
|
|
|
let addr_nibbles = Nibbles::from_bytes_be(addr_hashed.as_bytes()).unwrap();
|
|
|
|
let code = [0x60, 0x01, 0x60, 0x01, 0x01, 0x60, 0x00, 0x55, 0x00];
|
|
let code_hash = keccak(code);
|
|
|
|
let account_before = AccountRlp {
|
|
balance: 0x0de0b6b3a7640000u64.into(),
|
|
code_hash,
|
|
..AccountRlp::default()
|
|
};
|
|
|
|
let mut state_trie_before = HashedPartialTrie::from(Node::Empty);
|
|
|
|
state_trie_before.insert(addr_nibbles, rlp::encode(&account_before).to_vec());
|
|
|
|
let trie_inputs = TrieInputs {
|
|
state_trie: state_trie_before.clone(),
|
|
transactions_trie: Node::Empty.into(),
|
|
receipts_trie: Node::Empty.into(),
|
|
storage_tries: vec![(addr_hashed, Node::Empty.into())],
|
|
};
|
|
|
|
let initial_stack = vec![];
|
|
let mut interpreter = Interpreter::new_with_kernel(0, initial_stack);
|
|
|
|
// 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();
|
|
|
|
// The code should have added an element to the storage of `to_account`. We run
|
|
// `mpt_hash_state_trie` to check that.
|
|
let account_after = AccountRlp {
|
|
balance: 0x0de0b6b3a7640000u64.into(),
|
|
code_hash,
|
|
storage_root: HashedPartialTrie::from(Node::Leaf {
|
|
nibbles: Nibbles::from_h256_be(keccak([0u8; 32])),
|
|
value: vec![2],
|
|
})
|
|
.hash(),
|
|
..AccountRlp::default()
|
|
};
|
|
// Now, execute mpt_hash_state_trie.
|
|
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())
|
|
.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!(
|
|
interpreter.stack().len(),
|
|
2,
|
|
"Expected 2 items on stack after hashing, found {:?}",
|
|
interpreter.stack()
|
|
);
|
|
|
|
let hash = H256::from_uint(&interpreter.stack()[1]);
|
|
|
|
let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty);
|
|
expected_state_trie_after.insert(addr_nibbles, rlp::encode(&account_after).to_vec());
|
|
|
|
let expected_state_trie_hash = expected_state_trie_after.hash();
|
|
assert_eq!(hash, expected_state_trie_hash);
|
|
Ok(())
|
|
}
|
|
|
|
/// Tests an SLOAD within a code similar to the contract code in add11_yml.
|
|
#[test]
|
|
fn sload() -> Result<()> {
|
|
// We take the same `to` account as in add11_yml.
|
|
let addr = hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87");
|
|
|
|
let addr_hashed = keccak(addr);
|
|
|
|
let addr_nibbles = Nibbles::from_bytes_be(addr_hashed.as_bytes()).unwrap();
|
|
|
|
// This code is similar to the one in add11_yml's contract, but we pop the added value
|
|
// and carry out an SLOAD instead of an SSTORE. We also add a PUSH at the end.
|
|
let code = [
|
|
0x60, 0x01, 0x60, 0x01, 0x01, 0x50, 0x60, 0x00, 0x54, 0x60, 0x03, 0x00,
|
|
];
|
|
let code_hash = keccak(code);
|
|
|
|
let account_before = AccountRlp {
|
|
balance: 0x0de0b6b3a7640000u64.into(),
|
|
code_hash,
|
|
..AccountRlp::default()
|
|
};
|
|
|
|
let mut state_trie_before = HashedPartialTrie::from(Node::Empty);
|
|
|
|
state_trie_before.insert(addr_nibbles, rlp::encode(&account_before).to_vec());
|
|
|
|
let trie_inputs = TrieInputs {
|
|
state_trie: state_trie_before.clone(),
|
|
transactions_trie: Node::Empty.into(),
|
|
receipts_trie: Node::Empty.into(),
|
|
storage_tries: vec![(addr_hashed, Node::Empty.into())],
|
|
};
|
|
|
|
let initial_stack = vec![];
|
|
let mut interpreter = Interpreter::new_with_kernel(0, initial_stack);
|
|
|
|
// 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()
|
|
.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()
|
|
.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())
|
|
.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!(
|
|
interpreter.stack().len(),
|
|
2,
|
|
"Expected 2 items on stack after hashing, found {:?}",
|
|
interpreter.stack()
|
|
);
|
|
|
|
let trie_data_segment_len = interpreter.stack()[0];
|
|
assert_eq!(
|
|
trie_data_segment_len,
|
|
interpreter
|
|
.get_memory_segment(Segment::TrieData)
|
|
.len()
|
|
.into()
|
|
);
|
|
|
|
let hash = H256::from_uint(&interpreter.stack()[1]);
|
|
|
|
let expected_state_trie_hash = state_trie_before.hash();
|
|
assert_eq!(hash, expected_state_trie_hash);
|
|
Ok(())
|
|
}
|