From 862b1db0a311e2f55d88e963aae5b68c164147f1 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 08:26:05 -0300 Subject: [PATCH 01/52] blockchain: add Blockchain abstraction and start making block validations Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 68 +++++++++++++++++++++++++++++++++++ src/types/block.zig | 6 +++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/blockchain/blockchain.zig diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig new file mode 100644 index 0000000..4fe764a --- /dev/null +++ b/src/blockchain/blockchain.zig @@ -0,0 +1,68 @@ +const std = @import("std"); +const types = @import("../types/types.zig"); +const config = @import("../config/config.zig"); +const vm = @import("../vm/vm.zig"); // TODO: Avoid this import? +const Block = types.Block; +const BlockHeader = types.BlockHeader; +const StateDB = vm.StateDB; +const Hash32 = types.Hash32; + +pub const Blockchain = struct { + const BASE_FEE_MAX_CHANGE_DENOMINATOR = 8; + const ELASTICITY_MULTIPLIER = 2; + const GAS_LIMIT_ADJUSTMENT_FACTOR = 1024; + const GAS_LIMIT_MINIMUM = 5000; + + chain_id: config.ChainId, + flat_db: *StateDB, + last_256_blocks_hashes: [256]Hash32, + previous_block: Block, + + pub fn init( + chain_id: config.ChainId, + flat_db: *StateDB, + prev_block_header: BlockHeader, + last_256_blocks_hashes: [256]Hash32, + ) void { + return Blockchain{ + .chain_id = chain_id, + .flat_db = flat_db, + .prev_block_header = prev_block_header, + .last_256_blocks_hashes = last_256_blocks_hashes, + }; + } + + pub fn execute_block(self: Blockchain, block: Block) !void { + try self.validate_block(block); + // TODO: continue + } + + fn validateBlockHeader(prev_block: BlockHeader, block: BlockHeader) !void { + try checkGasLimit(block.gas_limit, prev_block.gas_limit); + if (block.gas_used > block.gas_limit) + return error.GasLimitExceeded; + + // Check base fee. + const parent_gas_target = prev_block.gas_limit / ELASTICITY_MULTIPLIER; + var expected_base_fee_per_gas = if (prev_block.gas_used == parent_gas_target) + prev_block.base_fee_per_gas + else if (prev_block.gas_used > parent_gas_target) blk: { + const gas_used_delta = prev_block.gas_used - parent_gas_target; + const base_fee_per_gas_delta = @max(prev_block.base_fee_per_gas * gas_used_delta / parent_gas_target / BASE_FEE_MAX_CHANGE_DENOMINATOR, 1); + break :blk prev_block.base_fee_per_gas + base_fee_per_gas_delta; + } else blk: { + const gas_used_delta = parent_gas_target - prev_block.gas_used; + const base_fee_per_gas_delta = prev_block.base_fee_per_gas * gas_used_delta / parent_gas_target / BASE_FEE_MAX_CHANGE_DENOMINATOR; + break :blk prev_block.base_fee_per_gas - base_fee_per_gas_delta; + }; + if (expected_base_fee_per_gas != block.base_fee_per_gas) + return error.InvalidBaseFee; + } + + fn checkGasLimit(gas_limit: u256, parent_gas_limit: u256) !void { + const max_delta = parent_gas_limit / GAS_LIMIT_ADJUSTMENT_FACTOR; + if (gas_limit >= parent_gas_limit + max_delta) return error.GasLimitTooHigh; + if (gas_limit <= parent_gas_limit - max_delta) return error.GasLimitTooLow; + return gas_limit >= GAS_LIMIT_MINIMUM; + } +}; diff --git a/src/types/block.zig b/src/types/block.zig index 913554b..97ba60a 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -6,7 +6,7 @@ const Transaction = types.Transaction; const BYTES_PER_LOGS_BLOOM = 256; pub const Block = struct { - header: Header, + header: BlockHeader, // TODO(jsign): missing fields (i.e: txns). // new returns a new Block deserialized from rlp_bytes. @@ -19,9 +19,13 @@ pub const Block = struct { } }; +<<<<<<< HEAD pub const empty_uncle_hash: types.Hash32 = [_]u8{ 29, 204, 77, 232, 222, 199, 93, 122, 171, 133, 181, 103, 182, 204, 212, 26, 211, 18, 69, 27, 148, 138, 116, 19, 240, 161, 66, 253, 64, 212, 147, 71 }; pub const Header = struct { +======= +pub const BlockHeader = struct { +>>>>>>> ae21c3d... blockchain: add Blockchain abstraction and start making block validations parent_hash: types.Hash32, uncle_hash: types.Hash32, fee_recipient: types.Address, From 3234577028734218c86a9304a9864b7bcd62a464 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 08:26:06 -0300 Subject: [PATCH 02/52] blockchain: finish block header validations Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 4fe764a..6b2bf40 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -1,7 +1,10 @@ const std = @import("std"); const types = @import("../types/types.zig"); const config = @import("../config/config.zig"); +const transaction = @import("../types/transaction.zig"); const vm = @import("../vm/vm.zig"); // TODO: Avoid this import? +const rlp = @import("zig-rlp"); +const Allocator = std.mem.Allocator; const Block = types.Block; const BlockHeader = types.BlockHeader; const StateDB = vm.StateDB; @@ -12,19 +15,23 @@ pub const Blockchain = struct { const ELASTICITY_MULTIPLIER = 2; const GAS_LIMIT_ADJUSTMENT_FACTOR = 1024; const GAS_LIMIT_MINIMUM = 5000; + const EMPTY_OMMER_HASH = [_]u8{0} ** 32; // TODO + allocator: Allocator, chain_id: config.ChainId, flat_db: *StateDB, last_256_blocks_hashes: [256]Hash32, previous_block: Block, pub fn init( + allocator: Allocator, chain_id: config.ChainId, flat_db: *StateDB, prev_block_header: BlockHeader, last_256_blocks_hashes: [256]Hash32, ) void { return Blockchain{ + .allocator = allocator, .chain_id = chain_id, .flat_db = flat_db, .prev_block_header = prev_block_header, @@ -33,13 +40,13 @@ pub const Blockchain = struct { } pub fn execute_block(self: Blockchain, block: Block) !void { - try self.validate_block(block); + try self.validate_block(self.allocator, block); // TODO: continue } - fn validateBlockHeader(prev_block: BlockHeader, block: BlockHeader) !void { - try checkGasLimit(block.gas_limit, prev_block.gas_limit); - if (block.gas_used > block.gas_limit) + fn validateBlockHeader(allocator: Allocator, prev_block: BlockHeader, curr_block: BlockHeader) !void { + try checkGasLimit(curr_block.gas_limit, prev_block.gas_limit); + if (curr_block.gas_used > curr_block.gas_limit) return error.GasLimitExceeded; // Check base fee. @@ -55,8 +62,26 @@ pub const Blockchain = struct { const base_fee_per_gas_delta = prev_block.base_fee_per_gas * gas_used_delta / parent_gas_target / BASE_FEE_MAX_CHANGE_DENOMINATOR; break :blk prev_block.base_fee_per_gas - base_fee_per_gas_delta; }; - if (expected_base_fee_per_gas != block.base_fee_per_gas) + if (expected_base_fee_per_gas != curr_block.base_fee_per_gas) return error.InvalidBaseFee; + + if (curr_block.timestamp > prev_block.timestamp) + return error.InvalidTimestamp; + if (curr_block.block_number != prev_block.block_number + 1) + return error.InvalidBlockNumber; + if (curr_block.extra_data.len > 32) + return error.ExtraDataTooLong; + + if (curr_block.difficulty != 0) + return error.InvalidDifficulty; + if (curr_block.nonce == [_]u8{0} ** 8) + return error.InvalidNonce; + if (curr_block.ommers_hash != EMPTY_OMMER_HASH) + return error.InvalidOmmersHash; + + const prev_block_hash = transaction.RLPHash(BlockHeader, allocator, prev_block, null); + if (curr_block.parent_hash != prev_block_hash) + return error.InvalidParentHash; } fn checkGasLimit(gas_limit: u256, parent_gas_limit: u256) !void { From 867d1fa8b94f2f7f8e492c276d932ef534557e03 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 08:26:06 -0300 Subject: [PATCH 03/52] types: nits Signed-off-by: Ignacio Hagopian --- src/types/block.zig | 4 ---- src/types/types.zig | 7 ++++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/types/block.zig b/src/types/block.zig index 97ba60a..13eb4d6 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -19,13 +19,9 @@ pub const Block = struct { } }; -<<<<<<< HEAD pub const empty_uncle_hash: types.Hash32 = [_]u8{ 29, 204, 77, 232, 222, 199, 93, 122, 171, 133, 181, 103, 182, 204, 212, 26, 211, 18, 69, 27, 148, 138, 116, 19, 240, 161, 66, 253, 64, 212, 147, 71 }; -pub const Header = struct { -======= pub const BlockHeader = struct { ->>>>>>> ae21c3d... blockchain: add Blockchain abstraction and start making block validations parent_hash: types.Hash32, uncle_hash: types.Hash32, fee_recipient: types.Address, diff --git a/src/types/types.zig b/src/types/types.zig index 6ca714a..5a3d6f5 100644 --- a/src/types/types.zig +++ b/src/types/types.zig @@ -11,10 +11,11 @@ pub const Address = [20]u8; pub const AccountState = @import("account_state.zig"); // Blocks -pub const Block = @import("block.zig").Block; -pub const BlockHeader = @import("block.zig").Header; +pub const block = @import("block.zig"); +pub const Block = block.Block; +pub const BlockHeader = block.BlockHeader; +pub const empty_uncle_hash = block.empty_uncle_hash; pub const Withdrawal = @import("withdrawal.zig"); -pub const empty_uncle_hash = @import("block.zig").empty_uncle_hash; // Transactions const transaction = @import("transaction.zig"); From 98b8c31da8efc9c5c1deef6937b2f907c79bb375 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 08:26:07 -0300 Subject: [PATCH 04/52] types/block: refactor Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 2 ++ src/engine_api/execution_payload.zig | 2 +- src/types/block.zig | 29 +++++++++++++++------------- src/vm/vm.zig | 16 +++++++-------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 6b2bf40..dd944f5 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -44,6 +44,8 @@ pub const Blockchain = struct { // TODO: continue } + // validateBlockHeader validates the header of a block itself and with respect with the parent. + // If isn't valid, it returns an error. fn validateBlockHeader(allocator: Allocator, prev_block: BlockHeader, curr_block: BlockHeader) !void { try checkGasLimit(curr_block.gas_limit, prev_block.gas_limit); if (curr_block.gas_used > curr_block.gas_limit) diff --git a/src/engine_api/execution_payload.zig b/src/engine_api/execution_payload.zig index 0ac3271..f62cb51 100644 --- a/src/engine_api/execution_payload.zig +++ b/src/engine_api/execution_payload.zig @@ -34,6 +34,7 @@ pub const ExecutionPayload = struct { .state_root = self.stateRoot, .receipts_root = self.receiptsRoot, .logs_bloom = self.logsBloom, + .difficulty = 0, .prev_randao = self.prevRandao, .block_number = @intCast(self.blockNumber), .gas_limit = @intCast(self.gasLimit), @@ -42,7 +43,6 @@ pub const ExecutionPayload = struct { .extra_data = self.extraData, .base_fee_per_gas = self.baseFeePerGas, .transactions_root = [_]u8{0} ** 32, - .mix_hash = 0, .nonce = [_]u8{0} ** 8, .blob_gas_used = null, .withdrawals_root = null, diff --git a/src/types/block.zig b/src/types/block.zig index 13eb4d6..244a6ed 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -1,6 +1,9 @@ const std = @import("std"); const rlp = @import("zig-rlp"); const types = @import("types.zig"); +const Hash32 = types.Hash32; +const Bytes32 = types.Bytes32; +const Address = types.Address; const Transaction = types.Transaction; const BYTES_PER_LOGS_BLOOM = 256; @@ -22,23 +25,23 @@ pub const Block = struct { pub const empty_uncle_hash: types.Hash32 = [_]u8{ 29, 204, 77, 232, 222, 199, 93, 122, 171, 133, 181, 103, 182, 204, 212, 26, 211, 18, 69, 27, 148, 138, 116, 19, 240, 161, 66, 253, 64, 212, 147, 71 }; pub const BlockHeader = struct { - parent_hash: types.Hash32, - uncle_hash: types.Hash32, - fee_recipient: types.Address, - state_root: types.Bytes32, - transactions_root: types.Bytes32, - receipts_root: types.Bytes32, + parent_hash: Hash32, + uncle_hash: Hash32, + fee_recipient: Address, + state_root: Bytes32, + transactions_root: Bytes32, + receipts_root: Bytes32, logs_bloom: [BYTES_PER_LOGS_BLOOM]u8, - prev_randao: types.Bytes32, - block_number: i64, - gas_limit: i64, + difficulty: u8, + block_number: u64, + gas_limit: u64, gas_used: u64, - timestamp: i64, + timestamp: u64, extra_data: []const u8, - mix_hash: u256, + prev_randao: Bytes32, nonce: [8]u8, - base_fee_per_gas: ?u256, - withdrawals_root: ?types.Hash32, + base_fee_per_gas: u256, + withdrawals_root: ?Hash32, blob_gas_used: ?u64, excess_blob_gas: ?u64, }; diff --git a/src/vm/vm.zig b/src/vm/vm.zig index ad7b10b..67d1138 100644 --- a/src/vm/vm.zig +++ b/src/vm/vm.zig @@ -31,11 +31,11 @@ const TxnContext = struct { const BlockContext = struct { coinbase: Address, - number: i64, - timestamp: i64, - gas_limit: i64, + number: u64, + timestamp: u64, + gas_limit: u64, prev_randao: u256, - base_fee: ?u256, + base_fee: u256, }; pub const VM = struct { @@ -210,12 +210,12 @@ pub const VM = struct { .tx_gas_price = util.to_evmc_bytes32(vm.context.?.txn.gas_price), .tx_origin = util.to_evmc_address(vm.context.?.txn.from), .block_coinbase = util.to_evmc_address(vm.context.?.block.coinbase), - .block_number = vm.context.?.block.number, - .block_timestamp = vm.context.?.block.timestamp, - .block_gas_limit = vm.context.?.block.gas_limit, + .block_number = @intCast(vm.context.?.block.number), + .block_timestamp = @intCast(vm.context.?.block.timestamp), + .block_gas_limit = @intCast(vm.context.?.block.gas_limit), .block_prev_randao = util.to_evmc_bytes32(vm.context.?.block.prev_randao), .chain_id = util.to_evmc_bytes32(vm.context.?.txn.chain_id), - .block_base_fee = util.to_evmc_bytes32(vm.context.?.block.base_fee.?), + .block_base_fee = util.to_evmc_bytes32(vm.context.?.block.base_fee), }; } From 14d6b6d673dba701ea35a10f85a77b14163221ee Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 08:26:07 -0300 Subject: [PATCH 05/52] types/block: add non-header fields Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 4 +-- src/engine_api/engine_api.zig | 20 +++++++++------ src/engine_api/execution_payload.zig | 13 ++++++---- src/types/block.zig | 38 +++++++++++++++------------- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index dd944f5..b0fbdbe 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -1,5 +1,6 @@ const std = @import("std"); const types = @import("../types/types.zig"); +const block = @import("../types/block.zig"); const config = @import("../config/config.zig"); const transaction = @import("../types/transaction.zig"); const vm = @import("../vm/vm.zig"); // TODO: Avoid this import? @@ -15,7 +16,6 @@ pub const Blockchain = struct { const ELASTICITY_MULTIPLIER = 2; const GAS_LIMIT_ADJUSTMENT_FACTOR = 1024; const GAS_LIMIT_MINIMUM = 5000; - const EMPTY_OMMER_HASH = [_]u8{0} ** 32; // TODO allocator: Allocator, chain_id: config.ChainId, @@ -78,7 +78,7 @@ pub const Blockchain = struct { return error.InvalidDifficulty; if (curr_block.nonce == [_]u8{0} ** 8) return error.InvalidNonce; - if (curr_block.ommers_hash != EMPTY_OMMER_HASH) + if (curr_block.ommers_hash != block.empty_uncle_hash) return error.InvalidOmmersHash; const prev_block_hash = transaction.RLPHash(BlockHeader, allocator, prev_block, null); diff --git a/src/engine_api/engine_api.zig b/src/engine_api/engine_api.zig index b9ed86c..0fe8f20 100644 --- a/src/engine_api/engine_api.zig +++ b/src/engine_api/engine_api.zig @@ -1,9 +1,13 @@ const std = @import("std"); const fmt = std.fmt; +const types = @import("../types/types.zig"); +const common = @import("../common/common.zig"); const Allocator = std.mem.Allocator; -pub const execution_payload = @import("./execution_payload.zig"); +const Withdrawal = types.Withdrawal; +const Txn = types.Txn; const ExecutionPayload = execution_payload.ExecutionPayload; -const common = @import("../common/common.zig"); + +pub const execution_payload = @import("./execution_payload.zig"); // This is an intermediate structure used to deserialize the hex strings // from the JSON request. I have seen some zig libraries that can do this @@ -30,11 +34,11 @@ const AllPossibleExecutionParams = struct { transactions: [][]const u8, pub fn to_execution_payload(self: *const AllPossibleExecutionParams, allocator: Allocator) !ExecutionPayload { - var transactions: [][]const u8 = &[_][]const u8{}; + var txns: []Txn = &[0]Txn{}; if (self.transactions.len > 0) { - transactions = try allocator.alloc([]const u8, self.transactions.len); - for (self.transactions, 0..) |tx, txidx| { - transactions[txidx] = try common.prefixedhex2byteslice(allocator, tx); + txns = try allocator.alloc(Txn, self.transactions.len); + for (self.transactions, 0..) |tx, i| { + txns[i] = try Txn.decode(tx); } } @@ -52,8 +56,8 @@ const AllPossibleExecutionParams = struct { .gasUsed = try common.prefixedhex2u64(self.gasUsed), .timestamp = try common.prefixedhex2u64(self.timestamp), .baseFeePerGas = try common.prefixedhex2u64(self.baseFeePerGas), - .transactions = transactions, - .withdrawals = null, + .transactions = txns, + .withdrawals = &[0]Withdrawal{}, .blobGasUsed = null, .excessBlobGas = null, .allocator = allocator, diff --git a/src/engine_api/execution_payload.zig b/src/engine_api/execution_payload.zig index f62cb51..5fbac03 100644 --- a/src/engine_api/execution_payload.zig +++ b/src/engine_api/execution_payload.zig @@ -1,6 +1,9 @@ const std = @import("std"); const types = @import("../types/types.zig"); const Allocator = std.mem.Allocator; +const BlockHeader = types.BlockHeader; +const Withdrawal = types.Withdrawal; +const Txn = types.Txn; pub const ExecutionPayload = struct { parentHash: types.Hash32, @@ -16,9 +19,9 @@ pub const ExecutionPayload = struct { extraData: []const u8, baseFeePerGas: u256, blockHash: types.Hash32, - transactions: [][]const u8, + transactions: []Txn, - withdrawals: ?[]types.Withdrawal, + withdrawals: []types.Withdrawal, blobGasUsed: ?u64, excessBlobGas: ?u64, // executionWitness : ?types.ExecutionWitness, @@ -48,9 +51,9 @@ pub const ExecutionPayload = struct { .withdrawals_root = null, .excess_blob_gas = null, }, - // .blockHash = self.blockHash, - // .transactions = self.transactions, - // .withdrawals = self.withdrawals, + .transactions = self.transactions, + .withdrawals = self.withdrawals, + .uncles = &[0]BlockHeader{}, }; } diff --git a/src/types/block.zig b/src/types/block.zig index 244a6ed..188fafb 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -1,28 +1,14 @@ const std = @import("std"); const rlp = @import("zig-rlp"); const types = @import("types.zig"); +const Withdrawal = types.Withdrawal; +const Txn = types.Txn; const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; const Address = types.Address; -const Transaction = types.Transaction; - -const BYTES_PER_LOGS_BLOOM = 256; - -pub const Block = struct { - header: BlockHeader, - // TODO(jsign): missing fields (i.e: txns). - - // new returns a new Block deserialized from rlp_bytes. - // The returned Block has references to the rlp_bytes slice. - pub fn init(rlp_bytes: []const u8) !Block { - var block = std.mem.zeroes(Block); - _ = try rlp.deserialize(Block, rlp_bytes, &block); - // TODO: consider strict checking of returned deserialized length vs rlp_bytes.len. - return block; - } -}; pub const empty_uncle_hash: types.Hash32 = [_]u8{ 29, 204, 77, 232, 222, 199, 93, 122, 171, 133, 181, 103, 182, 204, 212, 26, 211, 18, 69, 27, 148, 138, 116, 19, 240, 161, 66, 253, 64, 212, 147, 71 }; +const bytes_per_logs_bloom = 256; pub const BlockHeader = struct { parent_hash: Hash32, @@ -31,7 +17,7 @@ pub const BlockHeader = struct { state_root: Bytes32, transactions_root: Bytes32, receipts_root: Bytes32, - logs_bloom: [BYTES_PER_LOGS_BLOOM]u8, + logs_bloom: [bytes_per_logs_bloom]u8, difficulty: u8, block_number: u64, gas_limit: u64, @@ -46,6 +32,22 @@ pub const BlockHeader = struct { excess_blob_gas: ?u64, }; +pub const Block = struct { + header: BlockHeader, + transactions: []Txn, + uncles: []BlockHeader, + withdrawals: []Withdrawal, + + // new returns a new Block deserialized from rlp_bytes. + // The returned Block has references to the rlp_bytes slice. + pub fn init(rlp_bytes: []const u8) !Block { + var block = std.mem.zeroes(Block); + _ = try rlp.deserialize(Block, rlp_bytes, &block); + // TODO: consider strict checking of returned deserialized length vs rlp_bytes.len. + return block; + } +}; + // NOTE: this test uses a bock from an old, pre-shanghai testnet. // I have deactivated it and will replace it with a kaustinen // block when I publish my progress with zig-verkle. From 54dbc145d0423bf1a09056da3bfd73bd6f78b9b3 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 08:26:07 -0300 Subject: [PATCH 06/52] blockchain: add top-level run block logic Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index b0fbdbe..1c79e96 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -1,6 +1,6 @@ const std = @import("std"); const types = @import("../types/types.zig"); -const block = @import("../types/block.zig"); +const blocks = @import("../types/block.zig"); const config = @import("../config/config.zig"); const transaction = @import("../types/transaction.zig"); const vm = @import("../vm/vm.zig"); // TODO: Avoid this import? @@ -39,9 +39,29 @@ pub const Blockchain = struct { }; } - pub fn execute_block(self: Blockchain, block: Block) !void { + pub fn run_block(self: Blockchain, block: Block) !void { try self.validate_block(self.allocator, block); - // TODO: continue + if (block.uncles.len != 0) + return error.NotEmptyUncles; + + var result = try self.execute_block(block); + + if (result.gas_used != block.header.gas_used) + return error.InvalidGasUsed; + if (result.transactions_root != block.header.transactions_root) // TODO: Do before exec. + return error.InvalidTransactionsRoot; + if (result.receipts_root != block.header.receipts_root) // TODO: Do before exec. + return error.InvalidReceiptsRoot; + if (result.state.root() != block.header.state_root) + return error.InvalidStateRoot; + if (result.logs_bloom != block.header.logs_bloom) + return error.InvalidLogsBloom; + if (result.withdrawals_root != block.header.withdrawals_root) + return error.InvalidWithdrawalsRoot; + + // TODO: do this more efficiently with a circular buffer. + std.mem.copyForwards(Hash32, self.last_256_blocks_hashes, self.last_256_blocks_hashes[1..]); + self.last_256_blocks_hashes[255] = block.hash(); } // validateBlockHeader validates the header of a block itself and with respect with the parent. @@ -78,7 +98,7 @@ pub const Blockchain = struct { return error.InvalidDifficulty; if (curr_block.nonce == [_]u8{0} ** 8) return error.InvalidNonce; - if (curr_block.ommers_hash != block.empty_uncle_hash) + if (curr_block.ommers_hash != blocks.empty_uncle_hash) return error.InvalidOmmersHash; const prev_block_hash = transaction.RLPHash(BlockHeader, allocator, prev_block, null); From b5e1367cac517dad41fe908db9e1b0c418a7aef1 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 10:03:31 -0300 Subject: [PATCH 07/52] types: create type for LogsBloom Signed-off-by: Ignacio Hagopian --- src/types/block.zig | 4 ++-- src/types/types.zig | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/types/block.zig b/src/types/block.zig index 188fafb..70ff4a2 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -8,7 +8,7 @@ const Bytes32 = types.Bytes32; const Address = types.Address; pub const empty_uncle_hash: types.Hash32 = [_]u8{ 29, 204, 77, 232, 222, 199, 93, 122, 171, 133, 181, 103, 182, 204, 212, 26, 211, 18, 69, 27, 148, 138, 116, 19, 240, 161, 66, 253, 64, 212, 147, 71 }; -const bytes_per_logs_bloom = 256; +pub const LogsBloom = [256]u8; pub const BlockHeader = struct { parent_hash: Hash32, @@ -17,7 +17,7 @@ pub const BlockHeader = struct { state_root: Bytes32, transactions_root: Bytes32, receipts_root: Bytes32, - logs_bloom: [bytes_per_logs_bloom]u8, + logs_bloom: LogsBloom, difficulty: u8, block_number: u64, gas_limit: u64, diff --git a/src/types/types.zig b/src/types/types.zig index 5a3d6f5..4c01c54 100644 --- a/src/types/types.zig +++ b/src/types/types.zig @@ -12,9 +12,10 @@ pub const AccountState = @import("account_state.zig"); // Blocks pub const block = @import("block.zig"); +pub const empty_uncle_hash = block.empty_uncle_hash; pub const Block = block.Block; pub const BlockHeader = block.BlockHeader; -pub const empty_uncle_hash = block.empty_uncle_hash; +pub const LogsBloom = block.LogsBloom; pub const Withdrawal = @import("withdrawal.zig"); // Transactions From 1e5000821c210162c1e42264419296542834af4c Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 10:03:44 -0300 Subject: [PATCH 08/52] signer: remove old comments Signed-off-by: Ignacio Hagopian --- src/signer/signer.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/signer/signer.zig b/src/signer/signer.zig index 0c85011..aa408f6 100644 --- a/src/signer/signer.zig +++ b/src/signer/signer.zig @@ -12,8 +12,6 @@ const Address = @import("../types/types.zig").Address; // TODO: TxnSigner should be generalized to: // - Only accept correct transactions types depending on the fork we're in. -// - Handle "v" correctly depending on transaction type. -// For now it's a post London signer, and only support 1559 txns. pub const TxnSigner = struct { chain_id: u64, ecdsa_signer: ecdsa.Signer, From 91ac17c7424a9f5785d01c7206d68f250626619b Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 10:04:02 -0300 Subject: [PATCH 09/52] blockchain: add tx validations Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 57 +++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 1c79e96..c310c18 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -5,7 +5,9 @@ const config = @import("../config/config.zig"); const transaction = @import("../types/transaction.zig"); const vm = @import("../vm/vm.zig"); // TODO: Avoid this import? const rlp = @import("zig-rlp"); +const signer = @import("../signer/signer.zig"); const Allocator = std.mem.Allocator; +const LogsBloom = types.LogsBloom; const Block = types.Block; const BlockHeader = types.BlockHeader; const StateDB = vm.StateDB; @@ -17,6 +19,14 @@ pub const Blockchain = struct { const GAS_LIMIT_ADJUSTMENT_FACTOR = 1024; const GAS_LIMIT_MINIMUM = 5000; + const BlockExecutionResult = struct { + gas_used: u64, + transactions_root: Hash32, + receipts_root: Hash32, + logs_bloom: LogsBloom, + withdrawals_root: Hash32, + }; + allocator: Allocator, chain_id: config.ChainId, flat_db: *StateDB, @@ -44,13 +54,18 @@ pub const Blockchain = struct { if (block.uncles.len != 0) return error.NotEmptyUncles; - var result = try self.execute_block(block); + var arena = std.heap.ArenaAllocator.init(self.allocator); + defer arena.deinit(); + + // Execute block. + var result = try applyBody(arena, self, block); + // Post execution checks. if (result.gas_used != block.header.gas_used) return error.InvalidGasUsed; - if (result.transactions_root != block.header.transactions_root) // TODO: Do before exec. + if (result.transactions_root != block.header.transactions_root) return error.InvalidTransactionsRoot; - if (result.receipts_root != block.header.receipts_root) // TODO: Do before exec. + if (result.receipts_root != block.header.receipts_root) return error.InvalidReceiptsRoot; if (result.state.root() != block.header.state_root) return error.InvalidStateRoot; @@ -112,4 +127,40 @@ pub const Blockchain = struct { if (gas_limit <= parent_gas_limit - max_delta) return error.GasLimitTooLow; return gas_limit >= GAS_LIMIT_MINIMUM; } + + fn applyBody(allocator: Allocator, chain: Blockchain, block: Block) !BlockExecutionResult { + _ = chain; + _ = allocator; + const gas_available = block.header.gas_limit; + _ = gas_available; + for (block.transactions) |tx| { + _ = tx; + } + } + + fn checkTransaction(allocator: Allocator, tx: transaction.Txn, base_fee_per_gas: u64, gas_available: u64, chain_id: u64) !struct { sender_address: types.Address, effective_gas_price: u64 } { + if (tx.getGasLimit() > gas_available) + return error.InsufficientGas; + + const txn_signer = try signer.TxnSigner.init(chain_id); + const sender_address = txn_signer.get_sender(allocator, tx); + + const effective_gas_price = switch (tx) { + .FeeMarketTxn => |fm_tx| blk: { + if (fm_tx.max_fee_per_gas < fm_tx.max_priority_fee_per_gas) + return error.InvalidMaxFeePerGas; + if (fm_tx.max_fee_per_gas < base_fee_per_gas) + return error.MaxFeePerGasLowerThanBaseFee; + + const priority_fee_per_gas = @min(tx.max_priority_fee_per_gas, tx.max_fee_per_gas - base_fee_per_gas); + break :blk priority_fee_per_gas + base_fee_per_gas; + }, + .LegacyTxn, .AccessListTxn => blk: { + if (tx.getGasPrice() < base_fee_per_gas) + return error.GasPriceLowerThanBaseFee; + break :blk tx.getGasPrice(); + }, + }; + return .{ .sender_address = sender_address, .effective_gas_price = effective_gas_price }; + } }; From 74377b1472a7ddb68c7e093fb0db1f00da8b9255 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 12:25:04 -0300 Subject: [PATCH 10/52] blockchain: apply tx body code Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 81 ++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index c310c18..2dd4b47 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -12,6 +12,8 @@ const Block = types.Block; const BlockHeader = types.BlockHeader; const StateDB = vm.StateDB; const Hash32 = types.Hash32; +const Bytes32 = types.Bytes32; +const Address = types.Address; pub const Blockchain = struct { const BASE_FEE_MAX_CHANGE_DENOMINATOR = 8; @@ -19,14 +21,6 @@ pub const Blockchain = struct { const GAS_LIMIT_ADJUSTMENT_FACTOR = 1024; const GAS_LIMIT_MINIMUM = 5000; - const BlockExecutionResult = struct { - gas_used: u64, - transactions_root: Hash32, - receipts_root: Hash32, - logs_bloom: LogsBloom, - withdrawals_root: Hash32, - }; - allocator: Allocator, chain_id: config.ChainId, flat_db: *StateDB, @@ -128,21 +122,63 @@ pub const Blockchain = struct { return gas_limit >= GAS_LIMIT_MINIMUM; } + const BlockExecutionResult = struct { + gas_used: u64, + transactions_root: Hash32, + receipts_root: Hash32, + logs_bloom: LogsBloom, + withdrawals_root: Hash32, + }; + fn applyBody(allocator: Allocator, chain: Blockchain, block: Block) !BlockExecutionResult { - _ = chain; - _ = allocator; - const gas_available = block.header.gas_limit; - _ = gas_available; + var gas_available = block.header.gas_limit; for (block.transactions) |tx| { - _ = tx; + // TODO: add tx to txs tree. + + const txn_info = checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, chain.chain_id); + _ = txn_info; + const env = vm.Environment{ + .caller = txn_info.sender_address, + .origin = txn_info.sender_address, + .block_hashes = chain.last_256_blocks_hashes, + .coinbase = block.header.fee_recipient, + .number = block.header.block_number, + .gas_limit = block.header.gas_limit, + .base_fee_per_gas = block.header.base_fee_per_gas, + .gas_price = txn_info.effective_gas_price, + .time = block.header.timestamp, + .prev_randao = block.header.prev_randao, + .state = state, + .chain_id = chain.chain_id, + }; + + const exec_tx_result = try processTransaction(allocator, env, tx); + gas_available -= exec_tx_result.gas_used; + + // TODO: make receipt and add to receipt tree. + // TODO: do tx logs aggregation. } + + const block_gas_used = block.header.gas_limit - gas_available; + + // TODO: logs bloom calculation. + + // TODO: process withdrawals. + + return .{ + .gas_used = block_gas_used, + .transactions_root = std.mem.zeroes(Hash32), // TODO + .receipts_root = std.mem.zeroes(Hash32), // TODO + .logs_bloom = block.header.logs_bloom, + .withdrawals_root = std.mem.zeroes(Hash32), // TODO + }; } - fn checkTransaction(allocator: Allocator, tx: transaction.Txn, base_fee_per_gas: u64, gas_available: u64, chain_id: u64) !struct { sender_address: types.Address, effective_gas_price: u64 } { + fn checkTransaction(allocator: Allocator, tx: transaction.Txn, base_fee_per_gas: u64, gas_available: u64, chain_id: u64) !struct { sender_address: Address, effective_gas_price: config.ChainId } { if (tx.getGasLimit() > gas_available) return error.InsufficientGas; - const txn_signer = try signer.TxnSigner.init(chain_id); + const txn_signer = try signer.TxnSigner.init(@intFromEnum(chain_id)); const sender_address = txn_signer.get_sender(allocator, tx); const effective_gas_price = switch (tx) { @@ -163,4 +199,19 @@ pub const Blockchain = struct { }; return .{ .sender_address = sender_address, .effective_gas_price = effective_gas_price }; } + + const Environment = struct { + caller: Address, + block_hashes: [256]Hash32, + origin: Address, + coinbase: Address, + number: u64, + base_fee_per_gas: u256, + gas_limit: u64, + gas_price: u64, + time: u256, + prev_randao: Bytes32, + state: *State, + chain_id: config.ChainId, + }; }; From 1b31d5728b3fd008c1fb1d3c03ee3dc5b16af835 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 17:53:09 -0300 Subject: [PATCH 11/52] blockchain: process transaction progress Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 58 +++++++++++++++++++++++++++++++++-- src/main.zig | 7 +++-- src/types/transaction.zig | 2 +- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 2dd4b47..327db51 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -18,8 +18,19 @@ const Address = types.Address; pub const Blockchain = struct { const BASE_FEE_MAX_CHANGE_DENOMINATOR = 8; const ELASTICITY_MULTIPLIER = 2; + const GAS_LIMIT_ADJUSTMENT_FACTOR = 1024; const GAS_LIMIT_MINIMUM = 5000; + const GAS_INIT_CODE_WORD_COST = 2; + + const MAX_CODE_SIZE = 0x6000; + + const TX_BASE_COST = 21000; + const TX_DATA_COST_PER_ZERO = 4; + const TX_DATA_COST_PER_NON_ZERO = 16; + const TX_CREATE_COST = 32000; + const TX_ACCESS_LIST_ADDRESS_COST = 2400; + const TX_ACCESS_LIST_STORAGE_KEY_COST = 1900; allocator: Allocator, chain_id: config.ChainId, @@ -136,7 +147,6 @@ pub const Blockchain = struct { // TODO: add tx to txs tree. const txn_info = checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, chain.chain_id); - _ = txn_info; const env = vm.Environment{ .caller = txn_info.sender_address, .origin = txn_info.sender_address, @@ -148,7 +158,7 @@ pub const Blockchain = struct { .gas_price = txn_info.effective_gas_price, .time = block.header.timestamp, .prev_randao = block.header.prev_randao, - .state = state, + .state = std.mem.zeroes(State), // TODO ########## .chain_id = chain.chain_id, }; @@ -200,6 +210,8 @@ pub const Blockchain = struct { return .{ .sender_address = sender_address, .effective_gas_price = effective_gas_price }; } + const State = struct {}; + const Environment = struct { caller: Address, block_hashes: [256]Hash32, @@ -214,4 +226,46 @@ pub const Blockchain = struct { state: *State, chain_id: config.ChainId, }; + + fn processTransaction(env: Environment, tx: transaction.Txn) !struct { gas_left: u64 } { + _ = tx; + _ = env; + } + + fn validateTransaction(tx: transaction.Txn) bool { + if (calculateIntrinsicCost(tx) > tx.getGasLimit()) + return false; + if (tx.getNonce() >= (2 << 64) - 1) + return false; + if (tx.getTo() == null and tx.data.len > 2 * MAX_CODE_SIZE) + return false; + return true; + } + + fn calculateIntrinsicCost(tx: transaction.Txn) u64 { + var data_cost: u64 = 0; + for (tx.data) |byte| { + data_cost += if (byte == 0) TX_DATA_COST_PER_ZERO else TX_DATA_COST_PER_NON_ZERO; + } + + const create_cost = if (tx.to == null) TX_CREATE_COST + initCodeCost(tx.data.len) else 0; + + const access_list_cost = switch (tx) { + .LegacyTxn => 0, + inline else => |al_tx| blk: { + var sum: u64 = 0; + for (al_tx.access_list) |al| { + data_cost += TX_ACCESS_LIST_ADDRESS_COST; + data_cost += al.storage_keys.len * TX_ACCESS_LIST_STORAGE_KEY_COST; + } + break :blk sum; + }, + }; + + return TX_BASE_COST + data_cost + create_cost + access_list_cost; + } + + fn initCodeCost(code_length: usize) u64 { + return GAS_INIT_CODE_WORD_COST * @ceil(code_length / 32); + } }; diff --git a/src/main.zig b/src/main.zig index d7121dc..fa813d0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -33,7 +33,7 @@ pub fn main() !void { const txn_signer = try TxnSigner.init(@intFromEnum(config.ChainId.Mainnet)); // Create block. - const block = Block{ + const block: Block = .{ .header = .{ .parent_hash = [_]u8{0} ** 32, .uncle_hash = [_]u8{0} ** 32, @@ -48,13 +48,16 @@ pub fn main() !void { .gas_used = 0, .timestamp = 0, .extra_data = &[_]u8{}, - .mix_hash = 0, .nonce = [_]u8{0} ** 8, .base_fee_per_gas = 10, .withdrawals_root = null, .blob_gas_used = null, .excess_blob_gas = null, + .difficulty = 0, }, + .transactions = &[_]Txn{}, + .uncles = &[_]types.BlockHeader{}, + .withdrawals = &[_]types.Withdrawal{}, }; // Create some dummy transaction. diff --git a/src/types/transaction.zig b/src/types/transaction.zig index f870794..ec0ba90 100644 --- a/src/types/transaction.zig +++ b/src/types/transaction.zig @@ -170,7 +170,7 @@ pub const LegacyTxn = struct { pub const AccessListTuple = struct { address: Address, - StorageKeys: []Hash32, + storage_keys: []Hash32, }; pub const AccessListTxn = struct { From 3b9bdd0aee82e4247448b9d6e88902db7421ca55 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 2 Jan 2024 20:10:13 -0300 Subject: [PATCH 12/52] blockchain: process transaction more progress Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 40 +++++++++++++++++++-------- src/exec-spec-tests/execspectests.zig | 2 +- src/vm/statedb.zig | 29 +++++++------------ src/vm/vm.zig | 12 ++++---- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 327db51..f1ddf25 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -34,21 +34,21 @@ pub const Blockchain = struct { allocator: Allocator, chain_id: config.ChainId, - flat_db: *StateDB, + state: *StateDB, last_256_blocks_hashes: [256]Hash32, previous_block: Block, pub fn init( allocator: Allocator, chain_id: config.ChainId, - flat_db: *StateDB, + state: *StateDB, prev_block_header: BlockHeader, last_256_blocks_hashes: [256]Hash32, ) void { return Blockchain{ .allocator = allocator, .chain_id = chain_id, - .flat_db = flat_db, + .state = state, .prev_block_header = prev_block_header, .last_256_blocks_hashes = last_256_blocks_hashes, }; @@ -63,7 +63,7 @@ pub const Blockchain = struct { defer arena.deinit(); // Execute block. - var result = try applyBody(arena, self, block); + var result = try applyBody(arena, self, block, self.state); // Post execution checks. if (result.gas_used != block.header.gas_used) @@ -141,7 +141,7 @@ pub const Blockchain = struct { withdrawals_root: Hash32, }; - fn applyBody(allocator: Allocator, chain: Blockchain, block: Block) !BlockExecutionResult { + fn applyBody(allocator: Allocator, chain: Blockchain, state: StateDB, block: Block) !BlockExecutionResult { var gas_available = block.header.gas_limit; for (block.transactions) |tx| { // TODO: add tx to txs tree. @@ -158,7 +158,7 @@ pub const Blockchain = struct { .gas_price = txn_info.effective_gas_price, .time = block.header.timestamp, .prev_randao = block.header.prev_randao, - .state = std.mem.zeroes(State), // TODO ########## + .state = state, .chain_id = chain.chain_id, }; @@ -210,8 +210,6 @@ pub const Blockchain = struct { return .{ .sender_address = sender_address, .effective_gas_price = effective_gas_price }; } - const State = struct {}; - const Environment = struct { caller: Address, block_hashes: [256]Hash32, @@ -223,13 +221,33 @@ pub const Blockchain = struct { gas_price: u64, time: u256, prev_randao: Bytes32, - state: *State, + state: *StateDB, chain_id: config.ChainId, }; fn processTransaction(env: Environment, tx: transaction.Txn) !struct { gas_left: u64 } { - _ = tx; - _ = env; + if (!validateTransaction(tx)) + return error.InvalidTransaction; + + const sender = env.origin; + const sender_account = try env.state.getAccount(sender); + + const gas_fee = tx.getGasLimit() * tx.getGasPrice(); + + if (sender_account.nonce != tx.nonce) + return error.InvalidTxnNonce; + if (sender_account.balance < gas_fee + tx.value) + return error.NotEnoughBalance; + if (sender_account.code != null) + return error.SenderIsNotEOA; + + const effective_gas_fee = tx.getGasLimit() * env.gas_price; + const gas = tx.getGasLimit() - calculateIntrinsicCost(tx); + env.state.incrementNonce(sender); + _ = gas; + + const sender_balance_after_gas_fee = sender_account.balance - effective_gas_fee; + env.state.setBalance(sender, sender_balance_after_gas_fee); } fn validateTransaction(tx: transaction.Txn) bool { diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index f1f7240..1f52951 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -94,7 +94,7 @@ pub const FixtureTest = struct { while (it.next()) |entry| { var exp_account_state: AccountState = try entry.value_ptr.*.to_vm_accountstate(allocator, entry.key_ptr.*); std.debug.print("checking account state: {s}\n", .{std.fmt.fmtSliceHexLower(&exp_account_state.addr)}); - const got_account_state = try db.get(exp_account_state.addr); + const got_account_state = try db.getAccount(exp_account_state.addr); if (!std.mem.eql(u8, &got_account_state.addr, &exp_account_state.addr)) { return error.post_state_addr_mismatch; } diff --git a/src/vm/statedb.zig b/src/vm/statedb.zig index 3f1528d..ad50211 100644 --- a/src/vm/statedb.zig +++ b/src/vm/statedb.zig @@ -26,7 +26,7 @@ pub fn init(allocator: Allocator, accounts_state: []AccountState) !StateDB { }; } -pub fn get(self: *StateDB, addr: Address) !*AccountState { +pub fn getAccount(self: *StateDB, addr: Address) !*AccountState { var res = try self.db.getOrPut(addr); if (res.found_existing) { return res.value_ptr; @@ -36,31 +36,22 @@ pub fn get(self: *StateDB, addr: Address) !*AccountState { return res.value_ptr; } -pub fn set_storage(self: *StateDB, addr: Address, key: u256, value: u256) !void { - var account = try self.get(addr); +pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: u256) !void { + var account = try self.getAccount(addr); try account.storage.put(key, value); } -pub fn add_balance(self: *StateDB, addr: Address, amount: u256) !void { - var account = try self.get(addr); - account.balance += amount; -} - -pub fn sub_balance(self: *StateDB, addr: Address, amount: u256) !void { - var account = try self.get(addr); - account.balance -= amount; +pub fn setBalance(self: *StateDB, addr: Address, balance: u256) !void { + var account = try self.getAccount(addr); + account.balance = balance; } -pub fn set_nonce(self: *StateDB, addr: Address, nonce: u256) !void { - var account = try self.get(addr); - - if (nonce != account.nonce + 1) { - return error.InvalidNonce; - } - account.nonce = nonce; +pub fn incrementNonce(self: *StateDB, addr: Address) !void { + var account = try self.getAccount(addr); + account.nonce += 1; } -pub fn get_code(self: *StateDB, addr: Address) ![]const u8 { +pub fn getCode(self: *StateDB, addr: Address) ![]const u8 { var account = try self.get(addr); return account.code; } diff --git a/src/vm/vm.zig b/src/vm/vm.zig index 67d1138..232f8b6 100644 --- a/src/vm/vm.zig +++ b/src/vm/vm.zig @@ -136,7 +136,7 @@ pub const VM = struct { if (txn.getNonce() +% 1 < txn.getNonce()) { return error.MaxNonce; } - try self.statedb.*.set_nonce(from_addr, txn.getNonce() + 1); + try self.statedb.incrementNonce(from_addr); // Charge intrinsic gas costs. // TODO(jsign): this is incomplete. @@ -147,7 +147,7 @@ pub const VM = struct { // Send transaction value to the recipient. if (txn.getValue() > 0) { // TODO(jsign): incomplete - try self.statedb.add_balance(to_addr, txn.getValue()); + try self.statedb.setBalance(to_addr, txn.getValue()); // TODO: TEMP wrong } self.context.?.execution = ExecutionContext{ @@ -187,11 +187,11 @@ pub const VM = struct { // Coinbase rewards. const gas_tip = 0xa - 0x7; // TODO(jsign): fix, pull from tx_context. const coinbase_fee = gas_used * gas_tip; - try self.statedb.add_balance(self.context.?.block.coinbase, @as(u256, @intCast(coinbase_fee))); + try self.statedb.setBalance(self.context.?.block.coinbase, @as(u256, @intCast(coinbase_fee))); // TODO TEMP WRONG // Sender fees. const sender_fee = gas_used * 0xa; - try self.statedb.sub_balance(from_addr, @as(u256, @intCast(sender_fee))); + try self.statedb.setBalance(from_addr, @as(u256, @intCast(sender_fee))); // TODO TEMP WRONG } inline fn charge_gas(remaining_gas: *i64, charge: u64) !void { @@ -258,7 +258,7 @@ pub const VM = struct { const skey = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); const svalue = std.mem.readIntSlice(u256, &value.*.bytes, std.builtin.Endian.Big); - vm.statedb.set_storage(addr.*.bytes, skey, svalue) catch unreachable; // TODO(jsign): manage catch. + vm.statedb.setStorage(addr.*.bytes, skey, svalue) catch unreachable; // TODO(jsign): manage catch. return evmc.EVMC_STORAGE_ADDED; // TODO(jsign): fix } @@ -375,7 +375,7 @@ pub const VM = struct { ); // TODO(jsign): explore creating custom formatter? // Check if the target address is a contract, and do the appropiate call. - const recipient_account = vm.statedb.get(util.from_evmc_address(msg.*.code_address)) catch unreachable; // TODO(jsign): fix this. + const recipient_account = vm.statedb.getAccount(util.from_evmc_address(msg.*.code_address)) catch unreachable; // TODO(jsign): fix this. if (recipient_account.code.len != 0) { log.debug("contract call, codelen={d}", .{recipient_account.code.len}); // Persist the current context. We'll restore it after the call returns. From 301a00cff53650d42c2f0a5b8a3b6a8898cb9749 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 3 Jan 2024 21:29:14 -0300 Subject: [PATCH 13/52] blockchain: more impl progress Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 129 +++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 1 deletion(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index f1ddf25..e799a11 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -225,7 +225,11 @@ pub const Blockchain = struct { chain_id: config.ChainId, }; - fn processTransaction(env: Environment, tx: transaction.Txn) !struct { gas_left: u64 } { + const AddressSet = std.HashMap(Address, void); + const AddressKeyTuple = struct { address: Address, key: Bytes32 }; + const AddressKeySet = std.HashMap(AddressKeyTuple, void); + + fn processTransaction(allocator: Allocator, env: Environment, tx: transaction.Txn) !struct { gas_left: u64 } { if (!validateTransaction(tx)) return error.InvalidTransaction; @@ -248,6 +252,67 @@ pub const Blockchain = struct { const sender_balance_after_gas_fee = sender_account.balance - effective_gas_fee; env.state.setBalance(sender, sender_balance_after_gas_fee); + + var preaccessed_addresses = AddressSet.init(allocator); + defer preaccessed_addresses.deinit(); + var preaccessed_stoarge_keys = AddressKeySet.init(allocator); + defer preaccessed_stoarge_keys.deinit(); + preaccessed_addresses.put(env.coinbase, null); + switch (tx) { + .LegacyTxn => {}, + inline else => { + for (tx.access_list) |al| { + preaccessed_addresses.put(al.address, null); + for (al.storage_keys) |key| { + preaccessed_stoarge_keys.put(.{ .address = al.address, .key = key }, null); + } + } + }, + } + + const message = prepareMessage( + sender, + tx.getTo(), + tx.getValue(), + tx.getData(), + tx.getGasLimit(), + env, + preaccessed_addresses, + preaccessed_stoarge_keys, + ); + const output = processMessageCall(message, env); + + const gas_used = tx.getGasLimit() - output.gas_left; + const gas_refund = @min(gas_used / 5, output.refund_counter); + const gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price; + + const priority_fee_per_gas = env.gas_price - env.base_fee_per_gas; + const transaction_fee = (tx.getGasLimit() - output.gas_left) * priority_fee_per_gas; + const total_gas_used = gas_used - gas_refund; + + const sender_balance_after_refund = sender_account.balance + gas_refund_amount; + env.state.setBalance(sender, sender_balance_after_refund); + + const coinbase_account = try env.state.getAccount(env.coinbase); + const coinbase_balance_after_mining_fee = coinbase_account.balance + transaction_fee; + + if (coinbase_balance_after_mining_fee != 0) { + env.state.setBalance(env.coinbase, coinbase_balance_after_mining_fee); + } else if (env.state.accountExistsAndIsEmpty(env.coinbase)) { + env.state.destroyAccount(env.coinbase); + } + + for (output.accounts_to_delete) |account| { + env.state.destroyAccount(account); + } + + for (output.touched_accounts) |address| { + if (env.state.accountExistsAndIsEmpty(address)) { + env.state.destroyAccount(address); + } + } + + return .{ total_gas_used, output.logs, output.err }; } fn validateTransaction(tx: transaction.Txn) bool { @@ -286,4 +351,66 @@ pub const Blockchain = struct { fn initCodeCost(code_length: usize) u64 { return GAS_INIT_CODE_WORD_COST * @ceil(code_length / 32); } + + const Message = struct { + caller: Address, + target: ?Address, + current_target: Address, + gas: u64, + value: u256, + data: []const u8, + code_address: ?Address, + code: []const u8, + depth: u64, // TODO: narrow it down. + accessed_addresses: AddressSet, + accessed_storage_keys: AddressKeySet, + + pub fn deinit(self: *Message) void { + self.accessed_addresses.deinit(); + self.accessed_addresses = undefined; + self.accessed_storage_keys.deinit(); + self.accessed_storage_keys = undefined; + } + }; + + // prepareMessage prepares an EVM message. + // The caller must call deinit() on the returned Message. + fn prepareMessage(caller: Address, target: ?Address, value: u256, data: []const u8, gas: u64, env: Environment, code_address: ?Address, preprocessed_addresses: AddressSet, preprocessed_storage_keys: AddressKeySet) !Message { + const target_msg_code = if (target == null) + .{ try computeContractAddress(caller, env.state.getAccount(caller).nonce - 1), [_]u8{0}, data } + else + .{ target, data, env.state.getAccount(target).code }; + const current_target = target_msg_code[0]; + const msg_data = target_msg_code[1]; + const code = target_msg_code[2]; + + var accessed_addresses = try preprocessed_addresses.clone(); + accessed_addresses.put(current_target); + accessed_addresses.put(caller); + + return .{ + .caller = caller, + .target = target, + .current_target = current_target, + .gas = gas, + .value = value, + .data = data, + .code_address = code_address, + .code = code, + .depth = 0, + .accessed_addresses = accessed_addresses, + .accessed_storage_keys = preaccessed_storage_keys, + }; + } + + fn computeContractAddress(allocator: Allocator, address: Address, nonce: u64) !Address { + var out = std.ArrayList(u8).init(allocator); + defer out.deinit(); + try rlp.serialize(struct { addr: Address, nonce: u64 }, allocator, .{ address, nonce }, out); + const computed_address = std.crypto.hash.sha3.Keccak256(out.items); + const canonical_address = computed_address[12..]; + var padded_address: Address = std.mem.zeroes(Address); + @memcpy(padded_address[12..], canonical_address); + return padded_address; + } }; From f4dd7b00effcea112d102b39b7f0947e42e060b4 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 4 Jan 2024 21:14:25 -0300 Subject: [PATCH 14/52] blockchain: almost integrating EVMOne to new blockchain abstraction Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 98 ++++++++--- src/blockchain/vm.zig | 297 ++++++++++++++++++++++++++++++++++ src/common/hexutils.zig | 1 + src/vm/{vm.zig => foofoo.zig} | 43 +---- 4 files changed, 376 insertions(+), 63 deletions(-) create mode 100644 src/blockchain/vm.zig rename src/vm/{vm.zig => foofoo.zig} (89%) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index e799a11..cd82fcb 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -3,7 +3,7 @@ const types = @import("../types/types.zig"); const blocks = @import("../types/block.zig"); const config = @import("../config/config.zig"); const transaction = @import("../types/transaction.zig"); -const vm = @import("../vm/vm.zig"); // TODO: Avoid this import? +const vm = @import("vm.zig"); const rlp = @import("zig-rlp"); const signer = @import("../signer/signer.zig"); const Allocator = std.mem.Allocator; @@ -14,6 +14,7 @@ const StateDB = vm.StateDB; const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; const Address = types.Address; +const VM = vm.VM; pub const Blockchain = struct { const BASE_FEE_MAX_CHANGE_DENOMINATOR = 8; @@ -32,6 +33,20 @@ pub const Blockchain = struct { const TX_ACCESS_LIST_ADDRESS_COST = 2400; const TX_ACCESS_LIST_STORAGE_KEY_COST = 1900; + const PRE_COMPILED_CONTRACT_ADDRESSES = [_]Address{ + // TODO: see if it's worth importing some .h file from EVMOne + // an instantiate this at comptime to avoid maintaining this list. + [_]u8{0} ** 19 ++ [_]u8{1}, // ECRECOVER + [_]u8{0} ** 19 ++ [_]u8{2}, // SHA256 + [_]u8{0} ** 19 ++ [_]u8{3}, // RIPEMD160 + [_]u8{0} ** 19 ++ [_]u8{4}, // IDENTITY_ADDRESS + [_]u8{0} ** 19 ++ [_]u8{5}, // MODEXP_ADDRESS + [_]u8{0} ** 19 ++ [_]u8{6}, // ALT_BN128_ADD + [_]u8{0} ** 19 ++ [_]u8{7}, // ALT_BN128_MUL + [_]u8{0} ** 19 ++ [_]u8{8}, // ALT_BN128_PAIRING_CHECK + [_]u8{0} ** 19 ++ [_]u8{9}, // BLAKE2F + }; + allocator: Allocator, chain_id: config.ChainId, state: *StateDB, @@ -248,7 +263,6 @@ pub const Blockchain = struct { const effective_gas_fee = tx.getGasLimit() * env.gas_price; const gas = tx.getGasLimit() - calculateIntrinsicCost(tx); env.state.incrementNonce(sender); - _ = gas; const sender_balance_after_gas_fee = sender_account.balance - effective_gas_fee; env.state.setBalance(sender, sender_balance_after_gas_fee); @@ -275,7 +289,7 @@ pub const Blockchain = struct { tx.getTo(), tx.getValue(), tx.getData(), - tx.getGasLimit(), + gas, env, preaccessed_addresses, preaccessed_stoarge_keys, @@ -302,9 +316,7 @@ pub const Blockchain = struct { env.state.destroyAccount(env.coinbase); } - for (output.accounts_to_delete) |account| { - env.state.destroyAccount(account); - } + // Account destruction is already managed by EVMC `selfdestruct(...)` callback. for (output.touched_accounts) |address| { if (env.state.accountExistsAndIsEmpty(address)) { @@ -352,7 +364,7 @@ pub const Blockchain = struct { return GAS_INIT_CODE_WORD_COST * @ceil(code_length / 32); } - const Message = struct { + pub const Message = struct { caller: Address, target: ?Address, current_target: Address, @@ -375,18 +387,39 @@ pub const Blockchain = struct { // prepareMessage prepares an EVM message. // The caller must call deinit() on the returned Message. - fn prepareMessage(caller: Address, target: ?Address, value: u256, data: []const u8, gas: u64, env: Environment, code_address: ?Address, preprocessed_addresses: AddressSet, preprocessed_storage_keys: AddressKeySet) !Message { - const target_msg_code = if (target == null) - .{ try computeContractAddress(caller, env.state.getAccount(caller).nonce - 1), [_]u8{0}, data } - else - .{ target, data, env.state.getAccount(target).code }; - const current_target = target_msg_code[0]; - const msg_data = target_msg_code[1]; - const code = target_msg_code[2]; - - var accessed_addresses = try preprocessed_addresses.clone(); - accessed_addresses.put(current_target); - accessed_addresses.put(caller); + fn prepareMessage( + caller: Address, + target: ?Address, + value: u256, + data: []const u8, + gas: u64, + env: Environment, + code_address: ?Address, + preaccessed_addresses: AddressSet, + preaccessed_storage_keys: AddressKeySet, + ) !Message { + var current_target: Address = undefined; + var msg_data: []const u8 = undefined; + var code: []const u8 = undefined; + + if (target == null) { + current_target = try computeContractAddress(caller, env.state.getAccount(caller).nonce - 1); + msg_data = &[_]u8{0}; + code = data; + } else { + current_target = target; + msg_data = data; + code = env.state.getAccount(target).code; + if (code_address == null) + code_address = target; + } + + var accessed_addresses = try preaccessed_addresses.clone(); + try accessed_addresses.put(current_target); + try accessed_addresses.put(caller); + for (PRE_COMPILED_CONTRACT_ADDRESSES) |address| { + try accessed_addresses.put(address); + } return .{ .caller = caller, @@ -394,12 +427,12 @@ pub const Blockchain = struct { .current_target = current_target, .gas = gas, .value = value, - .data = data, + .data = msg_data, .code_address = code_address, .code = code, .depth = 0, .accessed_addresses = accessed_addresses, - .accessed_storage_keys = preaccessed_storage_keys, + .accessed_storage_keys = try preaccessed_storage_keys.clone(), }; } @@ -413,4 +446,27 @@ pub const Blockchain = struct { @memcpy(padded_address[12..], canonical_address); return padded_address; } + + const MessageCallOutput = struct { + gas_left: u64, + refund_counter: u256, + // logs: Union[Tuple[()], Tuple[Log, ...]] TODO + // accounts_to_delete: AddressKeySet, // TODO (delete?) + // touched_accounts: AddressKeySet, // TODO (delete?) + // error: Optional[Exception] TODO + }; + + fn processMessageCall(message: Message, env: Environment) !MessageCallOutput { + const vm_instance = VM.init(env); + defer vm_instance.deinit(); + + const result = try vm_instance.processMessageCall(message); + defer result.release(); + return .{ + .gas_left = result.gas_left, + .refund_counter = result.gas_refund, + // .accounts_to_delete = AddressKeySet, + // .touched_accounts = vm_instance.touched_accounts, + }; + } }; diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig new file mode 100644 index 0000000..e63bd4e --- /dev/null +++ b/src/blockchain/vm.zig @@ -0,0 +1,297 @@ +const evmc = @cImport({ + @cInclude("evmone.h"); +}); +const std = @import("std"); +const Allocator = std.mem.Allocator; +const util = @import("util.zig"); +const types = @import("../types/types.zig"); +const blockchain = @import("blockchain.zig").Blockchain; // TODO: unnest +const Environment = blockchain.Environment; +const Message = blockchain.Message; +const Txn = types.Txn; +const TxnSigner = @import("../signer/signer.zig").TxnSigner; +const Block = types.Block; +const AccountState = types.AccountState; +const Bytecode = types.Bytecode; +const Address = types.Address; +const assert = std.debug.assert; +const log = std.log.scoped(.vm); +const StateDB = @import("../vm/statedb.zig"); + +// TODO: Rename to instance? +pub const VM = struct { + env: Environment, + evm: [*c]evmc.evmc_vm, + host: evmc.struct_evmc_host_interface, + + pub fn init(env: Environment) VM { + var evm = evmc.evmc_create_evmone(); + log.info("evmone info: name={s}, version={s}, abi_version={d}", .{ evm.*.name, evm.*.version, evm.*.abi_version }); + return .{ + .env = env, + .evm = evm, + .host = evmc.struct_evmc_host_interface{ + .account_exists = account_exists, + .get_storage = get_storage, + .set_storage = set_storage, + .get_balance = get_balance, + .get_code_size = get_code_size, + .get_code_hash = get_code_hash, + .copy_code = copy_code, + .selfdestruct = self_destruct, + .call = call, + .get_tx_context = get_tx_context, + .get_block_hash = get_block_hash, + .emit_log = emit_log, + .access_account = access_account, + .access_storage = access_storage, + }, + }; + } + + pub fn deinit() void { + // TODO(jsign): check freeing evmone instance. + } + + pub fn processMessageCall(self: *VM, msg: Message) !evmc.struct_evmc_result { + const kind = if (msg.target) evmc.EVMC_CALL orelse evmc.EVMC_CREATE; + const evmc_message = evmc.struct_evmc_message{ + .kind = kind, + .flags = 0, // TODO: STATIC? + .depth = 0, + .gas = @intCast(msg.gas), // TODO(jsign): why evmc expects a i64 for gas instead of u64? + .recipient = util.to_evmc_address(msg.current_target), + .sender = util.to_evmc_address(msg.caller), + .input_data = msg.data.ptr, + .input_size = msg.data.len, + .value = blk: { + var txn_value: [32]u8 = undefined; + std.mem.writeIntSliceBig(u256, &txn_value, msg.value); + break :blk .{ .bytes = txn_value }; + }, + .create2_salt = undefined, // EVMC docs: field only mandatory for CREATE2 kind. + .code_address = undefined, // EVMC docs: field not mandatory for depth 0 calls. + }; + const result = call(@ptrCast(self), @ptrCast(&evmc_message)); + log.debug("execution result: status_code={}, gas_left={}", .{ result.status_code, result.gas_left }); + return result; + } + + // ### EVMC Host Interface ### + + fn get_tx_context(ctx: ?*evmc.struct_evmc_host_context) callconv(.C) evmc.struct_evmc_tx_context { + log.debug("get_tx_context()", .{}); + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + return evmc.struct_evmc_tx_context{ + .tx_gas_price = util.to_evmc_bytes32(vm.env.?.txn.gas_price), + .tx_origin = util.to_evmc_address(vm.env.?.txn.from), + .block_coinbase = util.to_evmc_address(vm.env.?.block.coinbase), + .block_number = @intCast(vm.env.?.block.number), + .block_timestamp = @intCast(vm.env.?.block.timestamp), + .block_gas_limit = @intCast(vm.env.?.block.gas_limit), + .block_prev_randao = util.to_evmc_bytes32(vm.env.?.block.prev_randao), + .chain_id = util.to_evmc_bytes32(vm.env.?.txn.chain_id), + .block_base_fee = util.to_evmc_bytes32(vm.env.?.block.base_fee), + }; + } + + fn get_block_hash( + ctx: ?*evmc.struct_evmc_host_context, + xx: i64, + ) callconv(.C) evmc.evmc_bytes32 { + _ = xx; + _ = ctx; + @panic("TODO"); + } + + fn account_exists( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + ) callconv(.C) bool { + _ = addr; + _ = ctx; + @panic("TODO"); + } + + fn get_storage( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + dest: [*c]const evmc.evmc_bytes32, + ) callconv(.C) evmc.evmc_bytes32 { + _ = ctx; + _ = dest; + _ = addr; + @panic("TODO"); + } + fn set_storage( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + key: [*c]const evmc.evmc_bytes32, + value: [*c]const evmc.evmc_bytes32, + ) callconv(.C) evmc.enum_evmc_storage_status { + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + + const skey = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); + const svalue = std.mem.readIntSlice(u256, &value.*.bytes, std.builtin.Endian.Big); + + vm.statedb.setStorage(addr.*.bytes, skey, svalue) catch unreachable; // TODO(jsign): manage catch. + + return evmc.EVMC_STORAGE_ADDED; // TODO(jsign): fix + } + + fn get_balance( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + ) callconv(.C) evmc.evmc_uint256be { + _ = ctx; + const addr_hex = std.fmt.bytesToHex(addr.*.bytes, std.fmt.Case.lower); + log.debug("evmc call -> getBalance(0x{s})", .{addr_hex}); + + var beval: [32]u8 = undefined; + std.mem.writeIntSliceBig(u256, &beval, 142); + + return evmc.evmc_uint256be{ + .bytes = beval, + }; + } + + fn get_code_size( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + ) callconv(.C) usize { + _ = addr; + _ = ctx; + @panic("TODO"); + } + + fn get_code_hash( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + ) callconv(.C) evmc.evmc_bytes32 { + _ = addr; + _ = ctx; + @panic("TODO"); + } + + fn copy_code( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + xxx: usize, + xxy: [*c]u8, + xxz: usize, + ) callconv(.C) usize { + _ = xxz; + _ = xxy; + _ = xxx; + _ = addr; + _ = ctx; + @panic("TODO"); + } + + fn self_destruct( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + addr2: [*c]const evmc.evmc_address, + ) callconv(.C) bool { + _ = addr2; + _ = addr; + _ = ctx; + @panic("TODO"); + } + + fn emit_log( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + xxx: [*c]const u8, + xxy: usize, + xxz: [*c]const evmc.evmc_bytes32, + xxxzz: usize, + ) callconv(.C) void { + _ = xxxzz; + _ = xxz; + _ = xxy; + _ = xxx; + _ = addr; + _ = ctx; + @panic("TODO"); + } + + fn access_account( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + ) callconv(.C) evmc.enum_evmc_access_status { + log.debug("access_account(addr={})", .{std.fmt.fmtSliceHexLower(&addr.*.bytes)}); + _ = ctx; + return evmc.EVMC_ACCESS_COLD; + } + + fn access_storage( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + value: [*c]const evmc.evmc_bytes32, + ) callconv(.C) evmc.enum_evmc_access_status { + _ = value; + _ = addr; + _ = ctx; + return evmc.EVMC_ACCESS_COLD; // TODO(jsign): fix + } + + fn call( + ctx: ?*evmc.struct_evmc_host_context, + msg: [*c]const evmc.struct_evmc_message, + ) callconv(.C) evmc.struct_evmc_result { + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + log.debug( + "call depth={d} sender={} recipient={}", + .{ + msg.*.depth, + std.fmt.fmtSliceHexLower(&msg.*.sender.bytes), + std.fmt.fmtSliceHexLower(&msg.*.recipient.bytes), + }, + ); // TODO(jsign): explore creating custom formatter? + + // Check if the target address is a contract, and do the appropiate call. + const recipient_account = vm.statedb.getAccount(util.from_evmc_address(msg.*.code_address)) catch unreachable; // TODO(jsign): fix this. + if (recipient_account.code.len != 0) { + log.debug("contract call, codelen={d}", .{recipient_account.code.len}); + // Persist the current context. We'll restore it after the call returns. + const prev_exec_context = vm.*.env.?.env; + + // Create the new context to be used to do the call. + vm.env.?.env = ExecutionContext{ .storage_address = util.from_evmc_address(msg.*.recipient) }; + + // TODO(jsign): EVMC_SHANGHAI should be configurable at runtime. + var result = vm.evm.*.execute.?( + vm.evm, + @ptrCast(&vm.host), + @ptrCast(vm), + evmc.EVMC_SHANGHAI, + msg, + recipient_account.code.ptr, + recipient_account.code.len, + ); + log.debug( + "internal call exec result: status_code={}, gas_left={}", + .{ result.status_code, result.gas_left }, + ); + + // Restore previous context after call() returned. + vm.env.?.env = prev_exec_context; + + return result; + } + + log.debug("non-contract call", .{}); + // TODO(jsign): verify. + return evmc.evmc_result{ + .status_code = evmc.EVMC_SUCCESS, + .gas_left = msg.*.gas, // TODO: fix + .gas_refund = 0, + .output_data = null, + .output_size = 0, + .release = null, + .create_address = std.mem.zeroes(evmc.evmc_address), + .padding = [_]u8{0} ** 4, + }; + } +}; diff --git a/src/common/hexutils.zig b/src/common/hexutils.zig index 0dda044..a13997d 100644 --- a/src/common/hexutils.zig +++ b/src/common/hexutils.zig @@ -1,6 +1,7 @@ const std = @import("std"); const fmt = std.fmt; const Allocator = std.mem.Allocator; +const types = @import("types.zig"); // This function turns an optionally '0x'-prefixed hex string // to a types.Hash32 diff --git a/src/vm/vm.zig b/src/vm/foofoo.zig similarity index 89% rename from src/vm/vm.zig rename to src/vm/foofoo.zig index 232f8b6..fa4365c 100644 --- a/src/vm/vm.zig +++ b/src/vm/foofoo.zig @@ -86,48 +86,7 @@ pub const VM = struct { // TODO(jsign): check freeing evmone instance. } - pub fn run_block(self: *VM, allocator: Allocator, txn_signer: TxnSigner, block: Block, txns: []const Txn) !void { - log.debug("run block number={d}", .{block.header.block_number}); - // TODO: stashing area. - - // Set the block context. - self.context = .{ - .execution = std.mem.zeroes(ExecutionContext), - .txn = std.mem.zeroes(TxnContext), - .block = gen_block_context(block), - }; - - for (txns) |txn| { - // Set the transaction context. - self.context.?.txn = try gen_txn_context(allocator, txn_signer, txn); - - try self.run_txn(txn); - } - - // Clean up the VM context. - self.context = null; - } - - fn gen_block_context(block: Block) BlockContext { - return BlockContext{ - .coinbase = block.header.fee_recipient, - .number = block.header.block_number, - .timestamp = block.header.timestamp, - .gas_limit = block.header.gas_limit, - .prev_randao = std.mem.readIntSlice(u256, &block.header.prev_randao, std.builtin.Endian.Big), - .base_fee = block.header.base_fee_per_gas, - }; - } - - fn gen_txn_context(allocator: Allocator, txn_signer: TxnSigner, txn: Txn) !TxnContext { - return TxnContext{ - .chain_id = txn.getChainId(), - .gas_price = txn.getGasPrice(), - .from = try txn_signer.get_sender(allocator, txn), - }; - } - - fn run_txn(self: *VM, txn: Txn) !void { + fn processMessage(self: *VM, msg: Message) !void { const from_addr = self.*.context.?.txn.from; var remaining_gas: i64 = @intCast(txn.getGasLimit()); From f17cc7413400956cbe3ce7d46dc15014ab0448a2 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 09:09:13 -0300 Subject: [PATCH 15/52] blockchain/vm: cleanup Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index e63bd4e..54b8fd4 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -17,6 +17,7 @@ const Address = types.Address; const assert = std.debug.assert; const log = std.log.scoped(.vm); const StateDB = @import("../vm/statedb.zig"); +const fmtSliceHexLower = std.fmt.fmtSliceHexLower; // TODO: Rename to instance? pub const VM = struct { @@ -49,8 +50,9 @@ pub const VM = struct { }; } - pub fn deinit() void { - // TODO(jsign): check freeing evmone instance. + pub fn deinit(self: *VM) void { + self.evm.destroy(); + self.evm = undefined; } pub fn processMessageCall(self: *VM, msg: Message) !evmc.struct_evmc_result { @@ -220,7 +222,7 @@ pub const VM = struct { ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address, ) callconv(.C) evmc.enum_evmc_access_status { - log.debug("access_account(addr={})", .{std.fmt.fmtSliceHexLower(&addr.*.bytes)}); + log.debug("access_account(addr={})", .{fmtSliceHexLower(&addr.*.bytes)}); _ = ctx; return evmc.EVMC_ACCESS_COLD; } @@ -236,19 +238,9 @@ pub const VM = struct { return evmc.EVMC_ACCESS_COLD; // TODO(jsign): fix } - fn call( - ctx: ?*evmc.struct_evmc_host_context, - msg: [*c]const evmc.struct_evmc_message, - ) callconv(.C) evmc.struct_evmc_result { + fn call(ctx: ?*evmc.struct_evmc_host_context, msg: [*c]const evmc.struct_evmc_message) callconv(.C) evmc.struct_evmc_result { const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - log.debug( - "call depth={d} sender={} recipient={}", - .{ - msg.*.depth, - std.fmt.fmtSliceHexLower(&msg.*.sender.bytes), - std.fmt.fmtSliceHexLower(&msg.*.recipient.bytes), - }, - ); // TODO(jsign): explore creating custom formatter? + log.debug("call depth={d} sender={} recipient={}", .{ msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); // TODO(jsign): explore creating custom formatter? // Check if the target address is a contract, and do the appropiate call. const recipient_account = vm.statedb.getAccount(util.from_evmc_address(msg.*.code_address)) catch unreachable; // TODO(jsign): fix this. From d76935c1227ee2ea185b885d5ad464509d129995 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 09:21:17 -0300 Subject: [PATCH 16/52] util: move package to common Signed-off-by: Ignacio Hagopian --- src/common/common.zig | 1 + src/common/hexutils.zig | 11 ++++++++++- src/util/util.zig | 9 --------- 3 files changed, 11 insertions(+), 10 deletions(-) delete mode 100644 src/util/util.zig diff --git a/src/common/common.zig b/src/common/common.zig index eec4d6e..c9011c8 100644 --- a/src/common/common.zig +++ b/src/common/common.zig @@ -2,3 +2,4 @@ const hexutils = @import("./hexutils.zig"); pub const prefixedhex2hash = hexutils.prefixedhex2hash; pub const prefixedhex2byteslice = hexutils.prefixedhex2byteslice; pub const prefixedhex2u64 = hexutils.prefixedhex2u64; +pub const hexToAddress = hexutils.hexToAddress; diff --git a/src/common/hexutils.zig b/src/common/hexutils.zig index a13997d..0dd51f4 100644 --- a/src/common/hexutils.zig +++ b/src/common/hexutils.zig @@ -1,7 +1,8 @@ const std = @import("std"); const fmt = std.fmt; -const Allocator = std.mem.Allocator; const types = @import("types.zig"); +const Allocator = std.mem.Allocator; +const Address = types.Address; // This function turns an optionally '0x'-prefixed hex string // to a types.Hash32 @@ -44,3 +45,11 @@ pub fn prefixedhex2u64(src: []const u8) !u64 { return std.fmt.parseInt(u64, src[skip0x..], 16); } + +// hexToAddress parses an optionally '0x'-prefixed hext string to an Address. +pub fn hexToAddress(account_hex: []const u8) Address { + const account_hex_strip = if (std.mem.startsWith(u8, account_hex, "0x")) account_hex[2..] else account_hex[0..]; + var address = std.mem.zeroes(Address); + _ = std.fmt.hexToBytes(&address, account_hex_strip) catch unreachable; + return address; +} diff --git a/src/util/util.zig b/src/util/util.zig deleted file mode 100644 index cd25e66..0000000 --- a/src/util/util.zig +++ /dev/null @@ -1,9 +0,0 @@ -const std = @import("std"); -const Address = @import("../types/types.zig").Address; - -pub fn hex_to_address(account_hex: []const u8) Address { - const account_hex_strip = if (std.mem.startsWith(u8, account_hex, "0x")) account_hex[2..] else account_hex[0..]; - var address = std.mem.zeroes(Address); - _ = std.fmt.hexToBytes(&address, account_hex_strip) catch unreachable; - return address; -} From c0d9b3e1f0e5d0481f077600abfa560b89eb51b6 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 09:36:44 -0300 Subject: [PATCH 17/52] vm & blockchain cleanups Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 1 - src/blockchain/vm.zig | 54 ++++- src/{vm => statedb}/statedb.zig | 0 src/vm/foofoo.zig | 380 -------------------------------- src/vm/util.zig | 41 ---- 5 files changed, 45 insertions(+), 431 deletions(-) rename src/{vm => statedb}/statedb.zig (100%) delete mode 100644 src/vm/foofoo.zig delete mode 100644 src/vm/util.zig diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index cd82fcb..87ec59a 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -373,7 +373,6 @@ pub const Blockchain = struct { data: []const u8, code_address: ?Address, code: []const u8, - depth: u64, // TODO: narrow it down. accessed_addresses: AddressSet, accessed_storage_keys: AddressKeySet, diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 54b8fd4..a240989 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -2,10 +2,9 @@ const evmc = @cImport({ @cInclude("evmone.h"); }); const std = @import("std"); -const Allocator = std.mem.Allocator; -const util = @import("util.zig"); const types = @import("../types/types.zig"); const blockchain = @import("blockchain.zig").Blockchain; // TODO: unnest +const Allocator = std.mem.Allocator; const Environment = blockchain.Environment; const Message = blockchain.Message; const Txn = types.Txn; @@ -14,12 +13,12 @@ const Block = types.Block; const AccountState = types.AccountState; const Bytecode = types.Bytecode; const Address = types.Address; -const assert = std.debug.assert; -const log = std.log.scoped(.vm); const StateDB = @import("../vm/statedb.zig"); const fmtSliceHexLower = std.fmt.fmtSliceHexLower; +const assert = std.debug.assert; + +const log = std.log.scoped(.vm); -// TODO: Rename to instance? pub const VM = struct { env: Environment, evm: [*c]evmc.evmc_vm, @@ -59,11 +58,11 @@ pub const VM = struct { const kind = if (msg.target) evmc.EVMC_CALL orelse evmc.EVMC_CREATE; const evmc_message = evmc.struct_evmc_message{ .kind = kind, - .flags = 0, // TODO: STATIC? + .flags = evmc.EVMC_STATIC, .depth = 0, - .gas = @intCast(msg.gas), // TODO(jsign): why evmc expects a i64 for gas instead of u64? - .recipient = util.to_evmc_address(msg.current_target), - .sender = util.to_evmc_address(msg.caller), + .gas = @intCast(msg.gas), + .recipient = toEVMCAddress(msg.current_target), + .sender = toEVMCAddress(msg.caller), .input_data = msg.data.ptr, .input_size = msg.data.len, .value = blk: { @@ -287,3 +286,40 @@ pub const VM = struct { }; } }; + +// toEVMCAddress transforms an Address or ?Address into an evmc_address. +fn toEVMCAddress(address: anytype) evmc.struct_evmc_address { + const addr_typeinfo = @typeInfo(@TypeOf(address)); + if (@TypeOf(address) != Address and addr_typeinfo.Optional.child != Address) { + @compileError("address must be of type Address or ?Address"); + } + + // Address type. + if (@TypeOf(address) == Address) { + return evmc.struct_evmc_address{ + .bytes = address, + }; + } + if (address) |addr| { + return toEVMCAddress(addr); + } + return evmc.struct_evmc_address{ + .bytes = [_]u8{0} ** 20, + }; +} + +// fromEVMCAddress transforms an evmc_address into an Address. +fn fromEVMCAddress(address: evmc.struct_evmc_address) Address { + return address.bytes; +} + +// toEVMCBytes32 transforms a u256 into an evmc_bytes32. +fn toEVMCBytes32(num: u256) evmc.evmc_bytes32 { + return evmc.struct_evmc_bytes32{ + .bytes = blk: { + var ret: [32]u8 = undefined; + std.mem.writeIntSliceBig(u256, &ret, num); + break :blk ret; + }, + }; +} diff --git a/src/vm/statedb.zig b/src/statedb/statedb.zig similarity index 100% rename from src/vm/statedb.zig rename to src/statedb/statedb.zig diff --git a/src/vm/foofoo.zig b/src/vm/foofoo.zig deleted file mode 100644 index fa4365c..0000000 --- a/src/vm/foofoo.zig +++ /dev/null @@ -1,380 +0,0 @@ -const evmc = @cImport({ - @cInclude("evmone.h"); -}); -const std = @import("std"); -const Allocator = std.mem.Allocator; -const util = @import("util.zig"); -const types = @import("../types/types.zig"); -const Txn = types.Txn; -const TxnSigner = @import("../signer/signer.zig").TxnSigner; -const Block = types.Block; -const AccountState = types.AccountState; -const Bytecode = types.Bytecode; -const Address = types.Address; -const assert = std.debug.assert; -const log = std.log.scoped(.vm); - -pub const StateDB = @import("statedb.zig"); - -const txn_base_gas = 21_000; - -const ExecutionContext = struct { - // storage_address is the current address used for storage access. - storage_address: Address, -}; - -const TxnContext = struct { - chain_id: u256, - gas_price: u256, - from: Address, -}; - -const BlockContext = struct { - coinbase: Address, - number: u64, - timestamp: u64, - gas_limit: u64, - prev_randao: u256, - base_fee: u256, -}; - -pub const VM = struct { - // statedb is the state database. - statedb: *StateDB, - // host is the EVMC host interface. - host: evmc.struct_evmc_host_interface, - // evm is the EVMC implementation. - evm: [*c]evmc.evmc_vm, - - // exec_context has the current execution context. - context: ?struct { - execution: ExecutionContext, - txn: TxnContext, - block: BlockContext, - }, - - pub fn init(statedb: *StateDB) VM { - var evm = evmc.evmc_create_evmone(); - log.info( - "evmone info: name={s}, version={s}, abi_version={d}", - .{ evm.*.name, evm.*.version, evm.*.abi_version }, - ); - return VM{ - .statedb = statedb, - .host = evmc.struct_evmc_host_interface{ - .account_exists = account_exists, - .get_storage = get_storage, - .set_storage = set_storage, - .get_balance = get_balance, - .get_code_size = get_code_size, - .get_code_hash = get_code_hash, - .copy_code = copy_code, - .selfdestruct = self_destruct, - .call = call, - .get_tx_context = get_tx_context, - .get_block_hash = get_block_hash, - .emit_log = emit_log, - .access_account = access_account, - .access_storage = access_storage, - }, - .evm = evm, - .context = null, - }; - } - - pub fn deinit() void { - // TODO(jsign): check freeing evmone instance. - } - - fn processMessage(self: *VM, msg: Message) !void { - const from_addr = self.*.context.?.txn.from; - - var remaining_gas: i64 = @intCast(txn.getGasLimit()); - - // Sender nonce updating. - if (txn.getNonce() +% 1 < txn.getNonce()) { - return error.MaxNonce; - } - try self.statedb.incrementNonce(from_addr); - - // Charge intrinsic gas costs. - // TODO(jsign): this is incomplete. - try charge_gas(&remaining_gas, txn_base_gas); - - if (txn.getTo()) |to_addr| { - assert(!std.mem.eql(u8, &to_addr, &std.mem.zeroes(Address))); - - // Send transaction value to the recipient. - if (txn.getValue() > 0) { // TODO(jsign): incomplete - try self.statedb.setBalance(to_addr, txn.getValue()); // TODO: TEMP wrong - } - - self.context.?.execution = ExecutionContext{ - .storage_address = from_addr, - }; - const msg = evmc.struct_evmc_message{ - .kind = evmc.EVMC_CALL, // TODO(jsign): generalize. - .flags = 0, // TODO: STATIC? - .depth = 0, - .gas = @intCast(remaining_gas), // TODO(jsign): why evmc expects a i64 for gas instead of u64? - .recipient = util.to_evmc_address(txn.getTo()), - .sender = util.to_evmc_address(from_addr), - .input_data = txn.getData().ptr, - .input_size = txn.getData().len, - .value = blk: { - var txn_value: [32]u8 = undefined; - std.mem.writeIntSliceBig(u256, &txn_value, txn.getValue()); - break :blk .{ .bytes = txn_value }; - }, - .create2_salt = .{ - .bytes = [_]u8{0} ** 32, // TODO: fix this. - }, - .code_address = util.to_evmc_address(txn.getTo()), // TODO: fix this when .kind is generalized. - }; - const result = call(@ptrCast(self), @ptrCast(&msg)); - - log.debug("execution result: status_code={}, gas_left={}", .{ result.status_code, result.gas_left }); - - remaining_gas = result.gas_left + result.gas_refund; - // } - } else { // Contract creation. - @panic("TODO contract creation"); - } - - const gas_used = @as(i64, @intCast(txn.getGasLimit())) - remaining_gas; // TODO(jsign): decide on casts. - - // Coinbase rewards. - const gas_tip = 0xa - 0x7; // TODO(jsign): fix, pull from tx_context. - const coinbase_fee = gas_used * gas_tip; - try self.statedb.setBalance(self.context.?.block.coinbase, @as(u256, @intCast(coinbase_fee))); // TODO TEMP WRONG - - // Sender fees. - const sender_fee = gas_used * 0xa; - try self.statedb.setBalance(from_addr, @as(u256, @intCast(sender_fee))); // TODO TEMP WRONG - } - - inline fn charge_gas(remaining_gas: *i64, charge: u64) !void { - if (remaining_gas.* < charge) { - return error.OutOfGas; - } - remaining_gas.* -= charge; - } - - // ### EVMC Host Interface ### - - fn get_tx_context(ctx: ?*evmc.struct_evmc_host_context) callconv(.C) evmc.struct_evmc_tx_context { - log.debug("get_tx_context()", .{}); - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - return evmc.struct_evmc_tx_context{ - .tx_gas_price = util.to_evmc_bytes32(vm.context.?.txn.gas_price), - .tx_origin = util.to_evmc_address(vm.context.?.txn.from), - .block_coinbase = util.to_evmc_address(vm.context.?.block.coinbase), - .block_number = @intCast(vm.context.?.block.number), - .block_timestamp = @intCast(vm.context.?.block.timestamp), - .block_gas_limit = @intCast(vm.context.?.block.gas_limit), - .block_prev_randao = util.to_evmc_bytes32(vm.context.?.block.prev_randao), - .chain_id = util.to_evmc_bytes32(vm.context.?.txn.chain_id), - .block_base_fee = util.to_evmc_bytes32(vm.context.?.block.base_fee), - }; - } - - fn get_block_hash( - ctx: ?*evmc.struct_evmc_host_context, - xx: i64, - ) callconv(.C) evmc.evmc_bytes32 { - _ = xx; - _ = ctx; - @panic("TODO"); - } - - fn account_exists( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - ) callconv(.C) bool { - _ = addr; - _ = ctx; - @panic("TODO"); - } - - fn get_storage( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - dest: [*c]const evmc.evmc_bytes32, - ) callconv(.C) evmc.evmc_bytes32 { - _ = ctx; - _ = dest; - _ = addr; - @panic("TODO"); - } - fn set_storage( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - key: [*c]const evmc.evmc_bytes32, - value: [*c]const evmc.evmc_bytes32, - ) callconv(.C) evmc.enum_evmc_storage_status { - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - - const skey = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); - const svalue = std.mem.readIntSlice(u256, &value.*.bytes, std.builtin.Endian.Big); - - vm.statedb.setStorage(addr.*.bytes, skey, svalue) catch unreachable; // TODO(jsign): manage catch. - - return evmc.EVMC_STORAGE_ADDED; // TODO(jsign): fix - } - - fn get_balance( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - ) callconv(.C) evmc.evmc_uint256be { - _ = ctx; - const addr_hex = std.fmt.bytesToHex(addr.*.bytes, std.fmt.Case.lower); - log.debug("evmc call -> getBalance(0x{s})", .{addr_hex}); - - var beval: [32]u8 = undefined; - std.mem.writeIntSliceBig(u256, &beval, 142); - - return evmc.evmc_uint256be{ - .bytes = beval, - }; - } - - fn get_code_size( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - ) callconv(.C) usize { - _ = addr; - _ = ctx; - @panic("TODO"); - } - - fn get_code_hash( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - ) callconv(.C) evmc.evmc_bytes32 { - _ = addr; - _ = ctx; - @panic("TODO"); - } - - fn copy_code( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - xxx: usize, - xxy: [*c]u8, - xxz: usize, - ) callconv(.C) usize { - _ = xxz; - _ = xxy; - _ = xxx; - _ = addr; - _ = ctx; - @panic("TODO"); - } - - fn self_destruct( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - addr2: [*c]const evmc.evmc_address, - ) callconv(.C) bool { - _ = addr2; - _ = addr; - _ = ctx; - @panic("TODO"); - } - - fn emit_log( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - xxx: [*c]const u8, - xxy: usize, - xxz: [*c]const evmc.evmc_bytes32, - xxxzz: usize, - ) callconv(.C) void { - _ = xxxzz; - _ = xxz; - _ = xxy; - _ = xxx; - _ = addr; - _ = ctx; - @panic("TODO"); - } - - fn access_account( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - ) callconv(.C) evmc.enum_evmc_access_status { - log.debug("access_account(addr={})", .{std.fmt.fmtSliceHexLower(&addr.*.bytes)}); - _ = ctx; - return evmc.EVMC_ACCESS_COLD; - } - - fn access_storage( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - value: [*c]const evmc.evmc_bytes32, - ) callconv(.C) evmc.enum_evmc_access_status { - _ = value; - _ = addr; - _ = ctx; - return evmc.EVMC_ACCESS_COLD; // TODO(jsign): fix - } - - fn call( - ctx: ?*evmc.struct_evmc_host_context, - msg: [*c]const evmc.struct_evmc_message, - ) callconv(.C) evmc.struct_evmc_result { - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - log.debug( - "call depth={d} sender={} recipient={}", - .{ - msg.*.depth, - std.fmt.fmtSliceHexLower(&msg.*.sender.bytes), - std.fmt.fmtSliceHexLower(&msg.*.recipient.bytes), - }, - ); // TODO(jsign): explore creating custom formatter? - - // Check if the target address is a contract, and do the appropiate call. - const recipient_account = vm.statedb.getAccount(util.from_evmc_address(msg.*.code_address)) catch unreachable; // TODO(jsign): fix this. - if (recipient_account.code.len != 0) { - log.debug("contract call, codelen={d}", .{recipient_account.code.len}); - // Persist the current context. We'll restore it after the call returns. - const prev_exec_context = vm.*.context.?.execution; - - // Create the new context to be used to do the call. - vm.context.?.execution = ExecutionContext{ .storage_address = util.from_evmc_address(msg.*.recipient) }; - - // TODO(jsign): EVMC_SHANGHAI should be configurable at runtime. - var result = vm.evm.*.execute.?( - vm.evm, - @ptrCast(&vm.host), - @ptrCast(vm), - evmc.EVMC_SHANGHAI, - msg, - recipient_account.code.ptr, - recipient_account.code.len, - ); - log.debug( - "internal call exec result: status_code={}, gas_left={}", - .{ result.status_code, result.gas_left }, - ); - - // Restore previous context after call() returned. - vm.context.?.execution = prev_exec_context; - - return result; - } - - log.debug("non-contract call", .{}); - // TODO(jsign): verify. - return evmc.evmc_result{ - .status_code = evmc.EVMC_SUCCESS, - .gas_left = msg.*.gas, // TODO: fix - .gas_refund = 0, - .output_data = null, - .output_size = 0, - .release = null, - .create_address = std.mem.zeroes(evmc.evmc_address), - .padding = [_]u8{0} ** 4, - }; - } -}; diff --git a/src/vm/util.zig b/src/vm/util.zig deleted file mode 100644 index 00cfbda..0000000 --- a/src/vm/util.zig +++ /dev/null @@ -1,41 +0,0 @@ -const std = @import("std"); -const types = @import("../types/types.zig"); -const Address = types.Address; -const evmc = @cImport({ - @cInclude("evmone.h"); -}); - -// to_evmc_address transforms an Address or ?Address into an evmc_address. -pub fn to_evmc_address(address: anytype) evmc.struct_evmc_address { - const addr_typeinfo = @typeInfo(@TypeOf(address)); - if (@TypeOf(address) != Address and addr_typeinfo.Optional.child != Address) { - @compileError("address must be of type Address or ?Address"); - } - - // Address type. - if (@TypeOf(address) == Address) { - return evmc.struct_evmc_address{ - .bytes = address, - }; - } - if (address) |addr| { - return to_evmc_address(addr); - } - return evmc.struct_evmc_address{ - .bytes = [_]u8{0} ** 20, - }; -} - -pub fn from_evmc_address(address: evmc.struct_evmc_address) Address { - return address.bytes; -} - -pub fn to_evmc_bytes32(num: u256) evmc.evmc_bytes32 { - return evmc.struct_evmc_bytes32{ - .bytes = blk: { - var ret: [32]u8 = undefined; - std.mem.writeIntSliceBig(u256, &ret, num); - break :blk ret; - }, - }; -} From c2c04849c8e405005236edd7c41542dba06a2d8d Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 09:53:23 -0300 Subject: [PATCH 18/52] blockchain/vm: refactor Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 60 ++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index a240989..fe44512 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -24,6 +24,7 @@ pub const VM = struct { evm: [*c]evmc.evmc_vm, host: evmc.struct_evmc_host_interface, + // init creates a new EVM VM instance. The caller must call deinit() when done. pub fn init(env: Environment) VM { var evm = evmc.evmc_create_evmone(); log.info("evmone info: name={s}, version={s}, abi_version={d}", .{ evm.*.name, evm.*.version, evm.*.abi_version }); @@ -31,24 +32,25 @@ pub const VM = struct { .env = env, .evm = evm, .host = evmc.struct_evmc_host_interface{ - .account_exists = account_exists, - .get_storage = get_storage, - .set_storage = set_storage, - .get_balance = get_balance, - .get_code_size = get_code_size, - .get_code_hash = get_code_hash, - .copy_code = copy_code, - .selfdestruct = self_destruct, - .call = call, - .get_tx_context = get_tx_context, - .get_block_hash = get_block_hash, - .emit_log = emit_log, - .access_account = access_account, - .access_storage = access_storage, + .account_exists = EVMOneHost.account_exists, + .get_storage = EVMOneHost.get_storage, + .set_storage = EVMOneHost.set_storage, + .get_balance = EVMOneHost.get_balance, + .get_code_size = EVMOneHost.get_code_size, + .get_code_hash = EVMOneHost.get_code_hash, + .copy_code = EVMOneHost.copy_code, + .selfdestruct = EVMOneHost.self_destruct, + .call = EVMOneHost.call, + .get_tx_context = EVMOneHost.get_tx_context, + .get_block_hash = EVMOneHost.get_block_hash, + .emit_log = EVMOneHost.emit_log, + .access_account = EVMOneHost.access_account, + .access_storage = EVMOneHost.access_storage, }, }; } + // deinit destroys a VM instance. pub fn deinit(self: *VM) void { self.evm.destroy(); self.evm = undefined; @@ -73,33 +75,33 @@ pub const VM = struct { .create2_salt = undefined, // EVMC docs: field only mandatory for CREATE2 kind. .code_address = undefined, // EVMC docs: field not mandatory for depth 0 calls. }; - const result = call(@ptrCast(self), @ptrCast(&evmc_message)); + const result = EVMOneHost.call(@ptrCast(self), @ptrCast(&evmc_message)); log.debug("execution result: status_code={}, gas_left={}", .{ result.status_code, result.gas_left }); return result; } +}; - // ### EVMC Host Interface ### - +// EVMOneHost contains the implementation of the EVMC host interface. +// https://evmc.ethereum.org/structevmc__host__interface.html +const EVMOneHost = struct { fn get_tx_context(ctx: ?*evmc.struct_evmc_host_context) callconv(.C) evmc.struct_evmc_tx_context { log.debug("get_tx_context()", .{}); - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); // TODO: alignCast needed? return evmc.struct_evmc_tx_context{ - .tx_gas_price = util.to_evmc_bytes32(vm.env.?.txn.gas_price), - .tx_origin = util.to_evmc_address(vm.env.?.txn.from), - .block_coinbase = util.to_evmc_address(vm.env.?.block.coinbase), + .tx_gas_price = toEVMCBytes32(vm.env.?.txn.gas_price), + .tx_origin = toEVMCAddress(vm.env.?.txn.from), + .block_coinbase = toEVMCAddress(vm.env.?.block.coinbase), .block_number = @intCast(vm.env.?.block.number), .block_timestamp = @intCast(vm.env.?.block.timestamp), .block_gas_limit = @intCast(vm.env.?.block.gas_limit), - .block_prev_randao = util.to_evmc_bytes32(vm.env.?.block.prev_randao), - .chain_id = util.to_evmc_bytes32(vm.env.?.txn.chain_id), - .block_base_fee = util.to_evmc_bytes32(vm.env.?.block.base_fee), + .block_prev_randao = toEVMCBytes32(vm.env.?.block.prev_randao), + .chain_id = toEVMCBytes32(vm.env.?.txn.chain_id), + .block_base_fee = toEVMCBytes32(vm.env.?.block.base_fee), }; } - fn get_block_hash( - ctx: ?*evmc.struct_evmc_host_context, - xx: i64, - ) callconv(.C) evmc.evmc_bytes32 { + fn get_block_hash(ctx: ?*evmc.struct_evmc_host_context, xx: i64) callconv(.C) evmc.evmc_bytes32 { _ = xx; _ = ctx; @panic("TODO"); @@ -242,7 +244,7 @@ pub const VM = struct { log.debug("call depth={d} sender={} recipient={}", .{ msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); // TODO(jsign): explore creating custom formatter? // Check if the target address is a contract, and do the appropiate call. - const recipient_account = vm.statedb.getAccount(util.from_evmc_address(msg.*.code_address)) catch unreachable; // TODO(jsign): fix this. + const recipient_account = vm.statedb.getAccount(fromEVMCAddress(msg.*.code_address)) catch unreachable; // TODO(jsign): fix this. if (recipient_account.code.len != 0) { log.debug("contract call, codelen={d}", .{recipient_account.code.len}); // Persist the current context. We'll restore it after the call returns. From 6922d6db8ec1a9966fcea60981d2fbfcf5588d61 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 10:04:34 -0300 Subject: [PATCH 19/52] blockchain: fixes Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 87ec59a..619c476 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -50,27 +50,27 @@ pub const Blockchain = struct { allocator: Allocator, chain_id: config.ChainId, state: *StateDB, - last_256_blocks_hashes: [256]Hash32, - previous_block: Block, + prev_block: BlockHeader, + last_256_blocks_hashes: [256]Hash32, // blocks ordered in asc order pub fn init( allocator: Allocator, chain_id: config.ChainId, state: *StateDB, - prev_block_header: BlockHeader, + prev_block: BlockHeader, last_256_blocks_hashes: [256]Hash32, ) void { return Blockchain{ .allocator = allocator, .chain_id = chain_id, .state = state, - .prev_block_header = prev_block_header, + .prev_block = prev_block, .last_256_blocks_hashes = last_256_blocks_hashes, }; } - pub fn run_block(self: Blockchain, block: Block) !void { - try self.validate_block(self.allocator, block); + pub fn runBlock(self: Blockchain, block: Block) !void { + try self.validateBlockHeader(self.allocator, self.prev_block, block.header); if (block.uncles.len != 0) return error.NotEmptyUncles; @@ -93,10 +93,6 @@ pub const Blockchain = struct { return error.InvalidLogsBloom; if (result.withdrawals_root != block.header.withdrawals_root) return error.InvalidWithdrawalsRoot; - - // TODO: do this more efficiently with a circular buffer. - std.mem.copyForwards(Hash32, self.last_256_blocks_hashes, self.last_256_blocks_hashes[1..]); - self.last_256_blocks_hashes[255] = block.hash(); } // validateBlockHeader validates the header of a block itself and with respect with the parent. From 3ec6d4778e690ea90d9a406d5ad5ba619f94307e Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 11:34:40 -0300 Subject: [PATCH 20/52] blockchain/vm: implement EVMOne block_hash Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index fe44512..89bbe38 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -101,10 +101,13 @@ const EVMOneHost = struct { }; } - fn get_block_hash(ctx: ?*evmc.struct_evmc_host_context, xx: i64) callconv(.C) evmc.evmc_bytes32 { - _ = xx; - _ = ctx; - @panic("TODO"); + fn get_block_hash(ctx: ?*evmc.struct_evmc_host_context, block_number: i64) callconv(.C) evmc.evmc_bytes32 { + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const idx = vm.env.number - block_number; + if (idx < 0 or idx >= vm.env.block_hashes.len) { + return .{ .bytes = [_]u8{0} ** 32 }; + } + return .{ .bytes = vm.env.block_hashes[idx] }; } fn account_exists( From d56ebb346decd208d78e9486a3fb11990f2f19b2 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 11:54:21 -0300 Subject: [PATCH 21/52] blockchain/vm: implement EVMOne account_exists Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 89bbe38..8991775 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -13,7 +13,7 @@ const Block = types.Block; const AccountState = types.AccountState; const Bytecode = types.Bytecode; const Address = types.Address; -const StateDB = @import("../vm/statedb.zig"); +const StateDB = @import("../statedb/statedb.zig"); const fmtSliceHexLower = std.fmt.fmtSliceHexLower; const assert = std.debug.assert; @@ -84,8 +84,10 @@ pub const VM = struct { // EVMOneHost contains the implementation of the EVMC host interface. // https://evmc.ethereum.org/structevmc__host__interface.html const EVMOneHost = struct { + const evmclog = std.log.scoped(.evmone); + fn get_tx_context(ctx: ?*evmc.struct_evmc_host_context) callconv(.C) evmc.struct_evmc_tx_context { - log.debug("get_tx_context()", .{}); + evmclog.debug("get_tx_context", .{}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); // TODO: alignCast needed? return evmc.struct_evmc_tx_context{ @@ -102,6 +104,8 @@ const EVMOneHost = struct { } fn get_block_hash(ctx: ?*evmc.struct_evmc_host_context, block_number: i64) callconv(.C) evmc.evmc_bytes32 { + evmclog.debug("get_tx_context block_number={}", .{block_number}); + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); const idx = vm.env.number - block_number; if (idx < 0 or idx >= vm.env.block_hashes.len) { @@ -110,13 +114,12 @@ const EVMOneHost = struct { return .{ .bytes = vm.env.block_hashes[idx] }; } - fn account_exists( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - ) callconv(.C) bool { - _ = addr; - _ = ctx; - @panic("TODO"); + fn account_exists(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) bool { + const address = fromEVMCAddress(addr.*); + evmclog.debug("account_exists addr=0x{}", .{fmtSliceHexLower(&address)}); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + return try vm.env.state.getAccount() != null; } fn get_storage( From edbc37104e5ae4337f59c7c7bd933c2a393668db Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 11:54:34 -0300 Subject: [PATCH 22/52] general fixes Signed-off-by: Ignacio Hagopian --- src/common/hexutils.zig | 2 +- src/exec-spec-tests/execspectests.zig | 2 +- src/main.zig | 8 ++++---- src/statedb/statedb.zig | 18 +++++------------- src/types/account_state.zig | 4 ++-- 5 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/common/hexutils.zig b/src/common/hexutils.zig index 0dd51f4..2bf533a 100644 --- a/src/common/hexutils.zig +++ b/src/common/hexutils.zig @@ -1,6 +1,6 @@ const std = @import("std"); const fmt = std.fmt; -const types = @import("types.zig"); +const types = @import("../types/types.zig"); const Allocator = std.mem.Allocator; const Address = types.Address; diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index 1f52951..251cde6 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -8,7 +8,7 @@ const AccountState = types.AccountState; const Block = types.Block; const BlockHeader = types.BlockHeader; const Txn = types.Txn; -const vm = @import("../vm/vm.zig"); +const vm = @import("../blockchain/vm.zig"); const VM = vm.VM; const StateDB = vm.StateDB; const TxnSigner = @import("../signer/signer.zig").TxnSigner; diff --git a/src/main.zig b/src/main.zig index fa813d0..172a86c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,8 +4,8 @@ const ecdsa = @import("crypto/ecdsa.zig"); const config = @import("config/config.zig"); const AccountState = types.AccountState; const Address = types.Address; -const VM = @import("vm/vm.zig").VM; -const StateDB = @import("vm/statedb.zig"); +const VM = @import("blockchain/vm.zig").VM; +const StateDB = @import("statedb/statedb.zig"); const Block = types.Block; const Txn = types.Txn; const TxnSigner = @import("signer/signer.zig").TxnSigner; @@ -102,10 +102,10 @@ pub fn main() !void { test "tests" { std.testing.log_level = .debug; - // TODO: at some point unify entrypoint per package. + // TODO: move to separate file for tests binary. _ = @import("exec-spec-tests/execspectests.zig"); _ = @import("types/types.zig"); - _ = @import("vm/vm.zig"); + _ = @import("blockchain/vm.zig"); _ = @import("crypto/ecdsa.zig"); _ = @import("engine_api/engine_api.zig"); } diff --git a/src/statedb/statedb.zig b/src/statedb/statedb.zig index ad50211..02be9c8 100644 --- a/src/statedb/statedb.zig +++ b/src/statedb/statedb.zig @@ -1,18 +1,16 @@ const std = @import("std"); -const Allocator = std.mem.Allocator; const types = @import("../types/types.zig"); +const Allocator = std.mem.Allocator; const Address = types.Address; const AccountState = types.AccountState; - const log = std.log.scoped(.statedb); const StateDB = @This(); - -const AccountDB = std.AutoHashMap(Address, AccountState); - allocator: Allocator, db: AccountDB, +const AccountDB = std.AutoHashMap(Address, AccountState); + pub fn init(allocator: Allocator, accounts_state: []AccountState) !StateDB { var db = AccountDB.init(allocator); try db.ensureTotalCapacity(@intCast(accounts_state.len)); @@ -26,14 +24,8 @@ pub fn init(allocator: Allocator, accounts_state: []AccountState) !StateDB { }; } -pub fn getAccount(self: *StateDB, addr: Address) !*AccountState { - var res = try self.db.getOrPut(addr); - if (res.found_existing) { - return res.value_ptr; - } - res.value_ptr.* = try AccountState.init(self.allocator, addr, 0, 0, &[_]u8{}); - - return res.value_ptr; +pub fn getAccount(self: *StateDB, addr: Address) !?AccountState { + return try self.db.get(addr); } pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: u256) !void { diff --git a/src/types/account_state.zig b/src/types/account_state.zig index d405240..c540451 100644 --- a/src/types/account_state.zig +++ b/src/types/account_state.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const util = @import("../util/util.zig"); +const common = @import("../common/common.zig"); const types = @import("types.zig"); const Address = types.Address; const Bytecode = types.Bytecode; @@ -45,7 +45,7 @@ pub fn storage_set(self: *AccountState, key: u256, value: u256) !void { const test_allocator = std.testing.allocator; test "storage" { - var account = try AccountState.init(test_allocator, util.hex_to_address("0x010142"), 0, 0, &[_]u8{}); + var account = try AccountState.init(test_allocator, common.hexToAddress("0x010142"), 0, 0, &[_]u8{}); defer account.deinit(); // Set key=0x42, val=0x43, and check get. From bf61d3fd2eb07f9505d04493ac404c1de776533c Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 12:05:28 -0300 Subject: [PATCH 23/52] blockchain/vm: implement EVMOne get_storage & fixes Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 4 ++-- src/blockchain/vm.zig | 17 ++++++++++------- src/exec-spec-tests/execspectests.zig | 10 +++++----- src/statedb/statedb.zig | 5 +++++ 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 619c476..a8fc5ab 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -10,7 +10,7 @@ const Allocator = std.mem.Allocator; const LogsBloom = types.LogsBloom; const Block = types.Block; const BlockHeader = types.BlockHeader; -const StateDB = vm.StateDB; +const StateDB = @import("../statedb/statedb.zig"); const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; const Address = types.Address; @@ -221,7 +221,7 @@ pub const Blockchain = struct { return .{ .sender_address = sender_address, .effective_gas_price = effective_gas_price }; } - const Environment = struct { + pub const Environment = struct { caller: Address, block_hashes: [256]Hash32, origin: Address, diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 8991775..103eedb 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -12,6 +12,7 @@ const TxnSigner = @import("../signer/signer.zig").TxnSigner; const Block = types.Block; const AccountState = types.AccountState; const Bytecode = types.Bytecode; +const Hash32 = types.Hash32; const Address = types.Address; const StateDB = @import("../statedb/statedb.zig"); const fmtSliceHexLower = std.fmt.fmtSliceHexLower; @@ -109,7 +110,7 @@ const EVMOneHost = struct { const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); const idx = vm.env.number - block_number; if (idx < 0 or idx >= vm.env.block_hashes.len) { - return .{ .bytes = [_]u8{0} ** 32 }; + return std.mem.zeroes(evmc.evmc_bytes32); } return .{ .bytes = vm.env.block_hashes[idx] }; } @@ -125,13 +126,15 @@ const EVMOneHost = struct { fn get_storage( ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address, - dest: [*c]const evmc.evmc_bytes32, + k: [*c]const evmc.evmc_bytes32, ) callconv(.C) evmc.evmc_bytes32 { - _ = ctx; - _ = dest; - _ = addr; - @panic("TODO"); + const address = fromEVMCAddress(addr.*); + evmclog.debug("get_storage addr=0x{} key={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&k.*) }); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + return vm.env.state.getStorage(address, k) orelse std.mem.zeroes(Hash32); } + fn set_storage( ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address, @@ -257,7 +260,7 @@ const EVMOneHost = struct { const prev_exec_context = vm.*.env.?.env; // Create the new context to be used to do the call. - vm.env.?.env = ExecutionContext{ .storage_address = util.from_evmc_address(msg.*.recipient) }; + // vm.env.?.env = ExecutionContext{ .storage_address = util.from_evmc_address(msg.*.recipient) }; // TODO(jsign): EVMC_SHANGHAI should be configurable at runtime. var result = vm.evm.*.execute.?( diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index 251cde6..c459b0e 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -3,14 +3,14 @@ const rlp = @import("rlp"); const Allocator = std.mem.Allocator; const config = @import("../config/config.zig"); const types = @import("../types/types.zig"); +const vm = @import("../blockchain/vm.zig"); const Address = types.Address; const AccountState = types.AccountState; const Block = types.Block; const BlockHeader = types.BlockHeader; const Txn = types.Txn; -const vm = @import("../blockchain/vm.zig"); const VM = vm.VM; -const StateDB = vm.StateDB; +const StateDB = @import("../statedb/statedb.zig"); const TxnSigner = @import("../signer/signer.zig").TxnSigner; const ecdsa = @import("../crypto/ecdsa.zig"); const log = std.log.scoped(.execspectests); @@ -68,8 +68,8 @@ pub const FixtureTest = struct { } break :blk accounts_state; }; - var db = try StateDB.init(allocator, accounts_state); - var evm = VM.init(&db); + var statedb = try StateDB.init(allocator, accounts_state); + var evm = VM.init(&statedb); // 2. Execute blocks. const txn_signer = try TxnSigner.init(0); // ChainID == 0 is used in tests. @@ -94,7 +94,7 @@ pub const FixtureTest = struct { while (it.next()) |entry| { var exp_account_state: AccountState = try entry.value_ptr.*.to_vm_accountstate(allocator, entry.key_ptr.*); std.debug.print("checking account state: {s}\n", .{std.fmt.fmtSliceHexLower(&exp_account_state.addr)}); - const got_account_state = try db.getAccount(exp_account_state.addr); + const got_account_state = try statedb.getAccount(exp_account_state.addr); if (!std.mem.eql(u8, &got_account_state.addr, &exp_account_state.addr)) { return error.post_state_addr_mismatch; } diff --git a/src/statedb/statedb.zig b/src/statedb/statedb.zig index 02be9c8..7348871 100644 --- a/src/statedb/statedb.zig +++ b/src/statedb/statedb.zig @@ -28,6 +28,11 @@ pub fn getAccount(self: *StateDB, addr: Address) !?AccountState { return try self.db.get(addr); } +pub fn getStorage(self: *StateDB, addr: Address, key: u256) !u256 { + const account = try self.getAccount(addr) orelse return 0; + return try account.storage.get(key) orelse 0; +} + pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: u256) !void { var account = try self.getAccount(addr); try account.storage.put(key, value); From fa1da46ae8f08fed85ebffbf4a9621f713711940 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 13:50:55 -0300 Subject: [PATCH 24/52] blockchain/vm: implement EVMOne set_storage Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 28 +++++++++++++++++----------- src/statedb/statedb.zig | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 103eedb..b4ee26b 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -29,6 +29,7 @@ pub const VM = struct { pub fn init(env: Environment) VM { var evm = evmc.evmc_create_evmone(); log.info("evmone info: name={s}, version={s}, abi_version={d}", .{ evm.*.name, evm.*.version, evm.*.abi_version }); + // TODO: database snapshoting, and (potential) revertion. return .{ .env = env, .evm = evm, @@ -126,12 +127,13 @@ const EVMOneHost = struct { fn get_storage( ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address, - k: [*c]const evmc.evmc_bytes32, + key: [*c]const evmc.evmc_bytes32, ) callconv(.C) evmc.evmc_bytes32 { - const address = fromEVMCAddress(addr.*); - evmclog.debug("get_storage addr=0x{} key={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&k.*) }); + evmclog.debug("get_storage addr=0x{} key={}", .{ fmtSliceHexLower(&addr), fmtSliceHexLower(&key.*) }); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const k = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); + const address = fromEVMCAddress(addr.*); return vm.env.state.getStorage(address, k) orelse std.mem.zeroes(Hash32); } @@ -141,14 +143,19 @@ const EVMOneHost = struct { key: [*c]const evmc.evmc_bytes32, value: [*c]const evmc.evmc_bytes32, ) callconv(.C) evmc.enum_evmc_storage_status { - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - - const skey = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); - const svalue = std.mem.readIntSlice(u256, &value.*.bytes, std.builtin.Endian.Big); + evmclog.debug("set_storage addr=0x{} key={} value={}", .{ fmtSliceHexLower(&addr), fmtSliceHexLower(&key.*), fmtSliceHexLower(&value.*) }); - vm.statedb.setStorage(addr.*.bytes, skey, svalue) catch unreachable; // TODO(jsign): manage catch. - - return evmc.EVMC_STORAGE_ADDED; // TODO(jsign): fix + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const address = fromEVMCAddress(addr.*); + const k = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); + const v = std.mem.readIntSlice(u256, &value.*.bytes, std.builtin.Endian.Big); + vm.env.state.setStorage(address, k, v) catch |err| switch (err) { + // From EVMC docs: "The VM MUST make sure that the account exists. This requirement is only a formality + // because VM implementations only modify storage of the account of the current execution context". + error.AccountDoesNotExist => @panic("set storage in non-existent account"), + else => @panic("OOO"), + }; + return evmc.EVMC_STORAGE_ADDED; // TODO(jsign): fix https://evmc.ethereum.org/group__EVMC.html#gae012fd6b8e5c23806b507c2d3e9fb1aa } fn get_balance( @@ -319,7 +326,6 @@ fn toEVMCAddress(address: anytype) evmc.struct_evmc_address { }; } -// fromEVMCAddress transforms an evmc_address into an Address. fn fromEVMCAddress(address: evmc.struct_evmc_address) Address { return address.bytes; } diff --git a/src/statedb/statedb.zig b/src/statedb/statedb.zig index 7348871..f1f7d01 100644 --- a/src/statedb/statedb.zig +++ b/src/statedb/statedb.zig @@ -34,7 +34,7 @@ pub fn getStorage(self: *StateDB, addr: Address, key: u256) !u256 { } pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: u256) !void { - var account = try self.getAccount(addr); + var account = try self.getAccount(addr) orelse return error.AccountDoesNotExist; try account.storage.put(key, value); } From 7458ac64bc124356f5e9820deaffbadbb1fc3c69 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 15:04:03 -0300 Subject: [PATCH 25/52] blockchain/vm: implement EVMOne get_balance Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index b4ee26b..ff403e9 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -158,20 +158,18 @@ const EVMOneHost = struct { return evmc.EVMC_STORAGE_ADDED; // TODO(jsign): fix https://evmc.ethereum.org/group__EVMC.html#gae012fd6b8e5c23806b507c2d3e9fb1aa } - fn get_balance( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - ) callconv(.C) evmc.evmc_uint256be { - _ = ctx; - const addr_hex = std.fmt.bytesToHex(addr.*.bytes, std.fmt.Case.lower); - log.debug("evmc call -> getBalance(0x{s})", .{addr_hex}); + fn get_balance(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) evmc.evmc_uint256be { + evmclog.debug("getBalance addr=0x{})", .{fmtSliceHexLower(&addr)}); - var beval: [32]u8 = undefined; - std.mem.writeIntSliceBig(u256, &beval, 142); - - return evmc.evmc_uint256be{ - .bytes = beval, + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const address = fromEVMCAddress(addr.*); + const balance_bytes = blk: { + const balance = vm.env.state.getAccount(address) orelse 0; + var buf: [32]u8 = undefined; + std.mem.writeIntSliceBig(u256, &buf, balance); + break :blk buf; }; + return evmc.evmc_uint256be{ .bytes = balance_bytes }; } fn get_code_size( From 267efaa7f160d27b7396b4e8906ef1acb0167f2a Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 15:13:50 -0300 Subject: [PATCH 26/52] blockchain/vm: implement EVMOne get_code_size Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 13 ++++++------- src/statedb/statedb.zig | 5 +++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index ff403e9..1301466 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -172,13 +172,12 @@ const EVMOneHost = struct { return evmc.evmc_uint256be{ .bytes = balance_bytes }; } - fn get_code_size( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - ) callconv(.C) usize { - _ = addr; - _ = ctx; - @panic("TODO"); + fn get_code_size(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) usize { + evmclog.debug("getCodeSize addr=0x{})", .{fmtSliceHexLower(&addr)}); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const address = fromEVMCAddress(addr.*); + return if (vm.env.state.getCode(address)) |code| code.len else 0; } fn get_code_hash( diff --git a/src/statedb/statedb.zig b/src/statedb/statedb.zig index f1f7d01..bb8b298 100644 --- a/src/statedb/statedb.zig +++ b/src/statedb/statedb.zig @@ -24,6 +24,7 @@ pub fn init(allocator: Allocator, accounts_state: []AccountState) !StateDB { }; } +// TODO: return a more focused parameter (balance, code, nonce) pub fn getAccount(self: *StateDB, addr: Address) !?AccountState { return try self.db.get(addr); } @@ -48,8 +49,8 @@ pub fn incrementNonce(self: *StateDB, addr: Address) !void { account.nonce += 1; } -pub fn getCode(self: *StateDB, addr: Address) ![]const u8 { - var account = try self.get(addr); +pub fn getCode(self: *StateDB, addr: Address) !?[]const u8 { + var account = try self.getAccount(addr) orelse return null; return account.code; } From ec364525e1cb7953b23421ce31f7579584d3824d Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 15:22:23 -0300 Subject: [PATCH 27/52] blockchain/vm: implement EVMOne get_code_hash Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 22 ++++++++++++++++------ src/common/common.zig | 2 ++ src/common/hexutils.zig | 6 ++++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 1301466..79c8b34 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -3,6 +3,7 @@ const evmc = @cImport({ }); const std = @import("std"); const types = @import("../types/types.zig"); +const common = @import("../common/common.zig"); const blockchain = @import("blockchain.zig").Blockchain; // TODO: unnest const Allocator = std.mem.Allocator; const Environment = blockchain.Environment; @@ -15,20 +16,23 @@ const Bytecode = types.Bytecode; const Hash32 = types.Hash32; const Address = types.Address; const StateDB = @import("../statedb/statedb.zig"); +const Keccak256 = std.crypto.hash.sha3.Keccak256; const fmtSliceHexLower = std.fmt.fmtSliceHexLower; const assert = std.debug.assert; -const log = std.log.scoped(.vm); +const empty_hash = common.comptimeHexToBytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); pub const VM = struct { env: Environment, evm: [*c]evmc.evmc_vm, host: evmc.struct_evmc_host_interface, + const vmlog = std.log.scoped(.vm); + // init creates a new EVM VM instance. The caller must call deinit() when done. pub fn init(env: Environment) VM { var evm = evmc.evmc_create_evmone(); - log.info("evmone info: name={s}, version={s}, abi_version={d}", .{ evm.*.name, evm.*.version, evm.*.abi_version }); + vmlog.info("evmone info: name={s}, version={s}, abi_version={d}", .{ evm.*.name, evm.*.version, evm.*.abi_version }); // TODO: database snapshoting, and (potential) revertion. return .{ .env = env, @@ -78,7 +82,7 @@ pub const VM = struct { .code_address = undefined, // EVMC docs: field not mandatory for depth 0 calls. }; const result = EVMOneHost.call(@ptrCast(self), @ptrCast(&evmc_message)); - log.debug("execution result: status_code={}, gas_left={}", .{ result.status_code, result.gas_left }); + vmlog.debug("processMessageCall status_code={}, gas_left={}", .{ result.status_code, result.gas_left }); return result; } }; @@ -184,9 +188,15 @@ const EVMOneHost = struct { ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address, ) callconv(.C) evmc.evmc_bytes32 { - _ = addr; - _ = ctx; - @panic("TODO"); + evmclog.debug("getCodeSize addr=0x{})", .{fmtSliceHexLower(&addr)}); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const address = fromEVMCAddress(addr.*); + const ret = empty_hash; + if (vm.env.state.getCode(address)) |code| { + Keccak256.hash(&ret, code, .{}); + } + return ret; } fn copy_code( diff --git a/src/common/common.zig b/src/common/common.zig index c9011c8..4d0d150 100644 --- a/src/common/common.zig +++ b/src/common/common.zig @@ -1,5 +1,7 @@ const hexutils = @import("./hexutils.zig"); + pub const prefixedhex2hash = hexutils.prefixedhex2hash; pub const prefixedhex2byteslice = hexutils.prefixedhex2byteslice; pub const prefixedhex2u64 = hexutils.prefixedhex2u64; pub const hexToAddress = hexutils.hexToAddress; +pub const comptimeHexToBytes = hexutils.comptimeHexToBytes; diff --git a/src/common/hexutils.zig b/src/common/hexutils.zig index 2bf533a..cb14a41 100644 --- a/src/common/hexutils.zig +++ b/src/common/hexutils.zig @@ -53,3 +53,9 @@ pub fn hexToAddress(account_hex: []const u8) Address { _ = std.fmt.hexToBytes(&address, account_hex_strip) catch unreachable; return address; } + +pub fn comptimeHexToBytes(comptime bytes: []const u8) [bytes.len / 2]u8 { + var result: [bytes.len / 2]u8 = undefined; + _ = try fmt.hexToBytes(result[0..], bytes); + return result; +} From 0061ed2d013620cb5bb60b2c89b14d8420f61947 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Jan 2024 15:37:43 -0300 Subject: [PATCH 28/52] blockchain/vm: implement EVMOne copy_code Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 79c8b34..7525298 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -202,16 +202,18 @@ const EVMOneHost = struct { fn copy_code( ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address, - xxx: usize, - xxy: [*c]u8, - xxz: usize, + code_offset: usize, + buffer_data: [*c]u8, + buffer_size: usize, ) callconv(.C) usize { - _ = xxz; - _ = xxy; - _ = xxx; - _ = addr; - _ = ctx; - @panic("TODO"); + evmclog.debug("copyCode addr=0x{} code_offset={})", .{ fmtSliceHexLower(&addr), code_offset }); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const address = fromEVMCAddress(addr.*); + const code = vm.env.state.getCode(address) orelse @panic("copyCode account doesn't exist"); + + const copy_len = @min(buffer_size, code.len - code_offset); + @memcpy(buffer_data, code[code_offset..][0..copy_len]); } fn self_destruct( @@ -246,7 +248,7 @@ const EVMOneHost = struct { ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address, ) callconv(.C) evmc.enum_evmc_access_status { - log.debug("access_account(addr={})", .{fmtSliceHexLower(&addr.*.bytes)}); + evmclog.debug("access_account(addr={})", .{fmtSliceHexLower(&addr.*.bytes)}); _ = ctx; return evmc.EVMC_ACCESS_COLD; } @@ -264,12 +266,12 @@ const EVMOneHost = struct { fn call(ctx: ?*evmc.struct_evmc_host_context, msg: [*c]const evmc.struct_evmc_message) callconv(.C) evmc.struct_evmc_result { const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - log.debug("call depth={d} sender={} recipient={}", .{ msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); // TODO(jsign): explore creating custom formatter? + evmclog.debug("call depth={d} sender={} recipient={}", .{ msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); // TODO(jsign): explore creating custom formatter? // Check if the target address is a contract, and do the appropiate call. const recipient_account = vm.statedb.getAccount(fromEVMCAddress(msg.*.code_address)) catch unreachable; // TODO(jsign): fix this. if (recipient_account.code.len != 0) { - log.debug("contract call, codelen={d}", .{recipient_account.code.len}); + evmclog.debug("contract call, codelen={d}", .{recipient_account.code.len}); // Persist the current context. We'll restore it after the call returns. const prev_exec_context = vm.*.env.?.env; @@ -286,7 +288,7 @@ const EVMOneHost = struct { recipient_account.code.ptr, recipient_account.code.len, ); - log.debug( + evmclog.debug( "internal call exec result: status_code={}, gas_left={}", .{ result.status_code, result.gas_left }, ); @@ -297,7 +299,7 @@ const EVMOneHost = struct { return result; } - log.debug("non-contract call", .{}); + evmclog.debug("non-contract call", .{}); // TODO(jsign): verify. return evmc.evmc_result{ .status_code = evmc.EVMC_SUCCESS, From 97bb4124144fd7c29fc2355d6e84ff1661c0b418 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Mon, 8 Jan 2024 09:20:17 -0300 Subject: [PATCH 29/52] blockchain/vm: implement access_account and access_storage & refactors Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 13 ++++---- src/blockchain/vm.zig | 63 ++++++++++++++++++++++++++--------- src/common/common.zig | 7 ++++ src/crypto/ecdsa.zig | 23 ++++--------- src/statedb/statedb.zig | 2 +- 5 files changed, 68 insertions(+), 40 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index a8fc5ab..e5ae0bd 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -1,5 +1,6 @@ const std = @import("std"); const types = @import("../types/types.zig"); +const common = @import("../common/common.zig"); const blocks = @import("../types/block.zig"); const config = @import("../config/config.zig"); const transaction = @import("../types/transaction.zig"); @@ -7,6 +8,9 @@ const vm = @import("vm.zig"); const rlp = @import("zig-rlp"); const signer = @import("../signer/signer.zig"); const Allocator = std.mem.Allocator; +const AddressSet = common.AddressSet; +const AddresssKey = common.AddressKey; +const AddressKeySet = common.AddressKeySet; const LogsBloom = types.LogsBloom; const Block = types.Block; const BlockHeader = types.BlockHeader; @@ -236,10 +240,6 @@ pub const Blockchain = struct { chain_id: config.ChainId, }; - const AddressSet = std.HashMap(Address, void); - const AddressKeyTuple = struct { address: Address, key: Bytes32 }; - const AddressKeySet = std.HashMap(AddressKeyTuple, void); - fn processTransaction(allocator: Allocator, env: Environment, tx: transaction.Txn) !struct { gas_left: u64 } { if (!validateTransaction(tx)) return error.InvalidTransaction; @@ -280,7 +280,7 @@ pub const Blockchain = struct { }, } - const message = prepareMessage( + const message = try prepareMessage( sender, tx.getTo(), tx.getValue(), @@ -290,6 +290,7 @@ pub const Blockchain = struct { preaccessed_addresses, preaccessed_stoarge_keys, ); + defer message.deinit(); const output = processMessageCall(message, env); const gas_used = tx.getGasLimit() - output.gas_left; @@ -447,7 +448,6 @@ pub const Blockchain = struct { refund_counter: u256, // logs: Union[Tuple[()], Tuple[Log, ...]] TODO // accounts_to_delete: AddressKeySet, // TODO (delete?) - // touched_accounts: AddressKeySet, // TODO (delete?) // error: Optional[Exception] TODO }; @@ -461,7 +461,6 @@ pub const Blockchain = struct { .gas_left = result.gas_left, .refund_counter = result.gas_refund, // .accounts_to_delete = AddressKeySet, - // .touched_accounts = vm_instance.touched_accounts, }; } }; diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 7525298..0ed81f4 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -6,6 +6,9 @@ const types = @import("../types/types.zig"); const common = @import("../common/common.zig"); const blockchain = @import("blockchain.zig").Blockchain; // TODO: unnest const Allocator = std.mem.Allocator; +const AddressSet = common.AddressSet; +const AddressKey = common.AddressKey; +const AddressKeySet = common.AddressKeySet; const Environment = blockchain.Environment; const Message = blockchain.Message; const Txn = types.Txn; @@ -27,13 +30,16 @@ pub const VM = struct { evm: [*c]evmc.evmc_vm, host: evmc.struct_evmc_host_interface, + // Call context scoped variables. + accessed_accounts: AddressSet, + accessed_storage_keys: AddressKeySet, + const vmlog = std.log.scoped(.vm); // init creates a new EVM VM instance. The caller must call deinit() when done. pub fn init(env: Environment) VM { var evm = evmc.evmc_create_evmone(); vmlog.info("evmone info: name={s}, version={s}, abi_version={d}", .{ evm.*.name, evm.*.version, evm.*.abi_version }); - // TODO: database snapshoting, and (potential) revertion. return .{ .env = env, .evm = evm, @@ -53,6 +59,10 @@ pub const VM = struct { .access_account = EVMOneHost.access_account, .access_storage = EVMOneHost.access_storage, }, + // TODO: remove this and move it to a "VM instance" or similar which has a + // context containing these things. + .accessed_accounts = undefined, + .accessed_storage_keys = undefined, }; } @@ -62,7 +72,8 @@ pub const VM = struct { self.evm = undefined; } - pub fn processMessageCall(self: *VM, msg: Message) !evmc.struct_evmc_result { + // processMessageCall executes a message call. + pub fn processMessageCall(self: *VM, msg: Message, allocator: Allocator) !evmc.struct_evmc_result { const kind = if (msg.target) evmc.EVMC_CALL orelse evmc.EVMC_CREATE; const evmc_message = evmc.struct_evmc_message{ .kind = kind, @@ -81,6 +92,16 @@ pub const VM = struct { .create2_salt = undefined, // EVMC docs: field only mandatory for CREATE2 kind. .code_address = undefined, // EVMC docs: field not mandatory for depth 0 calls. }; + + // TODO(improv): we clone here since it's the easiest way to manage ownership of the sets. + // Quite honestly, it will be better to avoid creating the sets at the caller level and do it here + // which would make the ownership problem disappear and avoid the clone. It's a very cheap + // clone, but also is simple to avoid it. + self.accessed_accounts = msg.accessed_addresses.cloneWithAllocator(allocator); + defer self.accessed_accounts.deinit(); + self.accessed_storage_keys = msg.accessed_storage_keys.cloneWithAllocator(allocator); + defer self.accessed_storage_keys.deinit(); + const result = EVMOneHost.call(@ptrCast(self), @ptrCast(&evmc_message)); vmlog.debug("processMessageCall status_code={}, gas_left={}", .{ result.status_code, result.gas_left }); return result; @@ -224,7 +245,8 @@ const EVMOneHost = struct { _ = addr2; _ = addr; _ = ctx; - @panic("TODO"); + // https://evmc.ethereum.org/group__EVMC.html#ga1aa9fa657b3f0de375e2f07e53b65bcc + @panic("self destruct not supported in verkle"); } fn emit_log( @@ -241,27 +263,34 @@ const EVMOneHost = struct { _ = xxx; _ = addr; _ = ctx; + // https://evmc.ethereum.org/group__EVMC.html#gaab96621b67d653758b3da15c2b596938 @panic("TODO"); } - fn access_account( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - ) callconv(.C) evmc.enum_evmc_access_status { - evmclog.debug("access_account(addr={})", .{fmtSliceHexLower(&addr.*.bytes)}); - _ = ctx; + fn access_account(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) evmc.enum_evmc_access_status { + evmclog.debug("accessAccount addr=0x{}", .{fmtSliceHexLower(&addr)}); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const address = fromEVMCAddress(addr); + if (vm.accessed_accounts.contains(address)) + return evmc.EVMC_ACCESS_WARM; + try vm.accessed_accounts.fetchPut(address, null); return evmc.EVMC_ACCESS_COLD; } fn access_storage( ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address, - value: [*c]const evmc.evmc_bytes32, + key: [*c]const evmc.evmc_bytes32, ) callconv(.C) evmc.enum_evmc_access_status { - _ = value; - _ = addr; - _ = ctx; - return evmc.EVMC_ACCESS_COLD; // TODO(jsign): fix + evmclog.debug("accessStorage addr=0x{} key=0x{}", .{ fmtSliceHexLower(addr), fmtSliceHexLower(key) }); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const address_key: AddressKey = .{ .address = fromEVMCAddress(addr), .key = key.*.bytes }; + if (vm.accessed_accounts.contains(address_key)) + return evmc.EVMC_ACCESS_WARM; + try vm.accessed_accounts.fetchPut(address_key, null); + return evmc.EVMC_ACCESS_COLD; } fn call(ctx: ?*evmc.struct_evmc_host_context, msg: [*c]const evmc.struct_evmc_message) callconv(.C) evmc.struct_evmc_result { @@ -276,7 +305,11 @@ const EVMOneHost = struct { const prev_exec_context = vm.*.env.?.env; // Create the new context to be used to do the call. - // vm.env.?.env = ExecutionContext{ .storage_address = util.from_evmc_address(msg.*.recipient) }; + // TODO: Snapshot + // TODO: self destruct? + // TODO: access accounts + // TODO: accessed_storage_keys + // TODO: vm.env.?.env = ExecutionContext{ .storage_address = util.from_evmc_address(msg.*.recipient) }; // TODO(jsign): EVMC_SHANGHAI should be configurable at runtime. var result = vm.evm.*.execute.?( diff --git a/src/common/common.zig b/src/common/common.zig index 4d0d150..33add51 100644 --- a/src/common/common.zig +++ b/src/common/common.zig @@ -1,3 +1,5 @@ +const std = @import("std"); +const types = @import("../types/types.zig"); const hexutils = @import("./hexutils.zig"); pub const prefixedhex2hash = hexutils.prefixedhex2hash; @@ -5,3 +7,8 @@ pub const prefixedhex2byteslice = hexutils.prefixedhex2byteslice; pub const prefixedhex2u64 = hexutils.prefixedhex2u64; pub const hexToAddress = hexutils.hexToAddress; pub const comptimeHexToBytes = hexutils.comptimeHexToBytes; + +pub const AddressSet = std.HashMap(types.Address, void); + +pub const AddressKey = struct { address: types.Address, key: types.Bytes32 }; +pub const AddressKeySet = std.HashMap(AddressKey, void); diff --git a/src/crypto/ecdsa.zig b/src/crypto/ecdsa.zig index 9f2f9bd..726bec7 100644 --- a/src/crypto/ecdsa.zig +++ b/src/crypto/ecdsa.zig @@ -1,6 +1,6 @@ const std = @import("std"); const secp256k1 = @import("zig-eth-secp256k1"); - +const common = @import("../common/common.zig"); pub const Signature = [65]u8; pub const Message = [32]u8; pub const PrivateKey = [32]u8; @@ -26,25 +26,14 @@ pub const Signer = struct { }; // The following test values where generated using geth, as a reference. -const hashed_msg = hexToBytes("0x05e0e0ff09b01e5626daac3165b82afa42be29197b82e8a5a8800740ee7519d2"); -const private_key = hexToBytes("0xf457cd3bd0186e342d243ea40ad78fe8e81743f90852e87074e68d8c94c2a42e"); -const signature = hexToBytes("0x5a62891eb3e26f3a2344f93a7bad7fe5e670dc45cbdbf0e5bbdba4399238b5e6614caf592f96ee273a2bf018a976e7bf4b63777f9e53ce819d96c5035611400600"); -const uncompressed_pubkey = hexToBytes("0x04682bade67348db99074fcaaffef29394192e7e227a2bdb49f930c74358060c6a42df70f7ef8aadd94854abe646e047142fad42811e325afbec4753342d630b1e"); -const compressed_pubkey = hexToBytes("0x02682bade67348db99074fcaaffef29394192e7e227a2bdb49f930c74358060c6a"); +const hashed_msg = common.comptimeHexToBytes("0x05e0e0ff09b01e5626daac3165b82afa42be29197b82e8a5a8800740ee7519d2"); +const private_key = common.comptimeHexToBytes("0xf457cd3bd0186e342d243ea40ad78fe8e81743f90852e87074e68d8c94c2a42e"); +const signature = common.comptimeHexToBytes("0x5a62891eb3e26f3a2344f93a7bad7fe5e670dc45cbdbf0e5bbdba4399238b5e6614caf592f96ee273a2bf018a976e7bf4b63777f9e53ce819d96c5035611400600"); +const uncompressed_pubkey = common.comptimeHexToBytes("0x04682bade67348db99074fcaaffef29394192e7e227a2bdb49f930c74358060c6a42df70f7ef8aadd94854abe646e047142fad42811e325afbec4753342d630b1e"); +const compressed_pubkey = common.comptimeHexToBytes("0x02682bade67348db99074fcaaffef29394192e7e227a2bdb49f930c74358060c6a"); test "erecover" { const signer = try Signer.init(); const got_pubkey = try signer.erecover(signature, hashed_msg); try std.testing.expectEqual(uncompressed_pubkey, got_pubkey); } - -// TODO: must to hexutils when a current PR gets merged. -fn hexToBytes(comptime hex: []const u8) [hex.len / 2 - if (std.mem.startsWith(u8, hex, "0x")) 1 else 0]u8 { - var target = hex; - if (std.mem.startsWith(u8, hex, "0x")) { - target = hex[2..]; - } - var ret: [target.len / 2]u8 = undefined; - _ = std.fmt.hexToBytes(&ret, target) catch unreachable; - return ret; -} diff --git a/src/statedb/statedb.zig b/src/statedb/statedb.zig index bb8b298..49bb53c 100644 --- a/src/statedb/statedb.zig +++ b/src/statedb/statedb.zig @@ -54,4 +54,4 @@ pub fn getCode(self: *StateDB, addr: Address) !?[]const u8 { return account.code; } -// TODO: get tests. +// TODO: tests. From a4e1cc16c621221a8df3858b893e21b98b7daa3a Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Mon, 8 Jan 2024 16:28:20 -0300 Subject: [PATCH 30/52] blockchain/vm: more progress Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 124 ++++++++++++++++++++++++---------------- src/statedb/statedb.zig | 26 +++++---- 2 files changed, 90 insertions(+), 60 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 0ed81f4..095d6d3 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -1,3 +1,4 @@ +// TODO(pre-review): check usage of all imports. const evmc = @cImport({ @cInclude("evmone.h"); }); @@ -23,6 +24,7 @@ const Keccak256 = std.crypto.hash.sha3.Keccak256; const fmtSliceHexLower = std.fmt.fmtSliceHexLower; const assert = std.debug.assert; +const STACK_DEPTH_LIMIT = 1024; const empty_hash = common.comptimeHexToBytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); pub const VM = struct { @@ -60,7 +62,7 @@ pub const VM = struct { .access_storage = EVMOneHost.access_storage, }, // TODO: remove this and move it to a "VM instance" or similar which has a - // context containing these things. + // context containing these things, and probably also the (snapshoted) statedb. .accessed_accounts = undefined, .accessed_storage_keys = undefined, }; @@ -77,7 +79,7 @@ pub const VM = struct { const kind = if (msg.target) evmc.EVMC_CALL orelse evmc.EVMC_CREATE; const evmc_message = evmc.struct_evmc_message{ .kind = kind, - .flags = evmc.EVMC_STATIC, + .flags = 0, .depth = 0, .gas = @intCast(msg.gas), .recipient = toEVMCAddress(msg.current_target), @@ -294,56 +296,80 @@ const EVMOneHost = struct { } fn call(ctx: ?*evmc.struct_evmc_host_context, msg: [*c]const evmc.struct_evmc_message) callconv(.C) evmc.struct_evmc_result { + evmclog.debug("call() kind={} depth={d} sender={} recipient={}", .{ msg.kind, msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); // TODO(jsign): explore creating custom formatter? + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - evmclog.debug("call depth={d} sender={} recipient={}", .{ msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); // TODO(jsign): explore creating custom formatter? - - // Check if the target address is a contract, and do the appropiate call. - const recipient_account = vm.statedb.getAccount(fromEVMCAddress(msg.*.code_address)) catch unreachable; // TODO(jsign): fix this. - if (recipient_account.code.len != 0) { - evmclog.debug("contract call, codelen={d}", .{recipient_account.code.len}); - // Persist the current context. We'll restore it after the call returns. - const prev_exec_context = vm.*.env.?.env; - - // Create the new context to be used to do the call. - // TODO: Snapshot - // TODO: self destruct? - // TODO: access accounts - // TODO: accessed_storage_keys - // TODO: vm.env.?.env = ExecutionContext{ .storage_address = util.from_evmc_address(msg.*.recipient) }; - - // TODO(jsign): EVMC_SHANGHAI should be configurable at runtime. - var result = vm.evm.*.execute.?( - vm.evm, - @ptrCast(&vm.host), - @ptrCast(vm), - evmc.EVMC_SHANGHAI, - msg, - recipient_account.code.ptr, - recipient_account.code.len, - ); - evmclog.debug( - "internal call exec result: status_code={}, gas_left={}", - .{ result.status_code, result.gas_left }, - ); - - // Restore previous context after call() returned. - vm.env.?.env = prev_exec_context; - - return result; + + if (msg.depth > STACK_DEPTH_LIMIT) { + return .{ + .status_code = evmc.EVMC_CALL_DEPTH_EXCEEDED, + .gas_left = 0, + .gas_refund = 0, + .output_data = null, + .output_size = 0, + .release = null, + .create_address = std.mem.zeroes(evmc.struct_evmc_address), + .padding = [_]u9{0} ** 4, + }; } - evmclog.debug("non-contract call", .{}); - // TODO(jsign): verify. - return evmc.evmc_result{ - .status_code = evmc.EVMC_SUCCESS, - .gas_left = msg.*.gas, // TODO: fix - .gas_refund = 0, - .output_data = null, - .output_size = 0, - .release = null, - .create_address = std.mem.zeroes(evmc.evmc_address), - .padding = [_]u8{0} ** 4, - }; + // Save current context. + const prev_accessed_accounts = vm.accessed_accounts; + const prev_accessed_storage_keys = vm.accessed_storage_keys; + const prev_statedb = vm.env.state; + + // Create new call context. + vm.accessed_accounts = vm.accessed_accounts.clone(); + defer vm.accessed_accounts.deinit(); + vm.accessed_storage_keys = vm.accessed_storage_keys.clone(); + defer vm.accessed_storage_keys.deinit(); + vm.env.state = vm.env.state.clone(); + defer vm.env.state.deinit(); + + // Send value. + if (msg.value.bytes != [_]u8{0} ** 32) { + const value = std.mem.readInt(u256, &msg.*.value.bytes, std.builtin.Endian.Big); + + const sender = toEVMCAddress(msg.sender); + const sender_balance = if (vm.env.state.getAccount(sender)) |acc| acc.balance else 0; + if (sender_balance < value) { + return .{ + .status_code = evmc.EVMC_INSUFFICIENT_BALANCE, + .gas_left = 0, + .gas_refund = 0, + .output_data = null, + .output_size = 0, + .release = null, + .create_address = std.mem.zeroes(evmc.struct_evmc_address), + .padding = [_]u9{0} ** 4, + }; + } + vm.env.state.setBalance(sender, sender_balance - value) catch @panic("OOO"); + const receipient_balance = if (vm.env.state.getAccount(toEVMCAddress(msg.recipient))) |acc| acc.balance else 0; + vm.env.state.setBalance(sender, receipient_balance + value) catch @panic("OOO"); + } + const value = std.mem.readInt(u256, &msg.*.value.bytes, std.builtin.Endian.Big); + _ = value; + + const code_address = toEVMCAddress(msg.code_address); + const code = vm.env.state.getCode(code_address); + var result = vm.evm.*.execute.?( + vm.evm, + @ptrCast(&vm.host), + @ptrCast(vm), + evmc.EVMC_SHANGHAI, // TODO: generalize from block_number. + msg, + code.code.ptr, + code.code.len, + ); + evmclog.debug("internal call exec result: status_code={}, gas_left={}", .{ result.status_code, result.gas_left }); + + // Restore previous context. + vm.accessed_accounts = prev_accessed_accounts; + vm.accessed_storage_keys = prev_accessed_storage_keys; + vm.env.state = prev_statedb; + + return result; } }; diff --git a/src/statedb/statedb.zig b/src/statedb/statedb.zig index 49bb53c..cdf882d 100644 --- a/src/statedb/statedb.zig +++ b/src/statedb/statedb.zig @@ -5,6 +5,8 @@ const Address = types.Address; const AccountState = types.AccountState; const log = std.log.scoped(.statedb); +// TODO: create container. + const StateDB = @This(); allocator: Allocator, db: AccountDB, @@ -25,33 +27,35 @@ pub fn init(allocator: Allocator, accounts_state: []AccountState) !StateDB { } // TODO: return a more focused parameter (balance, code, nonce) -pub fn getAccount(self: *StateDB, addr: Address) !?AccountState { - return try self.db.get(addr); +pub fn getAccount(self: *StateDB, addr: Address) ?AccountState { + return self.db.get(addr); } pub fn getStorage(self: *StateDB, addr: Address, key: u256) !u256 { - const account = try self.getAccount(addr) orelse return 0; + const account = self.getAccount(addr) orelse return 0; return try account.storage.get(key) orelse 0; } pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: u256) !void { - var account = try self.getAccount(addr) orelse return error.AccountDoesNotExist; + var account = self.getAccount(addr) orelse return error.AccountDoesNotExist; try account.storage.put(key, value); } pub fn setBalance(self: *StateDB, addr: Address, balance: u256) !void { - var account = try self.getAccount(addr); - account.balance = balance; + var account = self.db.getPtr(addr); + if (account) |acc| { + acc.balance = balance; + return; + } + try self.db.put(try AccountState.init(self.allocator, addr, 0, balance, &[_]u8{})); } pub fn incrementNonce(self: *StateDB, addr: Address) !void { - var account = try self.getAccount(addr); + var account = try self.getAccount(addr) orelse return error.AccountDoesNotExist; account.nonce += 1; } -pub fn getCode(self: *StateDB, addr: Address) !?[]const u8 { - var account = try self.getAccount(addr) orelse return null; +pub fn getCode(self: *StateDB, addr: Address) []const u8 { + var account = self.getAccount(addr) orelse &[_]u8{}; return account.code; } - -// TODO: tests. From 9fa5295d0c3ba710851965f9a97a332f877a25b7 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Mon, 8 Jan 2024 20:57:38 -0300 Subject: [PATCH 31/52] blockchain/vm: more progress Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 12 +++++++----- src/statedb/statedb.zig | 9 +++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 095d6d3..448b57b 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -323,7 +323,7 @@ const EVMOneHost = struct { defer vm.accessed_accounts.deinit(); vm.accessed_storage_keys = vm.accessed_storage_keys.clone(); defer vm.accessed_storage_keys.deinit(); - vm.env.state = vm.env.state.clone(); + vm.env.state = vm.env.state.snapshot(); defer vm.env.state.deinit(); // Send value. @@ -364,10 +364,12 @@ const EVMOneHost = struct { ); evmclog.debug("internal call exec result: status_code={}, gas_left={}", .{ result.status_code, result.gas_left }); - // Restore previous context. - vm.accessed_accounts = prev_accessed_accounts; - vm.accessed_storage_keys = prev_accessed_storage_keys; - vm.env.state = prev_statedb; + if (result.status_code != evmc.EVMC_SUCCESS) { + // Restore previous context. + vm.accessed_accounts = prev_accessed_accounts; + vm.accessed_storage_keys = prev_accessed_storage_keys; + vm.env.state = prev_statedb; + } return result; } diff --git a/src/statedb/statedb.zig b/src/statedb/statedb.zig index cdf882d..8de5da6 100644 --- a/src/statedb/statedb.zig +++ b/src/statedb/statedb.zig @@ -59,3 +59,12 @@ pub fn getCode(self: *StateDB, addr: Address) []const u8 { var account = self.getAccount(addr) orelse &[_]u8{}; return account.code; } + +pub fn snapshot(self: StateDB) StateDB { + // TODO: while simple this is quite inefficient. + // A much smarter way is doing some "diff" style snapshotting or similar. + return StateDB{ + .allocator = self.allocator, + .db = self.db.cloneWithAllocator(self.allocator), + }; +} From a8024619ce240a45b63699d53e5681fd4f89d425 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Mon, 8 Jan 2024 21:30:00 -0300 Subject: [PATCH 32/52] general: compilation fixes Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 85 +++++++++++++++------------ src/common/common.zig | 4 +- src/config/config.zig | 1 + src/exec-spec-tests/execspectests.zig | 16 ++--- src/signer/signer.zig | 2 +- src/types/transaction.zig | 2 +- 6 files changed, 60 insertions(+), 50 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index e5ae0bd..c87869a 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -54,16 +54,16 @@ pub const Blockchain = struct { allocator: Allocator, chain_id: config.ChainId, state: *StateDB, - prev_block: BlockHeader, + prev_block: ?BlockHeader, last_256_blocks_hashes: [256]Hash32, // blocks ordered in asc order pub fn init( allocator: Allocator, chain_id: config.ChainId, state: *StateDB, - prev_block: BlockHeader, + prev_block: ?BlockHeader, last_256_blocks_hashes: [256]Hash32, - ) void { + ) Blockchain { return Blockchain{ .allocator = allocator, .chain_id = chain_id, @@ -74,15 +74,16 @@ pub const Blockchain = struct { } pub fn runBlock(self: Blockchain, block: Block) !void { - try self.validateBlockHeader(self.allocator, self.prev_block, block.header); + try validateBlockHeader(self.allocator, self.prev_block, block.header); if (block.uncles.len != 0) return error.NotEmptyUncles; var arena = std.heap.ArenaAllocator.init(self.allocator); defer arena.deinit(); + const allocator = arena.allocator(); // Execute block. - var result = try applyBody(arena, self, block, self.state); + var result = try applyBody(allocator, self, self.state, block); // Post execution checks. if (result.gas_used != block.header.gas_used) @@ -101,51 +102,57 @@ pub const Blockchain = struct { // validateBlockHeader validates the header of a block itself and with respect with the parent. // If isn't valid, it returns an error. - fn validateBlockHeader(allocator: Allocator, prev_block: BlockHeader, curr_block: BlockHeader) !void { - try checkGasLimit(curr_block.gas_limit, prev_block.gas_limit); + fn validateBlockHeader(allocator: Allocator, parent_block: ?BlockHeader, curr_block: BlockHeader) !void { + if (parent_block) |prev_block| { + try checkGasLimit(curr_block.gas_limit, prev_block.gas_limit); + } if (curr_block.gas_used > curr_block.gas_limit) return error.GasLimitExceeded; // Check base fee. - const parent_gas_target = prev_block.gas_limit / ELASTICITY_MULTIPLIER; - var expected_base_fee_per_gas = if (prev_block.gas_used == parent_gas_target) - prev_block.base_fee_per_gas - else if (prev_block.gas_used > parent_gas_target) blk: { - const gas_used_delta = prev_block.gas_used - parent_gas_target; - const base_fee_per_gas_delta = @max(prev_block.base_fee_per_gas * gas_used_delta / parent_gas_target / BASE_FEE_MAX_CHANGE_DENOMINATOR, 1); - break :blk prev_block.base_fee_per_gas + base_fee_per_gas_delta; - } else blk: { - const gas_used_delta = parent_gas_target - prev_block.gas_used; - const base_fee_per_gas_delta = prev_block.base_fee_per_gas * gas_used_delta / parent_gas_target / BASE_FEE_MAX_CHANGE_DENOMINATOR; - break :blk prev_block.base_fee_per_gas - base_fee_per_gas_delta; - }; - if (expected_base_fee_per_gas != curr_block.base_fee_per_gas) - return error.InvalidBaseFee; + if (parent_block) |prev_block| { + const parent_gas_target = prev_block.gas_limit / ELASTICITY_MULTIPLIER; + var expected_base_fee_per_gas = if (prev_block.gas_used == parent_gas_target) + prev_block.base_fee_per_gas + else if (prev_block.gas_used > parent_gas_target) blk: { + const gas_used_delta = prev_block.gas_used - parent_gas_target; + const base_fee_per_gas_delta = @max(prev_block.base_fee_per_gas * gas_used_delta / parent_gas_target / BASE_FEE_MAX_CHANGE_DENOMINATOR, 1); + break :blk prev_block.base_fee_per_gas + base_fee_per_gas_delta; + } else blk: { + const gas_used_delta = parent_gas_target - prev_block.gas_used; + const base_fee_per_gas_delta = prev_block.base_fee_per_gas * gas_used_delta / parent_gas_target / BASE_FEE_MAX_CHANGE_DENOMINATOR; + break :blk prev_block.base_fee_per_gas - base_fee_per_gas_delta; + }; + if (expected_base_fee_per_gas != curr_block.base_fee_per_gas) + return error.InvalidBaseFee; - if (curr_block.timestamp > prev_block.timestamp) - return error.InvalidTimestamp; - if (curr_block.block_number != prev_block.block_number + 1) - return error.InvalidBlockNumber; + if (curr_block.timestamp > prev_block.timestamp) + return error.InvalidTimestamp; + if (curr_block.block_number != prev_block.block_number + 1) + return error.InvalidBlockNumber; + } if (curr_block.extra_data.len > 32) return error.ExtraDataTooLong; if (curr_block.difficulty != 0) return error.InvalidDifficulty; - if (curr_block.nonce == [_]u8{0} ** 8) + if (std.mem.eql(u8, &curr_block.nonce, &[_]u8{0} ** 8)) return error.InvalidNonce; - if (curr_block.ommers_hash != blocks.empty_uncle_hash) - return error.InvalidOmmersHash; + if (!std.mem.eql(u8, &curr_block.uncle_hash, &blocks.empty_uncle_hash)) + return error.InvalidUnclesHash; - const prev_block_hash = transaction.RLPHash(BlockHeader, allocator, prev_block, null); - if (curr_block.parent_hash != prev_block_hash) - return error.InvalidParentHash; + if (parent_block) |prev_block| { + const prev_block_hash = try transaction.RLPHash(BlockHeader, allocator, prev_block, null); + if (!std.mem.eql(u8, &curr_block.parent_hash, &prev_block_hash)) + return error.InvalidParentHash; + } } fn checkGasLimit(gas_limit: u256, parent_gas_limit: u256) !void { const max_delta = parent_gas_limit / GAS_LIMIT_ADJUSTMENT_FACTOR; if (gas_limit >= parent_gas_limit + max_delta) return error.GasLimitTooHigh; if (gas_limit <= parent_gas_limit - max_delta) return error.GasLimitTooLow; - return gas_limit >= GAS_LIMIT_MINIMUM; + if (gas_limit >= GAS_LIMIT_MINIMUM) return error.GasLimitLessThanMinimum; } const BlockExecutionResult = struct { @@ -156,13 +163,13 @@ pub const Blockchain = struct { withdrawals_root: Hash32, }; - fn applyBody(allocator: Allocator, chain: Blockchain, state: StateDB, block: Block) !BlockExecutionResult { + fn applyBody(allocator: Allocator, chain: Blockchain, state: *StateDB, block: Block) !BlockExecutionResult { var gas_available = block.header.gas_limit; for (block.transactions) |tx| { // TODO: add tx to txs tree. - const txn_info = checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, chain.chain_id); - const env = vm.Environment{ + const txn_info = try checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, chain.chain_id); + const env: Environment = .{ .caller = txn_info.sender_address, .origin = txn_info.sender_address, .block_hashes = chain.last_256_blocks_hashes, @@ -199,12 +206,12 @@ pub const Blockchain = struct { }; } - fn checkTransaction(allocator: Allocator, tx: transaction.Txn, base_fee_per_gas: u64, gas_available: u64, chain_id: u64) !struct { sender_address: Address, effective_gas_price: config.ChainId } { + fn checkTransaction(allocator: Allocator, tx: transaction.Txn, base_fee_per_gas: u256, gas_available: u64, chain_id: config.ChainId) !struct { sender_address: Address, effective_gas_price: u256 } { if (tx.getGasLimit() > gas_available) return error.InsufficientGas; const txn_signer = try signer.TxnSigner.init(@intFromEnum(chain_id)); - const sender_address = txn_signer.get_sender(allocator, tx); + const sender_address = try txn_signer.get_sender(allocator, tx); const effective_gas_price = switch (tx) { .FeeMarketTxn => |fm_tx| blk: { @@ -213,7 +220,7 @@ pub const Blockchain = struct { if (fm_tx.max_fee_per_gas < base_fee_per_gas) return error.MaxFeePerGasLowerThanBaseFee; - const priority_fee_per_gas = @min(tx.max_priority_fee_per_gas, tx.max_fee_per_gas - base_fee_per_gas); + const priority_fee_per_gas = @min(fm_tx.max_priority_fee_per_gas, fm_tx.max_fee_per_gas - base_fee_per_gas); break :blk priority_fee_per_gas + base_fee_per_gas; }, .LegacyTxn, .AccessListTxn => blk: { @@ -233,7 +240,7 @@ pub const Blockchain = struct { number: u64, base_fee_per_gas: u256, gas_limit: u64, - gas_price: u64, + gas_price: u256, time: u256, prev_randao: Bytes32, state: *StateDB, diff --git a/src/common/common.zig b/src/common/common.zig index 33add51..4a02c69 100644 --- a/src/common/common.zig +++ b/src/common/common.zig @@ -8,7 +8,7 @@ pub const prefixedhex2u64 = hexutils.prefixedhex2u64; pub const hexToAddress = hexutils.hexToAddress; pub const comptimeHexToBytes = hexutils.comptimeHexToBytes; -pub const AddressSet = std.HashMap(types.Address, void); +pub const AddressSet = std.AutoHashMap(types.Address, void); pub const AddressKey = struct { address: types.Address, key: types.Bytes32 }; -pub const AddressKeySet = std.HashMap(AddressKey, void); +pub const AddressKeySet = std.AutoHashMap(AddressKey, void); diff --git a/src/config/config.zig b/src/config/config.zig index 0309c28..3cb10fe 100644 --- a/src/config/config.zig +++ b/src/config/config.zig @@ -1,4 +1,5 @@ pub const ChainId = enum(u64) { + SpecTest = 0, Mainnet = 1, Goerli = 5, Holesky = 17000, diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index c459b0e..f204fe8 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -1,18 +1,20 @@ const std = @import("std"); const rlp = @import("rlp"); -const Allocator = std.mem.Allocator; const config = @import("../config/config.zig"); const types = @import("../types/types.zig"); +const blockchain = @import("../blockchain/blockchain.zig"); const vm = @import("../blockchain/vm.zig"); +const ecdsa = @import("../crypto/ecdsa.zig"); +const Allocator = std.mem.Allocator; const Address = types.Address; const AccountState = types.AccountState; const Block = types.Block; const BlockHeader = types.BlockHeader; const Txn = types.Txn; +const Hash32 = types.Hash32; const VM = vm.VM; const StateDB = @import("../statedb/statedb.zig"); const TxnSigner = @import("../signer/signer.zig").TxnSigner; -const ecdsa = @import("../crypto/ecdsa.zig"); const log = std.log.scoped(.execspectests); const HexString = []const u8; @@ -21,10 +23,10 @@ pub const Fixture = struct { const FixtureType = std.json.ArrayHashMap(FixtureTest); tests: std.json.Parsed(FixtureType), - pub fn new_from_bytes(allocator: Allocator, bytes: []const u8) !Fixture { + pub fn newFromBytes(allocator: Allocator, bytes: []const u8) !Fixture { const tests = try std.json.parseFromSlice(FixtureType, allocator, bytes, std.json.ParseOptions{ .ignore_unknown_fields = true, .allocate = std.json.AllocWhen.alloc_always }); - return Fixture{ .tests = tests }; + return .{ .tests = tests }; } pub fn deinit(self: *Fixture) void { @@ -69,7 +71,6 @@ pub const FixtureTest = struct { break :blk accounts_state; }; var statedb = try StateDB.init(allocator, accounts_state); - var evm = VM.init(&statedb); // 2. Execute blocks. const txn_signer = try TxnSigner.init(0); // ChainID == 0 is used in tests. @@ -86,7 +87,8 @@ pub const FixtureTest = struct { txns[i] = try tx_hex.to_vm_transaction(allocator, txn_signer); } - try evm.run_block(allocator, txn_signer, block, txns); + var chain = blockchain.Blockchain.init(allocator, config.ChainId.SpecTest, &statedb, null, std.mem.zeroes([256]Hash32)); + try chain.runBlock(block); } // 3. Verify that the post state matches what the fixture `postState` claims is true. @@ -216,7 +218,7 @@ const AccountStorageHex = std.json.ArrayHashMap(HexString); var test_allocator = std.testing.allocator; test "execution-spec-tests" { - var ft = try Fixture.new_from_bytes(test_allocator, @embedFile("fixtures/exec-spec-fixture.json")); + var ft = try Fixture.newFromBytes(test_allocator, @embedFile("fixtures/exec-spec-fixture.json")); defer ft.deinit(); var it = ft.tests.value.map.iterator(); diff --git a/src/signer/signer.zig b/src/signer/signer.zig index aa408f6..bc117cf 100644 --- a/src/signer/signer.zig +++ b/src/signer/signer.zig @@ -107,7 +107,7 @@ pub const TxnSigner = struct { chain_id: u64, nonce: u256, max_priority_fee_per_gas: u64, - max_fee_per_gas: u64, + max_fee_per_gas: u256, gas: u64, to: ?Address, value: u256, diff --git a/src/types/transaction.zig b/src/types/transaction.zig index ec0ba90..d1f2b91 100644 --- a/src/types/transaction.zig +++ b/src/types/transaction.zig @@ -209,7 +209,7 @@ pub const FeeMarketTxn = struct { chain_id: u64, nonce: u64, max_priority_fee_per_gas: u64, - max_fee_per_gas: u64, + max_fee_per_gas: u256, gas: u64, to: ?Address, value: u256, From 3175eecae3fc5b633d6270f5444524b0c35bd572 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 9 Jan 2024 10:35:06 -0300 Subject: [PATCH 33/52] general fixes Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 142 ++++++++++++++------------ src/blockchain/vm.zig | 30 +++--- src/crypto/ecdsa.zig | 10 +- src/exec-spec-tests/execspectests.zig | 20 ++-- src/statedb/statedb.zig | 121 +++++++++++++--------- src/types/account_state.zig | 4 +- 6 files changed, 179 insertions(+), 148 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index c87869a..b2d0850 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -14,11 +14,12 @@ const AddressKeySet = common.AddressKeySet; const LogsBloom = types.LogsBloom; const Block = types.Block; const BlockHeader = types.BlockHeader; -const StateDB = @import("../statedb/statedb.zig"); +const StateDB = @import("../statedb/statedb.zig").StateDB; const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; const Address = types.Address; const VM = vm.VM; +const Keccak256 = std.crypto.hash.sha3.Keccak256; pub const Blockchain = struct { const BASE_FEE_MAX_CHANGE_DENOMINATOR = 8; @@ -88,16 +89,21 @@ pub const Blockchain = struct { // Post execution checks. if (result.gas_used != block.header.gas_used) return error.InvalidGasUsed; - if (result.transactions_root != block.header.transactions_root) - return error.InvalidTransactionsRoot; - if (result.receipts_root != block.header.receipts_root) - return error.InvalidReceiptsRoot; - if (result.state.root() != block.header.state_root) - return error.InvalidStateRoot; - if (result.logs_bloom != block.header.logs_bloom) - return error.InvalidLogsBloom; - if (result.withdrawals_root != block.header.withdrawals_root) - return error.InvalidWithdrawalsRoot; + // TODO: disabled until txs root is calculated + // if (!std.mem.eql(u8, &result.transactions_root, &block.header.transactions_root)) + // return error.InvalidTransactionsRoot; + // TODO: disabled until receipts root is calculated + // if (!std.mem.eql(u8, &result.receipts_root, &block.header.receipts_root)) + // return error.InvalidReceiptsRoot; + // TODO: disabled until state root is calculated + // if (!std.mem.eql(u8, &self.state.root(), &block.header.state_root)) + // return error.InvalidStateRoot; + // TODO: disabled until logs bloom are calculated + // if (!std.mem.eql(u8, &result.logs_bloom, &block.header.logs_bloom)) + // return error.InvalidLogsBloom; + // TODO: disabled until withdrawals root is calculated + // if (!std.mem.eql(u8, &result.withdrawals_root, &block.header.withdrawals_root)) + // return error.InvalidWithdrawalsRoot; } // validateBlockHeader validates the header of a block itself and with respect with the parent. @@ -247,47 +253,48 @@ pub const Blockchain = struct { chain_id: config.ChainId, }; - fn processTransaction(allocator: Allocator, env: Environment, tx: transaction.Txn) !struct { gas_left: u64 } { + fn processTransaction(allocator: Allocator, env: Environment, tx: transaction.Txn) !struct { gas_used: u64 } { if (!validateTransaction(tx)) return error.InvalidTransaction; const sender = env.origin; - const sender_account = try env.state.getAccount(sender); + const sender_account = env.state.getAccount(sender); const gas_fee = tx.getGasLimit() * tx.getGasPrice(); - if (sender_account.nonce != tx.nonce) + if (sender_account.nonce != tx.getNonce()) return error.InvalidTxnNonce; - if (sender_account.balance < gas_fee + tx.value) + if (sender_account.balance < gas_fee + tx.getValue()) return error.NotEnoughBalance; - if (sender_account.code != null) + if (sender_account.code.len > 0) return error.SenderIsNotEOA; const effective_gas_fee = tx.getGasLimit() * env.gas_price; const gas = tx.getGasLimit() - calculateIntrinsicCost(tx); - env.state.incrementNonce(sender); + try env.state.incrementNonce(sender); const sender_balance_after_gas_fee = sender_account.balance - effective_gas_fee; - env.state.setBalance(sender, sender_balance_after_gas_fee); + try env.state.setBalance(sender, sender_balance_after_gas_fee); var preaccessed_addresses = AddressSet.init(allocator); defer preaccessed_addresses.deinit(); - var preaccessed_stoarge_keys = AddressKeySet.init(allocator); - defer preaccessed_stoarge_keys.deinit(); - preaccessed_addresses.put(env.coinbase, null); + var preaccessed_storage_keys = AddressKeySet.init(allocator); + defer preaccessed_storage_keys.deinit(); + try preaccessed_addresses.put(env.coinbase, {}); switch (tx) { .LegacyTxn => {}, - inline else => { - for (tx.access_list) |al| { - preaccessed_addresses.put(al.address, null); + inline else => |al_tx| { + for (al_tx.access_list) |al| { + try preaccessed_addresses.put(al.address, {}); for (al.storage_keys) |key| { - preaccessed_stoarge_keys.put(.{ .address = al.address, .key = key }, null); + try preaccessed_storage_keys.put(.{ .address = al.address, .key = key }, {}); } } }, } - const message = try prepareMessage( + var message = try prepareMessage( + allocator, sender, tx.getTo(), tx.getValue(), @@ -295,10 +302,10 @@ pub const Blockchain = struct { gas, env, preaccessed_addresses, - preaccessed_stoarge_keys, + preaccessed_storage_keys, ); defer message.deinit(); - const output = processMessageCall(message, env); + const output = try processMessageCall(allocator, message, env); const gas_used = tx.getGasLimit() - output.gas_left; const gas_refund = @min(gas_used / 5, output.refund_counter); @@ -309,26 +316,22 @@ pub const Blockchain = struct { const total_gas_used = gas_used - gas_refund; const sender_balance_after_refund = sender_account.balance + gas_refund_amount; - env.state.setBalance(sender, sender_balance_after_refund); + try env.state.setBalance(sender, sender_balance_after_refund); - const coinbase_account = try env.state.getAccount(env.coinbase); + const coinbase_account = env.state.getAccount(env.coinbase); const coinbase_balance_after_mining_fee = coinbase_account.balance + transaction_fee; if (coinbase_balance_after_mining_fee != 0) { - env.state.setBalance(env.coinbase, coinbase_balance_after_mining_fee); + try env.state.setBalance(env.coinbase, coinbase_balance_after_mining_fee); } else if (env.state.accountExistsAndIsEmpty(env.coinbase)) { env.state.destroyAccount(env.coinbase); } - // Account destruction is already managed by EVMC `selfdestruct(...)` callback. - - for (output.touched_accounts) |address| { - if (env.state.accountExistsAndIsEmpty(address)) { - env.state.destroyAccount(address); - } - } + // TODO: self destruct processing + // for address in output.accounts_to_delete: + // destroy_account(env.state, address) - return .{ total_gas_used, output.logs, output.err }; + return .{ .gas_used = total_gas_used }; } fn validateTransaction(tx: transaction.Txn) bool { @@ -336,18 +339,19 @@ pub const Blockchain = struct { return false; if (tx.getNonce() >= (2 << 64) - 1) return false; - if (tx.getTo() == null and tx.data.len > 2 * MAX_CODE_SIZE) + if (tx.getTo() == null and tx.getData().len > 2 * MAX_CODE_SIZE) return false; return true; } fn calculateIntrinsicCost(tx: transaction.Txn) u64 { var data_cost: u64 = 0; - for (tx.data) |byte| { + const data = tx.getData(); + for (data) |byte| { data_cost += if (byte == 0) TX_DATA_COST_PER_ZERO else TX_DATA_COST_PER_NON_ZERO; } - const create_cost = if (tx.to == null) TX_CREATE_COST + initCodeCost(tx.data.len) else 0; + const create_cost = if (tx.getTo() == null) TX_CREATE_COST + initCodeCost(data.len) else 0; const access_list_cost = switch (tx) { .LegacyTxn => 0, @@ -365,7 +369,7 @@ pub const Blockchain = struct { } fn initCodeCost(code_length: usize) u64 { - return GAS_INIT_CODE_WORD_COST * @ceil(code_length / 32); + return GAS_INIT_CODE_WORD_COST * (code_length + 31) / 32; } pub const Message = struct { @@ -391,37 +395,37 @@ pub const Blockchain = struct { // prepareMessage prepares an EVM message. // The caller must call deinit() on the returned Message. fn prepareMessage( + allocator: Allocator, caller: Address, target: ?Address, value: u256, data: []const u8, gas: u64, env: Environment, - code_address: ?Address, preaccessed_addresses: AddressSet, preaccessed_storage_keys: AddressKeySet, ) !Message { var current_target: Address = undefined; + var code_address: Address = undefined; var msg_data: []const u8 = undefined; var code: []const u8 = undefined; - if (target == null) { - current_target = try computeContractAddress(caller, env.state.getAccount(caller).nonce - 1); + if (target) |targ| { + current_target = targ; + msg_data = data; + code = env.state.getAccount(targ).code; + code_address = targ; + } else { + current_target = try computeContractAddress(allocator, caller, env.state.getAccount(caller).nonce - 1); msg_data = &[_]u8{0}; code = data; - } else { - current_target = target; - msg_data = data; - code = env.state.getAccount(target).code; - if (code_address == null) - code_address = target; } var accessed_addresses = try preaccessed_addresses.clone(); - try accessed_addresses.put(current_target); - try accessed_addresses.put(caller); + try accessed_addresses.put(current_target, {}); + try accessed_addresses.put(caller, {}); for (PRE_COMPILED_CONTRACT_ADDRESSES) |address| { - try accessed_addresses.put(address); + try accessed_addresses.put(address, {}); } return .{ @@ -433,7 +437,6 @@ pub const Blockchain = struct { .data = msg_data, .code_address = code_address, .code = code, - .depth = 0, .accessed_addresses = accessed_addresses, .accessed_storage_keys = try preaccessed_storage_keys.clone(), }; @@ -442,31 +445,36 @@ pub const Blockchain = struct { fn computeContractAddress(allocator: Allocator, address: Address, nonce: u64) !Address { var out = std.ArrayList(u8).init(allocator); defer out.deinit(); - try rlp.serialize(struct { addr: Address, nonce: u64 }, allocator, .{ address, nonce }, out); - const computed_address = std.crypto.hash.sha3.Keccak256(out.items); - const canonical_address = computed_address[12..]; + try rlp.serialize(struct { addr: Address, nonce: u64 }, allocator, .{ .addr = address, .nonce = nonce }, &out); + + var computed_address: [Keccak256.digest_length]u8 = undefined; + Keccak256.hash(out.items, &computed_address, .{}); + var padded_address: Address = std.mem.zeroes(Address); - @memcpy(padded_address[12..], canonical_address); + @memcpy(&padded_address, computed_address[12..]); + return padded_address; } const MessageCallOutput = struct { gas_left: u64, - refund_counter: u256, + refund_counter: u64, // logs: Union[Tuple[()], Tuple[Log, ...]] TODO // accounts_to_delete: AddressKeySet, // TODO (delete?) // error: Optional[Exception] TODO }; - fn processMessageCall(message: Message, env: Environment) !MessageCallOutput { - const vm_instance = VM.init(env); + fn processMessageCall(allocator: Allocator, message: Message, env: Environment) !MessageCallOutput { + var vm_instance = VM.init(env); defer vm_instance.deinit(); - const result = try vm_instance.processMessageCall(message); - defer result.release(); + const result = try vm_instance.processMessageCall(allocator, message); + defer { + if (result.release) |release| release(&result); + } return .{ - .gas_left = result.gas_left, - .refund_counter = result.gas_refund, + .gas_left = @intCast(result.gas_left), + .refund_counter = @intCast(result.gas_refund), // .accounts_to_delete = AddressKeySet, }; } diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 448b57b..a794cb8 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -75,10 +75,9 @@ pub const VM = struct { } // processMessageCall executes a message call. - pub fn processMessageCall(self: *VM, msg: Message, allocator: Allocator) !evmc.struct_evmc_result { - const kind = if (msg.target) evmc.EVMC_CALL orelse evmc.EVMC_CREATE; - const evmc_message = evmc.struct_evmc_message{ - .kind = kind, + pub fn processMessageCall(self: *VM, allocator: Allocator, msg: Message) !evmc.struct_evmc_result { + const evmc_message: evmc.struct_evmc_message = .{ + .kind = if (msg.target != null) evmc.EVMC_CALL else evmc.EVMC_CREATE, .flags = 0, .depth = 0, .gas = @intCast(msg.gas), @@ -99,9 +98,9 @@ pub const VM = struct { // Quite honestly, it will be better to avoid creating the sets at the caller level and do it here // which would make the ownership problem disappear and avoid the clone. It's a very cheap // clone, but also is simple to avoid it. - self.accessed_accounts = msg.accessed_addresses.cloneWithAllocator(allocator); + self.accessed_accounts = try msg.accessed_addresses.cloneWithAllocator(allocator); defer self.accessed_accounts.deinit(); - self.accessed_storage_keys = msg.accessed_storage_keys.cloneWithAllocator(allocator); + self.accessed_storage_keys = try msg.accessed_storage_keys.cloneWithAllocator(allocator); defer self.accessed_storage_keys.deinit(); const result = EVMOneHost.call(@ptrCast(self), @ptrCast(&evmc_message)); @@ -270,13 +269,14 @@ const EVMOneHost = struct { } fn access_account(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) evmc.enum_evmc_access_status { - evmclog.debug("accessAccount addr=0x{}", .{fmtSliceHexLower(&addr)}); + const address = fromEVMCAddress(addr.*); + evmclog.debug("accessAccount addr=0x{}", .{fmtSliceHexLower(&address)}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const address = fromEVMCAddress(addr); if (vm.accessed_accounts.contains(address)) return evmc.EVMC_ACCESS_WARM; - try vm.accessed_accounts.fetchPut(address, null); + _ = vm.accessed_accounts.fetchPut(address, {}) catch @panic("OOO"); + return evmc.EVMC_ACCESS_COLD; } @@ -285,18 +285,20 @@ const EVMOneHost = struct { addr: [*c]const evmc.evmc_address, key: [*c]const evmc.evmc_bytes32, ) callconv(.C) evmc.enum_evmc_access_status { - evmclog.debug("accessStorage addr=0x{} key=0x{}", .{ fmtSliceHexLower(addr), fmtSliceHexLower(key) }); + const address = fromEVMCAddress(addr.*); + evmclog.debug("accessStorage addr=0x{} key=0x{}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes) }); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const address_key: AddressKey = .{ .address = fromEVMCAddress(addr), .key = key.*.bytes }; - if (vm.accessed_accounts.contains(address_key)) + const address_key: AddressKey = .{ .address = address, .key = key.*.bytes }; + if (vm.accessed_storage_keys.contains(address_key)) return evmc.EVMC_ACCESS_WARM; - try vm.accessed_accounts.fetchPut(address_key, null); + _ = vm.accessed_storage_keys.fetchPut(address_key, {}) catch @panic("OOO"); + return evmc.EVMC_ACCESS_COLD; } fn call(ctx: ?*evmc.struct_evmc_host_context, msg: [*c]const evmc.struct_evmc_message) callconv(.C) evmc.struct_evmc_result { - evmclog.debug("call() kind={} depth={d} sender={} recipient={}", .{ msg.kind, msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); // TODO(jsign): explore creating custom formatter? + evmclog.debug("call() kind={d} depth={d} sender={} recipient={}", .{ msg.kind, msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); // TODO(jsign): explore creating custom formatter? const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); diff --git a/src/crypto/ecdsa.zig b/src/crypto/ecdsa.zig index 726bec7..57c0f1f 100644 --- a/src/crypto/ecdsa.zig +++ b/src/crypto/ecdsa.zig @@ -26,11 +26,11 @@ pub const Signer = struct { }; // The following test values where generated using geth, as a reference. -const hashed_msg = common.comptimeHexToBytes("0x05e0e0ff09b01e5626daac3165b82afa42be29197b82e8a5a8800740ee7519d2"); -const private_key = common.comptimeHexToBytes("0xf457cd3bd0186e342d243ea40ad78fe8e81743f90852e87074e68d8c94c2a42e"); -const signature = common.comptimeHexToBytes("0x5a62891eb3e26f3a2344f93a7bad7fe5e670dc45cbdbf0e5bbdba4399238b5e6614caf592f96ee273a2bf018a976e7bf4b63777f9e53ce819d96c5035611400600"); -const uncompressed_pubkey = common.comptimeHexToBytes("0x04682bade67348db99074fcaaffef29394192e7e227a2bdb49f930c74358060c6a42df70f7ef8aadd94854abe646e047142fad42811e325afbec4753342d630b1e"); -const compressed_pubkey = common.comptimeHexToBytes("0x02682bade67348db99074fcaaffef29394192e7e227a2bdb49f930c74358060c6a"); +const hashed_msg = common.comptimeHexToBytes("05e0e0ff09b01e5626daac3165b82afa42be29197b82e8a5a8800740ee7519d2"); +const private_key = common.comptimeHexToBytes("f457cd3bd0186e342d243ea40ad78fe8e81743f90852e87074e68d8c94c2a42e"); +const signature = common.comptimeHexToBytes("5a62891eb3e26f3a2344f93a7bad7fe5e670dc45cbdbf0e5bbdba4399238b5e6614caf592f96ee273a2bf018a976e7bf4b63777f9e53ce819d96c5035611400600"); +const uncompressed_pubkey = common.comptimeHexToBytes("04682bade67348db99074fcaaffef29394192e7e227a2bdb49f930c74358060c6a42df70f7ef8aadd94854abe646e047142fad42811e325afbec4753342d630b1e"); +const compressed_pubkey = common.comptimeHexToBytes("02682bade67348db99074fcaaffef29394192e7e227a2bdb49f930c74358060c6a"); test "erecover" { const signer = try Signer.init(); diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index f204fe8..4ca82d9 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -13,7 +13,7 @@ const BlockHeader = types.BlockHeader; const Txn = types.Txn; const Hash32 = types.Hash32; const VM = vm.VM; -const StateDB = @import("../statedb/statedb.zig"); +const StateDB = @import("../statedb/statedb.zig").StateDB; const TxnSigner = @import("../signer/signer.zig").TxnSigner; const log = std.log.scoped(.execspectests); @@ -96,21 +96,21 @@ pub const FixtureTest = struct { while (it.next()) |entry| { var exp_account_state: AccountState = try entry.value_ptr.*.to_vm_accountstate(allocator, entry.key_ptr.*); std.debug.print("checking account state: {s}\n", .{std.fmt.fmtSliceHexLower(&exp_account_state.addr)}); - const got_account_state = try statedb.getAccount(exp_account_state.addr); - if (!std.mem.eql(u8, &got_account_state.addr, &exp_account_state.addr)) { - return error.post_state_addr_mismatch; - } + const got_account_state = statedb.getAccount(exp_account_state.addr); if (got_account_state.nonce != exp_account_state.nonce) { log.err("expected nonce {d} but got {d}", .{ exp_account_state.nonce, got_account_state.nonce }); - return error.post_state_nonce_mismatch; + return error.PostStateNonceMismatch; } if (got_account_state.balance != exp_account_state.balance) { log.err("expected balance {d} but got {d}", .{ exp_account_state.balance, got_account_state.balance }); - return error.post_state_balance_mismatch; + return error.PostStateBalanceMismatch; } - if (got_account_state.storage.count() != exp_account_state.storage.count()) { - return error.post_state_storage_size_mismatch; + + const got_storage = statedb.getAllStorage(exp_account_state.addr) orelse return error.PostStateAccountMustExist; + if (got_storage.count() != exp_account_state.storage.count()) { + return error.PostStateStorageCountMismatch; } + // TODO: check each storage entry matches. } // TODO(jsign): verify gas used. @@ -189,7 +189,7 @@ pub const AccountStateHex = struct { // TODO(jsign): add init() and add assertions about lengths. pub fn to_vm_accountstate(self: *const AccountStateHex, allocator: Allocator, addr_hex: []const u8) !AccountState { - const nonce = try std.fmt.parseInt(u256, self.nonce[2..], 16); + const nonce = try std.fmt.parseInt(u64, self.nonce[2..], 16); const balance = try std.fmt.parseInt(u256, self.balance[2..], 16); var code = try allocator.alloc(u8, self.code[2..].len / 2); diff --git a/src/statedb/statedb.zig b/src/statedb/statedb.zig index 8de5da6..3d16edd 100644 --- a/src/statedb/statedb.zig +++ b/src/statedb/statedb.zig @@ -5,66 +5,87 @@ const Address = types.Address; const AccountState = types.AccountState; const log = std.log.scoped(.statedb); -// TODO: create container. +pub const StateDB = struct { + pub const AccountData = struct { + nonce: u64, + balance: u256, + code: []const u8, + }; + const AccountDB = std.AutoHashMap(Address, AccountState); -const StateDB = @This(); -allocator: Allocator, -db: AccountDB, + allocator: Allocator, + db: AccountDB, -const AccountDB = std.AutoHashMap(Address, AccountState); + pub fn init(allocator: Allocator, accounts: []AccountState) !StateDB { + var db = AccountDB.init(allocator); + try db.ensureTotalCapacity(@intCast(accounts.len)); + for (accounts) |account| { + db.putAssumeCapacityNoClobber(account.addr, account); + } + return .{ .allocator = allocator, .db = db }; + } -pub fn init(allocator: Allocator, accounts_state: []AccountState) !StateDB { - var db = AccountDB.init(allocator); - try db.ensureTotalCapacity(@intCast(accounts_state.len)); + pub fn getAccountOpt(self: *StateDB, addr: Address) ?AccountData { + const account_data = self.db.get(addr) orelse return null; + return .{ + .nonce = account_data.nonce, + .balance = account_data.balance, + .code = account_data.code, + }; + } - for (accounts_state) |account| { - db.putAssumeCapacityNoClobber(account.addr, account); + pub fn getAccount(self: *StateDB, addr: Address) AccountData { + return self.getAccountOpt(addr) orelse AccountData{ + .nonce = 0, + .balance = 0, + .code = &[_]u8{}, + }; } - return StateDB{ - .allocator = allocator, - .db = db, - }; -} -// TODO: return a more focused parameter (balance, code, nonce) -pub fn getAccount(self: *StateDB, addr: Address) ?AccountState { - return self.db.get(addr); -} + pub fn getStorage(self: *StateDB, addr: Address, key: u256) !u256 { + const account = self.db.get(addr) orelse return 0; + return try account.storage.get(key) orelse 0; + } -pub fn getStorage(self: *StateDB, addr: Address, key: u256) !u256 { - const account = self.getAccount(addr) orelse return 0; - return try account.storage.get(key) orelse 0; -} + pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: u256) !void { + var account = self.db.get(addr) orelse return error.AccountDoesNotExist; + try account.storage.put(key, value); + } -pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: u256) !void { - var account = self.getAccount(addr) orelse return error.AccountDoesNotExist; - try account.storage.put(key, value); -} + pub fn setBalance(self: *StateDB, addr: Address, balance: u256) !void { + var account = self.db.getPtr(addr); + if (account) |acc| { + acc.balance = balance; + return; + } + try self.db.put(addr, try AccountState.init(self.allocator, addr, 0, balance, &[_]u8{})); + } -pub fn setBalance(self: *StateDB, addr: Address, balance: u256) !void { - var account = self.db.getPtr(addr); - if (account) |acc| { - acc.balance = balance; - return; + pub fn incrementNonce(self: *StateDB, addr: Address) !void { + var account = self.db.getPtr(addr) orelse return error.AccountDoesNotExist; + account.nonce += 1; } - try self.db.put(try AccountState.init(self.allocator, addr, 0, balance, &[_]u8{})); -} -pub fn incrementNonce(self: *StateDB, addr: Address) !void { - var account = try self.getAccount(addr) orelse return error.AccountDoesNotExist; - account.nonce += 1; -} + pub fn destroyAccount(self: *StateDB, addr: Address) void { + self.db.remove(addr); + } -pub fn getCode(self: *StateDB, addr: Address) []const u8 { - var account = self.getAccount(addr) orelse &[_]u8{}; - return account.code; -} + pub fn accountExistsAndIsEmpty(self: *StateDB, addr: Address) bool { + const account = self.db.get(addr) orelse return false; + return account.nonce == 0 and account.balance == 0 and account.code.len == 0; + } -pub fn snapshot(self: StateDB) StateDB { - // TODO: while simple this is quite inefficient. - // A much smarter way is doing some "diff" style snapshotting or similar. - return StateDB{ - .allocator = self.allocator, - .db = self.db.cloneWithAllocator(self.allocator), - }; -} + pub fn snapshot(self: StateDB) StateDB { + // TODO: while simple this is quite inefficient. + // A much smarter way is doing some "diff" style snapshotting or similar. + return StateDB{ + .allocator = self.allocator, + .db = self.db.cloneWithAllocator(self.allocator), + }; + } + + pub fn getAllStorage(self: *StateDB, addr: Address) ?std.AutoHashMap(u256, u256) { + const account = self.db.get(addr) orelse return null; + return account.storage; + } +}; diff --git a/src/types/account_state.zig b/src/types/account_state.zig index c540451..6f7b588 100644 --- a/src/types/account_state.zig +++ b/src/types/account_state.zig @@ -12,14 +12,14 @@ const AccountState = @This(); allocator: Allocator, addr: Address, -nonce: u256, +nonce: u64, balance: u256, code: Bytecode, storage: std.AutoHashMap(u256, u256), // init initializes an account state with the given values. // deinit() must be called on the account state to free the storage. -pub fn init(allocator: Allocator, addr: Address, nonce: u256, balance: u256, code: Bytecode) !AccountState { +pub fn init(allocator: Allocator, addr: Address, nonce: u64, balance: u256, code: Bytecode) !AccountState { return AccountState{ .allocator = allocator, .addr = addr, From 90d168fcc3a3638975b3c7766da996a2930c5b6f Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 9 Jan 2024 12:28:25 -0300 Subject: [PATCH 34/52] more compilation fixes and refactors Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 4 +- src/blockchain/vm.zig | 139 ++++++++++++-------------- src/exec-spec-tests/execspectests.zig | 11 +- src/main.zig | 2 +- src/{statedb => state}/statedb.zig | 29 +++--- src/state/types.zig | 38 +++++++ src/types/account_state.zig | 63 ------------ src/types/types.zig | 4 - 8 files changed, 130 insertions(+), 160 deletions(-) rename src/{statedb => state}/statedb.zig (84%) create mode 100644 src/state/types.zig delete mode 100644 src/types/account_state.zig diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index b2d0850..db6bacc 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -14,7 +14,7 @@ const AddressKeySet = common.AddressKeySet; const LogsBloom = types.LogsBloom; const Block = types.Block; const BlockHeader = types.BlockHeader; -const StateDB = @import("../statedb/statedb.zig").StateDB; +const StateDB = @import("../state/statedb.zig").StateDB; const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; const Address = types.Address; @@ -247,7 +247,7 @@ pub const Blockchain = struct { base_fee_per_gas: u256, gas_limit: u64, gas_price: u256, - time: u256, + time: u64, prev_randao: Bytes32, state: *StateDB, chain_id: config.ChainId, diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index a794cb8..7c8e88d 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -19,7 +19,7 @@ const AccountState = types.AccountState; const Bytecode = types.Bytecode; const Hash32 = types.Hash32; const Address = types.Address; -const StateDB = @import("../statedb/statedb.zig"); +const StateDB = @import("../state/statedb.zig").StateDB; const Keccak256 = std.crypto.hash.sha3.Keccak256; const fmtSliceHexLower = std.fmt.fmtSliceHexLower; const assert = std.debug.assert; @@ -70,8 +70,9 @@ pub const VM = struct { // deinit destroys a VM instance. pub fn deinit(self: *VM) void { - self.evm.destroy(); - self.evm = undefined; + if (self.evm.*.destroy) |destroy| { + destroy(self.evm); + } } // processMessageCall executes a message call. @@ -119,15 +120,15 @@ const EVMOneHost = struct { const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); // TODO: alignCast needed? return evmc.struct_evmc_tx_context{ - .tx_gas_price = toEVMCBytes32(vm.env.?.txn.gas_price), - .tx_origin = toEVMCAddress(vm.env.?.txn.from), - .block_coinbase = toEVMCAddress(vm.env.?.block.coinbase), - .block_number = @intCast(vm.env.?.block.number), - .block_timestamp = @intCast(vm.env.?.block.timestamp), - .block_gas_limit = @intCast(vm.env.?.block.gas_limit), - .block_prev_randao = toEVMCBytes32(vm.env.?.block.prev_randao), - .chain_id = toEVMCBytes32(vm.env.?.txn.chain_id), - .block_base_fee = toEVMCBytes32(vm.env.?.block.base_fee), + .tx_gas_price = toEVMCUint256Be(vm.env.gas_price), + .tx_origin = toEVMCAddress(vm.env.origin), + .block_coinbase = toEVMCAddress(vm.env.coinbase), + .block_number = @intCast(vm.env.number), + .block_timestamp = @intCast(vm.env.time), + .block_gas_limit = @intCast(vm.env.gas_limit), + .block_prev_randao = .{ .bytes = vm.env.prev_randao }, + .chain_id = toEVMCUint256Be(@intFromEnum(vm.env.chain_id)), + .block_base_fee = toEVMCUint256Be(vm.env.base_fee_per_gas), }; } @@ -135,7 +136,7 @@ const EVMOneHost = struct { evmclog.debug("get_tx_context block_number={}", .{block_number}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const idx = vm.env.number - block_number; + const idx = vm.env.number - @as(u64, @intCast(block_number)); if (idx < 0 or idx >= vm.env.block_hashes.len) { return std.mem.zeroes(evmc.evmc_bytes32); } @@ -147,7 +148,7 @@ const EVMOneHost = struct { evmclog.debug("account_exists addr=0x{}", .{fmtSliceHexLower(&address)}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - return try vm.env.state.getAccount() != null; + return vm.env.state.getAccountOpt(address) != null; } fn get_storage( @@ -155,12 +156,12 @@ const EVMOneHost = struct { addr: [*c]const evmc.evmc_address, key: [*c]const evmc.evmc_bytes32, ) callconv(.C) evmc.evmc_bytes32 { - evmclog.debug("get_storage addr=0x{} key={}", .{ fmtSliceHexLower(&addr), fmtSliceHexLower(&key.*) }); + const address = fromEVMCAddress(addr.*); + evmclog.debug("get_storage addr=0x{} key={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes) }); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); const k = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); - const address = fromEVMCAddress(addr.*); - return vm.env.state.getStorage(address, k) orelse std.mem.zeroes(Hash32); + return .{ .bytes = vm.env.state.getStorage(address, k) }; } fn set_storage( @@ -169,10 +170,10 @@ const EVMOneHost = struct { key: [*c]const evmc.evmc_bytes32, value: [*c]const evmc.evmc_bytes32, ) callconv(.C) evmc.enum_evmc_storage_status { - evmclog.debug("set_storage addr=0x{} key={} value={}", .{ fmtSliceHexLower(&addr), fmtSliceHexLower(&key.*), fmtSliceHexLower(&value.*) }); + const address = fromEVMCAddress(addr.*); + evmclog.debug("set_storage addr=0x{} key={} value={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes), fmtSliceHexLower(&value.*.bytes) }); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const address = fromEVMCAddress(addr.*); const k = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); const v = std.mem.readIntSlice(u256, &value.*.bytes, std.builtin.Endian.Big); vm.env.state.setStorage(address, k, v) catch |err| switch (err) { @@ -185,40 +186,35 @@ const EVMOneHost = struct { } fn get_balance(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) evmc.evmc_uint256be { - evmclog.debug("getBalance addr=0x{})", .{fmtSliceHexLower(&addr)}); + const address = fromEVMCAddress(addr.*); + evmclog.debug("getBalance addr=0x{})", .{fmtSliceHexLower(&address)}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const address = fromEVMCAddress(addr.*); - const balance_bytes = blk: { - const balance = vm.env.state.getAccount(address) orelse 0; - var buf: [32]u8 = undefined; - std.mem.writeIntSliceBig(u256, &buf, balance); - break :blk buf; - }; - return evmc.evmc_uint256be{ .bytes = balance_bytes }; + return toEVMCUint256Be(vm.env.state.getAccount(address).balance); } fn get_code_size(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) usize { - evmclog.debug("getCodeSize addr=0x{})", .{fmtSliceHexLower(&addr)}); + const address = fromEVMCAddress(addr.*); + evmclog.debug("getCodeSize addr=0x{})", .{fmtSliceHexLower(&address)}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const address = fromEVMCAddress(addr.*); - return if (vm.env.state.getCode(address)) |code| code.len else 0; + return vm.env.state.getAccount(address).code.len; } fn get_code_hash( ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address, ) callconv(.C) evmc.evmc_bytes32 { - evmclog.debug("getCodeSize addr=0x{})", .{fmtSliceHexLower(&addr)}); + const address = fromEVMCAddress(addr.*); + evmclog.debug("getCodeSize addr=0x{})", .{fmtSliceHexLower(&address)}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const address = fromEVMCAddress(addr.*); - const ret = empty_hash; - if (vm.env.state.getCode(address)) |code| { - Keccak256.hash(&ret, code, .{}); - } - return ret; + var ret = empty_hash; + const code = vm.env.state.getAccount(address).code; + if (code.len > 0) + Keccak256.hash(code, &ret, .{}); + + return .{ .bytes = ret }; } fn copy_code( @@ -228,14 +224,16 @@ const EVMOneHost = struct { buffer_data: [*c]u8, buffer_size: usize, ) callconv(.C) usize { - evmclog.debug("copyCode addr=0x{} code_offset={})", .{ fmtSliceHexLower(&addr), code_offset }); + const address = fromEVMCAddress(addr.*); + evmclog.debug("copyCode addr=0x{} code_offset={})", .{ fmtSliceHexLower(&address), code_offset }); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const address = fromEVMCAddress(addr.*); - const code = vm.env.state.getCode(address) orelse @panic("copyCode account doesn't exist"); + const code = vm.env.state.getAccount(address).code; const copy_len = @min(buffer_size, code.len - code_offset); - @memcpy(buffer_data, code[code_offset..][0..copy_len]); + @memcpy(buffer_data[0..copy_len], code[code_offset..][0..copy_len]); + + return copy_len; } fn self_destruct( @@ -298,11 +296,11 @@ const EVMOneHost = struct { } fn call(ctx: ?*evmc.struct_evmc_host_context, msg: [*c]const evmc.struct_evmc_message) callconv(.C) evmc.struct_evmc_result { - evmclog.debug("call() kind={d} depth={d} sender={} recipient={}", .{ msg.kind, msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); // TODO(jsign): explore creating custom formatter? + evmclog.debug("call() kind={d} depth={d} sender={} recipient={}", .{ msg.*.kind, msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - if (msg.depth > STACK_DEPTH_LIMIT) { + if (msg.*.depth > STACK_DEPTH_LIMIT) { return .{ .status_code = evmc.EVMC_CALL_DEPTH_EXCEEDED, .gas_left = 0, @@ -311,29 +309,24 @@ const EVMOneHost = struct { .output_size = 0, .release = null, .create_address = std.mem.zeroes(evmc.struct_evmc_address), - .padding = [_]u9{0} ** 4, + .padding = [_]u8{0} ** 4, }; } - // Save current context. - const prev_accessed_accounts = vm.accessed_accounts; - const prev_accessed_storage_keys = vm.accessed_storage_keys; - const prev_statedb = vm.env.state; + // Persist current context in case we need it for scope revert. + // deinit-ing these sets will happen later if the scope doesn't revert, since we didn't end up + // using them. + const prev_accessed_accounts = vm.accessed_accounts.clone() catch @panic("OOO"); + const prev_accessed_storage_keys = vm.accessed_storage_keys.clone() catch @panic("OOO"); + const prev_statedb = vm.env.state.snapshot() catch @panic("OOO"); - // Create new call context. - vm.accessed_accounts = vm.accessed_accounts.clone(); - defer vm.accessed_accounts.deinit(); - vm.accessed_storage_keys = vm.accessed_storage_keys.clone(); - defer vm.accessed_storage_keys.deinit(); - vm.env.state = vm.env.state.snapshot(); - defer vm.env.state.deinit(); + // TODO: change env caller? // Send value. - if (msg.value.bytes != [_]u8{0} ** 32) { - const value = std.mem.readInt(u256, &msg.*.value.bytes, std.builtin.Endian.Big); - - const sender = toEVMCAddress(msg.sender); - const sender_balance = if (vm.env.state.getAccount(sender)) |acc| acc.balance else 0; + const value = std.mem.readInt(u256, &msg.*.value.bytes, std.builtin.Endian.Big); + if (value > 0) { + const sender = fromEVMCAddress(msg.*.sender); + const sender_balance = vm.env.state.getAccount(sender).balance; if (sender_balance < value) { return .{ .status_code = evmc.EVMC_INSUFFICIENT_BALANCE, @@ -343,34 +336,35 @@ const EVMOneHost = struct { .output_size = 0, .release = null, .create_address = std.mem.zeroes(evmc.struct_evmc_address), - .padding = [_]u9{0} ** 4, + .padding = [_]u8{0} ** 4, }; } vm.env.state.setBalance(sender, sender_balance - value) catch @panic("OOO"); - const receipient_balance = if (vm.env.state.getAccount(toEVMCAddress(msg.recipient))) |acc| acc.balance else 0; + const receipient_balance = vm.env.state.getAccount(fromEVMCAddress(msg.*.recipient)).balance; vm.env.state.setBalance(sender, receipient_balance + value) catch @panic("OOO"); } - const value = std.mem.readInt(u256, &msg.*.value.bytes, std.builtin.Endian.Big); - _ = value; - const code_address = toEVMCAddress(msg.code_address); - const code = vm.env.state.getCode(code_address); + const code_address = fromEVMCAddress(msg.*.code_address); + const code = vm.env.state.getAccount(code_address).code; var result = vm.evm.*.execute.?( vm.evm, @ptrCast(&vm.host), @ptrCast(vm), evmc.EVMC_SHANGHAI, // TODO: generalize from block_number. msg, - code.code.ptr, - code.code.len, + code.ptr, + code.len, ); evmclog.debug("internal call exec result: status_code={}, gas_left={}", .{ result.status_code, result.gas_left }); if (result.status_code != evmc.EVMC_SUCCESS) { - // Restore previous context. vm.accessed_accounts = prev_accessed_accounts; vm.accessed_storage_keys = prev_accessed_storage_keys; vm.env.state = prev_statedb; + } else { + prev_accessed_accounts.deinit(); + prev_accessed_storage_keys.deinit(); + prev_statedb.deinit(); } return result; @@ -402,9 +396,8 @@ fn fromEVMCAddress(address: evmc.struct_evmc_address) Address { return address.bytes; } -// toEVMCBytes32 transforms a u256 into an evmc_bytes32. -fn toEVMCBytes32(num: u256) evmc.evmc_bytes32 { - return evmc.struct_evmc_bytes32{ +fn toEVMCUint256Be(num: u256) evmc.evmc_uint256be { + return .{ .bytes = blk: { var ret: [32]u8 = undefined; std.mem.writeIntSliceBig(u256, &ret, num); diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index 4ca82d9..d137463 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -5,15 +5,17 @@ const types = @import("../types/types.zig"); const blockchain = @import("../blockchain/blockchain.zig"); const vm = @import("../blockchain/vm.zig"); const ecdsa = @import("../crypto/ecdsa.zig"); +const state = @import("../state/statedb.zig"); const Allocator = std.mem.Allocator; const Address = types.Address; -const AccountState = types.AccountState; const Block = types.Block; const BlockHeader = types.BlockHeader; const Txn = types.Txn; const Hash32 = types.Hash32; +const Bytes32 = types.Bytes32; const VM = vm.VM; -const StateDB = @import("../statedb/statedb.zig").StateDB; +const StateDB = state.StateDB; +const AccountState = state.AccountState; const TxnSigner = @import("../signer/signer.zig").TxnSigner; const log = std.log.scoped(.execspectests); @@ -206,8 +208,9 @@ pub const AccountStateHex = struct { while (it.next()) |entry| { const key = try std.fmt.parseUnsigned(u256, entry.key_ptr.*[2..], 16); const value = try std.fmt.parseUnsigned(u256, entry.value_ptr.*[2..], 16); - - try account.storage_set(key, value); + var value_bytes: Bytes32 = undefined; + std.mem.writeInt(u256, &value_bytes, value, .Big); + try account.storage.putNoClobber(key, value_bytes); } return account; diff --git a/src/main.zig b/src/main.zig index 172a86c..296cb94 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5,7 +5,7 @@ const config = @import("config/config.zig"); const AccountState = types.AccountState; const Address = types.Address; const VM = @import("blockchain/vm.zig").VM; -const StateDB = @import("statedb/statedb.zig"); +const StateDB = @import("state/statedb.zig"); const Block = types.Block; const Txn = types.Txn; const TxnSigner = @import("signer/signer.zig").TxnSigner; diff --git a/src/statedb/statedb.zig b/src/state/statedb.zig similarity index 84% rename from src/statedb/statedb.zig rename to src/state/statedb.zig index 3d16edd..8f651cb 100644 --- a/src/statedb/statedb.zig +++ b/src/state/statedb.zig @@ -1,16 +1,15 @@ const std = @import("std"); const types = @import("../types/types.zig"); +const statetypes = @import("types.zig"); +const Bytes32 = types.Bytes32; const Allocator = std.mem.Allocator; const Address = types.Address; -const AccountState = types.AccountState; const log = std.log.scoped(.statedb); +pub const AccountData = statetypes.AccountData; +pub const AccountState = statetypes.AccountState; + pub const StateDB = struct { - pub const AccountData = struct { - nonce: u64, - balance: u256, - code: []const u8, - }; const AccountDB = std.AutoHashMap(Address, AccountState); allocator: Allocator, @@ -25,6 +24,10 @@ pub const StateDB = struct { return .{ .allocator = allocator, .db = db }; } + pub fn deinit(self: *StateDB) void { + self.db.deinit(); + } + pub fn getAccountOpt(self: *StateDB, addr: Address) ?AccountData { const account_data = self.db.get(addr) orelse return null; return .{ @@ -42,12 +45,12 @@ pub const StateDB = struct { }; } - pub fn getStorage(self: *StateDB, addr: Address, key: u256) !u256 { + pub fn getStorage(self: *StateDB, addr: Address, key: u256) Bytes32 { const account = self.db.get(addr) orelse return 0; - return try account.storage.get(key) orelse 0; + return account.storage.get(key) orelse 0; } - pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: u256) !void { + pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: Bytes32) !void { var account = self.db.get(addr) orelse return error.AccountDoesNotExist; try account.storage.put(key, value); } @@ -67,7 +70,7 @@ pub const StateDB = struct { } pub fn destroyAccount(self: *StateDB, addr: Address) void { - self.db.remove(addr); + _ = self.db.remove(addr); } pub fn accountExistsAndIsEmpty(self: *StateDB, addr: Address) bool { @@ -75,16 +78,16 @@ pub const StateDB = struct { return account.nonce == 0 and account.balance == 0 and account.code.len == 0; } - pub fn snapshot(self: StateDB) StateDB { + pub fn snapshot(self: StateDB) !StateDB { // TODO: while simple this is quite inefficient. // A much smarter way is doing some "diff" style snapshotting or similar. return StateDB{ .allocator = self.allocator, - .db = self.db.cloneWithAllocator(self.allocator), + .db = try self.db.cloneWithAllocator(self.allocator), }; } - pub fn getAllStorage(self: *StateDB, addr: Address) ?std.AutoHashMap(u256, u256) { + pub fn getAllStorage(self: *StateDB, addr: Address) ?std.AutoHashMap(u256, Bytes32) { const account = self.db.get(addr) orelse return null; return account.storage; } diff --git a/src/state/types.zig b/src/state/types.zig new file mode 100644 index 0000000..10b8ca2 --- /dev/null +++ b/src/state/types.zig @@ -0,0 +1,38 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const types = @import("../types/types.zig"); +const Bytes32 = types.Bytes32; +const Address = types.Address; + +pub const AccountData = struct { + nonce: u64, + balance: u256, + code: []const u8, +}; + +pub const AccountState = struct { + allocator: Allocator, + addr: Address, + nonce: u64, + balance: u256, + code: []const u8, + storage: std.AutoHashMap(u256, Bytes32), + + // init initializes an account state with the given values. + // The caller must call deinit() when done. + pub fn init(allocator: Allocator, addr: Address, nonce: u64, balance: u256, code: []const u8) !AccountState { + return AccountState{ + .allocator = allocator, + .addr = addr, + .nonce = nonce, + .balance = balance, + .code = try allocator.dupe(u8, code), + .storage = std.AutoHashMap(u256, Bytes32).init(allocator), + }; + } + + pub fn deinit(self: *AccountState) void { + self.storage.deinit(); + self.allocator.free(self.code); + } +}; diff --git a/src/types/account_state.zig b/src/types/account_state.zig deleted file mode 100644 index 6f7b588..0000000 --- a/src/types/account_state.zig +++ /dev/null @@ -1,63 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; -const common = @import("../common/common.zig"); -const types = @import("types.zig"); -const Address = types.Address; -const Bytecode = types.Bytecode; -const assert = std.debug.assert; - -const log = std.log.scoped(.account_state); - -const AccountState = @This(); - -allocator: Allocator, -addr: Address, -nonce: u64, -balance: u256, -code: Bytecode, -storage: std.AutoHashMap(u256, u256), - -// init initializes an account state with the given values. -// deinit() must be called on the account state to free the storage. -pub fn init(allocator: Allocator, addr: Address, nonce: u64, balance: u256, code: Bytecode) !AccountState { - return AccountState{ - .allocator = allocator, - .addr = addr, - .nonce = nonce, - .balance = balance, - .code = try allocator.dupe(u8, code), - .storage = std.AutoHashMap(u256, u256).init(allocator), - }; -} - -pub fn deinit(self: *AccountState) void { - self.storage.deinit(); - self.allocator.free(self.code); -} - -pub fn storage_get(self: *const AccountState, key: u256) ?u256 { - return self.storage.get(key); -} - -pub fn storage_set(self: *AccountState, key: u256, value: u256) !void { - try self.storage.put(key, value); -} - -const test_allocator = std.testing.allocator; -test "storage" { - var account = try AccountState.init(test_allocator, common.hexToAddress("0x010142"), 0, 0, &[_]u8{}); - defer account.deinit(); - - // Set key=0x42, val=0x43, and check get. - try account.storage_set(0x42, 0x43); - try std.testing.expect(account.storage_get(0x42) == 0x43); - - // Get a key that doesn't exist. - if (account.storage_get(0x44)) |_| { - return error.ExpectedError; - } - - // Set existing key=0x42 to new value and get. - try account.storage_set(0x42, 0x13); - try std.testing.expect(account.storage_get(0x42) == 0x13); -} diff --git a/src/types/types.zig b/src/types/types.zig index 4c01c54..8dc2efd 100644 --- a/src/types/types.zig +++ b/src/types/types.zig @@ -4,12 +4,8 @@ pub const Bytes32 = [32]u8; pub const Bytes31 = [31]u8; // Ethereum execution layer types. -pub const Bytecode = []const u8; pub const Address = [20]u8; -// State. -pub const AccountState = @import("account_state.zig"); - // Blocks pub const block = @import("block.zig"); pub const empty_uncle_hash = block.empty_uncle_hash; From 57b9512b9960fab44f4f8a16731218a5ef5ece7b Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 9 Jan 2024 12:44:02 -0300 Subject: [PATCH 35/52] fix last compilation errors Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 11 +++++------ src/state/statedb.zig | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 7c8e88d..078ff5f 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -175,8 +175,7 @@ const EVMOneHost = struct { const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); const k = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); - const v = std.mem.readIntSlice(u256, &value.*.bytes, std.builtin.Endian.Big); - vm.env.state.setStorage(address, k, v) catch |err| switch (err) { + vm.env.state.setStorage(address, k, value.*.bytes) catch |err| switch (err) { // From EVMC docs: "The VM MUST make sure that the account exists. This requirement is only a formality // because VM implementations only modify storage of the account of the current execution context". error.AccountDoesNotExist => @panic("set storage in non-existent account"), @@ -316,9 +315,9 @@ const EVMOneHost = struct { // Persist current context in case we need it for scope revert. // deinit-ing these sets will happen later if the scope doesn't revert, since we didn't end up // using them. - const prev_accessed_accounts = vm.accessed_accounts.clone() catch @panic("OOO"); - const prev_accessed_storage_keys = vm.accessed_storage_keys.clone() catch @panic("OOO"); - const prev_statedb = vm.env.state.snapshot() catch @panic("OOO"); + var prev_accessed_accounts = vm.accessed_accounts.clone() catch @panic("OOO"); + var prev_accessed_storage_keys = vm.accessed_storage_keys.clone() catch @panic("OOO"); + var prev_statedb = vm.env.state.snapshot() catch @panic("OOO"); // TODO: change env caller? @@ -360,7 +359,7 @@ const EVMOneHost = struct { if (result.status_code != evmc.EVMC_SUCCESS) { vm.accessed_accounts = prev_accessed_accounts; vm.accessed_storage_keys = prev_accessed_storage_keys; - vm.env.state = prev_statedb; + vm.env.state.* = prev_statedb; } else { prev_accessed_accounts.deinit(); prev_accessed_storage_keys.deinit(); diff --git a/src/state/statedb.zig b/src/state/statedb.zig index 8f651cb..e60dccb 100644 --- a/src/state/statedb.zig +++ b/src/state/statedb.zig @@ -46,8 +46,8 @@ pub const StateDB = struct { } pub fn getStorage(self: *StateDB, addr: Address, key: u256) Bytes32 { - const account = self.db.get(addr) orelse return 0; - return account.storage.get(key) orelse 0; + const account = self.db.get(addr) orelse return std.mem.zeroes(Bytes32); + return account.storage.get(key) orelse std.mem.zeroes(Bytes32); } pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: Bytes32) !void { From 320a6a3a32587f1156aba6838b3b446e5ec85f8d Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 9 Jan 2024 14:39:44 -0300 Subject: [PATCH 36/52] mod: update zig-rlp and fix codebase Signed-off-by: Ignacio Hagopian --- build.zig.zon | 24 ++++++++++------------- src/engine_api/engine_api.zig | 2 +- src/exec-spec-tests/execspectests.zig | 14 +++++++------- src/signer/signer.zig | 4 +++- src/types/block.zig | 9 ++++----- src/types/transaction.zig | 28 ++++++++++++++------------- src/types/types.zig | 2 +- 7 files changed, 41 insertions(+), 42 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index eb8f32f..3d1d17f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,18 +1,14 @@ .{ .name = "phant", .version = "0.0.1-beta-0", - .dependencies = .{ - .@"zig-rlp" = .{ - .url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.0.1-beta-9.tar.gz", - .hash = "12203e6f5225dec28e2661b9c4d93dd142bea37ab1f2cac165d93dc56c65fb259be8", - }, - .@"zig-eth-secp256k1" = .{ - .url = "https://github.com/jsign/zig-eth-secp256k1/archive/refs/tags/v0.1.0-beta-3.tar.gz", - .hash = "1220fcf062f8ee89b343e1588ac3cc002f37ee3f72841dd7f9493d9c09acad7915a3", - }, - .httpz = .{ - .url = "https://github.com/karlseguin/http.zig/archive/1a82beb0dfc22e6fc38e9918e323f8fbd3cb78a3.tar.gz", - .hash = "122020cbd399273b1220f11c4b2d734b86c94cc5f1c2d6d90fc766580bc0e25b0dfb", - } - }, + .dependencies = .{ .@"zig-rlp" = .{ + .url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.0.1-beta-10.tar.gz", + .hash = "1220a98462cbf50ce04b24586966e4dc209596e7301d89dfd05dda0dd59ed97f0c09", + }, .@"zig-eth-secp256k1" = .{ + .url = "https://github.com/jsign/zig-eth-secp256k1/archive/refs/tags/v0.1.0-beta-3.tar.gz", + .hash = "1220fcf062f8ee89b343e1588ac3cc002f37ee3f72841dd7f9493d9c09acad7915a3", + }, .httpz = .{ + .url = "https://github.com/karlseguin/http.zig/archive/1a82beb0dfc22e6fc38e9918e323f8fbd3cb78a3.tar.gz", + .hash = "122020cbd399273b1220f11c4b2d734b86c94cc5f1c2d6d90fc766580bc0e25b0dfb", + } }, } diff --git a/src/engine_api/engine_api.zig b/src/engine_api/engine_api.zig index 0fe8f20..70e01a7 100644 --- a/src/engine_api/engine_api.zig +++ b/src/engine_api/engine_api.zig @@ -38,7 +38,7 @@ const AllPossibleExecutionParams = struct { if (self.transactions.len > 0) { txns = try allocator.alloc(Txn, self.transactions.len); for (self.transactions, 0..) |tx, i| { - txns[i] = try Txn.decode(tx); + txns[i] = try Txn.decode(allocator, tx); } } diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index d137463..aa86aa2 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -75,19 +75,19 @@ pub const FixtureTest = struct { var statedb = try StateDB.init(allocator, accounts_state); // 2. Execute blocks. - const txn_signer = try TxnSigner.init(0); // ChainID == 0 is used in tests. + // const txn_signer = try TxnSigner.init(0); // ChainID == 0 is used in tests. for (self.blocks) |encoded_block| { var out = try allocator.alloc(u8, encoded_block.rlp.len / 2); defer allocator.free(out); const rlp_bytes = try std.fmt.hexToBytes(out, encoded_block.rlp[2..]); - const block = try Block.init(rlp_bytes); + const block = try Block.decode(allocator, rlp_bytes); - var txns = try allocator.alloc(Txn, encoded_block.transactions.len); - defer allocator.free(txns); - for (encoded_block.transactions, 0..) |tx_hex, i| { - txns[i] = try tx_hex.to_vm_transaction(allocator, txn_signer); - } + // var txns = try allocator.alloc(Txn, encoded_block.transactions.len); + // defer allocator.free(txns); + // for (encoded_block.transactions, 0..) |tx_hex, i| { + // txns[i] = try tx_hex.to_vm_transaction(allocator, txn_signer); + // } var chain = blockchain.Blockchain.init(allocator, config.ChainId.SpecTest, &statedb, null, std.mem.zeroes([256]Hash32)); try chain.runBlock(block); diff --git a/src/signer/signer.zig b/src/signer/signer.zig index bc117cf..6788936 100644 --- a/src/signer/signer.zig +++ b/src/signer/signer.zig @@ -161,6 +161,8 @@ pub const TxnSigner = struct { }; test "Mainnet transactions signature recovery/verification" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); const testCase = struct { rlp_encoded: []const u8, expected_sender: []const u8, @@ -183,7 +185,7 @@ test "Mainnet transactions signature recovery/verification" { inline for (test_cases) |testcase| { var txn_bytes: [testcase.rlp_encoded.len / 2]u8 = undefined; _ = try std.fmt.hexToBytes(&txn_bytes, testcase.rlp_encoded); - const txn = try Txn.decode(&txn_bytes); + const txn = try Txn.decode(arena.allocator(), &txn_bytes); const signer = try TxnSigner.init(@intFromEnum(config.ChainId.Mainnet)); const sender = try signer.get_sender(std.testing.allocator, txn); diff --git a/src/types/block.zig b/src/types/block.zig index 70ff4a2..a0352a9 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -1,6 +1,8 @@ const std = @import("std"); const rlp = @import("zig-rlp"); const types = @import("types.zig"); +const Allocator = std.mem.Allocator; +const Arena = std.heap.ArenaAllocator; const Withdrawal = types.Withdrawal; const Txn = types.Txn; const Hash32 = types.Hash32; @@ -38,12 +40,9 @@ pub const Block = struct { uncles: []BlockHeader, withdrawals: []Withdrawal, - // new returns a new Block deserialized from rlp_bytes. - // The returned Block has references to the rlp_bytes slice. - pub fn init(rlp_bytes: []const u8) !Block { + pub fn decode(arena: Allocator, rlp_bytes: []const u8) !Block { var block = std.mem.zeroes(Block); - _ = try rlp.deserialize(Block, rlp_bytes, &block); - // TODO: consider strict checking of returned deserialized length vs rlp_bytes.len. + _ = try rlp.deserialize(Block, rlp_bytes, &block, arena); return block; } }; diff --git a/src/types/transaction.zig b/src/types/transaction.zig index d1f2b91..c708846 100644 --- a/src/types/transaction.zig +++ b/src/types/transaction.zig @@ -24,20 +24,20 @@ pub const Txn = union(TxnTypes) { } // decode decodes a transaction from bytes. The provided bytes are referenced in the returned transaction. - pub fn decode(bytes: []const u8) !Txn { + pub fn decode(arena: Allocator, bytes: []const u8) !Txn { if (bytes.len == 0) { return error.EncodedTxnCannotBeEmpty; } // EIP-2718: Transaction Type Transaction if (bytes[0] <= 0x7f) { - if (bytes[0] == 0x01) return Txn{ .AccessListTxn = try AccessListTxn.decode(bytes[1..]) }; - if (bytes[0] == 0x02) return Txn{ .FeeMarketTxn = try FeeMarketTxn.decode(bytes[1..]) }; + if (bytes[0] == 0x01) return Txn{ .AccessListTxn = try AccessListTxn.decode(arena, bytes[1..]) }; + if (bytes[0] == 0x02) return Txn{ .FeeMarketTxn = try FeeMarketTxn.decode(arena, bytes[1..]) }; return error.UnsupportedEIP2930TxnType; } // LegacyTxn - if (bytes[0] >= 0xc0 and bytes[0] <= 0xfe) return Txn{ .LegacyTxn = try LegacyTxn.decode(bytes) }; + if (bytes[0] >= 0xc0 and bytes[0] <= 0xfe) return Txn{ .LegacyTxn = try LegacyTxn.decode(arena, bytes) }; return error.UnsupportedTxnType; } @@ -142,8 +142,8 @@ pub const LegacyTxn = struct { // decode decodes a transaction from bytes. No bytes from the input slice are referenced in the // output transaction. - pub fn decode(bytes: []const u8) !LegacyTxn { - return try RLPDecode(LegacyTxn, bytes); + pub fn decode(arena: Allocator, bytes: []const u8) !LegacyTxn { + return try RLPDecode(LegacyTxn, arena, bytes); } pub fn hash(self: LegacyTxn, allocator: Allocator) !Hash32 { @@ -200,8 +200,8 @@ pub const AccessListTxn = struct { } // decode decodes a transaction from bytes. - pub fn decode(bytes: []const u8) !AccessListTxn { - return try RLPDecode(AccessListTxn, bytes); + pub fn decode(arena: Allocator, bytes: []const u8) !AccessListTxn { + return try RLPDecode(AccessListTxn, arena, bytes); } }; @@ -233,14 +233,14 @@ pub const FeeMarketTxn = struct { } // decode decodes a transaction from bytes. - pub fn decode(bytes: []const u8) !FeeMarketTxn { - return try RLPDecode(FeeMarketTxn, bytes); + pub fn decode(arena: Allocator, bytes: []const u8) !FeeMarketTxn { + return try RLPDecode(FeeMarketTxn, arena, bytes); } }; -pub fn RLPDecode(comptime T: type, bytes: []const u8) !T { +pub fn RLPDecode(comptime T: type, arena: Allocator, bytes: []const u8) !T { var ret: T = std.mem.zeroes(T); - _ = try rlp.deserialize(T, bytes, &ret); + _ = try rlp.deserialize(T, bytes, &ret, arena); return ret; } @@ -255,6 +255,8 @@ pub fn RLPHash(comptime T: type, allocator: Allocator, txn: T, prefix: ?[]const } test "Mainnet transactions hashing" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); const testCase = struct { rlp_encoded: []const u8, expected_hash: []const u8, @@ -287,7 +289,7 @@ test "Mainnet transactions hashing" { var txn_bytes: [testcase.rlp_encoded.len / 2]u8 = undefined; _ = try std.fmt.hexToBytes(&txn_bytes, testcase.rlp_encoded); - const txn = try Txn.decode(&txn_bytes); + const txn = try Txn.decode(arena.allocator(), &txn_bytes); const hash = try txn.hash(std.testing.allocator); try std.testing.expectEqualStrings(testcase.expected_hash, &std.fmt.bytesToHex(hash, std.fmt.Case.lower)); } diff --git a/src/types/types.zig b/src/types/types.zig index 8dc2efd..48ad184 100644 --- a/src/types/types.zig +++ b/src/types/types.zig @@ -12,7 +12,7 @@ pub const empty_uncle_hash = block.empty_uncle_hash; pub const Block = block.Block; pub const BlockHeader = block.BlockHeader; pub const LogsBloom = block.LogsBloom; -pub const Withdrawal = @import("withdrawal.zig"); +pub const Withdrawal = @import("withdrawal.zig").Withdrawal; // Transactions const transaction = @import("transaction.zig"); From 37830274ad662b923b3e411996b26236f96c38dd Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 9 Jan 2024 20:00:11 -0300 Subject: [PATCH 37/52] types/transaction: add customized RLP decoder for Txn Signed-off-by: Ignacio Hagopian --- src/types/transaction.zig | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/types/transaction.zig b/src/types/transaction.zig index c708846..7c316a3 100644 --- a/src/types/transaction.zig +++ b/src/types/transaction.zig @@ -42,6 +42,22 @@ pub const Txn = union(TxnTypes) { return error.UnsupportedTxnType; } + pub fn decodeRLP(self: *Txn, serialized: []const u8) !usize { + var alloc = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(alloc); + if (serialized[0] > 0xC0) { // Is a RLP struct (i.e: LegacyTx) + var ltx: LegacyTxn = undefined; + const size = rlp.deserialize(LegacyTxn, serialized, <x, arena.allocator()); + self.* = .{ .LegacyTxn = ltx }; + return size; + } + var str: []const u8 = undefined; + const size = try rlp.deserialize([]const u8, serialized, &str, arena.allocator()); + self.* = try Txn.decode(arena.allocator(), str); + + return size; + } + pub fn hash(self: Txn, allocator: Allocator) !Hash32 { return switch (self) { Txn.LegacyTxn => |txn| try txn.hash(allocator), From b1b04147483968170d5a8d4509f39a89a41a909e Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 9 Jan 2024 20:32:25 -0300 Subject: [PATCH 38/52] signer: implement pubkey recovery pre EIP-155 Signed-off-by: Ignacio Hagopian --- src/signer/signer.zig | 69 ++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/src/signer/signer.zig b/src/signer/signer.zig index 6788936..1a9511d 100644 --- a/src/signer/signer.zig +++ b/src/signer/signer.zig @@ -38,6 +38,7 @@ pub const TxnSigner = struct { pub fn get_sender(self: TxnSigner, allocator: Allocator, tx: Txn) !Address { const txn_hash = try self.hashTxn(allocator, tx); + std.log.warn("FOOOOOO {}", .{std.fmt.fmtSliceHexLower(&txn_hash)}); var sig: ecdsa.Signature = undefined; @@ -75,30 +76,52 @@ pub const TxnSigner = struct { fn hashTxn(self: TxnSigner, allocator: Allocator, transaction: Txn) !Hash32 { return switch (transaction) { Txn.LegacyTxn => |txn| blk: { - // Txn encoding using EIP-155 (since ~Nov 2016). - const legacyTxnRLP = struct { - nonce: u64, - gas_price: u256, - gas_limit: u64, - to: ?Address, - value: u256, - data: []const u8, - chain_id: u64, - zero1: u8 = 0, - zero2: u8 = 0, - }; - var out = std.ArrayList(u8).init(allocator); defer out.deinit(); - try rlp.serialize(legacyTxnRLP, allocator, .{ - .nonce = txn.nonce, - .gas_price = txn.gas_price, - .gas_limit = txn.gas_limit, - .to = txn.to, - .value = txn.value, - .data = txn.data, - .chain_id = self.chain_id, - }, &out); + + if (self.chain_id != @intFromEnum(config.ChainId.SpecTest)) { + // Post EIP-155 (since ~Nov 2016). + const legacyTxnRLP = struct { + nonce: u64, + gas_price: u256, + gas_limit: u64, + to: ?Address, + value: u256, + data: []const u8, + chain_id: u64, + zero1: u8 = 0, + zero2: u8 = 0, + }; + + try rlp.serialize(legacyTxnRLP, allocator, .{ + .nonce = txn.nonce, + .gas_price = txn.gas_price, + .gas_limit = txn.gas_limit, + .to = txn.to, + .value = txn.value, + .data = txn.data, + .chain_id = self.chain_id, + }, &out); + } else { + // Pre EIP-155. + const legacyTxnRLP = struct { + nonce: u64, + gas_price: u256, + gas_limit: u64, + to: ?Address, + value: u256, + data: []const u8, + }; + + try rlp.serialize(legacyTxnRLP, allocator, .{ + .nonce = txn.nonce, + .gas_price = txn.gas_price, + .gas_limit = txn.gas_limit, + .to = txn.to, + .value = txn.value, + .data = txn.data, + }, &out); + } break :blk hasher.keccak256(out.items); }, @@ -160,7 +183,7 @@ pub const TxnSigner = struct { } }; -test "Mainnet transactions signature recovery/verification" { +test "mainnet transactions signature recovery/verification" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const testCase = struct { From 6dd3c95a5cdff46175a05fc75666a7bb3d61923c Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 9 Jan 2024 21:02:19 -0300 Subject: [PATCH 39/52] blockchain/vm: fixes Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 7 +++---- src/blockchain/vm.zig | 3 ++- src/engine_api/execution_payload.zig | 2 -- src/signer/signer.zig | 1 - src/types/block.zig | 6 ++---- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index db6bacc..deb904a 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -87,8 +87,9 @@ pub const Blockchain = struct { var result = try applyBody(allocator, self, self.state, block); // Post execution checks. - if (result.gas_used != block.header.gas_used) + if (result.gas_used != block.header.gas_used) { return error.InvalidGasUsed; + } // TODO: disabled until txs root is calculated // if (!std.mem.eql(u8, &result.transactions_root, &block.header.transactions_root)) // return error.InvalidTransactionsRoot; @@ -142,7 +143,7 @@ pub const Blockchain = struct { if (curr_block.difficulty != 0) return error.InvalidDifficulty; - if (std.mem.eql(u8, &curr_block.nonce, &[_]u8{0} ** 8)) + if (!std.mem.eql(u8, &curr_block.nonce, &[_]u8{0} ** 8)) return error.InvalidNonce; if (!std.mem.eql(u8, &curr_block.uncle_hash, &blocks.empty_uncle_hash)) return error.InvalidUnclesHash; @@ -172,8 +173,6 @@ pub const Blockchain = struct { fn applyBody(allocator: Allocator, chain: Blockchain, state: *StateDB, block: Block) !BlockExecutionResult { var gas_available = block.header.gas_limit; for (block.transactions) |tx| { - // TODO: add tx to txs tree. - const txn_info = try checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, chain.chain_id); const env: Environment = .{ .caller = txn_info.sender_address, diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 078ff5f..11e6356 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -92,7 +92,7 @@ pub const VM = struct { break :blk .{ .bytes = txn_value }; }, .create2_salt = undefined, // EVMC docs: field only mandatory for CREATE2 kind. - .code_address = undefined, // EVMC docs: field not mandatory for depth 0 calls. + .code_address = toEVMCAddress(msg.code_address), }; // TODO(improv): we clone here since it's the easiest way to manage ownership of the sets. @@ -345,6 +345,7 @@ const EVMOneHost = struct { const code_address = fromEVMCAddress(msg.*.code_address); const code = vm.env.state.getAccount(code_address).code; + std.log.warn("codeAddr={} codeBytes={}", .{ std.fmt.fmtSliceHexLower(&code_address), std.fmt.fmtSliceHexLower(code) }); var result = vm.evm.*.execute.?( vm.evm, @ptrCast(&vm.host), diff --git a/src/engine_api/execution_payload.zig b/src/engine_api/execution_payload.zig index 5fbac03..7986d15 100644 --- a/src/engine_api/execution_payload.zig +++ b/src/engine_api/execution_payload.zig @@ -47,9 +47,7 @@ pub const ExecutionPayload = struct { .base_fee_per_gas = self.baseFeePerGas, .transactions_root = [_]u8{0} ** 32, .nonce = [_]u8{0} ** 8, - .blob_gas_used = null, .withdrawals_root = null, - .excess_blob_gas = null, }, .transactions = self.transactions, .withdrawals = self.withdrawals, diff --git a/src/signer/signer.zig b/src/signer/signer.zig index 1a9511d..f5fb672 100644 --- a/src/signer/signer.zig +++ b/src/signer/signer.zig @@ -38,7 +38,6 @@ pub const TxnSigner = struct { pub fn get_sender(self: TxnSigner, allocator: Allocator, tx: Txn) !Address { const txn_hash = try self.hashTxn(allocator, tx); - std.log.warn("FOOOOOO {}", .{std.fmt.fmtSliceHexLower(&txn_hash)}); var sig: ecdsa.Signature = undefined; diff --git a/src/types/block.zig b/src/types/block.zig index a0352a9..f466f5d 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -20,7 +20,7 @@ pub const BlockHeader = struct { transactions_root: Bytes32, receipts_root: Bytes32, logs_bloom: LogsBloom, - difficulty: u8, + difficulty: u64, block_number: u64, gas_limit: u64, gas_used: u64, @@ -30,8 +30,6 @@ pub const BlockHeader = struct { nonce: [8]u8, base_fee_per_gas: u256, withdrawals_root: ?Hash32, - blob_gas_used: ?u64, - excess_blob_gas: ?u64, }; pub const Block = struct { @@ -41,7 +39,7 @@ pub const Block = struct { withdrawals: []Withdrawal, pub fn decode(arena: Allocator, rlp_bytes: []const u8) !Block { - var block = std.mem.zeroes(Block); + var block: Block = undefined; _ = try rlp.deserialize(Block, rlp_bytes, &block, arena); return block; } From b9e1fc0ab8ace9cd2a706bb3c8b2df8df5f285a8 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 10 Jan 2024 07:52:34 -0300 Subject: [PATCH 40/52] general fixes Signed-off-by: Ignacio Hagopian --- build.zig.zon | 24 ++++++++++++++---------- src/blockchain/blockchain.zig | 8 ++++---- src/blockchain/vm.zig | 15 ++++++++------- src/exec-spec-tests/execspectests.zig | 13 +++---------- src/state/statedb.zig | 2 +- 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 3d1d17f..fc2d890 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,14 +1,18 @@ .{ .name = "phant", .version = "0.0.1-beta-0", - .dependencies = .{ .@"zig-rlp" = .{ - .url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.0.1-beta-10.tar.gz", - .hash = "1220a98462cbf50ce04b24586966e4dc209596e7301d89dfd05dda0dd59ed97f0c09", - }, .@"zig-eth-secp256k1" = .{ - .url = "https://github.com/jsign/zig-eth-secp256k1/archive/refs/tags/v0.1.0-beta-3.tar.gz", - .hash = "1220fcf062f8ee89b343e1588ac3cc002f37ee3f72841dd7f9493d9c09acad7915a3", - }, .httpz = .{ - .url = "https://github.com/karlseguin/http.zig/archive/1a82beb0dfc22e6fc38e9918e323f8fbd3cb78a3.tar.gz", - .hash = "122020cbd399273b1220f11c4b2d734b86c94cc5f1c2d6d90fc766580bc0e25b0dfb", - } }, + .dependencies = .{ + .@"zig-rlp" = .{ + .url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.0.1-beta-10.tar.gz", + .hash = "1220a98462cbf50ce04b24586966e4dc209596e7301d89dfd05dda0dd59ed97f0c09", + }, + .@"zig-eth-secp256k1" = .{ + .url = "https://github.com/jsign/zig-eth-secp256k1/archive/refs/tags/v0.1.0-beta-3.tar.gz", + .hash = "1220fcf062f8ee89b343e1588ac3cc002f37ee3f72841dd7f9493d9c09acad7915a3", + }, + .httpz = .{ + .url = "https://github.com/karlseguin/http.zig/archive/1a82beb0dfc22e6fc38e9918e323f8fbd3cb78a3.tar.gz", + .hash = "122020cbd399273b1220f11c4b2d734b86c94cc5f1c2d6d90fc766580bc0e25b0dfb", + }, + }, } diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index deb904a..5274042 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -87,9 +87,8 @@ pub const Blockchain = struct { var result = try applyBody(allocator, self, self.state, block); // Post execution checks. - if (result.gas_used != block.header.gas_used) { + if (result.gas_used != block.header.gas_used) return error.InvalidGasUsed; - } // TODO: disabled until txs root is calculated // if (!std.mem.eql(u8, &result.transactions_root, &block.header.transactions_root)) // return error.InvalidTransactionsRoot; @@ -257,10 +256,10 @@ pub const Blockchain = struct { return error.InvalidTransaction; const sender = env.origin; - const sender_account = env.state.getAccount(sender); const gas_fee = tx.getGasLimit() * tx.getGasPrice(); + var sender_account = env.state.getAccount(sender); if (sender_account.nonce != tx.getNonce()) return error.InvalidTxnNonce; if (sender_account.balance < gas_fee + tx.getValue()) @@ -268,8 +267,8 @@ pub const Blockchain = struct { if (sender_account.code.len > 0) return error.SenderIsNotEOA; - const effective_gas_fee = tx.getGasLimit() * env.gas_price; const gas = tx.getGasLimit() - calculateIntrinsicCost(tx); + const effective_gas_fee = tx.getGasLimit() * env.gas_price; try env.state.incrementNonce(sender); const sender_balance_after_gas_fee = sender_account.balance - effective_gas_fee; @@ -314,6 +313,7 @@ pub const Blockchain = struct { const transaction_fee = (tx.getGasLimit() - output.gas_left) * priority_fee_per_gas; const total_gas_used = gas_used - gas_refund; + sender_account = env.state.getAccount(sender); const sender_balance_after_refund = sender_account.balance + gas_refund_amount; try env.state.setBalance(sender, sender_balance_after_refund); diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 11e6356..eabc825 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -118,7 +118,7 @@ const EVMOneHost = struct { fn get_tx_context(ctx: ?*evmc.struct_evmc_host_context) callconv(.C) evmc.struct_evmc_tx_context { evmclog.debug("get_tx_context", .{}); - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); // TODO: alignCast needed? + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); return evmc.struct_evmc_tx_context{ .tx_gas_price = toEVMCUint256Be(vm.env.gas_price), .tx_origin = toEVMCAddress(vm.env.origin), @@ -133,7 +133,7 @@ const EVMOneHost = struct { } fn get_block_hash(ctx: ?*evmc.struct_evmc_host_context, block_number: i64) callconv(.C) evmc.evmc_bytes32 { - evmclog.debug("get_tx_context block_number={}", .{block_number}); + evmclog.debug("get_block_hash block_number={}", .{block_number}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); const idx = vm.env.number - @as(u64, @intCast(block_number)); @@ -179,8 +179,9 @@ const EVMOneHost = struct { // From EVMC docs: "The VM MUST make sure that the account exists. This requirement is only a formality // because VM implementations only modify storage of the account of the current execution context". error.AccountDoesNotExist => @panic("set storage in non-existent account"), - else => @panic("OOO"), + error.OutOfMemory => @panic("OOO"), }; + return evmc.EVMC_STORAGE_ADDED; // TODO(jsign): fix https://evmc.ethereum.org/group__EVMC.html#gae012fd6b8e5c23806b507c2d3e9fb1aa } @@ -295,9 +296,8 @@ const EVMOneHost = struct { } fn call(ctx: ?*evmc.struct_evmc_host_context, msg: [*c]const evmc.struct_evmc_message) callconv(.C) evmc.struct_evmc_result { - evmclog.debug("call() kind={d} depth={d} sender={} recipient={}", .{ msg.*.kind, msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + evmclog.debug("call() kind={d} depth={d} sender={} recipient={}", .{ msg.*.kind, msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); if (msg.*.depth > STACK_DEPTH_LIMIT) { return .{ @@ -345,7 +345,6 @@ const EVMOneHost = struct { const code_address = fromEVMCAddress(msg.*.code_address); const code = vm.env.state.getAccount(code_address).code; - std.log.warn("codeAddr={} codeBytes={}", .{ std.fmt.fmtSliceHexLower(&code_address), std.fmt.fmtSliceHexLower(code) }); var result = vm.evm.*.execute.?( vm.evm, @ptrCast(&vm.host), @@ -355,11 +354,13 @@ const EVMOneHost = struct { code.ptr, code.len, ); - evmclog.debug("internal call exec result: status_code={}, gas_left={}", .{ result.status_code, result.gas_left }); if (result.status_code != evmc.EVMC_SUCCESS) { + vm.accessed_accounts.deinit(); vm.accessed_accounts = prev_accessed_accounts; + vm.accessed_storage_keys.deinit(); vm.accessed_storage_keys = prev_accessed_storage_keys; + vm.env.state.deinit(); vm.env.state.* = prev_statedb; } else { prev_accessed_accounts.deinit(); diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index aa86aa2..8611517 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -77,19 +77,12 @@ pub const FixtureTest = struct { // 2. Execute blocks. // const txn_signer = try TxnSigner.init(0); // ChainID == 0 is used in tests. for (self.blocks) |encoded_block| { + var chain = blockchain.Blockchain.init(allocator, config.ChainId.SpecTest, &statedb, null, std.mem.zeroes([256]Hash32)); + var out = try allocator.alloc(u8, encoded_block.rlp.len / 2); defer allocator.free(out); const rlp_bytes = try std.fmt.hexToBytes(out, encoded_block.rlp[2..]); - const block = try Block.decode(allocator, rlp_bytes); - - // var txns = try allocator.alloc(Txn, encoded_block.transactions.len); - // defer allocator.free(txns); - // for (encoded_block.transactions, 0..) |tx_hex, i| { - // txns[i] = try tx_hex.to_vm_transaction(allocator, txn_signer); - // } - - var chain = blockchain.Blockchain.init(allocator, config.ChainId.SpecTest, &statedb, null, std.mem.zeroes([256]Hash32)); try chain.runBlock(block); } @@ -110,6 +103,7 @@ pub const FixtureTest = struct { const got_storage = statedb.getAllStorage(exp_account_state.addr) orelse return error.PostStateAccountMustExist; if (got_storage.count() != exp_account_state.storage.count()) { + log.err("expected storage count {d} but got {d}", .{ exp_account_state.storage.count(), got_storage.count() }); return error.PostStateStorageCountMismatch; } // TODO: check each storage entry matches. @@ -226,7 +220,6 @@ test "execution-spec-tests" { var it = ft.tests.value.map.iterator(); var count: usize = 0; - while (it.next()) |entry| { log.debug("##### Executing fixture {s} #####", .{entry.key_ptr.*}); try std.testing.expect(try entry.value_ptr.*.run(test_allocator)); diff --git a/src/state/statedb.zig b/src/state/statedb.zig index e60dccb..32f06b7 100644 --- a/src/state/statedb.zig +++ b/src/state/statedb.zig @@ -51,7 +51,7 @@ pub const StateDB = struct { } pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: Bytes32) !void { - var account = self.db.get(addr) orelse return error.AccountDoesNotExist; + var account = self.db.getPtr(addr) orelse return error.AccountDoesNotExist; try account.storage.put(key, value); } From 5b1ec076925ad31e1d2a6332a16e64351d212287 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 10 Jan 2024 08:13:50 -0300 Subject: [PATCH 41/52] blockchain: refactor Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 111 ++++++++++++---------------------- src/blockchain/params.zig | 36 +++++++++++ src/common/common.zig | 5 +- 3 files changed, 78 insertions(+), 74 deletions(-) create mode 100644 src/blockchain/params.zig diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 5274042..3c42517 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -7,6 +7,7 @@ const transaction = @import("../types/transaction.zig"); const vm = @import("vm.zig"); const rlp = @import("zig-rlp"); const signer = @import("../signer/signer.zig"); +const params = @import("params.zig"); const Allocator = std.mem.Allocator; const AddressSet = common.AddressSet; const AddresssKey = common.AddressKey; @@ -22,50 +23,20 @@ const VM = vm.VM; const Keccak256 = std.crypto.hash.sha3.Keccak256; pub const Blockchain = struct { - const BASE_FEE_MAX_CHANGE_DENOMINATOR = 8; - const ELASTICITY_MULTIPLIER = 2; - - const GAS_LIMIT_ADJUSTMENT_FACTOR = 1024; - const GAS_LIMIT_MINIMUM = 5000; - const GAS_INIT_CODE_WORD_COST = 2; - - const MAX_CODE_SIZE = 0x6000; - - const TX_BASE_COST = 21000; - const TX_DATA_COST_PER_ZERO = 4; - const TX_DATA_COST_PER_NON_ZERO = 16; - const TX_CREATE_COST = 32000; - const TX_ACCESS_LIST_ADDRESS_COST = 2400; - const TX_ACCESS_LIST_STORAGE_KEY_COST = 1900; - - const PRE_COMPILED_CONTRACT_ADDRESSES = [_]Address{ - // TODO: see if it's worth importing some .h file from EVMOne - // an instantiate this at comptime to avoid maintaining this list. - [_]u8{0} ** 19 ++ [_]u8{1}, // ECRECOVER - [_]u8{0} ** 19 ++ [_]u8{2}, // SHA256 - [_]u8{0} ** 19 ++ [_]u8{3}, // RIPEMD160 - [_]u8{0} ** 19 ++ [_]u8{4}, // IDENTITY_ADDRESS - [_]u8{0} ** 19 ++ [_]u8{5}, // MODEXP_ADDRESS - [_]u8{0} ** 19 ++ [_]u8{6}, // ALT_BN128_ADD - [_]u8{0} ** 19 ++ [_]u8{7}, // ALT_BN128_MUL - [_]u8{0} ** 19 ++ [_]u8{8}, // ALT_BN128_PAIRING_CHECK - [_]u8{0} ** 19 ++ [_]u8{9}, // BLAKE2F - }; - allocator: Allocator, chain_id: config.ChainId, state: *StateDB, - prev_block: ?BlockHeader, - last_256_blocks_hashes: [256]Hash32, // blocks ordered in asc order + prev_block: BlockHeader, + last_256_blocks_hashes: [256]Hash32, // ordered in asc order pub fn init( allocator: Allocator, chain_id: config.ChainId, state: *StateDB, - prev_block: ?BlockHeader, + prev_block: BlockHeader, last_256_blocks_hashes: [256]Hash32, ) Blockchain { - return Blockchain{ + return .{ .allocator = allocator, .chain_id = chain_id, .state = state, @@ -108,35 +79,31 @@ pub const Blockchain = struct { // validateBlockHeader validates the header of a block itself and with respect with the parent. // If isn't valid, it returns an error. - fn validateBlockHeader(allocator: Allocator, parent_block: ?BlockHeader, curr_block: BlockHeader) !void { - if (parent_block) |prev_block| { - try checkGasLimit(curr_block.gas_limit, prev_block.gas_limit); - } + fn validateBlockHeader(allocator: Allocator, prev_block: ?BlockHeader, curr_block: BlockHeader) !void { + try checkGasLimit(curr_block.gas_limit, prev_block.gas_limit); if (curr_block.gas_used > curr_block.gas_limit) return error.GasLimitExceeded; // Check base fee. - if (parent_block) |prev_block| { - const parent_gas_target = prev_block.gas_limit / ELASTICITY_MULTIPLIER; - var expected_base_fee_per_gas = if (prev_block.gas_used == parent_gas_target) - prev_block.base_fee_per_gas - else if (prev_block.gas_used > parent_gas_target) blk: { - const gas_used_delta = prev_block.gas_used - parent_gas_target; - const base_fee_per_gas_delta = @max(prev_block.base_fee_per_gas * gas_used_delta / parent_gas_target / BASE_FEE_MAX_CHANGE_DENOMINATOR, 1); - break :blk prev_block.base_fee_per_gas + base_fee_per_gas_delta; - } else blk: { - const gas_used_delta = parent_gas_target - prev_block.gas_used; - const base_fee_per_gas_delta = prev_block.base_fee_per_gas * gas_used_delta / parent_gas_target / BASE_FEE_MAX_CHANGE_DENOMINATOR; - break :blk prev_block.base_fee_per_gas - base_fee_per_gas_delta; - }; - if (expected_base_fee_per_gas != curr_block.base_fee_per_gas) - return error.InvalidBaseFee; + const parent_gas_target = prev_block.gas_limit / params.elasticity_multiplier; + var expected_base_fee_per_gas = if (prev_block.gas_used == parent_gas_target) + prev_block.base_fee_per_gas + else if (prev_block.gas_used > parent_gas_target) blk: { + const gas_used_delta = prev_block.gas_used - parent_gas_target; + const base_fee_per_gas_delta = @max(prev_block.base_fee_per_gas * gas_used_delta / parent_gas_target / params.base_fee_max_change_denominator, 1); + break :blk prev_block.base_fee_per_gas + base_fee_per_gas_delta; + } else blk: { + const gas_used_delta = parent_gas_target - prev_block.gas_used; + const base_fee_per_gas_delta = prev_block.base_fee_per_gas * gas_used_delta / parent_gas_target / params.base_fee_max_change_denominator; + break :blk prev_block.base_fee_per_gas - base_fee_per_gas_delta; + }; + if (expected_base_fee_per_gas != curr_block.base_fee_per_gas) + return error.InvalidBaseFee; - if (curr_block.timestamp > prev_block.timestamp) - return error.InvalidTimestamp; - if (curr_block.block_number != prev_block.block_number + 1) - return error.InvalidBlockNumber; - } + if (curr_block.timestamp > prev_block.timestamp) + return error.InvalidTimestamp; + if (curr_block.block_number != prev_block.block_number + 1) + return error.InvalidBlockNumber; if (curr_block.extra_data.len > 32) return error.ExtraDataTooLong; @@ -147,18 +114,16 @@ pub const Blockchain = struct { if (!std.mem.eql(u8, &curr_block.uncle_hash, &blocks.empty_uncle_hash)) return error.InvalidUnclesHash; - if (parent_block) |prev_block| { - const prev_block_hash = try transaction.RLPHash(BlockHeader, allocator, prev_block, null); - if (!std.mem.eql(u8, &curr_block.parent_hash, &prev_block_hash)) - return error.InvalidParentHash; - } + const prev_block_hash = try transaction.RLPHash(BlockHeader, allocator, prev_block, null); + if (!std.mem.eql(u8, &curr_block.parent_hash, &prev_block_hash)) + return error.InvalidParentHash; } fn checkGasLimit(gas_limit: u256, parent_gas_limit: u256) !void { - const max_delta = parent_gas_limit / GAS_LIMIT_ADJUSTMENT_FACTOR; + const max_delta = parent_gas_limit / params.gas_limit_adjustement_factor; if (gas_limit >= parent_gas_limit + max_delta) return error.GasLimitTooHigh; if (gas_limit <= parent_gas_limit - max_delta) return error.GasLimitTooLow; - if (gas_limit >= GAS_LIMIT_MINIMUM) return error.GasLimitLessThanMinimum; + if (gas_limit >= params.gas_limit_minimum) return error.GasLimitLessThanMinimum; } const BlockExecutionResult = struct { @@ -338,7 +303,7 @@ pub const Blockchain = struct { return false; if (tx.getNonce() >= (2 << 64) - 1) return false; - if (tx.getTo() == null and tx.getData().len > 2 * MAX_CODE_SIZE) + if (tx.getTo() == null and tx.getData().len > 2 * params.max_code_size) return false; return true; } @@ -347,28 +312,28 @@ pub const Blockchain = struct { var data_cost: u64 = 0; const data = tx.getData(); for (data) |byte| { - data_cost += if (byte == 0) TX_DATA_COST_PER_ZERO else TX_DATA_COST_PER_NON_ZERO; + data_cost += if (byte == 0) params.tx_data_cost_per_zero else params.tx_data_cost_per_non_zero; } - const create_cost = if (tx.getTo() == null) TX_CREATE_COST + initCodeCost(data.len) else 0; + const create_cost = if (tx.getTo() == null) params.tx_create_cost + initCodeCost(data.len) else 0; const access_list_cost = switch (tx) { .LegacyTxn => 0, inline else => |al_tx| blk: { var sum: u64 = 0; for (al_tx.access_list) |al| { - data_cost += TX_ACCESS_LIST_ADDRESS_COST; - data_cost += al.storage_keys.len * TX_ACCESS_LIST_STORAGE_KEY_COST; + data_cost += params.tx_access_list_address_cost; + data_cost += al.storage_keys.len * params.tx_access_list_storage_key_cost; } break :blk sum; }, }; - return TX_BASE_COST + data_cost + create_cost + access_list_cost; + return params.tx_base_cost + data_cost + create_cost + access_list_cost; } fn initCodeCost(code_length: usize) u64 { - return GAS_INIT_CODE_WORD_COST * (code_length + 31) / 32; + return params.gas_init_code_word_const * (code_length + 31) / 32; } pub const Message = struct { @@ -423,7 +388,7 @@ pub const Blockchain = struct { var accessed_addresses = try preaccessed_addresses.clone(); try accessed_addresses.put(current_target, {}); try accessed_addresses.put(caller, {}); - for (PRE_COMPILED_CONTRACT_ADDRESSES) |address| { + for (params.precompiled_contract_addresses) |address| { try accessed_addresses.put(address, {}); } diff --git a/src/blockchain/params.zig b/src/blockchain/params.zig new file mode 100644 index 0000000..622f0b4 --- /dev/null +++ b/src/blockchain/params.zig @@ -0,0 +1,36 @@ +const std = @import("std"); +const types = @import("../types/types.zig"); +const Address = types.Address; + +pub const gas_limit_adjustement_factor = 1024; +pub const gas_limit_minimum = 5000; +pub const gas_init_code_word_const = 2; + +pub const tx_base_cost = 21000; +pub const tx_data_cost_per_zero = 4; +pub const tx_data_cost_per_non_zero = 16; +pub const tx_create_cost = 32000; +pub const tx_access_list_address_cost = 2400; +pub const tx_access_list_storage_key_cost = 1900; + +pub const base_fee_max_change_denominator = 8; +pub const elasticity_multiplier = 2; +pub const max_code_size = 0x6000; + +pub const precompiled_contract_addresses = [_]Address{ + addressFromInt(1), // ECRECOVER + addressFromInt(2), // SHA256 + addressFromInt(3), // RIPEMD160 + addressFromInt(4), // IDENTITY_ADDRESS + addressFromInt(5), // MODEXP_ADDRESS + addressFromInt(6), // ALT_BN128_ADD + addressFromInt(7), // ALT_BN128_MUL + addressFromInt(8), // ALT_BN128_PAIRING_CHECK + addressFromInt(9), // BLAKE2F +}; + +fn addressFromInt(comptime i: u256) Address { + var addr: Address = undefined; + std.mem.writeInt(u256, &addr, i, .Big); + return addr; +} diff --git a/src/common/common.zig b/src/common/common.zig index 4a02c69..6ea76cb 100644 --- a/src/common/common.zig +++ b/src/common/common.zig @@ -2,13 +2,16 @@ const std = @import("std"); const types = @import("../types/types.zig"); const hexutils = @import("./hexutils.zig"); +// Hex. pub const prefixedhex2hash = hexutils.prefixedhex2hash; pub const prefixedhex2byteslice = hexutils.prefixedhex2byteslice; pub const prefixedhex2u64 = hexutils.prefixedhex2u64; pub const hexToAddress = hexutils.hexToAddress; pub const comptimeHexToBytes = hexutils.comptimeHexToBytes; +// Sets. pub const AddressSet = std.AutoHashMap(types.Address, void); - pub const AddressKey = struct { address: types.Address, key: types.Bytes32 }; pub const AddressKeySet = std.AutoHashMap(AddressKey, void); + +// Fmt From 9f7c506f6f416ee23a3aaabf23e31f0e0364e141 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 10 Jan 2024 10:36:08 -0300 Subject: [PATCH 42/52] blockchain: include validations with parent block & update zig-rlp which requied fixes Signed-off-by: Ignacio Hagopian --- build.zig.zon | 4 +-- src/blockchain/blockchain.zig | 6 ++--- src/blockchain/params.zig | 4 +-- src/engine_api/execution_payload.zig | 6 ++--- src/exec-spec-tests/execspectests.zig | 35 ++++++++++++++------------- src/types/block.zig | 2 +- src/types/transaction.zig | 5 ++-- 7 files changed, 32 insertions(+), 30 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index fc2d890..4b23b67 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,8 +3,8 @@ .version = "0.0.1-beta-0", .dependencies = .{ .@"zig-rlp" = .{ - .url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.0.1-beta-10.tar.gz", - .hash = "1220a98462cbf50ce04b24586966e4dc209596e7301d89dfd05dda0dd59ed97f0c09", + .url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.0.1-beta-11.tar.gz", + .hash = "12203209eed686ebe19350c9d97578291025ba2d99dfdba3cfe920aee7c46937eb64", }, .@"zig-eth-secp256k1" = .{ .url = "https://github.com/jsign/zig-eth-secp256k1/archive/refs/tags/v0.1.0-beta-3.tar.gz", diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 3c42517..fd689e3 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -79,7 +79,7 @@ pub const Blockchain = struct { // validateBlockHeader validates the header of a block itself and with respect with the parent. // If isn't valid, it returns an error. - fn validateBlockHeader(allocator: Allocator, prev_block: ?BlockHeader, curr_block: BlockHeader) !void { + fn validateBlockHeader(allocator: Allocator, prev_block: BlockHeader, curr_block: BlockHeader) !void { try checkGasLimit(curr_block.gas_limit, prev_block.gas_limit); if (curr_block.gas_used > curr_block.gas_limit) return error.GasLimitExceeded; @@ -100,7 +100,7 @@ pub const Blockchain = struct { if (expected_base_fee_per_gas != curr_block.base_fee_per_gas) return error.InvalidBaseFee; - if (curr_block.timestamp > prev_block.timestamp) + if (curr_block.timestamp <= prev_block.timestamp) return error.InvalidTimestamp; if (curr_block.block_number != prev_block.block_number + 1) return error.InvalidBlockNumber; @@ -123,7 +123,7 @@ pub const Blockchain = struct { const max_delta = parent_gas_limit / params.gas_limit_adjustement_factor; if (gas_limit >= parent_gas_limit + max_delta) return error.GasLimitTooHigh; if (gas_limit <= parent_gas_limit - max_delta) return error.GasLimitTooLow; - if (gas_limit >= params.gas_limit_minimum) return error.GasLimitLessThanMinimum; + if (gas_limit < params.gas_limit_minimum) return error.GasLimitLessThanMinimum; } const BlockExecutionResult = struct { diff --git a/src/blockchain/params.zig b/src/blockchain/params.zig index 622f0b4..20fc812 100644 --- a/src/blockchain/params.zig +++ b/src/blockchain/params.zig @@ -29,8 +29,8 @@ pub const precompiled_contract_addresses = [_]Address{ addressFromInt(9), // BLAKE2F }; -fn addressFromInt(comptime i: u256) Address { +fn addressFromInt(comptime i: u160) Address { var addr: Address = undefined; - std.mem.writeInt(u256, &addr, i, .Big); + std.mem.writeInt(u160, &addr, i, .Big); return addr; } diff --git a/src/engine_api/execution_payload.zig b/src/engine_api/execution_payload.zig index 7986d15..3cca27c 100644 --- a/src/engine_api/execution_payload.zig +++ b/src/engine_api/execution_payload.zig @@ -28,7 +28,7 @@ pub const ExecutionPayload = struct { allocator: Allocator, - pub fn to_block(self: *const ExecutionPayload) types.Block { + pub fn toBlock(self: *const ExecutionPayload) types.Block { return types.Block{ .header = types.BlockHeader{ .parent_hash = self.parentHash, @@ -47,7 +47,7 @@ pub const ExecutionPayload = struct { .base_fee_per_gas = self.baseFeePerGas, .transactions_root = [_]u8{0} ** 32, .nonce = [_]u8{0} ** 8, - .withdrawals_root = null, + .withdrawals_root = [_]u8{0} ** 32, }, .transactions = self.transactions, .withdrawals = self.withdrawals, @@ -70,7 +70,7 @@ pub fn newPayloadV2Handler(params: *ExecutionPayload, allocator: std.mem.Allocat // But so far, just print the content of the payload std.log.info("newPayloadV2Handler: {any}", .{params}); - var block = params.to_block(); + var block = params.toBlock(); std.debug.print("block number={}\n", .{block.header.block_number}); params.deinit(allocator); } diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index 8611517..41f8f81 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -25,9 +25,8 @@ pub const Fixture = struct { const FixtureType = std.json.ArrayHashMap(FixtureTest); tests: std.json.Parsed(FixtureType), - pub fn newFromBytes(allocator: Allocator, bytes: []const u8) !Fixture { + pub fn fromBytes(allocator: Allocator, bytes: []const u8) !Fixture { const tests = try std.json.parseFromSlice(FixtureType, allocator, bytes, std.json.ParseOptions{ .ignore_unknown_fields = true, .allocate = std.json.AllocWhen.alloc_always }); - return .{ .tests = tests }; } @@ -60,36 +59,39 @@ pub const FixtureTest = struct { defer arena.deinit(); var allocator = arena.allocator(); - // 1. We parse the account state "prestate" from the test, and create our + // We parse the account state "prestate" from the test, and create our // statedb with this initial state of accounts. var accounts_state = blk: { var accounts_state = try allocator.alloc(AccountState, self.pre.map.count()); var it = self.pre.map.iterator(); var i: usize = 0; while (it.next()) |entry| { - accounts_state[i] = try entry.value_ptr.*.to_vm_accountstate(allocator, entry.key_ptr.*); + accounts_state[i] = try entry.value_ptr.*.toAccountState(allocator, entry.key_ptr.*); i = i + 1; } break :blk accounts_state; }; var statedb = try StateDB.init(allocator, accounts_state); - // 2. Execute blocks. - // const txn_signer = try TxnSigner.init(0); // ChainID == 0 is used in tests. - for (self.blocks) |encoded_block| { - var chain = blockchain.Blockchain.init(allocator, config.ChainId.SpecTest, &statedb, null, std.mem.zeroes([256]Hash32)); + // Initialize the blockchain with the preloaded statedb and the genesis + // block as the previous block. + var out = try allocator.alloc(u8, self.genesisRLP.len / 2); + var rlp_bytes = try std.fmt.hexToBytes(out, self.genesisRLP[2..]); + const parent_block = try Block.decode(allocator, rlp_bytes); + var chain = blockchain.Blockchain.init(allocator, config.ChainId.SpecTest, &statedb, parent_block.header, std.mem.zeroes([256]Hash32)); - var out = try allocator.alloc(u8, encoded_block.rlp.len / 2); - defer allocator.free(out); - const rlp_bytes = try std.fmt.hexToBytes(out, encoded_block.rlp[2..]); + // Execute blocks. + for (self.blocks) |encoded_block| { + out = try allocator.alloc(u8, encoded_block.rlp.len / 2); + rlp_bytes = try std.fmt.hexToBytes(out, encoded_block.rlp[2..]); const block = try Block.decode(allocator, rlp_bytes); try chain.runBlock(block); } - // 3. Verify that the post state matches what the fixture `postState` claims is true. + // Verify that the post state matches what the fixture `postState` claims is true. var it = self.postState.map.iterator(); while (it.next()) |entry| { - var exp_account_state: AccountState = try entry.value_ptr.*.to_vm_accountstate(allocator, entry.key_ptr.*); + var exp_account_state: AccountState = try entry.value_ptr.*.toAccountState(allocator, entry.key_ptr.*); std.debug.print("checking account state: {s}\n", .{std.fmt.fmtSliceHexLower(&exp_account_state.addr)}); const got_account_state = statedb.getAccount(exp_account_state.addr); if (got_account_state.nonce != exp_account_state.nonce) { @@ -147,7 +149,7 @@ pub const TransactionHex = struct { data: HexString, gasLimit: HexString, - pub fn to_vm_transaction(self: TransactionHex, allocator: Allocator, txn_signer: TxnSigner) !Txn { + pub fn toTx(self: TransactionHex, allocator: Allocator, txn_signer: TxnSigner) !Txn { const type_ = try std.fmt.parseInt(u8, self.type[2..], 16); std.debug.assert(type_ == 0); const chain_id = try std.fmt.parseInt(u64, self.chainId[2..], 16); @@ -184,7 +186,7 @@ pub const AccountStateHex = struct { // TODO(jsign): add init() and add assertions about lengths. - pub fn to_vm_accountstate(self: *const AccountStateHex, allocator: Allocator, addr_hex: []const u8) !AccountState { + pub fn toAccountState(self: *const AccountStateHex, allocator: Allocator, addr_hex: []const u8) !AccountState { const nonce = try std.fmt.parseInt(u64, self.nonce[2..], 16); const balance = try std.fmt.parseInt(u256, self.balance[2..], 16); @@ -215,13 +217,12 @@ const AccountStorageHex = std.json.ArrayHashMap(HexString); var test_allocator = std.testing.allocator; test "execution-spec-tests" { - var ft = try Fixture.newFromBytes(test_allocator, @embedFile("fixtures/exec-spec-fixture.json")); + var ft = try Fixture.fromBytes(test_allocator, @embedFile("fixtures/exec-spec-fixture.json")); defer ft.deinit(); var it = ft.tests.value.map.iterator(); var count: usize = 0; while (it.next()) |entry| { - log.debug("##### Executing fixture {s} #####", .{entry.key_ptr.*}); try std.testing.expect(try entry.value_ptr.*.run(test_allocator)); count += 1; diff --git a/src/types/block.zig b/src/types/block.zig index f466f5d..8d9d500 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -29,7 +29,7 @@ pub const BlockHeader = struct { prev_randao: Bytes32, nonce: [8]u8, base_fee_per_gas: u256, - withdrawals_root: ?Hash32, + withdrawals_root: Hash32, }; pub const Block = struct { diff --git a/src/types/transaction.zig b/src/types/transaction.zig index 7c316a3..9b2b03b 100644 --- a/src/types/transaction.zig +++ b/src/types/transaction.zig @@ -254,16 +254,17 @@ pub const FeeMarketTxn = struct { } }; +// TODO: move to common. pub fn RLPDecode(comptime T: type, arena: Allocator, bytes: []const u8) !T { var ret: T = std.mem.zeroes(T); _ = try rlp.deserialize(T, bytes, &ret, arena); return ret; } -pub fn RLPHash(comptime T: type, allocator: Allocator, txn: T, prefix: ?[]const u8) !Hash32 { +pub fn RLPHash(comptime T: type, allocator: Allocator, value: T, prefix: ?[]const u8) !Hash32 { var out = std.ArrayList(u8).init(allocator); defer out.deinit(); - try rlp.serialize(T, allocator, txn, &out); + try rlp.serialize(T, allocator, value, &out); if (prefix) |pre| { return hasher.keccak256WithPrefix(pre, out.items); } From 558bcd0c7748ae4f7a654bd3d468d9a2ab164ef4 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 10 Jan 2024 11:58:00 -0300 Subject: [PATCH 43/52] mod: update to tentative zig-rlp and avoid workaround Signed-off-by: Ignacio Hagopian --- build.zig.zon | 2 +- src/types/block.zig | 2 +- src/types/transaction.zig | 12 +++++------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 4b23b67..881994c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,7 +4,7 @@ .dependencies = .{ .@"zig-rlp" = .{ .url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.0.1-beta-11.tar.gz", - .hash = "12203209eed686ebe19350c9d97578291025ba2d99dfdba3cfe920aee7c46937eb64", + .hash = "12203abe1db56e12ccfc89e1a862759879a17175c0356635f485489ea30e58b070d1", }, .@"zig-eth-secp256k1" = .{ .url = "https://github.com/jsign/zig-eth-secp256k1/archive/refs/tags/v0.1.0-beta-3.tar.gz", diff --git a/src/types/block.zig b/src/types/block.zig index 8d9d500..b1768ed 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -40,7 +40,7 @@ pub const Block = struct { pub fn decode(arena: Allocator, rlp_bytes: []const u8) !Block { var block: Block = undefined; - _ = try rlp.deserialize(Block, rlp_bytes, &block, arena); + _ = try rlp.deserialize(Block, arena, rlp_bytes, &block); return block; } }; diff --git a/src/types/transaction.zig b/src/types/transaction.zig index 9b2b03b..84eb5cb 100644 --- a/src/types/transaction.zig +++ b/src/types/transaction.zig @@ -42,18 +42,16 @@ pub const Txn = union(TxnTypes) { return error.UnsupportedTxnType; } - pub fn decodeRLP(self: *Txn, serialized: []const u8) !usize { - var alloc = std.testing.allocator; - var arena = std.heap.ArenaAllocator.init(alloc); + pub fn decodeFromRLP(self: *Txn, arena: Allocator, serialized: []const u8) !usize { if (serialized[0] > 0xC0) { // Is a RLP struct (i.e: LegacyTx) var ltx: LegacyTxn = undefined; - const size = rlp.deserialize(LegacyTxn, serialized, <x, arena.allocator()); + const size = rlp.deserialize(LegacyTxn, arena, serialized, <x); self.* = .{ .LegacyTxn = ltx }; return size; } var str: []const u8 = undefined; - const size = try rlp.deserialize([]const u8, serialized, &str, arena.allocator()); - self.* = try Txn.decode(arena.allocator(), str); + const size = try rlp.deserialize([]const u8, arena, serialized, &str); + self.* = try Txn.decode(arena, str); return size; } @@ -257,7 +255,7 @@ pub const FeeMarketTxn = struct { // TODO: move to common. pub fn RLPDecode(comptime T: type, arena: Allocator, bytes: []const u8) !T { var ret: T = std.mem.zeroes(T); - _ = try rlp.deserialize(T, bytes, &ret, arena); + _ = try rlp.deserialize(T, arena, bytes, &ret); return ret; } From 8d59b337a150e10e9be1c165805943d1ee0736f3 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 10 Jan 2024 12:27:58 -0300 Subject: [PATCH 44/52] blockchain: manage prev_block reference and last 256 hashes Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 22 +++++++++++++++++----- src/exec-spec-tests/execspectests.zig | 2 +- src/types/block.zig | 11 +++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index fd689e3..9d7445b 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -29,23 +29,25 @@ pub const Blockchain = struct { prev_block: BlockHeader, last_256_blocks_hashes: [256]Hash32, // ordered in asc order + // init initializes a blockchain. + // The caller **does not** transfer ownership of prev_block. pub fn init( allocator: Allocator, chain_id: config.ChainId, state: *StateDB, prev_block: BlockHeader, last_256_blocks_hashes: [256]Hash32, - ) Blockchain { + ) !Blockchain { return .{ .allocator = allocator, .chain_id = chain_id, .state = state, - .prev_block = prev_block, + .prev_block = try prev_block.clone(allocator), .last_256_blocks_hashes = last_256_blocks_hashes, }; } - pub fn runBlock(self: Blockchain, block: Block) !void { + pub fn runBlock(self: *Blockchain, block: Block) !void { try validateBlockHeader(self.allocator, self.prev_block, block.header); if (block.uncles.len != 0) return error.NotEmptyUncles; @@ -75,6 +77,17 @@ pub const Blockchain = struct { // TODO: disabled until withdrawals root is calculated // if (!std.mem.eql(u8, &result.withdrawals_root, &block.header.withdrawals_root)) // return error.InvalidWithdrawalsRoot; + + // Add the current block to the last 256 block hashes. + // TODO: this can be done more efficiently with some ring buffer to avoid copying the slice + // to make room for the new block hash. + std.mem.copyForwards(Hash32, &self.last_256_blocks_hashes, self.last_256_blocks_hashes[1..255]); + self.last_256_blocks_hashes[255] = try transaction.RLPHash(BlockHeader, allocator, block.header, null); + + // Note that we free and clone with the Blockchain allocator, and not the arena allocator. + // This is required since Blockchain field lifetimes are longer than the block execution processing. + self.prev_block.deinit(self.allocator); + self.prev_block = try block.header.clone(self.allocator); } // validateBlockHeader validates the header of a block itself and with respect with the parent. @@ -134,7 +147,7 @@ pub const Blockchain = struct { withdrawals_root: Hash32, }; - fn applyBody(allocator: Allocator, chain: Blockchain, state: *StateDB, block: Block) !BlockExecutionResult { + fn applyBody(allocator: Allocator, chain: *Blockchain, state: *StateDB, block: Block) !BlockExecutionResult { var gas_available = block.header.gas_limit; for (block.transactions) |tx| { const txn_info = try checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, chain.chain_id); @@ -439,7 +452,6 @@ pub const Blockchain = struct { return .{ .gas_left = @intCast(result.gas_left), .refund_counter = @intCast(result.gas_refund), - // .accounts_to_delete = AddressKeySet, }; } }; diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index 41f8f81..54d2db3 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -78,7 +78,7 @@ pub const FixtureTest = struct { var out = try allocator.alloc(u8, self.genesisRLP.len / 2); var rlp_bytes = try std.fmt.hexToBytes(out, self.genesisRLP[2..]); const parent_block = try Block.decode(allocator, rlp_bytes); - var chain = blockchain.Blockchain.init(allocator, config.ChainId.SpecTest, &statedb, parent_block.header, std.mem.zeroes([256]Hash32)); + var chain = try blockchain.Blockchain.init(allocator, config.ChainId.SpecTest, &statedb, parent_block.header, std.mem.zeroes([256]Hash32)); // Execute blocks. for (self.blocks) |encoded_block| { diff --git a/src/types/block.zig b/src/types/block.zig index b1768ed..093a097 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -30,6 +30,17 @@ pub const BlockHeader = struct { nonce: [8]u8, base_fee_per_gas: u256, withdrawals_root: Hash32, + + pub fn clone(self: BlockHeader, allocator: Allocator) !BlockHeader { + var ret = self; + ret.extra_data = try allocator.dupe(u8, self.extra_data); + return ret; + } + + pub fn deinit(self: *BlockHeader, allocator: Allocator) void { + allocator.free(self.extra_data); + self.* = undefined; + } }; pub const Block = struct { From 87d2054bebd70b6c308875c5335d55d65520de37 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 10 Jan 2024 12:35:26 -0300 Subject: [PATCH 45/52] common: add rlp helpers Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 6 +++--- src/blockchain/vm.zig | 6 ------ src/common/common.zig | 9 ++++++--- src/common/rlp.zig | 22 ++++++++++++++++++++++ src/types/transaction.zig | 33 ++++++++------------------------- 5 files changed, 39 insertions(+), 37 deletions(-) create mode 100644 src/common/rlp.zig diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 9d7445b..a812b88 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -82,7 +82,7 @@ pub const Blockchain = struct { // TODO: this can be done more efficiently with some ring buffer to avoid copying the slice // to make room for the new block hash. std.mem.copyForwards(Hash32, &self.last_256_blocks_hashes, self.last_256_blocks_hashes[1..255]); - self.last_256_blocks_hashes[255] = try transaction.RLPHash(BlockHeader, allocator, block.header, null); + self.last_256_blocks_hashes[255] = try common.decodeRLPAndHash(BlockHeader, allocator, block.header, null); // Note that we free and clone with the Blockchain allocator, and not the arena allocator. // This is required since Blockchain field lifetimes are longer than the block execution processing. @@ -127,7 +127,7 @@ pub const Blockchain = struct { if (!std.mem.eql(u8, &curr_block.uncle_hash, &blocks.empty_uncle_hash)) return error.InvalidUnclesHash; - const prev_block_hash = try transaction.RLPHash(BlockHeader, allocator, prev_block, null); + const prev_block_hash = try common.decodeRLPAndHash(BlockHeader, allocator, prev_block, null); if (!std.mem.eql(u8, &curr_block.parent_hash, &prev_block_hash)) return error.InvalidParentHash; } @@ -438,7 +438,7 @@ pub const Blockchain = struct { refund_counter: u64, // logs: Union[Tuple[()], Tuple[Log, ...]] TODO // accounts_to_delete: AddressKeySet, // TODO (delete?) - // error: Optional[Exception] TODO + // error TODO (required for future receipts) }; fn processMessageCall(allocator: Allocator, message: Message, env: Environment) !MessageCallOutput { diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index eabc825..d84ce51 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -1,4 +1,3 @@ -// TODO(pre-review): check usage of all imports. const evmc = @cImport({ @cInclude("evmone.h"); }); @@ -12,14 +11,9 @@ const AddressKey = common.AddressKey; const AddressKeySet = common.AddressKeySet; const Environment = blockchain.Environment; const Message = blockchain.Message; -const Txn = types.Txn; -const TxnSigner = @import("../signer/signer.zig").TxnSigner; const Block = types.Block; -const AccountState = types.AccountState; -const Bytecode = types.Bytecode; const Hash32 = types.Hash32; const Address = types.Address; -const StateDB = @import("../state/statedb.zig").StateDB; const Keccak256 = std.crypto.hash.sha3.Keccak256; const fmtSliceHexLower = std.fmt.fmtSliceHexLower; const assert = std.debug.assert; diff --git a/src/common/common.zig b/src/common/common.zig index 6ea76cb..b323ac8 100644 --- a/src/common/common.zig +++ b/src/common/common.zig @@ -1,17 +1,20 @@ const std = @import("std"); const types = @import("../types/types.zig"); const hexutils = @import("./hexutils.zig"); +const rlp = @import("./rlp.zig"); -// Hex. +// Hex pub const prefixedhex2hash = hexutils.prefixedhex2hash; pub const prefixedhex2byteslice = hexutils.prefixedhex2byteslice; pub const prefixedhex2u64 = hexutils.prefixedhex2u64; pub const hexToAddress = hexutils.hexToAddress; pub const comptimeHexToBytes = hexutils.comptimeHexToBytes; -// Sets. +// Sets pub const AddressSet = std.AutoHashMap(types.Address, void); pub const AddressKey = struct { address: types.Address, key: types.Bytes32 }; pub const AddressKeySet = std.AutoHashMap(AddressKey, void); -// Fmt +// RLP +pub const decodeRLP = rlp.decodeRLP; +pub const decodeRLPAndHash = rlp.decodeRLPAndHash; diff --git a/src/common/rlp.zig b/src/common/rlp.zig new file mode 100644 index 0000000..7b73976 --- /dev/null +++ b/src/common/rlp.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const rlp = @import("zig-rlp"); +const types = @import("../types/types.zig"); +const hasher = @import("../crypto/hasher.zig"); +const Allocator = std.mem.Allocator; +const Hash32 = types.Hash32; + +pub fn decodeRLP(comptime T: type, arena: Allocator, bytes: []const u8) !T { + var ret: T = std.mem.zeroes(T); + _ = try rlp.deserialize(T, arena, bytes, &ret); + return ret; +} + +pub fn decodeRLPAndHash(comptime T: type, allocator: Allocator, value: T, prefix: ?[]const u8) !Hash32 { + var out = std.ArrayList(u8).init(allocator); + defer out.deinit(); + try rlp.serialize(T, allocator, value, &out); + if (prefix) |pre| { + return hasher.keccak256WithPrefix(pre, out.items); + } + return hasher.keccak256(out.items); +} diff --git a/src/types/transaction.zig b/src/types/transaction.zig index 84eb5cb..be94cb0 100644 --- a/src/types/transaction.zig +++ b/src/types/transaction.zig @@ -1,8 +1,8 @@ const std = @import("std"); -const Allocator = std.mem.Allocator; const rlp = @import("zig-rlp"); -const hasher = @import("../crypto/hasher.zig"); const types = @import("types.zig"); +const common = @import("../common/common.zig"); +const Allocator = std.mem.Allocator; const Address = types.Address; const Hash32 = types.Hash32; @@ -157,13 +157,13 @@ pub const LegacyTxn = struct { // decode decodes a transaction from bytes. No bytes from the input slice are referenced in the // output transaction. pub fn decode(arena: Allocator, bytes: []const u8) !LegacyTxn { - return try RLPDecode(LegacyTxn, arena, bytes); + return try common.decodeRLP(LegacyTxn, arena, bytes); } pub fn hash(self: LegacyTxn, allocator: Allocator) !Hash32 { // TODO: consider caching the calculated txnHash to avoid further // allocations and keccaking. But be careful since struct fields are public. - return try RLPHash(LegacyTxn, allocator, self, null); + return try common.decodeRLPAndHash(LegacyTxn, allocator, self, null); } pub fn setSignature(self: *LegacyTxn, v: u256, r: u256, s: u256) void { @@ -204,7 +204,7 @@ pub const AccessListTxn = struct { // TODO: consider caching the calculated txnHash to avoid further // allocations and keccaking. But be careful since struct fields are public. const prefix = [_]u8{@intFromEnum(TxnTypes.AccessListTxn)}; - return try RLPHash(AccessListTxn, allocator, self, &prefix); + return try common.decodeRLPAndHash(AccessListTxn, allocator, self, &prefix); } pub fn setSignature(self: *AccessListTxn, v: u256, r: u256, s: u256) void { @@ -215,7 +215,7 @@ pub const AccessListTxn = struct { // decode decodes a transaction from bytes. pub fn decode(arena: Allocator, bytes: []const u8) !AccessListTxn { - return try RLPDecode(AccessListTxn, arena, bytes); + return try common.decodeRLP(AccessListTxn, arena, bytes); } }; @@ -237,7 +237,7 @@ pub const FeeMarketTxn = struct { // TODO: consider caching the calculated txnHash to avoid further // allocations and keccaking. But be careful since struct fields are public. const prefix = [_]u8{@intFromEnum(TxnTypes.FeeMarketTxn)}; - return try RLPHash(FeeMarketTxn, allocator, self, &prefix); + return try common.decodeRLPAndHash(FeeMarketTxn, allocator, self, &prefix); } pub fn setSignature(self: *FeeMarketTxn, v: u256, r: u256, s: u256) void { @@ -248,27 +248,10 @@ pub const FeeMarketTxn = struct { // decode decodes a transaction from bytes. pub fn decode(arena: Allocator, bytes: []const u8) !FeeMarketTxn { - return try RLPDecode(FeeMarketTxn, arena, bytes); + return try common.decodeRLP(FeeMarketTxn, arena, bytes); } }; -// TODO: move to common. -pub fn RLPDecode(comptime T: type, arena: Allocator, bytes: []const u8) !T { - var ret: T = std.mem.zeroes(T); - _ = try rlp.deserialize(T, arena, bytes, &ret); - return ret; -} - -pub fn RLPHash(comptime T: type, allocator: Allocator, value: T, prefix: ?[]const u8) !Hash32 { - var out = std.ArrayList(u8).init(allocator); - defer out.deinit(); - try rlp.serialize(T, allocator, value, &out); - if (prefix) |pre| { - return hasher.keccak256WithPrefix(pre, out.items); - } - return hasher.keccak256(out.items); -} - test "Mainnet transactions hashing" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); From 1fccb1b37809461bb92060231b069cf10f10a3d1 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 10 Jan 2024 12:38:59 -0300 Subject: [PATCH 46/52] blockchain: refactor Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 38 +++-------------------------- src/blockchain/types.zig | 45 +++++++++++++++++++++++++++++++++++ src/blockchain/vm.zig | 6 ++--- 3 files changed, 51 insertions(+), 38 deletions(-) create mode 100644 src/blockchain/types.zig diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index a812b88..d345d3f 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -8,6 +8,7 @@ const vm = @import("vm.zig"); const rlp = @import("zig-rlp"); const signer = @import("../signer/signer.zig"); const params = @import("params.zig"); +const blockchain_types = @import("types.zig"); const Allocator = std.mem.Allocator; const AddressSet = common.AddressSet; const AddresssKey = common.AddressKey; @@ -15,6 +16,8 @@ const AddressKeySet = common.AddressKeySet; const LogsBloom = types.LogsBloom; const Block = types.Block; const BlockHeader = types.BlockHeader; +const Environment = blockchain_types.Environment; +const Message = blockchain_types.Message; const StateDB = @import("../state/statedb.zig").StateDB; const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; @@ -214,21 +217,6 @@ pub const Blockchain = struct { return .{ .sender_address = sender_address, .effective_gas_price = effective_gas_price }; } - pub const Environment = struct { - caller: Address, - block_hashes: [256]Hash32, - origin: Address, - coinbase: Address, - number: u64, - base_fee_per_gas: u256, - gas_limit: u64, - gas_price: u256, - time: u64, - prev_randao: Bytes32, - state: *StateDB, - chain_id: config.ChainId, - }; - fn processTransaction(allocator: Allocator, env: Environment, tx: transaction.Txn) !struct { gas_used: u64 } { if (!validateTransaction(tx)) return error.InvalidTransaction; @@ -349,26 +337,6 @@ pub const Blockchain = struct { return params.gas_init_code_word_const * (code_length + 31) / 32; } - pub const Message = struct { - caller: Address, - target: ?Address, - current_target: Address, - gas: u64, - value: u256, - data: []const u8, - code_address: ?Address, - code: []const u8, - accessed_addresses: AddressSet, - accessed_storage_keys: AddressKeySet, - - pub fn deinit(self: *Message) void { - self.accessed_addresses.deinit(); - self.accessed_addresses = undefined; - self.accessed_storage_keys.deinit(); - self.accessed_storage_keys = undefined; - } - }; - // prepareMessage prepares an EVM message. // The caller must call deinit() on the returned Message. fn prepareMessage( diff --git a/src/blockchain/types.zig b/src/blockchain/types.zig new file mode 100644 index 0000000..2cfbca6 --- /dev/null +++ b/src/blockchain/types.zig @@ -0,0 +1,45 @@ +const types = @import("../types/types.zig"); +const config = @import("../config/config.zig"); +const common = @import("../common/common.zig"); +const StateDB = @import("../state/statedb.zig").StateDB; +const Address = types.Address; +const Hash32 = types.Hash32; +const Bytes32 = types.Bytes32; +const AddressSet = common.AddressSet; +const AddresssKey = common.AddressKey; +const AddressKeySet = common.AddressKeySet; + +pub const Environment = struct { + caller: Address, + block_hashes: [256]Hash32, + origin: Address, + coinbase: Address, + number: u64, + base_fee_per_gas: u256, + gas_limit: u64, + gas_price: u256, + time: u64, + prev_randao: Bytes32, + state: *StateDB, + chain_id: config.ChainId, +}; + +pub const Message = struct { + caller: Address, + target: ?Address, + current_target: Address, + gas: u64, + value: u256, + data: []const u8, + code_address: ?Address, + code: []const u8, + accessed_addresses: AddressSet, + accessed_storage_keys: AddressKeySet, + + pub fn deinit(self: *Message) void { + self.accessed_addresses.deinit(); + self.accessed_addresses = undefined; + self.accessed_storage_keys.deinit(); + self.accessed_storage_keys = undefined; + } +}; diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index d84ce51..ffb2586 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -4,13 +4,13 @@ const evmc = @cImport({ const std = @import("std"); const types = @import("../types/types.zig"); const common = @import("../common/common.zig"); -const blockchain = @import("blockchain.zig").Blockchain; // TODO: unnest +const blockchain_types = @import("types.zig"); const Allocator = std.mem.Allocator; const AddressSet = common.AddressSet; const AddressKey = common.AddressKey; const AddressKeySet = common.AddressKeySet; -const Environment = blockchain.Environment; -const Message = blockchain.Message; +const Environment = blockchain_types.Environment; +const Message = blockchain_types.Message; const Block = types.Block; const Hash32 = types.Hash32; const Address = types.Address; From c2e3e4788d69b5b82a80226042a99f41c486b8fa Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 10 Jan 2024 14:08:12 -0300 Subject: [PATCH 47/52] blockchain: make accessed addresses and storage keys into statedb making snapshoting more seameless Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 33 +++++---------- src/blockchain/params.zig | 2 + src/blockchain/types.zig | 10 ----- src/blockchain/vm.zig | 76 +++++++++++++---------------------- src/state/statedb.zig | 42 ++++++++++++++++--- 5 files changed, 76 insertions(+), 87 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index d345d3f..a4d68c8 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -155,7 +155,6 @@ pub const Blockchain = struct { for (block.transactions) |tx| { const txn_info = try checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, chain.chain_id); const env: Environment = .{ - .caller = txn_info.sender_address, .origin = txn_info.sender_address, .block_hashes = chain.last_256_blocks_hashes, .coinbase = block.header.fee_recipient, @@ -240,18 +239,14 @@ pub const Blockchain = struct { const sender_balance_after_gas_fee = sender_account.balance - effective_gas_fee; try env.state.setBalance(sender, sender_balance_after_gas_fee); - var preaccessed_addresses = AddressSet.init(allocator); - defer preaccessed_addresses.deinit(); - var preaccessed_storage_keys = AddressKeySet.init(allocator); - defer preaccessed_storage_keys.deinit(); - try preaccessed_addresses.put(env.coinbase, {}); + try env.state.putAccessedAccount(env.coinbase); switch (tx) { .LegacyTxn => {}, inline else => |al_tx| { for (al_tx.access_list) |al| { - try preaccessed_addresses.put(al.address, {}); + try env.state.putAccessedAccount(al.address); for (al.storage_keys) |key| { - try preaccessed_storage_keys.put(.{ .address = al.address, .key = key }, {}); + try env.state.putAccessedStorageKeys(.{ .address = al.address, .key = key }); } } }, @@ -265,11 +260,8 @@ pub const Blockchain = struct { tx.getData(), gas, env, - preaccessed_addresses, - preaccessed_storage_keys, ); - defer message.deinit(); - const output = try processMessageCall(allocator, message, env); + const output = try processMessageCall(message, env); const gas_used = tx.getGasLimit() - output.gas_left; const gas_refund = @min(gas_used / 5, output.refund_counter); @@ -347,8 +339,6 @@ pub const Blockchain = struct { data: []const u8, gas: u64, env: Environment, - preaccessed_addresses: AddressSet, - preaccessed_storage_keys: AddressKeySet, ) !Message { var current_target: Address = undefined; var code_address: Address = undefined; @@ -366,11 +356,10 @@ pub const Blockchain = struct { code = data; } - var accessed_addresses = try preaccessed_addresses.clone(); - try accessed_addresses.put(current_target, {}); - try accessed_addresses.put(caller, {}); - for (params.precompiled_contract_addresses) |address| { - try accessed_addresses.put(address, {}); + try env.state.putAccessedAccount(current_target); + try env.state.putAccessedAccount(caller); + for (params.precompiled_contract_addresses) |precompile_addr| { + try env.state.putAccessedAccount(precompile_addr); } return .{ @@ -382,8 +371,6 @@ pub const Blockchain = struct { .data = msg_data, .code_address = code_address, .code = code, - .accessed_addresses = accessed_addresses, - .accessed_storage_keys = try preaccessed_storage_keys.clone(), }; } @@ -409,11 +396,11 @@ pub const Blockchain = struct { // error TODO (required for future receipts) }; - fn processMessageCall(allocator: Allocator, message: Message, env: Environment) !MessageCallOutput { + fn processMessageCall(message: Message, env: Environment) !MessageCallOutput { var vm_instance = VM.init(env); defer vm_instance.deinit(); - const result = try vm_instance.processMessageCall(allocator, message); + const result = try vm_instance.processMessageCall(message); defer { if (result.release) |release| release(&result); } diff --git a/src/blockchain/params.zig b/src/blockchain/params.zig index 20fc812..7fd276f 100644 --- a/src/blockchain/params.zig +++ b/src/blockchain/params.zig @@ -17,6 +17,8 @@ pub const base_fee_max_change_denominator = 8; pub const elasticity_multiplier = 2; pub const max_code_size = 0x6000; +pub const stack_depth_limit = 1024; + pub const precompiled_contract_addresses = [_]Address{ addressFromInt(1), // ECRECOVER addressFromInt(2), // SHA256 diff --git a/src/blockchain/types.zig b/src/blockchain/types.zig index 2cfbca6..a953021 100644 --- a/src/blockchain/types.zig +++ b/src/blockchain/types.zig @@ -10,7 +10,6 @@ const AddresssKey = common.AddressKey; const AddressKeySet = common.AddressKeySet; pub const Environment = struct { - caller: Address, block_hashes: [256]Hash32, origin: Address, coinbase: Address, @@ -33,13 +32,4 @@ pub const Message = struct { data: []const u8, code_address: ?Address, code: []const u8, - accessed_addresses: AddressSet, - accessed_storage_keys: AddressKeySet, - - pub fn deinit(self: *Message) void { - self.accessed_addresses.deinit(); - self.accessed_addresses = undefined; - self.accessed_storage_keys.deinit(); - self.accessed_storage_keys = undefined; - } }; diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index ffb2586..5295294 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -4,6 +4,7 @@ const evmc = @cImport({ const std = @import("std"); const types = @import("../types/types.zig"); const common = @import("../common/common.zig"); +const params = @import("params.zig"); const blockchain_types = @import("types.zig"); const Allocator = std.mem.Allocator; const AddressSet = common.AddressSet; @@ -18,20 +19,15 @@ const Keccak256 = std.crypto.hash.sha3.Keccak256; const fmtSliceHexLower = std.fmt.fmtSliceHexLower; const assert = std.debug.assert; -const STACK_DEPTH_LIMIT = 1024; const empty_hash = common.comptimeHexToBytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); pub const VM = struct { + const vmlog = std.log.scoped(.vm); + env: Environment, evm: [*c]evmc.evmc_vm, host: evmc.struct_evmc_host_interface, - // Call context scoped variables. - accessed_accounts: AddressSet, - accessed_storage_keys: AddressKeySet, - - const vmlog = std.log.scoped(.vm); - // init creates a new EVM VM instance. The caller must call deinit() when done. pub fn init(env: Environment) VM { var evm = evmc.evmc_create_evmone(); @@ -55,10 +51,6 @@ pub const VM = struct { .access_account = EVMOneHost.access_account, .access_storage = EVMOneHost.access_storage, }, - // TODO: remove this and move it to a "VM instance" or similar which has a - // context containing these things, and probably also the (snapshoted) statedb. - .accessed_accounts = undefined, - .accessed_storage_keys = undefined, }; } @@ -70,7 +62,7 @@ pub const VM = struct { } // processMessageCall executes a message call. - pub fn processMessageCall(self: *VM, allocator: Allocator, msg: Message) !evmc.struct_evmc_result { + pub fn processMessageCall(self: *VM, msg: Message) !evmc.struct_evmc_result { const evmc_message: evmc.struct_evmc_message = .{ .kind = if (msg.target != null) evmc.EVMC_CALL else evmc.EVMC_CREATE, .flags = 0, @@ -85,22 +77,11 @@ pub const VM = struct { std.mem.writeIntSliceBig(u256, &txn_value, msg.value); break :blk .{ .bytes = txn_value }; }, - .create2_salt = undefined, // EVMC docs: field only mandatory for CREATE2 kind. + .create2_salt = undefined, // EVMC docs: field only mandatory for CREATE2 kind which doesn't apply at depth 0. .code_address = toEVMCAddress(msg.code_address), }; - // TODO(improv): we clone here since it's the easiest way to manage ownership of the sets. - // Quite honestly, it will be better to avoid creating the sets at the caller level and do it here - // which would make the ownership problem disappear and avoid the clone. It's a very cheap - // clone, but also is simple to avoid it. - self.accessed_accounts = try msg.accessed_addresses.cloneWithAllocator(allocator); - defer self.accessed_accounts.deinit(); - self.accessed_storage_keys = try msg.accessed_storage_keys.cloneWithAllocator(allocator); - defer self.accessed_storage_keys.deinit(); - - const result = EVMOneHost.call(@ptrCast(self), @ptrCast(&evmc_message)); - vmlog.debug("processMessageCall status_code={}, gas_left={}", .{ result.status_code, result.gas_left }); - return result; + return EVMOneHost.call(@ptrCast(self), @ptrCast(&evmc_message)); } }; @@ -265,9 +246,11 @@ const EVMOneHost = struct { evmclog.debug("accessAccount addr=0x{}", .{fmtSliceHexLower(&address)}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - if (vm.accessed_accounts.contains(address)) + if (vm.env.state.accessedAccountsContains(address)) return evmc.EVMC_ACCESS_WARM; - _ = vm.accessed_accounts.fetchPut(address, {}) catch @panic("OOO"); + vm.env.state.putAccessedAccount(address) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; return evmc.EVMC_ACCESS_COLD; } @@ -282,9 +265,11 @@ const EVMOneHost = struct { const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); const address_key: AddressKey = .{ .address = address, .key = key.*.bytes }; - if (vm.accessed_storage_keys.contains(address_key)) + if (vm.env.state.accessedStorageKeysContains(address_key)) return evmc.EVMC_ACCESS_WARM; - _ = vm.accessed_storage_keys.fetchPut(address_key, {}) catch @panic("OOO"); + _ = vm.env.state.putAccessedStorageKeys(address_key) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; return evmc.EVMC_ACCESS_COLD; } @@ -293,7 +278,7 @@ const EVMOneHost = struct { const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); evmclog.debug("call() kind={d} depth={d} sender={} recipient={}", .{ msg.*.kind, msg.*.depth, fmtSliceHexLower(&msg.*.sender.bytes), fmtSliceHexLower(&msg.*.recipient.bytes) }); - if (msg.*.depth > STACK_DEPTH_LIMIT) { + if (msg.*.depth > params.stack_depth_limit) { return .{ .status_code = evmc.EVMC_CALL_DEPTH_EXCEEDED, .gas_left = 0, @@ -307,11 +292,9 @@ const EVMOneHost = struct { } // Persist current context in case we need it for scope revert. - // deinit-ing these sets will happen later if the scope doesn't revert, since we didn't end up - // using them. - var prev_accessed_accounts = vm.accessed_accounts.clone() catch @panic("OOO"); - var prev_accessed_storage_keys = vm.accessed_storage_keys.clone() catch @panic("OOO"); - var prev_statedb = vm.env.state.snapshot() catch @panic("OOO"); + var prev_statedb = vm.env.state.snapshot() catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; // TODO: change env caller? @@ -332,9 +315,13 @@ const EVMOneHost = struct { .padding = [_]u8{0} ** 4, }; } - vm.env.state.setBalance(sender, sender_balance - value) catch @panic("OOO"); + vm.env.state.setBalance(sender, sender_balance - value) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; const receipient_balance = vm.env.state.getAccount(fromEVMCAddress(msg.*.recipient)).balance; - vm.env.state.setBalance(sender, receipient_balance + value) catch @panic("OOO"); + vm.env.state.setBalance(sender, receipient_balance + value) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; } const code_address = fromEVMCAddress(msg.*.code_address); @@ -349,18 +336,11 @@ const EVMOneHost = struct { code.len, ); - if (result.status_code != evmc.EVMC_SUCCESS) { - vm.accessed_accounts.deinit(); - vm.accessed_accounts = prev_accessed_accounts; - vm.accessed_storage_keys.deinit(); - vm.accessed_storage_keys = prev_accessed_storage_keys; - vm.env.state.deinit(); - vm.env.state.* = prev_statedb; - } else { - prev_accessed_accounts.deinit(); - prev_accessed_storage_keys.deinit(); + // If the *CALL failed, we restore the previous statedb. + if (result.status_code != evmc.EVMC_SUCCESS) + vm.env.state.* = prev_statedb + else // otherwise, we free the backup and indireclty commit to the changes that happened. prev_statedb.deinit(); - } return result; } diff --git a/src/state/statedb.zig b/src/state/statedb.zig index 32f06b7..36498ee 100644 --- a/src/state/statedb.zig +++ b/src/state/statedb.zig @@ -1,9 +1,14 @@ const std = @import("std"); const types = @import("../types/types.zig"); +const common = @import("../common/common.zig"); const statetypes = @import("types.zig"); const Bytes32 = types.Bytes32; const Allocator = std.mem.Allocator; const Address = types.Address; +const AddressSet = common.AddressSet; +const AddressKey = common.AddressKey; +const AddressKeySet = common.AddressKeySet; + const log = std.log.scoped(.statedb); pub const AccountData = statetypes.AccountData; @@ -14,6 +19,8 @@ pub const StateDB = struct { allocator: Allocator, db: AccountDB, + accessed_accounts: AddressSet, + accessed_storage_keys: AddressKeySet, pub fn init(allocator: Allocator, accounts: []AccountState) !StateDB { var db = AccountDB.init(allocator); @@ -21,7 +28,12 @@ pub const StateDB = struct { for (accounts) |account| { db.putAssumeCapacityNoClobber(account.addr, account); } - return .{ .allocator = allocator, .db = db }; + return .{ + .allocator = allocator, + .db = db, + .accessed_accounts = AddressSet.init(allocator), + .accessed_storage_keys = AddressKeySet.init(allocator), + }; } pub fn deinit(self: *StateDB) void { @@ -50,6 +62,11 @@ pub const StateDB = struct { return account.storage.get(key) orelse std.mem.zeroes(Bytes32); } + pub fn getAllStorage(self: *StateDB, addr: Address) ?std.AutoHashMap(u256, Bytes32) { + const account = self.db.get(addr) orelse return null; + return account.storage; + } + pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: Bytes32) !void { var account = self.db.getPtr(addr) orelse return error.AccountDoesNotExist; try account.storage.put(key, value); @@ -78,17 +95,30 @@ pub const StateDB = struct { return account.nonce == 0 and account.balance == 0 and account.code.len == 0; } + pub fn accessedAccountsContains(self: *StateDB, addr: Address) bool { + return self.accessed_accounts.contains(addr); + } + + pub fn putAccessedAccount(self: *StateDB, addr: Address) !void { + try self.accessed_accounts.putNoClobber(addr, {}); + } + + pub fn accessedStorageKeysContains(self: *StateDB, addrkey: AddressKey) bool { + return self.accessed_storage_keys.contains(addrkey); + } + + pub fn putAccessedStorageKeys(self: *StateDB, addrkey: AddressKey) !void { + try self.accessed_storage_keys.putNoClobber(addrkey, {}); + } + pub fn snapshot(self: StateDB) !StateDB { // TODO: while simple this is quite inefficient. // A much smarter way is doing some "diff" style snapshotting or similar. return StateDB{ .allocator = self.allocator, .db = try self.db.cloneWithAllocator(self.allocator), + .accessed_accounts = try self.accessed_accounts.cloneWithAllocator(self.allocator), + .accessed_storage_keys = try self.accessed_storage_keys.cloneWithAllocator(self.allocator), }; } - - pub fn getAllStorage(self: *StateDB, addr: Address) ?std.AutoHashMap(u256, Bytes32) { - const account = self.db.get(addr) orelse return null; - return account.storage; - } }; From bc5f106bf0062a0b2f7ab28d1b27acd2dc993c00 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 10 Jan 2024 14:12:24 -0300 Subject: [PATCH 48/52] blockchain: cleanups Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 5295294..c3d7aea 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -91,7 +91,7 @@ const EVMOneHost = struct { const evmclog = std.log.scoped(.evmone); fn get_tx_context(ctx: ?*evmc.struct_evmc_host_context) callconv(.C) evmc.struct_evmc_tx_context { - evmclog.debug("get_tx_context", .{}); + evmclog.debug("getTxContext", .{}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); return evmc.struct_evmc_tx_context{ @@ -108,21 +108,23 @@ const EVMOneHost = struct { } fn get_block_hash(ctx: ?*evmc.struct_evmc_host_context, block_number: i64) callconv(.C) evmc.evmc_bytes32 { - evmclog.debug("get_block_hash block_number={}", .{block_number}); + evmclog.debug("getBlockHash block_number={}", .{block_number}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); const idx = vm.env.number - @as(u64, @intCast(block_number)); if (idx < 0 or idx >= vm.env.block_hashes.len) { return std.mem.zeroes(evmc.evmc_bytes32); } + return .{ .bytes = vm.env.block_hashes[idx] }; } fn account_exists(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) bool { const address = fromEVMCAddress(addr.*); - evmclog.debug("account_exists addr=0x{}", .{fmtSliceHexLower(&address)}); + evmclog.debug("accountExists addr=0x{}", .{fmtSliceHexLower(&address)}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + return vm.env.state.getAccountOpt(address) != null; } @@ -132,10 +134,11 @@ const EVMOneHost = struct { key: [*c]const evmc.evmc_bytes32, ) callconv(.C) evmc.evmc_bytes32 { const address = fromEVMCAddress(addr.*); - evmclog.debug("get_storage addr=0x{} key={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes) }); + evmclog.debug("getStorage addr=0x{} key={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes) }); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); const k = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); + return .{ .bytes = vm.env.state.getStorage(address, k) }; } @@ -146,7 +149,7 @@ const EVMOneHost = struct { value: [*c]const evmc.evmc_bytes32, ) callconv(.C) evmc.enum_evmc_storage_status { const address = fromEVMCAddress(addr.*); - evmclog.debug("set_storage addr=0x{} key={} value={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes), fmtSliceHexLower(&value.*.bytes) }); + evmclog.debug("setStorage addr=0x{} key={} value={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes), fmtSliceHexLower(&value.*.bytes) }); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); const k = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); @@ -165,6 +168,7 @@ const EVMOneHost = struct { evmclog.debug("getBalance addr=0x{})", .{fmtSliceHexLower(&address)}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + return toEVMCUint256Be(vm.env.state.getAccount(address).balance); } @@ -173,6 +177,7 @@ const EVMOneHost = struct { evmclog.debug("getCodeSize addr=0x{})", .{fmtSliceHexLower(&address)}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + return vm.env.state.getAccount(address).code.len; } @@ -181,7 +186,7 @@ const EVMOneHost = struct { addr: [*c]const evmc.evmc_address, ) callconv(.C) evmc.evmc_bytes32 { const address = fromEVMCAddress(addr.*); - evmclog.debug("getCodeSize addr=0x{})", .{fmtSliceHexLower(&address)}); + evmclog.debug("getCodeHash addr=0x{})", .{fmtSliceHexLower(&address)}); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); var ret = empty_hash; @@ -220,21 +225,21 @@ const EVMOneHost = struct { _ = addr; _ = ctx; // https://evmc.ethereum.org/group__EVMC.html#ga1aa9fa657b3f0de375e2f07e53b65bcc - @panic("self destruct not supported in verkle"); + @panic("TODO"); } fn emit_log( ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address, - xxx: [*c]const u8, - xxy: usize, - xxz: [*c]const evmc.evmc_bytes32, - xxxzz: usize, + data: [*c]const u8, + data_size: usize, + topics: [*c]const evmc.evmc_bytes32, + topics_count: usize, ) callconv(.C) void { - _ = xxxzz; - _ = xxz; - _ = xxy; - _ = xxx; + _ = topics_count; + _ = topics; + _ = data_size; + _ = data; _ = addr; _ = ctx; // https://evmc.ethereum.org/group__EVMC.html#gaab96621b67d653758b3da15c2b596938 From c27d93ac95a0e548c0978c623205531fe3887a70 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 10 Jan 2024 15:03:45 -0300 Subject: [PATCH 49/52] reorganize packages and unit tests Signed-off-by: Ignacio Hagopian --- build.zig | 2 +- src/blockchain/blockchain.zig | 2 +- src/blockchain/types.zig | 2 +- src/common/common.zig | 4 +- src/common/rlp.zig | 2 +- src/crypto/crypto.zig | 2 + src/engine_api/engine_api.zig | 4 +- src/exec-spec-tests/execspectests.zig | 6 +-- src/lib.zig | 13 +++++ src/main.zig | 77 ++------------------------- src/signer/signer.zig | 7 +-- src/state/state.zig | 8 +++ src/state/statedb.zig | 10 ++-- src/types/types.zig | 2 +- 14 files changed, 47 insertions(+), 94 deletions(-) create mode 100644 src/crypto/crypto.zig create mode 100644 src/lib.zig create mode 100644 src/state/state.zig diff --git a/build.zig b/build.zig index 2da1bea..bc3ae85 100644 --- a/build.zig +++ b/build.zig @@ -138,7 +138,7 @@ pub fn build(b: *std.Build) void { // Creates a step for unit testing. This only builds the test executable // but does not run it. const unit_tests = b.addTest(.{ - .root_source_file = .{ .path = "src/main.zig" }, + .root_source_file = .{ .path = "src/lib.zig" }, .target = target, .optimize = optimize, }); diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index a4d68c8..e47735e 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -18,7 +18,7 @@ const Block = types.Block; const BlockHeader = types.BlockHeader; const Environment = blockchain_types.Environment; const Message = blockchain_types.Message; -const StateDB = @import("../state/statedb.zig").StateDB; +const StateDB = @import("../state/state.zig").StateDB; const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; const Address = types.Address; diff --git a/src/blockchain/types.zig b/src/blockchain/types.zig index a953021..146c837 100644 --- a/src/blockchain/types.zig +++ b/src/blockchain/types.zig @@ -1,7 +1,7 @@ const types = @import("../types/types.zig"); const config = @import("../config/config.zig"); const common = @import("../common/common.zig"); -const StateDB = @import("../state/statedb.zig").StateDB; +const StateDB = @import("../state/state.zig").StateDB; const Address = types.Address; const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; diff --git a/src/common/common.zig b/src/common/common.zig index b323ac8..d8bcbed 100644 --- a/src/common/common.zig +++ b/src/common/common.zig @@ -1,7 +1,7 @@ const std = @import("std"); const types = @import("../types/types.zig"); -const hexutils = @import("./hexutils.zig"); -const rlp = @import("./rlp.zig"); +const hexutils = @import("hexutils.zig"); +const rlp = @import("rlp.zig"); // Hex pub const prefixedhex2hash = hexutils.prefixedhex2hash; diff --git a/src/common/rlp.zig b/src/common/rlp.zig index 7b73976..94d5904 100644 --- a/src/common/rlp.zig +++ b/src/common/rlp.zig @@ -1,7 +1,7 @@ const std = @import("std"); const rlp = @import("zig-rlp"); const types = @import("../types/types.zig"); -const hasher = @import("../crypto/hasher.zig"); +const hasher = @import("../crypto/crypto.zig").hasher; const Allocator = std.mem.Allocator; const Hash32 = types.Hash32; diff --git a/src/crypto/crypto.zig b/src/crypto/crypto.zig new file mode 100644 index 0000000..fe0e876 --- /dev/null +++ b/src/crypto/crypto.zig @@ -0,0 +1,2 @@ +pub const ecdsa = @import("ecdsa.zig"); +pub const hasher = @import("hasher.zig"); diff --git a/src/engine_api/engine_api.zig b/src/engine_api/engine_api.zig index 70e01a7..e6ca184 100644 --- a/src/engine_api/engine_api.zig +++ b/src/engine_api/engine_api.zig @@ -7,7 +7,7 @@ const Withdrawal = types.Withdrawal; const Txn = types.Txn; const ExecutionPayload = execution_payload.ExecutionPayload; -pub const execution_payload = @import("./execution_payload.zig"); +pub const execution_payload = @import("execution_payload.zig"); // This is an intermediate structure used to deserialize the hex strings // from the JSON request. I have seen some zig libraries that can do this @@ -86,7 +86,7 @@ test "deserialize sample engine_newPayloadV2" { const json = std.json; const expect = std.testing.expect; - const filePath = "./src/engine_api/test_req.json"; + const filePath = "src/engine_api/test_req.json"; const file = try std.fs.cwd().openFile(filePath, .{}); defer file.close(); diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index 54d2db3..26f6541 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -4,8 +4,9 @@ const config = @import("../config/config.zig"); const types = @import("../types/types.zig"); const blockchain = @import("../blockchain/blockchain.zig"); const vm = @import("../blockchain/vm.zig"); -const ecdsa = @import("../crypto/ecdsa.zig"); -const state = @import("../state/statedb.zig"); +const ecdsa = @import("../crypto/crypto.zig").ecdsa; +const state = @import("../state/state.zig"); +const TxnSigner = @import("../signer/signer.zig").TxnSigner; const Allocator = std.mem.Allocator; const Address = types.Address; const Block = types.Block; @@ -16,7 +17,6 @@ const Bytes32 = types.Bytes32; const VM = vm.VM; const StateDB = state.StateDB; const AccountState = state.AccountState; -const TxnSigner = @import("../signer/signer.zig").TxnSigner; const log = std.log.scoped(.execspectests); const HexString = []const u8; diff --git a/src/lib.zig b/src/lib.zig new file mode 100644 index 0000000..a622b39 --- /dev/null +++ b/src/lib.zig @@ -0,0 +1,13 @@ +const std = @import("std"); + +test "tests" { + std.testing.log_level = .debug; + + std.testing.refAllDeclsRecursive(@import("blockchain/blockchain.zig")); + std.testing.refAllDeclsRecursive(@import("config/config.zig")); + std.testing.refAllDeclsRecursive(@import("crypto/crypto.zig")); + std.testing.refAllDeclsRecursive(@import("engine_api/engine_api.zig")); + std.testing.refAllDeclsRecursive(@import("exec-spec-tests/execspectests.zig")); + std.testing.refAllDeclsRecursive(@import("state/state.zig")); + std.testing.refAllDeclsRecursive(@import("types/types.zig")); +} diff --git a/src/main.zig b/src/main.zig index 296cb94..c8fa61f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,11 +1,12 @@ const std = @import("std"); const types = @import("types/types.zig"); -const ecdsa = @import("crypto/ecdsa.zig"); +const crypto = @import("crypto/crypto.zig"); +const ecdsa = crypto.ecdsa; const config = @import("config/config.zig"); -const AccountState = types.AccountState; +const AccountState = @import("state/state.zig").AccountState; const Address = types.Address; const VM = @import("blockchain/vm.zig").VM; -const StateDB = @import("state/statedb.zig"); +const StateDB = @import("state/state.zig").StateDB; const Block = types.Block; const Txn = types.Txn; const TxnSigner = @import("signer/signer.zig").TxnSigner; @@ -30,65 +31,6 @@ pub fn main() !void { var allocator = gpa.allocator(); std.log.info("Welcome to phant! 🐘", .{}); - const txn_signer = try TxnSigner.init(@intFromEnum(config.ChainId.Mainnet)); - - // Create block. - const block: Block = .{ - .header = .{ - .parent_hash = [_]u8{0} ** 32, - .uncle_hash = [_]u8{0} ** 32, - .fee_recipient = [_]u8{0} ** 20, - .state_root = [_]u8{0} ** 32, - .transactions_root = [_]u8{0} ** 32, - .receipts_root = [_]u8{0} ** 32, - .logs_bloom = [_]u8{0} ** 256, - .prev_randao = [_]u8{0} ** 32, - .block_number = 100, - .gas_limit = 10_000, - .gas_used = 0, - .timestamp = 0, - .extra_data = &[_]u8{}, - .nonce = [_]u8{0} ** 8, - .base_fee_per_gas = 10, - .withdrawals_root = null, - .blob_gas_used = null, - .excess_blob_gas = null, - .difficulty = 0, - }, - .transactions = &[_]Txn{}, - .uncles = &[_]types.BlockHeader{}, - .withdrawals = &[_]types.Withdrawal{}, - }; - - // Create some dummy transaction. - var txn = Txn.initLegacyTxn(0, 10, 0, [_]u8{0} ** 18 ++ [_]u8{ 0x41, 0x42 }, &[_]u8{}, 100_000); - var privkey: ecdsa.PrivateKey = undefined; - _ = try std.fmt.hexToBytes(&privkey, "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"); - const sig = try txn_signer.sign(allocator, txn, privkey); - txn.setSignature(sig.v, sig.r, sig.s); - - // Create the corresponding AccountState for txn.to, in particular with relevant bytecode - // so the transaction can be properly executed. - const code = [_]u8{ - 0x61, 0x41, 0x42, // PUSH2 0x4142 - 0x31, // BALANCE - }; - const sender_addr = try txn_signer.get_sender(allocator, txn); - var account_state = try AccountState.init(allocator, sender_addr, 0, 1_000_000, &code); - defer account_state.deinit(); - - // Create the statedb, with the created account state. - var account_states = [_]AccountState{account_state}; - var statedb = try StateDB.init(allocator, &account_states); - - // Create the VM with the initialized statedb - var vm = VM.init(&statedb); - - // Execute block with txns. - vm.run_block(allocator, txn_signer, block, &[_]Txn{txn}) catch |err| { - std.log.err("error executing transaction: {}", .{err}); - return; - }; var engine_api_server = try httpz.Server().init(allocator, .{ .port = 8551, @@ -98,14 +40,3 @@ pub fn main() !void { std.log.info("Listening on 8551", .{}); try engine_api_server.listen(); } - -test "tests" { - std.testing.log_level = .debug; - - // TODO: move to separate file for tests binary. - _ = @import("exec-spec-tests/execspectests.zig"); - _ = @import("types/types.zig"); - _ = @import("blockchain/vm.zig"); - _ = @import("crypto/ecdsa.zig"); - _ = @import("engine_api/engine_api.zig"); -} diff --git a/src/signer/signer.zig b/src/signer/signer.zig index f5fb672..9f76f4d 100644 --- a/src/signer/signer.zig +++ b/src/signer/signer.zig @@ -1,14 +1,15 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const config = @import("../config/config.zig"); -const ecdsa = @import("../crypto/ecdsa.zig"); +const crypto = @import("../crypto/crypto.zig"); +const ecdsa = crypto.ecdsa; +const hasher = crypto.hasher; const types = @import("../types/types.zig"); const rlp = @import("zig-rlp"); -const hasher = @import("../crypto/hasher.zig"); +const Address = @import("../types/types.zig").Address; const AccessListTuple = types.AccessListTuple; const Txn = types.Txn; const Hash32 = types.Hash32; -const Address = @import("../types/types.zig").Address; // TODO: TxnSigner should be generalized to: // - Only accept correct transactions types depending on the fork we're in. diff --git a/src/state/state.zig b/src/state/state.zig new file mode 100644 index 0000000..4473d2a --- /dev/null +++ b/src/state/state.zig @@ -0,0 +1,8 @@ +const std = @import("std"); +const types = @import("../types/types.zig"); +const statedb = @import("statedb.zig"); +const statetypes = @import("types.zig"); + +pub const AccountData = statetypes.AccountData; +pub const AccountState = statetypes.AccountState; +pub const StateDB = statedb.StateDB; diff --git a/src/state/statedb.zig b/src/state/statedb.zig index 36498ee..a441ffe 100644 --- a/src/state/statedb.zig +++ b/src/state/statedb.zig @@ -1,19 +1,17 @@ const std = @import("std"); const types = @import("../types/types.zig"); const common = @import("../common/common.zig"); -const statetypes = @import("types.zig"); -const Bytes32 = types.Bytes32; +const state = @import("state.zig"); const Allocator = std.mem.Allocator; const Address = types.Address; const AddressSet = common.AddressSet; const AddressKey = common.AddressKey; const AddressKeySet = common.AddressKeySet; - +const AccountData = state.AccountData; +const AccountState = state.AccountState; +const Bytes32 = types.Bytes32; const log = std.log.scoped(.statedb); -pub const AccountData = statetypes.AccountData; -pub const AccountState = statetypes.AccountState; - pub const StateDB = struct { const AccountDB = std.AutoHashMap(Address, AccountState); diff --git a/src/types/types.zig b/src/types/types.zig index 48ad184..4d9696d 100644 --- a/src/types/types.zig +++ b/src/types/types.zig @@ -21,4 +21,4 @@ pub const Txn = transaction.Txn; pub const TxnTypes = transaction.TxnTypes; pub const LegacyTxn = transaction.LegacyTxn; pub const AccessListTxn = transaction.AccessListTxn; -pub const MarketFeeTxn = transaction.MarketFeeTxn; +pub const MarketFeeTxn = transaction.FeeMarketTxn; From 6534007a944e294c367d9e46913e6c8060a6d34a Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 10 Jan 2024 15:11:33 -0300 Subject: [PATCH 50/52] execspectests: compare post-state entry by entry Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 2 -- src/exec-spec-tests/execspectests.zig | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index c3d7aea..cc1cca8 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -301,8 +301,6 @@ const EVMOneHost = struct { error.OutOfMemory => @panic("OOO"), }; - // TODO: change env caller? - // Send value. const value = std.mem.readInt(u256, &msg.*.value.bytes, std.builtin.Endian.Big); if (value > 0) { diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index 26f6541..c1a7e61 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -66,7 +66,7 @@ pub const FixtureTest = struct { var it = self.pre.map.iterator(); var i: usize = 0; while (it.next()) |entry| { - accounts_state[i] = try entry.value_ptr.*.toAccountState(allocator, entry.key_ptr.*); + accounts_state[i] = try entry.value_ptr.toAccountState(allocator, entry.key_ptr.*); i = i + 1; } break :blk accounts_state; @@ -91,7 +91,7 @@ pub const FixtureTest = struct { // Verify that the post state matches what the fixture `postState` claims is true. var it = self.postState.map.iterator(); while (it.next()) |entry| { - var exp_account_state: AccountState = try entry.value_ptr.*.toAccountState(allocator, entry.key_ptr.*); + var exp_account_state: AccountState = try entry.value_ptr.toAccountState(allocator, entry.key_ptr.*); std.debug.print("checking account state: {s}\n", .{std.fmt.fmtSliceHexLower(&exp_account_state.addr)}); const got_account_state = statedb.getAccount(exp_account_state.addr); if (got_account_state.nonce != exp_account_state.nonce) { @@ -108,9 +108,13 @@ pub const FixtureTest = struct { log.err("expected storage count {d} but got {d}", .{ exp_account_state.storage.count(), got_storage.count() }); return error.PostStateStorageCountMismatch; } - // TODO: check each storage entry matches. + var it_got = got_storage.iterator(); + while (it_got.next()) |storage_entry| { + const val = exp_account_state.storage.get(storage_entry.key_ptr.*) orelse return error.PostStateStorageKeyMustExist; + if (!std.mem.eql(u8, storage_entry.value_ptr, &val)) + return error.PostStateStorageValueMismatch; + } } - // TODO(jsign): verify gas used. return true; } @@ -184,15 +188,11 @@ pub const AccountStateHex = struct { code: HexString, storage: AccountStorageHex, - // TODO(jsign): add init() and add assertions about lengths. - - pub fn toAccountState(self: *const AccountStateHex, allocator: Allocator, addr_hex: []const u8) !AccountState { + pub fn toAccountState(self: AccountStateHex, allocator: Allocator, addr_hex: []const u8) !AccountState { const nonce = try std.fmt.parseInt(u64, self.nonce[2..], 16); const balance = try std.fmt.parseInt(u256, self.balance[2..], 16); var code = try allocator.alloc(u8, self.code[2..].len / 2); - // TODO(jsign): check this. - //defer allocator.free(code); _ = try std.fmt.hexToBytes(code, self.code[2..]); var addr: Address = undefined; @@ -223,7 +223,7 @@ test "execution-spec-tests" { var it = ft.tests.value.map.iterator(); var count: usize = 0; while (it.next()) |entry| { - try std.testing.expect(try entry.value_ptr.*.run(test_allocator)); + try std.testing.expect(try entry.value_ptr.run(test_allocator)); count += 1; // TODO: Only run the first test for now. Then we can enable all and continue with the integration. From 57400ebadda42da181a891dd9298d93e709ca7df Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 11 Jan 2024 09:00:28 -0300 Subject: [PATCH 51/52] blockchain: create unique txn signer Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index e47735e..47b431f 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -22,6 +22,7 @@ const StateDB = @import("../state/state.zig").StateDB; const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; const Address = types.Address; +const TxnSigner = signer.TxnSigner; const VM = vm.VM; const Keccak256 = std.crypto.hash.sha3.Keccak256; @@ -31,6 +32,7 @@ pub const Blockchain = struct { state: *StateDB, prev_block: BlockHeader, last_256_blocks_hashes: [256]Hash32, // ordered in asc order + txn_signer: TxnSigner, // init initializes a blockchain. // The caller **does not** transfer ownership of prev_block. @@ -47,6 +49,7 @@ pub const Blockchain = struct { .state = state, .prev_block = try prev_block.clone(allocator), .last_256_blocks_hashes = last_256_blocks_hashes, + .txn_signer = try signer.TxnSigner.init(@intFromEnum(chain_id)), }; } @@ -60,7 +63,7 @@ pub const Blockchain = struct { const allocator = arena.allocator(); // Execute block. - var result = try applyBody(allocator, self, self.state, block); + var result = try applyBody(allocator, self, self.state, block, self.txn_signer); // Post execution checks. if (result.gas_used != block.header.gas_used) @@ -150,10 +153,10 @@ pub const Blockchain = struct { withdrawals_root: Hash32, }; - fn applyBody(allocator: Allocator, chain: *Blockchain, state: *StateDB, block: Block) !BlockExecutionResult { + fn applyBody(allocator: Allocator, chain: *Blockchain, state: *StateDB, block: Block, txn_signer: TxnSigner) !BlockExecutionResult { var gas_available = block.header.gas_limit; for (block.transactions) |tx| { - const txn_info = try checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, chain.chain_id); + const txn_info = try checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, txn_signer); const env: Environment = .{ .origin = txn_info.sender_address, .block_hashes = chain.last_256_blocks_hashes, @@ -190,11 +193,10 @@ pub const Blockchain = struct { }; } - fn checkTransaction(allocator: Allocator, tx: transaction.Txn, base_fee_per_gas: u256, gas_available: u64, chain_id: config.ChainId) !struct { sender_address: Address, effective_gas_price: u256 } { + fn checkTransaction(allocator: Allocator, tx: transaction.Txn, base_fee_per_gas: u256, gas_available: u64, txn_signer: TxnSigner) !struct { sender_address: Address, effective_gas_price: u256 } { if (tx.getGasLimit() > gas_available) return error.InsufficientGas; - const txn_signer = try signer.TxnSigner.init(@intFromEnum(chain_id)); const sender_address = try txn_signer.get_sender(allocator, tx); const effective_gas_price = switch (tx) { From 555ea0f2ccbf9b907886207325ba8cf63c68fb42 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 12 Jan 2024 09:29:36 -0300 Subject: [PATCH 52/52] mod: use zig-rlp v0.1.0 Signed-off-by: Ignacio Hagopian --- build.zig.zon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 881994c..ea83da7 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,8 +3,8 @@ .version = "0.0.1-beta-0", .dependencies = .{ .@"zig-rlp" = .{ - .url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.0.1-beta-11.tar.gz", - .hash = "12203abe1db56e12ccfc89e1a862759879a17175c0356635f485489ea30e58b070d1", + .url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.1.0.tar.gz", + .hash = "1220ebf62505957cdd9c87b9c517ceba60c46f5c9578a504ab3299b3970cc3ab0a1d", }, .@"zig-eth-secp256k1" = .{ .url = "https://github.com/jsign/zig-eth-secp256k1/archive/refs/tags/v0.1.0-beta-3.tar.gz",