Skip to content

Commit 675c801

Browse files
authored
Merge pull request #667 from ethereum/tx_type
Refactor transaction validation and JSON loading
2 parents 606ba82 + 968caea commit 675c801

7 files changed

+67
-44
lines changed

test/state/state.cpp

+26-16
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,34 @@ int64_t compute_tx_intrinsic_cost(evmc_revision rev, const Transaction& tx) noex
5353
}
5454

5555
/// Validates transaction and computes its execution gas limit (the amount of gas provided to EVM).
56-
/// @return Non-negative execution gas limit for valid transaction
57-
/// or negative value for invalid transaction.
56+
/// @return Execution gas limit or transaction validation error.
5857
std::variant<int64_t, std::error_code> validate_transaction(const Account& sender_acc,
5958
const BlockInfo& block, const Transaction& tx, evmc_revision rev) noexcept
6059
{
61-
if (rev < EVMC_LONDON && tx.kind == Transaction::Kind::eip1559)
62-
return make_error_code(TX_TYPE_NOT_SUPPORTED);
60+
switch (tx.type)
61+
{
62+
case Transaction::Type::eip1559:
63+
if (rev < EVMC_LONDON)
64+
return make_error_code(TX_TYPE_NOT_SUPPORTED);
65+
66+
if (tx.max_priority_gas_price > tx.max_gas_price)
67+
return make_error_code(TIP_GT_FEE_CAP); // Priority gas price is too high.
68+
[[fallthrough]];
6369

64-
if (rev < EVMC_BERLIN && !tx.access_list.empty())
65-
return make_error_code(TX_TYPE_NOT_SUPPORTED);
70+
case Transaction::Type::access_list:
71+
if (rev < EVMC_BERLIN)
72+
return make_error_code(TX_TYPE_NOT_SUPPORTED);
73+
[[fallthrough]];
74+
75+
case Transaction::Type::legacy:;
76+
}
6677

67-
if (tx.max_priority_gas_price > tx.max_gas_price)
68-
return make_error_code(TIP_GT_FEE_CAP); // Priority gas price is too high.
78+
assert(tx.max_priority_gas_price <= tx.max_gas_price);
6979

7080
if (tx.gas_limit > block.gas_limit)
7181
return make_error_code(GAS_LIMIT_REACHED);
7282

73-
if (rev >= EVMC_LONDON && tx.max_gas_price < block.base_fee)
83+
if (tx.max_gas_price < block.base_fee)
7484
return make_error_code(FEE_CAP_LESS_THEN_BLOCKS);
7585

7686
if (!sender_acc.code.empty())
@@ -91,11 +101,11 @@ std::variant<int64_t, std::error_code> validate_transaction(const Account& sende
91101
sender_acc.balance < tx_cost_limit_512)
92102
return make_error_code(INSUFFICIENT_FUNDS);
93103

94-
const auto intrinsic_cost = compute_tx_intrinsic_cost(rev, tx);
95-
if (intrinsic_cost > tx.gas_limit)
104+
const auto execution_gas_limit = tx.gas_limit - compute_tx_intrinsic_cost(rev, tx);
105+
if (execution_gas_limit < 0)
96106
return make_error_code(INTRINSIC_GAS_TOO_LOW);
97107

98-
return tx.gas_limit - intrinsic_cost;
108+
return execution_gas_limit;
99109
}
100110

