Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions test/state/block.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ struct BlockInfo
/// The EIP-1559 base fee, since London.
uint64_t base_fee = 0;

/// The chain id transactions must target.
uint64_t chain_id = 1;

/// The "blob gas used" parameter from EIP-4844
std::optional<uint64_t> blob_gas_used;

Expand Down
3 changes: 3 additions & 0 deletions test/state/errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum ErrorCode : int // NOLINT(*-use-enum-class)
CREATE_SET_CODE_TX,
EMPTY_AUTHORIZATION_LIST,
MAX_GAS_LIMIT_EXCEEDED,
INVALID_CHAIN_ID,
UNKNOWN_ERROR,

// Block-level validation.
Expand Down Expand Up @@ -100,6 +101,8 @@ inline const std::error_category& evmone_category() noexcept
return "empty authorization list";
case MAX_GAS_LIMIT_EXCEEDED:
return "max gas limit exceeded";
case INVALID_CHAIN_ID:
return "invalid transaction chain id";
case UNKNOWN_ERROR:
return "Unknown error";
case INCORRECT_BLOCK_FORMAT:
Expand Down
2 changes: 1 addition & 1 deletion test/state/host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ evmc_tx_context Host::get_tx_context() const noexcept
m_block.timestamp,
m_block.gas_limit,
m_block.prev_randao,
0x01_bytes32, // Chain ID is expected to be 1.
uint256be{m_block.chain_id},
uint256be{m_block.base_fee},
intx::be::store<uint256be>(m_block.blob_base_fee.value_or(0)),
m_tx.blob_hashes.data(),
Expand Down
6 changes: 6 additions & 0 deletions test/state/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,12 @@ std::variant<TransactionProperties, std::error_code> validate_transaction(
const StateView& state_view, const BlockInfo& block, const Transaction& tx, evmc_revision rev,
int64_t block_gas_left, int64_t blob_gas_left) noexcept
{
// chain_id must match the chain; a legacy tx may use 0 (unspecified, EIP-155).
// TODO: chain_id 0 is not "unspecified" for a legacy tx protected for chain 0 (v 35/36);
// telling it apart from an unprotected tx (v 27/28) needs the signature v from txbytes.
if (tx.chain_id != block.chain_id && (tx.type != Transaction::Type::legacy || tx.chain_id != 0))
return make_error_code(INVALID_CHAIN_ID);

switch (tx.type) // Validate "special" transaction types.
{
case Transaction::Type::blob:
Expand Down
1 change: 1 addition & 0 deletions test/unittests/state_transition.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class state_transition : public ExportableFixture
.max_gas_price = block.base_fee + 1,
.max_priority_gas_price = block.base_fee + 1,
.sender = Sender,
.chain_id = 1,
.nonce = 1,
};
TestState pre;
Expand Down
26 changes: 26 additions & 0 deletions test/unittests/state_transition_tx_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,32 @@ TEST_F(state_transition, invalid_tx_non_existing_sender)
expect.post[Sender].exists = false;
}

TEST_F(state_transition, invalid_tx_wrong_chain_id)
{
tx.to = To;
tx.chain_id = 2; // Mismatches the block chain id (1).
expect.tx_error = INVALID_CHAIN_ID;
}

TEST_F(state_transition, invalid_tx_wrong_chain_id_legacy)
{
tx.type = Transaction::Type::legacy;
tx.to = To;
tx.chain_id = 2; // Mismatches the block chain id (1).
expect.tx_error = INVALID_CHAIN_ID;
}

TEST_F(state_transition, tx_legacy_unprotected_chain_id)
{
rev = EVMC_ISTANBUL;
block.base_fee = 0; // should be 0 before London
tx.type = Transaction::Type::legacy;
tx.to = To;
tx.chain_id = 0; // Unprotected legacy tx is valid on any chain (pre-EIP-155).

expect.post.at(Sender).nonce = pre[Sender].nonce + 1;
}

TEST_F(state_transition, tx_blob_gas_price)
{
rev = EVMC_CANCUN;
Expand Down
2 changes: 2 additions & 0 deletions test/unittests/state_tx_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ TEST(state_tx, validate_blob_tx)
.gas_limit = 60000,
.max_gas_price = block.base_fee,
.sender = 0x02_address,
.chain_id = 1,
};
const TestState state{{tx.sender, {.balance = 1'000'000}}};

