Skip to content

Commit

Permalink
Add deposit transactions (#445)
Browse files Browse the repository at this point in the history
* Init

* Fix evm tests

* Implement wrapper + configure jsons

* Fix event type

* Fix many tests

* Fix test await

* Simple fixes

* Move deposit data push

* Refactor hook params

* Fix init param

* Add code in sys tx test (asserts wrong, success = false)

* Revert L1blockhashtest

* Lint fix

* Fix block gas limit test

* Sample encode - lint fix

* Reintroduce bridge contract in blockhash test + fix contract init

* Use HexTx for serialize/deserialize
  • Loading branch information
otaliptus authored Apr 29, 2024
1 parent 607fa19 commit 60ddff5
Show file tree
Hide file tree
Showing 33 changed files with 641 additions and 247 deletions.
13 changes: 9 additions & 4 deletions bin/citrea/src/test_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ fn regular_test_helper(payload: serde_json::Value, expected: &serde_json::Value)
],
phantom_data: PhantomData,
pub_key: vec![],
deposit_data: vec![
"aaaaab".as_bytes().to_vec(),
"eeeeeeeeee".as_bytes().to_vec(),
],
l1_fee_rate: 0,
timestamp: 0,
},
Expand All @@ -167,6 +171,7 @@ fn regular_test_helper(payload: serde_json::Value, expected: &serde_json::Value)
tx_receipts: batch2_tx_receipts(),
phantom_data: PhantomData,
pub_key: vec![],
deposit_data: vec!["c44444".as_bytes().to_vec()],
l1_fee_rate: 0,
timestamp: 0,
},
Expand Down Expand Up @@ -318,7 +323,7 @@ fn test_get_batches() {
fn test_get_soft_batch() {
// Get the first soft batch by number
let payload = jsonrpc_req!("ledger_getSoftBatchByNumber", [1]);
let expected = jsonrpc_result!({"da_slot_height":0,"da_slot_hash":"0000000000000000000000000000000000000000000000000000000000000000","da_slot_txs_commitment":"0101010101010101010101010101010101010101010101010101010101010101","hash":"b5515a80204963f7db40e98af11aedb49a394b1c7e3d8b5b7a33346b8627444f","txs":["74783120626f6479", "74783220626f6479"],"pre_state_root":"","post_state_root":"","soft_confirmation_signature":"","pub_key":"","l1_fee_rate":0, "timestamp": 0});
let expected = jsonrpc_result!({"da_slot_height":0,"da_slot_hash":"0000000000000000000000000000000000000000000000000000000000000000","da_slot_txs_commitment":"0101010101010101010101010101010101010101010101010101010101010101","deposit_data": ["616161616162", "65656565656565656565"],"hash":"b5515a80204963f7db40e98af11aedb49a394b1c7e3d8b5b7a33346b8627444f","txs":["74783120626f6479", "74783220626f6479"],"pre_state_root":"","post_state_root":"","soft_confirmation_signature":"","pub_key":"", "l1_fee_rate":0, "timestamp": 0});
regular_test_helper(payload, &expected);

// Get the first soft batch by hash
Expand All @@ -335,7 +340,7 @@ fn test_get_soft_batch() {
.map(|tx_receipt| tx_receipt.body_to_save.unwrap().encode_hex::<String>())
.collect::<Vec<String>>();
let expected = jsonrpc_result!(
{"da_slot_height":1,"da_slot_hash":"0202020202020202020202020202020202020202020202020202020202020202","da_slot_txs_commitment":"0303030303030303030303030303030303030303030303030303030303030303","hash":"f85fe0cb36fdaeca571c896ed476b49bb3c8eff00d935293a8967e1e9a62071e","txs": txs, "pre_state_root":"","post_state_root":"","soft_confirmation_signature":"","pub_key":"","l1_fee_rate":0, "timestamp": 0}
{"da_slot_height":1,"da_slot_hash":"0202020202020202020202020202020202020202020202020202020202020202","da_slot_txs_commitment":"0303030303030303030303030303030303030303030303030303030303030303","deposit_data": ["633434343434"],"hash":"f85fe0cb36fdaeca571c896ed476b49bb3c8eff00d935293a8967e1e9a62071e","txs": txs, "pre_state_root":"","post_state_root":"","soft_confirmation_signature":"","pub_key":"","l1_fee_rate":0, "timestamp": 0}
);
regular_test_helper(payload, &expected);

Expand All @@ -355,8 +360,8 @@ fn test_get_soft_batch() {
.collect::<Vec<String>>();
let expected = jsonrpc_result!(
[
{"da_slot_height":0,"da_slot_hash":"0000000000000000000000000000000000000000000000000000000000000000","da_slot_txs_commitment":"0101010101010101010101010101010101010101010101010101010101010101","hash":"b5515a80204963f7db40e98af11aedb49a394b1c7e3d8b5b7a33346b8627444f","txs":["74783120626f6479", "74783220626f6479"],"pre_state_root":"","post_state_root":"","soft_confirmation_signature":"","pub_key":"","l1_fee_rate":0, "timestamp": 0},
{"da_slot_height":1,"da_slot_hash":"0202020202020202020202020202020202020202020202020202020202020202","da_slot_txs_commitment":"0303030303030303030303030303030303030303030303030303030303030303","hash":"f85fe0cb36fdaeca571c896ed476b49bb3c8eff00d935293a8967e1e9a62071e","txs": txs, "pre_state_root":"","post_state_root":"","soft_confirmation_signature":"","pub_key":"","l1_fee_rate":0, "timestamp": 0}
{"da_slot_height":0,"da_slot_hash":"0000000000000000000000000000000000000000000000000000000000000000","da_slot_txs_commitment":"0101010101010101010101010101010101010101010101010101010101010101","deposit_data": ["616161616162", "65656565656565656565"],"hash":"b5515a80204963f7db40e98af11aedb49a394b1c7e3d8b5b7a33346b8627444f","txs":["74783120626f6479", "74783220626f6479"],"pre_state_root":"","post_state_root":"","soft_confirmation_signature":"","pub_key":"","l1_fee_rate":0, "timestamp": 0},
{"da_slot_height":1,"da_slot_hash":"0202020202020202020202020202020202020202020202020202020202020202","da_slot_txs_commitment":"0303030303030303030303030303030303030303030303030303030303030303","deposit_data": ["633434343434"],"hash":"f85fe0cb36fdaeca571c896ed476b49bb3c8eff00d935293a8967e1e9a62071e","txs": txs, "pre_state_root":"","post_state_root":"","soft_confirmation_signature":"","pub_key":"","l1_fee_rate":0, "timestamp": 0}
]
);
regular_test_helper(payload, &expected);
Expand Down
19 changes: 10 additions & 9 deletions bin/citrea/tests/e2e/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1421,7 +1421,7 @@ async fn test_system_transactons() -> Result<(), anyhow::Error> {
.await;

if block_num == 1 {
assert_eq!(block.transactions.len(), 2);
assert_eq!(block.transactions.len(), 3);

let init_tx = &block.transactions[0];
let set_tx = &block.transactions[1];
Expand Down Expand Up @@ -1551,28 +1551,28 @@ async fn test_system_tx_effect_on_block_gas_limit() -> Result<(), anyhow::Error>

let seq_port = seq_port_rx.await.unwrap();
let seq_test_client = make_test_client(seq_port).await;
// sys tx use 43615 + 43615 = 117196gas
// the block gas limit is 1_000_000 because the system txs gas limit is 1_000_000
// sys tx use L1BlockHash(43615 + 73581) + Bridge(1030769) = 142504 gas
// the block gas limit is 1_500_000 because the system txs gas limit is 1_500_000 (decided with @eyusufatik and @okkothejawa as bridge init takes 1M gas)

// 1000000 - 117196 = 882804 gas left in block
// 882804 / 21000 = 42.038 so 42 ether transfer transactions can be included in the block
// 1000000 - 1147965 = 352.035 gas left in block
// 352.035 / 21000 = 16,7... so 16 ether transfer transactions can be included in the block

// send 41 ether transfer transactions
// send 16 ether transfer transactions
let addr = Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap();

for _ in 0..41 {
for _ in 0..15 {
seq_test_client
.send_eth(addr, None, None, None, 0u128)
.await
.unwrap();
}

// 42nd tx should be the last tx in the soft batch
// 16th tx should be the last tx in the soft batch
let last_in_tx = seq_test_client
.send_eth(addr, None, None, None, 0u128)
.await;

// this tx should not be in soft batch
// 17th tx should not be in soft batch
let not_in_tx = seq_test_client
.send_eth(addr, None, None, None, 0u128)
.await;
Expand Down Expand Up @@ -1653,6 +1653,7 @@ async fn test_system_tx_effect_on_block_gas_limit() -> Result<(), anyhow::Error>
assert!(block2.transactions.iter().any(|tx| tx == &not_in_hash));

seq_task.abort();

Ok(())
}

Expand Down
2 changes: 1 addition & 1 deletion bin/citrea/tests/evm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ async fn execute(client: &Box<TestClient>) -> Result<(), Box<dyn std::error::Err
.eth_get_block_by_number(Some(BlockNumberOrTag::Number(1)))
.await;
assert_eq!(first_block.number.unwrap().as_u64(), 1);
assert_eq!(first_block.transactions.len(), 3);
assert_eq!(first_block.transactions.len(), 4);

let set_arg = 923;
let tx_hash = {
Expand Down
5 changes: 5 additions & 0 deletions bin/test-data/genesis/demo-tests/bitcoin-regtest/evm.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions bin/test-data/genesis/demo-tests/mock/evm.json

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions bin/test-data/genesis/integration-tests/evm.json

Large diffs are not rendered by default.

11 changes: 2 additions & 9 deletions crates/citrea-stf/src/hooks_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,8 @@ impl<C: Context, Da: DaSpec> ApplySoftConfirmationHooks<Da> for Runtime<C, Da> {
self.soft_confirmation_rule_enforcer
.begin_soft_confirmation_hook(soft_batch, working_set)?;

self.evm.begin_soft_confirmation_hook(
soft_batch.da_slot_hash(),
soft_batch.da_slot_height,
soft_batch.da_slot_txs_commitment(),
&soft_batch.pre_state_root(),
soft_batch.l1_fee_rate(),
soft_batch.timestamp(),
working_set,
);
self.evm
.begin_soft_confirmation_hook(soft_batch, working_set);

Ok(())
}
Expand Down
8 changes: 7 additions & 1 deletion crates/evm/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::evm::executor::{self};
use crate::evm::handler::{CitreaExternal, CitreaExternalExt};
use crate::evm::primitive_types::{BlockEnv, Receipt, TransactionSignedAndRecovered};
use crate::evm::{EvmChainConfig, RlpEvmTransaction};
use crate::system_contracts::BitcoinLightClient;
use crate::system_contracts::{BitcoinLightClient, Bridge};
use crate::system_events::{create_system_transactions, SYSTEM_SIGNER};
use crate::{Evm, PendingTransaction, SystemEvent};

Expand Down Expand Up @@ -57,6 +57,12 @@ impl<C: sov_modules_api::Context> Evm<C> {
return;
}

let bridge_contract_exists = self.accounts.get(&Bridge::address(), working_set).is_some();
if !bridge_contract_exists {
tracing::error!("System contract not found: Bridge");
return;
}

let system_nonce = self
.accounts
.get(&SYSTEM_SIGNER, working_set)
Expand Down
25 changes: 25 additions & 0 deletions crates/evm/src/evm/system_contracts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,28 @@ impl BitcoinLightClient {
Bytes::from(func_selector)
}
}

/// Bridge wrapper.
pub struct Bridge {}

impl Bridge {
pub(crate) fn address() -> Address {
address!("3100000000000000000000000000000000000002")
}

pub(crate) fn initialize(data: Vec<u8>) -> Bytes {
let mut func_selector: Vec<u8> = vec![0xc1, 0x24, 0x28, 0x9c]; // initialize(uint32,bytes,bytes,uint256,address) c124289c

func_selector.extend_from_slice(data.as_slice());

Bytes::from(func_selector)
}

pub(crate) fn deposit(data: Vec<u8>) -> Bytes {
let mut func_selector: Vec<u8> = vec![0xdd, 0x95, 0xc7, 0xc6]; // deposit((bytes4,bytes2,bytes,bytes,bytes,bytes4,bytes,uint256,uint256)) dd95c7c6

func_selector.extend_from_slice(data.as_slice());

Bytes::from(func_selector)
}
}
24 changes: 23 additions & 1 deletion crates/evm/src/evm/system_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use reth_primitives::{
TransactionSignedEcRecovered, TransactionSignedNoHash, TxEip1559, U256,
};

use super::system_contracts::BitcoinLightClient;
use super::system_contracts::{BitcoinLightClient, Bridge};

/// This is a special signature to force tx.signer to be set to SYSTEM_SIGNER
pub const SYSTEM_SIGNATURE: Signature = Signature {
Expand All @@ -21,6 +21,8 @@ pub const SYSTEM_SIGNER: Address = address!("deaddeaddeaddeaddeaddeaddeaddeaddea
pub(crate) enum SystemEvent {
BitcoinLightClientInitialize(/*block number*/ u64),
BitcoinLightClientSetBlockInfo(/*hash*/ [u8; 32], /*merkle root*/ [u8; 32]),
BridgeInitialize(Vec<u8>), // levels, deposit script, script suffix, required sigs count
BridgeDeposit(Vec<u8>), // version, flag, vin, vout, witness, locktime, intermediate nodes, block height, index
}

fn system_event_to_transaction(event: SystemEvent, nonce: u64, chain_id: u64) -> Transaction {
Expand All @@ -45,6 +47,26 @@ fn system_event_to_transaction(event: SystemEvent, nonce: u64, chain_id: u64) ->
max_fee_per_gas: u64::MAX as u128,
..Default::default()
},
SystemEvent::BridgeInitialize(data) => TxEip1559 {
to: TransactionKind::Call(Bridge::address()),
input: Bridge::initialize(data),
nonce,
chain_id,
value: U256::ZERO,
gas_limit: 1_500_000u64,
max_fee_per_gas: u64::MAX as u128,
..Default::default()
},
SystemEvent::BridgeDeposit(data) => TxEip1559 {
to: TransactionKind::Call(Bridge::address()),
input: Bridge::deposit(data),
nonce,
chain_id,
value: U256::ZERO,
gas_limit: 1_000_000u64,
max_fee_per_gas: u64::MAX as u128,
..Default::default()
},
};
Transaction::Eip1559(body)
}
Expand Down
68 changes: 51 additions & 17 deletions crates/evm/src/hooks.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use alloy_primitives::B256;
use reth_primitives::{Bloom, Bytes, U256};
use sov_modules_api::hooks::HookSoftConfirmationInfo;
use sov_modules_api::prelude::*;
use sov_modules_api::{AccessoryWorkingSet, Spec, WorkingSet};
use sov_state::Storage;
Expand All @@ -16,20 +17,15 @@ where
#[allow(clippy::too_many_arguments)]
pub fn begin_soft_confirmation_hook(
&self,
da_slot_hash: [u8; 32],
da_slot_height: u64,
da_slot_txs_commitment: [u8; 32],
pre_state_root: &[u8],
l1_fee_rate: u64,
timestamp: u64,
soft_confirmation_info: &HookSoftConfirmationInfo,
working_set: &mut WorkingSet<C>,
) {
let mut parent_block = self
.head
.get(working_set)
.expect("Head block should always be set");

parent_block.header.state_root = B256::from_slice(pre_state_root);
parent_block.header.state_root = B256::from_slice(&soft_confirmation_info.pre_state_root);
self.head.set(&parent_block, working_set);

let sealed_parent_block = parent_block.clone().seal();
Expand All @@ -45,31 +41,67 @@ where
// populate system events
let mut system_events = vec![];
if let Some(last_l1_hash) = self.last_l1_hash.get(working_set) {
if last_l1_hash != da_slot_hash {
if last_l1_hash != soft_confirmation_info.da_slot_hash {
// That's a new L1 block
system_events.push(SystemEvent::BitcoinLightClientSetBlockInfo(
da_slot_hash,
da_slot_txs_commitment,
soft_confirmation_info.da_slot_hash,
soft_confirmation_info.da_slot_txs_commitment,
));
}
} else {
// That's the first L2 block in the first seen L1 block.
system_events.push(SystemEvent::BitcoinLightClientInitialize(da_slot_height));
system_events.push(SystemEvent::BitcoinLightClientInitialize(
soft_confirmation_info.da_slot_height,
));
system_events.push(SystemEvent::BitcoinLightClientSetBlockInfo(
da_slot_hash,
da_slot_txs_commitment,
soft_confirmation_info.da_slot_hash,
soft_confirmation_info.da_slot_txs_commitment,
));
system_events.push(SystemEvent::BridgeInitialize(
// couldn't figure out how encoding worked lol
vec![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 246, 134, 146, 192, 62, 185, 192, 101, 109,
103, 111, 47, 75, 209, 62, 186, 64, 209, 183, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 175, 195, 32, 52,
2, 237, 230, 131, 149, 51, 30, 39, 151, 225, 216, 253, 43, 169, 81, 56, 107,
170, 179, 45, 20, 64, 37, 44, 50, 20, 224, 112, 143, 228, 121, 173, 32, 193,
140, 89, 52, 128, 244, 245, 90, 63, 215, 97, 124, 157, 246, 227, 218, 188, 128,
252, 165, 146, 127, 102, 210, 0, 80, 200, 42, 32, 18, 190, 122, 173, 32, 137,
195, 16, 192, 123, 60, 57, 1, 86, 42, 63, 0, 12, 74, 71, 127, 203, 94, 191,
211, 98, 222, 61, 7, 160, 191, 249, 39, 242, 145, 19, 1, 173, 32, 103, 222,
104, 248, 235, 129, 108, 134, 57, 104, 2, 179, 137, 222, 222, 192, 23, 3, 215,
158, 153, 16, 224, 200, 70, 244, 137, 32, 163, 227, 61, 215, 173, 32, 64, 241,
80, 103, 2, 228, 0, 184, 209, 174, 210, 222, 5, 191, 119, 110, 109, 118, 2, 55,
138, 176, 131, 74, 125, 119, 16, 57, 69, 74, 245, 110, 173, 81, 0, 99, 20, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 104, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0,
],
));
}

soft_confirmation_info
.deposit_data
.iter()
.for_each(|deposit_data| {
system_events.push(SystemEvent::BridgeDeposit(deposit_data.clone()));
});

let cfg = self
.cfg
.get(working_set)
.expect("EVM chain config should be set");
let new_pending_env = BlockEnv {
number: parent_block.header.number + 1,
coinbase: cfg.coinbase,
timestamp,
prevrandao: da_slot_hash.into(),
timestamp: soft_confirmation_info.timestamp,
prevrandao: soft_confirmation_info.da_slot_hash.into(),
basefee: parent_block
.header
.next_block_base_fee(cfg.base_fee_params)
Expand All @@ -78,7 +110,8 @@ where
};

self.block_env.set(&new_pending_env, working_set);
self.l1_fee_rate.set(&l1_fee_rate, working_set);
self.l1_fee_rate
.set(&soft_confirmation_info.l1_fee_rate, working_set);

if !system_events.is_empty() {
self.execute_system_events(system_events, working_set);
Expand All @@ -94,7 +127,8 @@ where
self.latest_block_hashes
.remove(&U256::from(new_pending_env.number - 257), working_set);
}
self.last_l1_hash.set(&da_slot_hash.into(), working_set);
self.last_l1_hash
.set(&soft_confirmation_info.da_slot_hash.into(), working_set);
}

/// Logic executed at the end of the slot. Here, we generate an authenticated block and set it as the new head of the chain.
Expand Down
Loading

0 comments on commit 60ddff5

Please sign in to comment.