101111
evmc_message build_message(const Transaction& tx, int64_t execution_gas_limit) noexcept
@@ -195,7 +205,7 @@ std::variant<TransactionReceipt, std::error_code> transition(
195205
std::erase_if(state.get_accounts(),
196206
[](const std::pair<const address, Account>& p) noexcept { return p.second.destructed; });
197207

198-
auto receipt = TransactionReceipt{tx.kind, result.status_code, gas_used, host.take_logs(), {}};
208+
auto receipt = TransactionReceipt{tx.type, result.status_code, gas_used, host.take_logs(), {}};
199209

200210
// Cannot put it into constructor call because logs are std::moved from host instance.
201211
receipt.logs_bloom_filter = compute_bloom_filter(receipt.logs);
@@ -210,13 +220,13 @@ std::variant<TransactionReceipt, std::error_code> transition(
210220

211221
[[nodiscard]] bytes rlp_encode(const Transaction& tx)
212222
{
213-
if (tx.kind == Transaction::Kind::legacy)
223+
if (tx.type == Transaction::Type::legacy)
214224
{
215225
// rlp [nonce, gas_price, gas_limit, to, value, data, v, r, s];
216226
return rlp::encode_tuple(tx.nonce, tx.max_gas_price, static_cast<uint64_t>(tx.gas_limit),
217227
tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data, tx.v, tx.r, tx.s);
218228
}
219-
else if (tx.kind == Transaction::Kind::eip2930)
229+
else if (tx.type == Transaction::Type::access_list)
220230
{
221231
if (tx.v > 1)
222232
throw std::invalid_argument("`v` value for eip2930 transaction must be 0 or 1");
@@ -245,7 +255,7 @@ std::variant<TransactionReceipt, std::error_code> transition(
245255

246256
[[nodiscard]] bytes rlp_encode(const TransactionReceipt& receipt)
247257
{
248-
const auto prefix = receipt.kind == Transaction::Kind::eip1559 ? bytes{0x02} : bytes{};
258+
const auto prefix = receipt.type == Transaction::Type::eip1559 ? bytes{0x02} : bytes{};
249259
return prefix + rlp::encode_tuple(receipt.status == EVMC_SUCCESS,
250260
static_cast<uint64_t>(receipt.gas_used),
251261
bytes_view(receipt.logs_bloom_filter), receipt.logs);

test/state/state.hpp

+16-5
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,25 @@ using AccessList = std::vector<std::pair<address, std::vector<bytes32>>>;
9393

9494
struct Transaction
9595
{
96-
enum class Kind : uint8_t
96+
/// The type of the transaction.
97+
///
98+
/// The format is defined by EIP-2718: Typed Transaction Envelope.
99+
/// https://eips.ethereum.org/EIPS/eip-2718.
100+
enum class Type : uint8_t
97101
{
102+
/// The legacy RLP-encoded transaction without leading "type" byte.
98103
legacy = 0,
99-
eip2930 = 1, ///< Transaction with access list https://eips.ethereum.org/EIPS/eip-2930
100-
eip1559 = 2 ///< EIP1559 transaction https://eips.ethereum.org/EIPS/eip-1559
104+
105+
/// The typed transaction with optional account/storage access list.
106+
/// Introduced by EIP-2930 https://eips.ethereum.org/EIPS/eip-2930.
107+
access_list = 1,
108+
109+
/// The typed transaction with priority gas price.
110+
/// Introduced by EIP-1559 https://eips.ethereum.org/EIPS/eip-1559.
111+
eip1559 = 2,
101112
};
102113

103-
Kind kind = Kind::legacy;
114+
Type type = Type::legacy;
104115
bytes data;
105116
int64_t gas_limit;
106117
intx::uint256 max_gas_price;
@@ -125,7 +136,7 @@ struct Log
125136

126137
struct TransactionReceipt
127138
{
128-
Transaction::Kind kind = Transaction::Kind::legacy;
139+
Transaction::Type type = Transaction::Type::legacy;
129140
evmc_status_code status = EVMC_INTERNAL_ERROR;
130141
int64_t gas_used = 0;
131142
std::vector<Log> logs;

test/statetest/statetest_loader.cpp

+9-7
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ static void from_json_tx_common(const json::json& j, state::Transaction& o)
256256

257257
if (const auto gas_price_it = j.find("gasPrice"); gas_price_it != j.end())
258258
{
259-
o.kind = state::Transaction::Kind::legacy;
259+
o.type = state::Transaction::Type::legacy;
260260
o.max_gas_price = from_json<intx::uint256>(*gas_price_it);
261261
o.max_priority_gas_price = o.max_gas_price;
262262
if (j.contains("maxFeePerGas") || j.contains("maxPriorityFeePerGas"))
@@ -267,7 +267,7 @@ static void from_json_tx_common(const json::json& j, state::Transaction& o)
267267
}
268268
else
269269
{
270-
o.kind = state::Transaction::Kind::eip1559;
270+
o.type = state::Transaction::Type::eip1559;
271271
o.max_gas_price = from_json<intx::uint256>(j.at("maxFeePerGas"));
272272
o.max_priority_gas_price = from_json<intx::uint256>(j.at("maxPriorityFeePerGas"));
273273
}
@@ -287,13 +287,13 @@ state::Transaction from_json<state::Transaction>(const json::json& j)
287287
if (const auto ac_it = j.find("accessList"); ac_it != j.end())
288288
{
289289
o.access_list = from_json<state::AccessList>(*ac_it);
290-
if (o.kind == state::Transaction::Kind::legacy) // Upgrade tx type if tx has "accessList"
291-
o.kind = state::Transaction::Kind::eip2930;
290+
if (o.type == state::Transaction::Type::legacy) // Upgrade tx type if tx has access list
291+
o.type = state::Transaction::Type::access_list;
292292
}
293293

294294
if (const auto type_it = j.find("type"); type_it != j.end())
295295
{
296-
if (stdx::to_underlying(o.kind) != from_json<uint8_t>(*type_it))
296+
if (stdx::to_underlying(o.type) != from_json<uint8_t>(*type_it))
297297
throw std::invalid_argument("wrong transaction type");
298298
}
299299

@@ -312,10 +312,12 @@ static void from_json(const json::json& j, TestMultiTransaction& o)
312312
for (const auto& j_data : j.at("data"))
313313
o.inputs.emplace_back(from_json<bytes>(j_data));
314314

315-
if (j.contains("accessLists"))
315+
if (const auto ac_it = j.find("accessLists"); ac_it != j.end())
316316
{
317-
for (const auto& j_access_list : j["accessLists"])
317+
for (const auto& j_access_list : *ac_it)
318318
o.access_lists.emplace_back(from_json<state::AccessList>(j_access_list));
319+
if (o.type == state::Transaction::Type::legacy) // Upgrade tx type if tx has access lists
320+
o.type = state::Transaction::Type::access_list;
319321
}
320322

321323
for (const auto& j_gas_limit : j.at("gasLimit"))

test/unittests/state_mpt_hash_test.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ TEST(state_mpt_hash, one_transactions)
7070

7171
Transaction tx{};
7272

73-
tx.kind = Transaction::Kind::eip1559;
73+
tx.type = Transaction::Type::eip1559;
7474
tx.data =
7575
"04a7e62e00000000000000000000000000000000000000000000000000000000000000c0000000000000000000"
7676
"000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000"
@@ -186,7 +186,7 @@ TEST(state_mpt_hash, legacy_and_eip1559_receipt_three_logs_no_logs)
186186
//}
187187

188188
TransactionReceipt receipt0{};
189-
receipt0.kind = evmone::state::Transaction::Kind::legacy;
189+
receipt0.type = evmone::state::Transaction::Type::legacy;
190190
receipt0.status = EVMC_SUCCESS;
191191
receipt0.gas_used = 0x24522;
192192

@@ -236,7 +236,7 @@ TEST(state_mpt_hash, legacy_and_eip1559_receipt_three_logs_no_logs)
236236
//}
237237

238238
TransactionReceipt receipt1{};
239-
receipt1.kind = evmone::state::Transaction::Kind::eip1559;
239+
receipt1.type = evmone::state::Transaction::Type::eip1559;
240240
receipt1.status = EVMC_SUCCESS;
241241
receipt1.gas_used = 0x2cd9b;
242242
receipt1.logs_bloom_filter = compute_bloom_filter(receipt1.logs);

test/unittests/state_rlp_test.cpp

+8-8
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ TEST(state_rlp, tx_to_rlp_legacy)
204204
// https://eips.ethereum.org/EIPS/eip-155
205205

206206
state::Transaction tx{};
207-
tx.kind = evmone::state::Transaction::Kind::legacy;
207+
tx.type = evmone::state::Transaction::Type::legacy;
208208
tx.data = ""_b;
209209
tx.gas_limit = 21000;
210210
tx.max_gas_price = 20000000000;
@@ -239,7 +239,7 @@ TEST(state_rlp, tx_to_rlp_legacy_with_data)
239239
// https://etherscan.io/tx/0x033e9f8db737193d4666911a164e218d58d80edc64f4ed393d0c48c1ce2673e7
240240

241241
state::Transaction tx{};
242-
tx.kind = evmone::state::Transaction::Kind::legacy;
242+
tx.type = evmone::state::Transaction::Type::legacy;
243243
tx.data = "0xa0712d680000000000000000000000000000000000000000000000000000000000000003"_hex;
244244
tx.gas_limit = 421566;
245245
tx.max_gas_price = 14829580649;
@@ -278,7 +278,7 @@ TEST(state_rlp, tx_to_rlp_eip1559)
278278

279279
state::Transaction tx{};
280280

281-
tx.kind = evmone::state::Transaction::Kind::eip1559;
281+
tx.type = evmone::state::Transaction::Type::eip1559;
282282
tx.data = ""_b;
283283
tx.gas_limit = 30000;
284284
tx.max_gas_price = 14237787676;
@@ -320,7 +320,7 @@ TEST(state_rlp, tx_to_rlp_eip1559_with_data)
320320
// https://etherscan.io/tx/0xf9400dd4722908fa7b8d514429aebfd4cd04aaa9faaf044554d2f550422baef9
321321

322322
state::Transaction tx{};
323-
tx.kind = evmone::state::Transaction::Kind::eip1559;
323+
tx.type = evmone::state::Transaction::Type::eip1559;
324324
tx.data =
325325
"095ea7b3"
326326
"0000000000000000000000001111111254eeb25477b68fb85ed929f73a960582"
@@ -361,7 +361,7 @@ TEST(state_rlp, tx_to_rlp_eip1559_with_data)
361361
TEST(state_rlp, tx_to_rlp_eip1559_invalid_v_value)
362362
{
363363
state::Transaction tx{};
364-
tx.kind = evmone::state::Transaction::Kind::eip1559;
364+
tx.type = evmone::state::Transaction::Type::eip1559;
365365
tx.data = ""_hex;
366366
tx.gas_limit = 1;
367367
tx.max_gas_price = 1;
@@ -383,7 +383,7 @@ TEST(state_rlp, tx_to_rlp_eip1559_invalid_v_value)
383383
TEST(state_rlp, tx_to_rlp_eip2930_invalid_v_value)
384384
{
385385
state::Transaction tx{};
386-
tx.kind = evmone::state::Transaction::Kind::eip2930;
386+
tx.type = evmone::state::Transaction::Type::access_list;
387387
tx.data = ""_hex;
388388
tx.gas_limit = 1;
389389
tx.max_gas_price = 1;
@@ -405,7 +405,7 @@ TEST(state_rlp, tx_to_rlp_eip2930_invalid_v_value)
405405
TEST(state_rlp, tx_to_rlp_eip1559_with_non_empty_access_list)
406406
{
407407
state::Transaction tx{};
408-
tx.kind = evmone::state::Transaction::Kind::eip1559;
408+
tx.type = evmone::state::Transaction::Type::eip1559;
409409
tx.data = "00"_hex;
410410
tx.gas_limit = 0x3d0900;
411411
tx.max_gas_price = 0x7d0;
@@ -431,7 +431,7 @@ TEST(state_rlp, tx_to_rlp_eip2930_with_non_empty_access_list)
431431
// https://etherscan.io/tx/0xf076e75aa935552e20e5d9fd4d1dda4ff33399ff3d6ac22843ae646f82c385d4
432432

433433
state::Transaction tx{};
434-
tx.kind = evmone::state::Transaction::Kind::eip2930;
434+
tx.type = evmone::state::Transaction::Type::access_list;
435435
tx.data =
436436
"0x095ea7b3000000000000000000000000f17d23136b4fead139f54fb766c8795faae09660ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"_hex;
437437
tx.gas_limit = 51253;

test/unittests/statetest_loader_test.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ TEST(statetest_loader, load_minimal_test)
102102
EXPECT_EQ(st.block.coinbase, address{});
103103
EXPECT_EQ(st.block.prev_randao, bytes32{});
104104
EXPECT_EQ(st.block.base_fee, 0);
105-
EXPECT_EQ(st.multi_tx.kind, test::TestMultiTransaction::Kind::legacy);
105+
EXPECT_EQ(st.multi_tx.type, test::TestMultiTransaction::Type::legacy);
106106
EXPECT_EQ(st.multi_tx.data, bytes{});
107107
EXPECT_EQ(st.multi_tx.gas_limit, 0);
108108
EXPECT_EQ(st.multi_tx.max_gas_price, 0);

test/unittests/statetest_loader_tx_test.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ TEST(statetest_loader, tx_create_legacy)
2929
})";
3030

3131
const auto tx = test::from_json<state::Transaction>(json::json::parse(input));
32-
EXPECT_EQ(tx.kind, state::Transaction::Kind::legacy);
32+
EXPECT_EQ(tx.type, state::Transaction::Type::legacy);
3333
EXPECT_EQ(tx.data, (bytes{0xb0, 0xb1}));
3434
EXPECT_EQ(tx.gas_limit, 0x9091);
3535
EXPECT_EQ(tx.chain_id, 5);
@@ -63,7 +63,7 @@ TEST(statetest_loader, tx_eip1559)
6363
})";
6464

6565
const auto tx = test::from_json<state::Transaction>(json::json::parse(input));
66-
EXPECT_EQ(tx.kind, state::Transaction::Kind::eip1559);
66+
EXPECT_EQ(tx.type, state::Transaction::Type::eip1559);
6767
EXPECT_EQ(tx.data, (bytes{0xb0, 0xb1}));
6868
EXPECT_EQ(tx.gas_limit, 0x9091);
6969
EXPECT_EQ(tx.chain_id, 0);
@@ -100,7 +100,7 @@ TEST(statetest_loader, tx_access_list)
100100
})";
101101

102102
const auto tx = test::from_json<state::Transaction>(json::json::parse(input));
103-
EXPECT_EQ(tx.kind, state::Transaction::Kind::eip1559);
103+
EXPECT_EQ(tx.type, state::Transaction::Type::eip1559);
104104
EXPECT_TRUE(tx.data.empty());
105105
EXPECT_EQ(tx.gas_limit, 0);
106106
EXPECT_EQ(tx.value, 0);
@@ -163,7 +163,7 @@ TEST(statetest_loader, tx_type_1)
163163
})";
164164

165165
const auto tx = test::from_json<state::Transaction>(json::json::parse(input));
166-
EXPECT_EQ(tx.kind, state::Transaction::Kind::eip2930);
166+
EXPECT_EQ(tx.type, state::Transaction::Type::access_list);
167167
EXPECT_TRUE(tx.data.empty());
168168
EXPECT_EQ(tx.gas_limit, 0);
169169
EXPECT_EQ(tx.value, 0);

0 commit comments

Comments
 (0)