Expand Down Expand Up @@ -216,6 +217,7 @@ TEST(state_tx, max_blob_count)
.max_blob_gas_price = 1,
.sender = 0x02_address,
.to = 0x01_address,
.chain_id = 1,
};
const TestState state{{tx.sender, {.balance = 1'000'000}}};
const auto blob_gas_limit =
Expand Down
6 changes: 6 additions & 0 deletions test/utils/blockchaintest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,13 @@ BlockchainTest load_blockchain_test_case(const std::string& name, const json::js
bt.pre_state = from_json<TestState>(j.at("pre"));
bt.network = j.at("network").get<std::string>();
bt.rev = to_rev_schedule(bt.network);
uint64_t chain_id = 1;
if (const auto config_it = j.find("config"); config_it != j.end())
{
if (const auto bs_it = config_it->find("blobSchedule"); bs_it != config_it->end())
bt.blob_schedule = from_json<BlobSchedule>(*bs_it);
if (const auto cid_it = config_it->find("chainid"); cid_it != config_it->end())
chain_id = from_json<uint64_t>(*cid_it);
}
for (const auto& el : j.at("blocks"))
{
Expand Down Expand Up @@ -206,6 +209,9 @@ BlockchainTest load_blockchain_test_case(const std::string& name, const json::js
}
}

for (auto& tb : bt.test_blocks)
tb.block_info.chain_id = chain_id;

bt.expectation.last_block_hash = from_json<hash256>(j.at("lastblockhash"));

if (const auto it = j.find("postState"); it != j.end())
Expand Down
2 changes: 2 additions & 0 deletions test/utils/rlp_encode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ namespace evmone::state
if (tx.type == Transaction::Type::legacy)
{
// rlp [nonce, gas_price, gas_limit, to, value, data, v, r, s];
// v is written verbatim. TODO: chain_id 0 is ambiguous — unprotected (v 27/28) vs
// protected for chain 0 (v 35/36) — so v must not be re-derived from chain_id.
return rlp::encode_tuple(tx.nonce, tx.max_gas_price, static_cast<uint64_t>(tx.gas_limit),
tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data, tx.v, tx.r, tx.s);
}
Expand Down
3 changes: 3 additions & 0 deletions test/utils/statetest_export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ std::string_view to_test_fork_name(evmc_revision rev) noexcept
return "TR_BLOBVERSION_INVALID";
case BLOB_GAS_LIMIT_EXCEEDED:
return "TR_BLOBLIST_OVERSIZE";
case INVALID_CHAIN_ID:
return "TransactionException.INVALID_CHAINID";
case UNKNOWN_ERROR:
return "Unknown error";
default:
Expand Down Expand Up @@ -118,6 +120,7 @@ json::json to_state_test(std::string_view test_name, const state::BlockInfo& blo
jtx["to"] = hex0x(*tx.to);
jtx["sender"] = hex0x(tx.sender);
jtx["secretKey"] = hex0x(SenderSecretKey);
jtx["chainId"] = hex0x(tx.chain_id);
jtx["nonce"] = hex0x(tx.nonce);
if (tx.type >= Transaction::Type::eip1559)
{
Expand Down
12 changes: 9 additions & 3 deletions test/utils/statetest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,18 +520,24 @@ static void from_json(const json::json& j_t, StateTransitionTest& o)
// LCOV_EXCL_STOP
}

uint64_t chain_id = 1;
if (const auto config_it = j_t.find("config"); config_it != j_t.end())
{
if (const auto bs_it = config_it->find("blobSchedule"); bs_it != config_it->end())
o.blob_schedule = from_json<BlobSchedule>(*bs_it);
if (const auto cid_it = config_it->find("chainid"); cid_it != config_it->end())
chain_id = from_json<uint64_t>(*cid_it);
}

for (const auto& [rev_name, expectations] : j_t.at("post").items())
{
const auto blob_params = get_blob_params(to_rev(rev_name), o.blob_schedule);
o.cases.emplace_back(to_rev(rev_name),
const auto rev = to_rev(rev_name);
const auto blob_params = get_blob_params(rev, o.blob_schedule);
auto block = from_json_with_rev(j_t.at("env"), rev, blob_params);
block.chain_id = chain_id;
o.cases.emplace_back(rev,
expectations.get<std::vector<StateTransitionTest::Case::Expectation>>(),
from_json_with_rev(j_t.at("env"), to_rev(rev_name), blob_params));
std::move(block));
}
}

Expand Down
1 change: 1 addition & 0 deletions test/utils/t8n.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ void t8n(evmc::VM& vm, const T8NArgs& args)
block = from_json_with_rev(j, rev, blob_params);
block_hashes = from_json<TestBlockHashes>(j);
}
block.chain_id = args.chain_id;

JSON j_result;

Expand Down
2 changes: 1 addition & 1 deletion test/utils/t8n.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace evmone::tooling
struct T8NArgs
{
evmc_revision rev = {};
uint64_t chain_id = 0;
uint64_t chain_id = 1;
std::optional<uint64_t> block_reward;
bool pre_state_only = false;

Expand Down
2 changes: 1 addition & 1 deletion tools/evmone/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ struct HexOrFileValidator : CLI::Validator
struct T8nOptions
{
std::string state_fork;
uint64_t state_chainid = 0;
uint64_t state_chainid = 1;
std::optional<int64_t> state_reward;
fs::path alloc_file;
fs::path env_file;
Expand Down