From 0eb7180e647add9cd3ea762933844f6a99c0cdfe Mon Sep 17 00:00:00 2001 From: Shashank Date: Mon, 22 Jun 2026 11:14:20 +0530 Subject: [PATCH 01/11] use NotNullVec --- src/lotus_json/vec.rs | 3 +- src/rpc/methods/eth.rs | 38 ++++++++++++++++--- .../forest__rpc__tests__rpc__v0.snap | 17 +++------ .../forest__rpc__tests__rpc__v1.snap | 17 +++------ .../forest__rpc__tests__rpc__v2.snap | 17 +++------ 5 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/lotus_json/vec.rs b/src/lotus_json/vec.rs index 342139fa9463..afae9ed49c2a 100644 --- a/src/lotus_json/vec.rs +++ b/src/lotus_json/vec.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0, MIT use super::*; +use get_size2::GetSize; impl HasLotusJson for Vec // TODO(forest): https://github.com/ChainSafe/forest/issues/4032 @@ -37,7 +38,7 @@ where // while an empty `NotNullVec` serializes into `[]` // this is a temporary workaround and will likely be deprecated once // other issues on serde of `Vec` are resolved. -#[derive(Debug, Clone, PartialEq, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, JsonSchema, GetSize)] pub struct NotNullVec(pub Vec); impl HasLotusJson for NotNullVec diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index b8f1c3f99436..46347eafdbd6 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -26,7 +26,7 @@ use crate::eth::{ EAMMethod, EVMMethod, EthChainId as EthChainIdType, EthEip1559TxArgs, EthLegacyEip155TxArgs, EthLegacyHomesteadTxArgs, parse_eth_transaction, }; -use crate::lotus_json::{HasLotusJson, lotus_json_with_self}; +use crate::lotus_json::{HasLotusJson, NotNullVec, lotus_json_with_self}; use crate::message::{ChainMessage, MessageRead as _, MessageReadWrite as _, SignedMessage}; use crate::networks::Height; use crate::prelude::*; @@ -642,9 +642,9 @@ pub struct ApiEthTx { pub max_priority_fee_per_gas: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub gas_price: Option, - #[schemars(with = "Option>")] + #[schemars(with = "Vec")] #[serde(with = "crate::lotus_json")] - pub access_list: Vec, + pub access_list: NotNullVec, pub v: EthBigInt, pub r: EthBigInt, pub s: EthBigInt, @@ -846,7 +846,7 @@ impl RpcMethod<0> for EthAccounts { ); type Params = (); - type Ok = Vec; + type Ok = NotNullVec; async fn handle( _: Ctx, @@ -854,7 +854,7 @@ impl RpcMethod<0> for EthAccounts { _: &http::Extensions, ) -> Result { // EthAccounts will always return [] since we don't expect Forest to manage private keys - Ok(vec![]) + Ok(NotNullVec(vec![])) } } @@ -1263,7 +1263,7 @@ fn eth_tx_from_native_message( gas: EthUint64(msg.gas_limit), max_fee_per_gas: Some(msg.gas_fee_cap.clone().into()), max_priority_fee_per_gas: Some(msg.gas_premium.clone().into()), - access_list: vec![], + access_list: NotNullVec(vec![]), ..ApiEthTx::default() }) } @@ -4202,6 +4202,32 @@ mod test { } } + #[test] + fn empty_access_list_serializes_as_empty_array() { + let tx = ApiEthTx::default(); + assert!(tx.access_list.0.is_empty()); + let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); + assert_eq!(json["accessList"], serde_json::json!([])); + } + + #[test] + fn populated_access_list_serializes_as_array() { + let tx = ApiEthTx { + access_list: NotNullVec(vec![EthHash::default()]), + ..Default::default() + }; + let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); + assert!(json["accessList"].is_array()); + assert_eq!(json["accessList"].as_array().unwrap().len(), 1); + } + + #[test] + fn empty_eth_accounts_serializes_as_empty_array() { + let accounts: NotNullVec = NotNullVec(vec![]); + let json = serde_json::to_value(accounts.into_lotus_json()).unwrap(); + assert_eq!(json, serde_json::json!([])); + } + #[quickcheck] fn gas_price_result_serde_roundtrip(i: u128) { let r = EthBigInt(ethereum_types::U256::from(i)); diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap index 3c699e5a5fbd..129bb4729d0d 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap @@ -474,11 +474,9 @@ methods: params: [] result: name: Filecoin.EthAccounts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: type: string paramStructure: by-position @@ -487,11 +485,9 @@ methods: params: [] result: name: eth_accounts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: type: string paramStructure: by-position @@ -4759,9 +4755,7 @@ components: type: object properties: accessList: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthHash" blockHash: @@ -4820,6 +4814,7 @@ components: - type - input - gas + - accessList - v - r - s diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap index ab6785285969..5a019a2b68e2 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap @@ -470,11 +470,9 @@ methods: params: [] result: name: Filecoin.EthAccounts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: type: string paramStructure: by-position @@ -483,11 +481,9 @@ methods: params: [] result: name: eth_accounts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: type: string paramStructure: by-position @@ -4829,9 +4825,7 @@ components: type: object properties: accessList: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthHash" blockHash: @@ -4890,6 +4884,7 @@ components: - type - input - gas + - accessList - v - r - s diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap index 719d85d9ad27..3a248fba219e 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap @@ -74,11 +74,9 @@ methods: params: [] result: name: Filecoin.EthAccounts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: type: string paramStructure: by-position @@ -87,11 +85,9 @@ methods: params: [] result: name: eth_accounts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: type: string paramStructure: by-position @@ -1686,9 +1682,7 @@ components: type: object properties: accessList: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthHash" blockHash: @@ -1747,6 +1741,7 @@ components: - type - input - gas + - accessList - v - r - s From e4615f4ddc4900924f7da6574d92dadfd7dacdc3 Mon Sep 17 00:00:00 2001 From: Shashank Date: Mon, 22 Jun 2026 11:36:42 +0530 Subject: [PATCH 02/11] fix other methods --- src/rpc/methods/eth.rs | 37 +++++----- .../forest__rpc__tests__rpc__v0.snap | 72 +++++++------------ .../forest__rpc__tests__rpc__v1.snap | 72 +++++++------------ .../forest__rpc__tests__rpc__v2.snap | 72 +++++++------------ 4 files changed, 91 insertions(+), 162 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 46347eafdbd6..864076fd88ff 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -1602,7 +1602,7 @@ impl RpcMethod<1> for EthGetBlockReceipts { ); type Params = (BlockNumberOrHash,); - type Ok = Vec; + type Ok = NotNullVec; async fn handle( ctx: Ctx, @@ -1615,6 +1615,7 @@ impl RpcMethod<1> for EthGetBlockReceipts { .await?; get_block_receipts(&ctx, ts, None) .await + .map(NotNullVec) .map_err(ServerError::from) } } @@ -1631,7 +1632,7 @@ impl RpcMethod<2> for EthGetBlockReceiptsLimited { ); type Params = (BlockNumberOrHash, ChainEpoch); - type Ok = Vec; + type Ok = NotNullVec; async fn handle( ctx: Ctx, @@ -1644,6 +1645,7 @@ impl RpcMethod<2> for EthGetBlockReceiptsLimited { .await?; get_block_receipts(&ctx, ts, Some(limit)) .await + .map(NotNullVec) .map_err(ServerError::from) } } @@ -3573,7 +3575,7 @@ impl RpcMethod<1> for EthTraceBlock { const DESCRIPTION: Option<&'static str> = Some("Returns traces created at given block."); type Params = (BlockNumberOrHash,); - type Ok = Vec; + type Ok = NotNullVec; async fn handle( ctx: Ctx, (block_param,): Self::Params, @@ -3583,7 +3585,9 @@ impl RpcMethod<1> for EthTraceBlock { let ts = resolver .tipset_by_block_number_or_hash(block_param, ResolveNullTipset::TakeOlder) .await?; - eth_trace_block(&ctx.state_manager, &ts).await + eth_trace_block(&ctx.state_manager, &ts) + .await + .map(NotNullVec) } } @@ -3972,7 +3976,7 @@ impl RpcMethod<1> for EthTraceTransaction { Some("Returns the traces for a specific transaction."); type Params = (String,); - type Ok = Vec; + type Ok = NotNullVec; async fn handle( ctx: Ctx, (tx_hash,): Self::Params, @@ -3993,7 +3997,7 @@ impl RpcMethod<1> for EthTraceTransaction { .into_iter() .filter(|trace| trace.transaction_hash == eth_hash) .collect(); - Ok(traces) + Ok(NotNullVec(traces)) } } @@ -4010,7 +4014,7 @@ impl RpcMethod<2> for EthTraceReplayBlockTransactions { ); type Params = (BlockNumberOrHash, Vec); - type Ok = Vec; + type Ok = NotNullVec; async fn handle( ctx: Ctx, @@ -4029,7 +4033,9 @@ impl RpcMethod<2> for EthTraceReplayBlockTransactions { .tipset_by_block_number_or_hash(block_param, ResolveNullTipset::TakeOlder) .await?; - eth_trace_replay_block_transactions(&ctx, &ts).await + eth_trace_replay_block_transactions(&ctx, &ts) + .await + .map(NotNullVec) } } @@ -4082,7 +4088,7 @@ impl RpcMethod<1> for EthTraceFilter { const DESCRIPTION: Option<&'static str> = Some("Returns the traces for transactions matching the filter criteria."); type Params = (EthTraceFilterCriteria,); - type Ok = Vec; + type Ok = NotNullVec; async fn handle( ctx: Ctx, @@ -4116,7 +4122,9 @@ impl RpcMethod<1> for EthTraceFilter { return Err(EthErrors::limit_exceeded(max_block_range, range).into()); } } - Ok(trace_filter(ctx, filter, from_block, to_block, ext).await?) + Ok(NotNullVec( + trace_filter(ctx, filter, from_block, to_block, ext).await?, + )) } } @@ -4148,7 +4156,7 @@ async fn trace_filter( ext, ) .await?; - for block_trace in block_traces { + for block_trace in block_traces.0 { if block_trace .trace .match_filter_criteria(filter.from_address.as_ref(), filter.to_address.as_ref())? @@ -4221,13 +4229,6 @@ mod test { assert_eq!(json["accessList"].as_array().unwrap().len(), 1); } - #[test] - fn empty_eth_accounts_serializes_as_empty_array() { - let accounts: NotNullVec = NotNullVec(vec![]); - let json = serde_json::to_value(accounts.into_lotus_json()).unwrap(); - assert_eq!(json, serde_json::json!([])); - } - #[quickcheck] fn gas_price_result_serde_roundtrip(i: u128) { let r = EthBigInt(ethereum_types::U256::from(i)); diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap index 129bb4729d0d..d3bd5e2ac6f3 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap @@ -844,11 +844,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: Filecoin.EthGetBlockReceipts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -861,11 +859,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: eth_getBlockReceipts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -883,11 +879,9 @@ methods: format: int64 result: name: Filecoin.EthGetBlockReceiptsLimited.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -905,11 +899,9 @@ methods: format: int64 result: name: eth_getBlockReceiptsLimited.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -1616,11 +1608,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: Filecoin.EthTraceBlock.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1633,11 +1623,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: trace_block.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1650,11 +1638,9 @@ methods: $ref: "#/components/schemas/EthTraceFilterCriteria" result: name: Filecoin.EthTraceFilter.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1667,11 +1653,9 @@ methods: $ref: "#/components/schemas/EthTraceFilterCriteria" result: name: trace_filter.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1684,11 +1668,9 @@ methods: type: string result: name: Filecoin.EthTraceTransaction.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1701,11 +1683,9 @@ methods: type: string result: name: trace_transaction.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1726,11 +1706,9 @@ methods: type: string result: name: Filecoin.EthTraceReplayBlockTransactions.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthReplayBlockTransactionTrace" paramStructure: by-position @@ -1751,11 +1729,9 @@ methods: type: string result: name: trace_replayBlockTransactions.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthReplayBlockTransactionTrace" paramStructure: by-position diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap index 5a019a2b68e2..a7a23e7ad689 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap @@ -840,11 +840,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: Filecoin.EthGetBlockReceipts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -857,11 +855,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: eth_getBlockReceipts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -879,11 +875,9 @@ methods: format: int64 result: name: Filecoin.EthGetBlockReceiptsLimited.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -901,11 +895,9 @@ methods: format: int64 result: name: eth_getBlockReceiptsLimited.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -1612,11 +1604,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: Filecoin.EthTraceBlock.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1629,11 +1619,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: trace_block.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1692,11 +1680,9 @@ methods: $ref: "#/components/schemas/EthTraceFilterCriteria" result: name: Filecoin.EthTraceFilter.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1709,11 +1695,9 @@ methods: $ref: "#/components/schemas/EthTraceFilterCriteria" result: name: trace_filter.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1726,11 +1710,9 @@ methods: type: string result: name: Filecoin.EthTraceTransaction.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1743,11 +1725,9 @@ methods: type: string result: name: trace_transaction.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1806,11 +1786,9 @@ methods: type: string result: name: Filecoin.EthTraceReplayBlockTransactions.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthReplayBlockTransactionTrace" paramStructure: by-position @@ -1831,11 +1809,9 @@ methods: type: string result: name: trace_replayBlockTransactions.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthReplayBlockTransactionTrace" paramStructure: by-position diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap index 3a248fba219e..dc9b8e5d1ac8 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap @@ -444,11 +444,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: Filecoin.EthGetBlockReceipts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -461,11 +459,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: eth_getBlockReceipts.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -483,11 +479,9 @@ methods: format: int64 result: name: Filecoin.EthGetBlockReceiptsLimited.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -505,11 +499,9 @@ methods: format: int64 result: name: eth_getBlockReceiptsLimited.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthTxReceipt" paramStructure: by-position @@ -1216,11 +1208,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: Filecoin.EthTraceBlock.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1233,11 +1223,9 @@ methods: $ref: "#/components/schemas/BlockNumberOrHash" result: name: trace_block.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1296,11 +1284,9 @@ methods: $ref: "#/components/schemas/EthTraceFilterCriteria" result: name: Filecoin.EthTraceFilter.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1313,11 +1299,9 @@ methods: $ref: "#/components/schemas/EthTraceFilterCriteria" result: name: trace_filter.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1330,11 +1314,9 @@ methods: type: string result: name: Filecoin.EthTraceTransaction.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1347,11 +1329,9 @@ methods: type: string result: name: trace_transaction.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthBlockTrace" paramStructure: by-position @@ -1410,11 +1390,9 @@ methods: type: string result: name: Filecoin.EthTraceReplayBlockTransactions.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthReplayBlockTransactionTrace" paramStructure: by-position @@ -1435,11 +1413,9 @@ methods: type: string result: name: trace_replayBlockTransactions.Result - required: false + required: true schema: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthReplayBlockTransactionTrace" paramStructure: by-position From e9d6f48fefba174098a3db964c6d123339d8666b Mon Sep 17 00:00:00 2001 From: Shashank Date: Mon, 22 Jun 2026 11:44:54 +0530 Subject: [PATCH 03/11] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89a0b17906d8..0176e9e1ec7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,8 @@ - [#6975](https://github.com/ChainSafe/forest/issues/6975): Fixed `Filecoin.MpoolSelect` to not remove the messages from the live pool, only simulate the head change. +- [#7205](https://github.com/ChainSafe/forest/issues/7205): Fixed eth methods serializing empty lists as `null` instead of `[]`. + ## Forest v0.33.6 "Ebb" Non-mandatory release for all node operators. It fixes a critical memory leak in `v0.33.5`. (Earlier releases are not affected) From 57a72628b872c9387562dc6fd7188f63999a7f9b Mon Sep 17 00:00:00 2001 From: Shashank Date: Mon, 22 Jun 2026 13:15:22 +0530 Subject: [PATCH 04/11] make access list option --- src/rpc/methods/eth.rs | 14 ++++++++------ src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap | 5 +++-- src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap | 5 +++-- src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap | 5 +++-- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 864076fd88ff..50fb92ee3467 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -642,9 +642,9 @@ pub struct ApiEthTx { pub max_priority_fee_per_gas: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub gas_price: Option, - #[schemars(with = "Vec")] + #[schemars(with = "Option>")] #[serde(with = "crate::lotus_json")] - pub access_list: NotNullVec, + pub access_list: Option>, pub v: EthBigInt, pub r: EthBigInt, pub s: EthBigInt, @@ -1263,7 +1263,7 @@ fn eth_tx_from_native_message( gas: EthUint64(msg.gas_limit), max_fee_per_gas: Some(msg.gas_fee_cap.clone().into()), max_priority_fee_per_gas: Some(msg.gas_premium.clone().into()), - access_list: NotNullVec(vec![]), + access_list: Some(NotNullVec(vec![])), ..ApiEthTx::default() }) } @@ -4212,8 +4212,10 @@ mod test { #[test] fn empty_access_list_serializes_as_empty_array() { - let tx = ApiEthTx::default(); - assert!(tx.access_list.0.is_empty()); + let tx = ApiEthTx { + access_list: Some(NotNullVec(vec![])), + ..Default::default() + }; let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); assert_eq!(json["accessList"], serde_json::json!([])); } @@ -4221,7 +4223,7 @@ mod test { #[test] fn populated_access_list_serializes_as_array() { let tx = ApiEthTx { - access_list: NotNullVec(vec![EthHash::default()]), + access_list: Some(NotNullVec(vec![EthHash::default()])), ..Default::default() }; let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap index d3bd5e2ac6f3..33de3da4fbee 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap @@ -4731,7 +4731,9 @@ components: type: object properties: accessList: - type: array + type: + - array + - "null" items: $ref: "#/components/schemas/EthHash" blockHash: @@ -4790,7 +4792,6 @@ components: - type - input - gas - - accessList - v - r - s diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap index a7a23e7ad689..a2312c629b44 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap @@ -4801,7 +4801,9 @@ components: type: object properties: accessList: - type: array + type: + - array + - "null" items: $ref: "#/components/schemas/EthHash" blockHash: @@ -4860,7 +4862,6 @@ components: - type - input - gas - - accessList - v - r - s diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap index dc9b8e5d1ac8..b1b96964be81 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap @@ -1658,7 +1658,9 @@ components: type: object properties: accessList: - type: array + type: + - array + - "null" items: $ref: "#/components/schemas/EthHash" blockHash: @@ -1717,7 +1719,6 @@ components: - type - input - gas - - accessList - v - r - s From 4452ae9b5d36407d5334129ccbb3f24378e0f12d Mon Sep 17 00:00:00 2001 From: Shashank Date: Tue, 23 Jun 2026 13:06:14 +0530 Subject: [PATCH 05/11] fix --- CHANGELOG.md | 2 + docs/openrpc-specs/v0.json | 103 +++++------------- docs/openrpc-specs/v1.json | 103 +++++------------- docs/openrpc-specs/v2.json | 103 +++++------------- src/rpc/methods/eth.rs | 42 ++++++- src/rpc/methods/eth/eth_tx.rs | 1 + .../forest__rpc__tests__rpc__v0.snap | 4 +- .../forest__rpc__tests__rpc__v1.snap | 4 +- .../forest__rpc__tests__rpc__v2.snap | 4 +- 9 files changed, 133 insertions(+), 233 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 074bd2cc60e1..a67279c83dfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ ### Fixed +- [#7214](https://github.com/ChainSafe/forest/pull/7214): Aligned the `accessList` field of `eth` transactions with go-ethereum/reth: typed (EIP-1559) transactions now serialize it as `[]` (never `null`), and legacy (Homestead/EIP-155) transactions omit the field entirely. + ## Forest v0.33.7 "Shimmergloom" ### Added diff --git a/docs/openrpc-specs/v0.json b/docs/openrpc-specs/v0.json index 3c0047325fa5..cd7c792ed7b3 100644 --- a/docs/openrpc-specs/v0.json +++ b/docs/openrpc-specs/v0.json @@ -806,12 +806,9 @@ "params": [], "result": { "name": "Filecoin.EthAccounts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "type": "string" } @@ -1095,12 +1092,9 @@ ], "result": { "name": "Filecoin.EthGetBlockReceipts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -1130,12 +1124,9 @@ ], "result": { "name": "Filecoin.EthGetBlockReceiptsLimited.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -1748,12 +1739,9 @@ ], "result": { "name": "Filecoin.EthTraceBlock.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -1775,12 +1763,9 @@ ], "result": { "name": "Filecoin.EthTraceFilter.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -1815,12 +1800,9 @@ ], "result": { "name": "Filecoin.EthTraceReplayBlockTransactions.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthReplayBlockTransactionTrace" } @@ -1842,12 +1824,9 @@ ], "result": { "name": "Filecoin.EthTraceTransaction.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -6215,12 +6194,9 @@ "params": [], "result": { "name": "eth_accounts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "type": "string" } @@ -6483,12 +6459,9 @@ ], "result": { "name": "eth_getBlockReceipts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -6518,12 +6491,9 @@ ], "result": { "name": "eth_getBlockReceiptsLimited.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -7196,12 +7166,9 @@ ], "result": { "name": "trace_block.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -7223,12 +7190,9 @@ ], "result": { "name": "trace_filter.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -7263,12 +7227,9 @@ ], "result": { "name": "trace_replayBlockTransactions.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthReplayBlockTransactionTrace" } @@ -7290,12 +7251,9 @@ ], "result": { "name": "trace_transaction.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -7682,10 +7640,7 @@ "type": "object", "properties": { "accessList": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthHash" } diff --git a/docs/openrpc-specs/v1.json b/docs/openrpc-specs/v1.json index 846aab8563e7..100bf72a6893 100644 --- a/docs/openrpc-specs/v1.json +++ b/docs/openrpc-specs/v1.json @@ -798,12 +798,9 @@ "params": [], "result": { "name": "Filecoin.EthAccounts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "type": "string" } @@ -1087,12 +1084,9 @@ ], "result": { "name": "Filecoin.EthGetBlockReceipts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -1122,12 +1116,9 @@ ], "result": { "name": "Filecoin.EthGetBlockReceiptsLimited.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -1740,12 +1731,9 @@ ], "result": { "name": "Filecoin.EthTraceBlock.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -1767,12 +1755,9 @@ ], "result": { "name": "Filecoin.EthTraceFilter.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -1807,12 +1792,9 @@ ], "result": { "name": "Filecoin.EthTraceReplayBlockTransactions.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthReplayBlockTransactionTrace" } @@ -1834,12 +1816,9 @@ ], "result": { "name": "Filecoin.EthTraceTransaction.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -6271,12 +6250,9 @@ "params": [], "result": { "name": "eth_accounts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "type": "string" } @@ -6539,12 +6515,9 @@ ], "result": { "name": "eth_getBlockReceipts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -6574,12 +6547,9 @@ ], "result": { "name": "eth_getBlockReceiptsLimited.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -7252,12 +7222,9 @@ ], "result": { "name": "trace_block.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -7321,12 +7288,9 @@ ], "result": { "name": "trace_filter.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -7361,12 +7325,9 @@ ], "result": { "name": "trace_replayBlockTransactions.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthReplayBlockTransactionTrace" } @@ -7388,12 +7349,9 @@ ], "result": { "name": "trace_transaction.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -7822,10 +7780,7 @@ "type": "object", "properties": { "accessList": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthHash" } diff --git a/docs/openrpc-specs/v2.json b/docs/openrpc-specs/v2.json index 7f46e8c5a2f3..abb3d0890769 100644 --- a/docs/openrpc-specs/v2.json +++ b/docs/openrpc-specs/v2.json @@ -45,12 +45,9 @@ "params": [], "result": { "name": "Filecoin.EthAccounts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "type": "string" } @@ -334,12 +331,9 @@ ], "result": { "name": "Filecoin.EthGetBlockReceipts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -369,12 +363,9 @@ ], "result": { "name": "Filecoin.EthGetBlockReceiptsLimited.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -987,12 +978,9 @@ ], "result": { "name": "Filecoin.EthTraceBlock.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -1014,12 +1002,9 @@ ], "result": { "name": "Filecoin.EthTraceFilter.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -1054,12 +1039,9 @@ ], "result": { "name": "Filecoin.EthTraceReplayBlockTransactions.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthReplayBlockTransactionTrace" } @@ -1081,12 +1063,9 @@ ], "result": { "name": "Filecoin.EthTraceTransaction.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -1514,12 +1493,9 @@ "params": [], "result": { "name": "eth_accounts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "type": "string" } @@ -1782,12 +1758,9 @@ ], "result": { "name": "eth_getBlockReceipts.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -1817,12 +1790,9 @@ ], "result": { "name": "eth_getBlockReceiptsLimited.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthTxReceipt" } @@ -2495,12 +2465,9 @@ ], "result": { "name": "trace_block.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -2564,12 +2531,9 @@ ], "result": { "name": "trace_filter.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -2604,12 +2568,9 @@ ], "result": { "name": "trace_replayBlockTransactions.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthReplayBlockTransactionTrace" } @@ -2631,12 +2592,9 @@ ], "result": { "name": "trace_transaction.Result", - "required": false, + "required": true, "schema": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthBlockTrace" } @@ -2765,10 +2723,7 @@ "type": "object", "properties": { "accessList": { - "type": [ - "array", - "null" - ], + "type": "array", "items": { "$ref": "#/components/schemas/EthHash" } diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 6d5e68eb6697..2be6af311bc9 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -642,8 +642,12 @@ pub struct ApiEthTx { pub max_priority_fee_per_gas: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub gas_price: Option, - #[schemars(with = "Option>")] - #[serde(with = "crate::lotus_json")] + #[schemars(with = "Vec")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "crate::lotus_json" + )] pub access_list: Option>, pub v: EthBigInt, pub r: EthBigInt, @@ -4230,6 +4234,40 @@ mod test { assert_eq!(json["accessList"].as_array().unwrap().len(), 1); } + #[test] + fn none_access_list_is_omitted_not_null() { + let tx = ApiEthTx { + access_list: None, + ..Default::default() + }; + let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); + assert!(!json.as_object().unwrap().contains_key("accessList")); + } + + #[test] + fn legacy_homestead_tx_omits_access_list() { + let tx: ApiEthTx = EthLegacyHomesteadTxArgs::default().into(); + assert_eq!(tx.access_list, None); + let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); + assert!(!json.as_object().unwrap().contains_key("accessList")); + } + + #[test] + fn legacy_eip155_tx_omits_access_list() { + let tx: ApiEthTx = EthLegacyEip155TxArgs::default().into(); + assert_eq!(tx.access_list, None); + let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); + assert!(!json.as_object().unwrap().contains_key("accessList")); + } + + #[test] + fn eip1559_tx_serializes_empty_access_list() { + let tx: ApiEthTx = EthEip1559TxArgs::default().into(); + assert_eq!(tx.access_list, Some(NotNullVec(vec![]))); + let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); + assert_eq!(json["accessList"], serde_json::json!([])); + } + #[quickcheck] fn gas_price_result_serde_roundtrip(i: u128) { let r = EthBigInt(ethereum_types::U256::from(i)); diff --git a/src/rpc/methods/eth/eth_tx.rs b/src/rpc/methods/eth/eth_tx.rs index 09107a2d76f8..c16eca72a53e 100644 --- a/src/rpc/methods/eth/eth_tx.rs +++ b/src/rpc/methods/eth/eth_tx.rs @@ -98,6 +98,7 @@ impl From for ApiEthTx { v: v.into(), r: r.into(), s: s.into(), + access_list: Some(NotNullVec(vec![])), ..Default::default() } } diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap index 52e0c6d860a0..1359e66eaefb 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap @@ -4751,9 +4751,7 @@ components: type: object properties: accessList: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthHash" blockHash: diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap index bfa6cc5cdb3b..eec3d5e347c0 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap @@ -4821,9 +4821,7 @@ components: type: object properties: accessList: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthHash" blockHash: diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap index 505deb195082..35cd5a0c0a7b 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap @@ -1658,9 +1658,7 @@ components: type: object properties: accessList: - type: - - array - - "null" + type: array items: $ref: "#/components/schemas/EthHash" blockHash: From fad7607e2eac8ab31570565239b3b6503fda56ec Mon Sep 17 00:00:00 2001 From: Shashank Date: Tue, 23 Jun 2026 13:12:39 +0530 Subject: [PATCH 06/11] update changelog --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a67279c83dfe..8a563192b5ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ ### Fixed -- [#7214](https://github.com/ChainSafe/forest/pull/7214): Aligned the `accessList` field of `eth` transactions with go-ethereum/reth: typed (EIP-1559) transactions now serialize it as `[]` (never `null`), and legacy (Homestead/EIP-155) transactions omit the field entirely. +- [#7214](https://github.com/ChainSafe/forest/pull/7214): Aligned the `eth` transaction `accessList` field with go-ethereum/reth (typed: `[]`, legacy: omitted, never `null`). ## Forest v0.33.7 "Shimmergloom" @@ -63,8 +63,6 @@ - [#6975](https://github.com/ChainSafe/forest/issues/6975): Fixed `Filecoin.MpoolSelect` to not remove the messages from the live pool, only simulate the head change. -- [#7205](https://github.com/ChainSafe/forest/issues/7205): Fixed eth methods serializing empty lists as `null` instead of `[]`. - - [#7217](https://github.com/ChainSafe/forest/pull/7217): Fixed a bug that `Filecoin.StateCirculatingSupply` returns error on mainnet. ## Forest v0.33.6 "Ebb" From 735b1b2e86dfcbda6c4445048f0b812482936397 Mon Sep 17 00:00:00 2001 From: Shashank Date: Tue, 23 Jun 2026 13:27:46 +0530 Subject: [PATCH 07/11] Add more test --- src/rpc/methods/eth.rs | 92 +++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 2be6af311bc9..d92ad50c7235 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -4213,59 +4213,51 @@ mod test { } } - #[test] - fn empty_access_list_serializes_as_empty_array() { - let tx = ApiEthTx { - access_list: Some(NotNullVec(vec![])), - ..Default::default() - }; - let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); - assert_eq!(json["accessList"], serde_json::json!([])); - } - - #[test] - fn populated_access_list_serializes_as_array() { - let tx = ApiEthTx { - access_list: Some(NotNullVec(vec![EthHash::default()])), - ..Default::default() - }; - let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); - assert!(json["accessList"].is_array()); - assert_eq!(json["accessList"].as_array().unwrap().len(), 1); - } - - #[test] - fn none_access_list_is_omitted_not_null() { - let tx = ApiEthTx { - access_list: None, - ..Default::default() - }; - let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); - assert!(!json.as_object().unwrap().contains_key("accessList")); - } - - #[test] - fn legacy_homestead_tx_omits_access_list() { - let tx: ApiEthTx = EthLegacyHomesteadTxArgs::default().into(); - assert_eq!(tx.access_list, None); - let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); - assert!(!json.as_object().unwrap().contains_key("accessList")); - } - - #[test] - fn legacy_eip155_tx_omits_access_list() { - let tx: ApiEthTx = EthLegacyEip155TxArgs::default().into(); - assert_eq!(tx.access_list, None); + #[rstest] + #[case::empty_array(ApiEthTx { access_list: Some(NotNullVec(vec![])), ..Default::default() }, Some(0))] + #[case::populated_array(ApiEthTx { access_list: Some(NotNullVec(vec![EthHash::default()])), ..Default::default() }, Some(1))] + #[case::explicit_none_omitted(ApiEthTx { access_list: None, ..Default::default() }, None)] + #[case::legacy_homestead_omitted(EthLegacyHomesteadTxArgs::default().into(), None)] + #[case::legacy_eip155_omitted(EthLegacyEip155TxArgs::default().into(), None)] + #[case::eip1559_empty_array(EthEip1559TxArgs::default().into(), Some(0))] + fn access_list_serialization(#[case] tx: ApiEthTx, #[case] expected: Option) { let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); - assert!(!json.as_object().unwrap().contains_key("accessList")); + match expected { + Some(len) => assert_eq!( + json["accessList"] + .as_array() + .expect("accessList should serialize as an array") + .len(), + len + ), + None => assert!(!json.as_object().unwrap().contains_key("accessList")), + } } - #[test] - fn eip1559_tx_serializes_empty_access_list() { - let tx: ApiEthTx = EthEip1559TxArgs::default().into(); - assert_eq!(tx.access_list, Some(NotNullVec(vec![]))); - let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); - assert_eq!(json["accessList"], serde_json::json!([])); + #[rstest] + #[case::null_to_none(Some(serde_json::Value::Null), None)] + #[case::missing_to_none(None, None)] + #[case::empty_array_to_some(Some(serde_json::json!([])), Some(NotNullVec(vec![])))] + #[case::populated_array_to_some( + Some(serde_json::json!([EthHash::default()])), + Some(NotNullVec(vec![EthHash::default()])) + )] + fn access_list_deserialization( + #[case] access_list_value: Option, + #[case] expected: Option>, + ) { + let mut json = serde_json::to_value(ApiEthTx::default().into_lotus_json()).unwrap(); + let obj = json.as_object_mut().unwrap(); + match access_list_value { + Some(value) => { + obj.insert("accessList".into(), value); + } + None => { + obj.remove("accessList"); + } + } + let tx = ApiEthTx::from_lotus_json(serde_json::from_value(json).unwrap()); + assert_eq!(tx.access_list, expected); } #[quickcheck] From 8a8ea73f94e467f829c1d895adf374b40b36c4ba Mon Sep 17 00:00:00 2001 From: Shashank Date: Tue, 23 Jun 2026 15:42:08 +0530 Subject: [PATCH 08/11] Update test snapshots --- src/tool/subcommands/api_cmd/test_snapshots.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index 310ed20b0bc7..986cab5c8565 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -46,7 +46,7 @@ filecoin_eam_statedecodeparams_1756139121347218.rpcsnap.json.zst filecoin_eam_statedecodeparams_1756139121347288.rpcsnap.json.zst filecoin_eam_statedecodeparams_1756139121347364.rpcsnap.json.zst filecoin_ethaccount_statedecodeparams_1756186350854913.rpcsnap.json.zst -filecoin_ethaccounts_1737446676689764.rpcsnap.json.zst +filecoin_ethaccounts_1782207886518096.rpcsnap.json.zst filecoin_ethaddresstofilecoinaddress_1740132538465536.rpcsnap.json.zst filecoin_ethbasefee_1778755118342258.rpcsnap.json.zst filecoin_ethblocknumber_1741272348346171.rpcsnap.json.zst @@ -97,7 +97,7 @@ filecoin_ethgetstorageat_v2_finalized_1769611091511354.rpcsnap.json.zst filecoin_ethgetstorageat_v2_safe_1769611091439347.rpcsnap.json.zst filecoin_ethgetstorageat_v2_unknown_addr_1770288667897031.rpcsnap.json.zst filecoin_ethgettransactionbyblockhashandindex_1773159088537607.rpcsnap.json.zst -filecoin_ethgettransactionbyblocknumberandindex_1740132538304408.rpcsnap.json.zst +filecoin_ethgettransactionbyblocknumberandindex_1782207831415446.rpcsnap.json.zst filecoin_ethgettransactionbyblocknumberandindex_latest_1769103643171646.rpcsnap.json.zst filecoin_ethgettransactionbyblocknumberandindex_pending_1769103643171774.rpcsnap.json.zst filecoin_ethgettransactionbyblocknumberandindex_v2_1769103643172125.rpcsnap.json.zst From 1eb1ebb8da8d491147c03098c22664e024e1d462 Mon Sep 17 00:00:00 2001 From: Shashank Date: Tue, 23 Jun 2026 16:40:05 +0530 Subject: [PATCH 09/11] filter --- scripts/tests/api_compare/filter-list | 6 ++++++ scripts/tests/api_compare/filter-list-gateway | 5 +++++ scripts/tests/api_compare/filter-list-offline | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/scripts/tests/api_compare/filter-list b/scripts/tests/api_compare/filter-list index f749dc6fadbc..a1663aba00b1 100644 --- a/scripts/tests/api_compare/filter-list +++ b/scripts/tests/api_compare/filter-list @@ -4,3 +4,9 @@ !Filecoin.EthEstimateGas !Filecoin.EthGetBlockByHash !Filecoin.EthGetBlockByNumber +# Lotus empty-slice null vs Forest accessList [] for type-0x2 txs. +# https://github.com/filecoin-project/lotus/issues/12214 +!Filecoin.EthGetTransactionByBlockNumberAndIndex +!Filecoin.EthGetTransactionByBlockHashAndIndex +!Filecoin.EthGetTransactionByHash +!Filecoin.EthGetTransactionByHashLimited diff --git a/scripts/tests/api_compare/filter-list-gateway b/scripts/tests/api_compare/filter-list-gateway index 870225d6f8c0..c4e230011073 100644 --- a/scripts/tests/api_compare/filter-list-gateway +++ b/scripts/tests/api_compare/filter-list-gateway @@ -70,3 +70,8 @@ # https://github.com/filecoin-project/lotus/pull/13618 !Filecoin.EthGetBlockByHash !Filecoin.EthGetBlockByNumber +# Lotus empty-slice null vs Forest accessList [] for type-0x2 txs. +# https://github.com/filecoin-project/lotus/issues/12214 +!Filecoin.EthGetTransactionByBlockNumberAndIndex +!Filecoin.EthGetTransactionByBlockHashAndIndex +!Filecoin.EthGetTransactionByHash diff --git a/scripts/tests/api_compare/filter-list-offline b/scripts/tests/api_compare/filter-list-offline index 782ee74579fe..68c35af678df 100644 --- a/scripts/tests/api_compare/filter-list-offline +++ b/scripts/tests/api_compare/filter-list-offline @@ -35,3 +35,8 @@ !Filecoin.EthTraceReplayBlockTransactions !Filecoin.EthEstimateGas !Filecoin.EthGetBlockByHash +# Lotus empty-slice null vs Forest accessList [] for type-0x2 txs. +# https://github.com/filecoin-project/lotus/issues/12214 +!Filecoin.EthGetTransactionByBlockHashAndIndex +!Filecoin.EthGetTransactionByHash +!Filecoin.EthGetTransactionByHashLimited From 7ca0e2dd316bbe57340fe1c94b62ae0db4dc35c8 Mon Sep 17 00:00:00 2001 From: Shashank Date: Tue, 23 Jun 2026 16:55:07 +0530 Subject: [PATCH 10/11] fmt --- scripts/tests/api_compare/filter-list | 1 - scripts/tests/api_compare/filter-list-gateway | 1 - scripts/tests/api_compare/filter-list-offline | 1 - 3 files changed, 3 deletions(-) diff --git a/scripts/tests/api_compare/filter-list b/scripts/tests/api_compare/filter-list index a1663aba00b1..cee3bb63d03c 100644 --- a/scripts/tests/api_compare/filter-list +++ b/scripts/tests/api_compare/filter-list @@ -4,7 +4,6 @@ !Filecoin.EthEstimateGas !Filecoin.EthGetBlockByHash !Filecoin.EthGetBlockByNumber -# Lotus empty-slice null vs Forest accessList [] for type-0x2 txs. # https://github.com/filecoin-project/lotus/issues/12214 !Filecoin.EthGetTransactionByBlockNumberAndIndex !Filecoin.EthGetTransactionByBlockHashAndIndex diff --git a/scripts/tests/api_compare/filter-list-gateway b/scripts/tests/api_compare/filter-list-gateway index c4e230011073..471f402c6e21 100644 --- a/scripts/tests/api_compare/filter-list-gateway +++ b/scripts/tests/api_compare/filter-list-gateway @@ -70,7 +70,6 @@ # https://github.com/filecoin-project/lotus/pull/13618 !Filecoin.EthGetBlockByHash !Filecoin.EthGetBlockByNumber -# Lotus empty-slice null vs Forest accessList [] for type-0x2 txs. # https://github.com/filecoin-project/lotus/issues/12214 !Filecoin.EthGetTransactionByBlockNumberAndIndex !Filecoin.EthGetTransactionByBlockHashAndIndex diff --git a/scripts/tests/api_compare/filter-list-offline b/scripts/tests/api_compare/filter-list-offline index 68c35af678df..d7d506e61482 100644 --- a/scripts/tests/api_compare/filter-list-offline +++ b/scripts/tests/api_compare/filter-list-offline @@ -35,7 +35,6 @@ !Filecoin.EthTraceReplayBlockTransactions !Filecoin.EthEstimateGas !Filecoin.EthGetBlockByHash -# Lotus empty-slice null vs Forest accessList [] for type-0x2 txs. # https://github.com/filecoin-project/lotus/issues/12214 !Filecoin.EthGetTransactionByBlockHashAndIndex !Filecoin.EthGetTransactionByHash From ba9526aab35d52eed25e5ebbba7f436f3764302a Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 24 Jun 2026 18:44:49 +0530 Subject: [PATCH 11/11] deserialize null accessList --- scripts/tests/api_compare/filter-list | 5 ----- scripts/tests/api_compare/filter-list-gateway | 4 ---- scripts/tests/api_compare/filter-list-offline | 4 ---- src/lotus_json/vec.rs | 16 ++++++++++++++++ src/rpc/methods/eth.rs | 15 +++++++++++---- 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/scripts/tests/api_compare/filter-list b/scripts/tests/api_compare/filter-list index c53202b12fde..c2c32dc64f46 100644 --- a/scripts/tests/api_compare/filter-list +++ b/scripts/tests/api_compare/filter-list @@ -3,8 +3,3 @@ !Filecoin.EthGetBlockByHash !Filecoin.EthGetBlockByNumber -# https://github.com/filecoin-project/lotus/issues/12214 -!Filecoin.EthGetTransactionByBlockNumberAndIndex -!Filecoin.EthGetTransactionByBlockHashAndIndex -!Filecoin.EthGetTransactionByHash -!Filecoin.EthGetTransactionByHashLimited diff --git a/scripts/tests/api_compare/filter-list-gateway b/scripts/tests/api_compare/filter-list-gateway index 83a683c2b926..06ec86cdac01 100644 --- a/scripts/tests/api_compare/filter-list-gateway +++ b/scripts/tests/api_compare/filter-list-gateway @@ -68,7 +68,3 @@ # https://github.com/filecoin-project/lotus/pull/13618 !Filecoin.EthGetBlockByHash !Filecoin.EthGetBlockByNumber -# https://github.com/filecoin-project/lotus/issues/12214 -!Filecoin.EthGetTransactionByBlockNumberAndIndex -!Filecoin.EthGetTransactionByBlockHashAndIndex -!Filecoin.EthGetTransactionByHash diff --git a/scripts/tests/api_compare/filter-list-offline b/scripts/tests/api_compare/filter-list-offline index 253ae3e9f63b..6546cb5d6b72 100644 --- a/scripts/tests/api_compare/filter-list-offline +++ b/scripts/tests/api_compare/filter-list-offline @@ -34,7 +34,3 @@ !Filecoin.EthTraceFilter !Filecoin.EthTraceReplayBlockTransactions !Filecoin.EthGetBlockByHash -# https://github.com/filecoin-project/lotus/issues/12214 -!Filecoin.EthGetTransactionByBlockHashAndIndex -!Filecoin.EthGetTransactionByHash -!Filecoin.EthGetTransactionByHashLimited diff --git a/src/lotus_json/vec.rs b/src/lotus_json/vec.rs index afae9ed49c2a..cbdafb6161cd 100644 --- a/src/lotus_json/vec.rs +++ b/src/lotus_json/vec.rs @@ -61,6 +61,22 @@ where } } +/// Deserialize `Option>`, mapping `null` and empty `[]` to `None`. +/// +/// Generic `HasLotusJson for Option` keeps `Some(NotNullVec([]))` on `[]`; +/// Lotus sends `null` for empty access lists and we treat both as absent. +pub fn deserialize_empty_not_null_opt<'de, T, D>( + deserializer: D, +) -> Result>, D::Error> +where + T: Deserialize<'de>, + D: Deserializer<'de>, +{ + Ok(Option::>::deserialize(deserializer)? + .filter(|l| !l.is_empty()) + .map(NotNullVec)) +} + #[test] fn snapshots() { assert_one_snapshot(json!([{"/": "baeaaaaa"}]), vec![::cid::Cid::default()]); diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 8c6ea85490c0..2c7d8bed3ba5 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -646,7 +646,8 @@ pub struct ApiEthTx { #[serde( default, skip_serializing_if = "Option::is_none", - with = "crate::lotus_json" + serialize_with = "crate::lotus_json::serialize", + deserialize_with = "crate::lotus_json::deserialize_empty_not_null_opt" )] pub access_list: Option>, pub v: EthBigInt, @@ -4222,11 +4223,13 @@ mod test { } #[rstest] - #[case::empty_array(ApiEthTx { access_list: Some(NotNullVec(vec![])), ..Default::default() }, Some(0))] + // Non-empty access list → JSON array. #[case::populated_array(ApiEthTx { access_list: Some(NotNullVec(vec![EthHash::default()])), ..Default::default() }, Some(1))] + // `access_list: None` → field omitted. #[case::explicit_none_omitted(ApiEthTx { access_list: None, ..Default::default() }, None)] + // Legacy tx → field omitted. #[case::legacy_homestead_omitted(EthLegacyHomesteadTxArgs::default().into(), None)] - #[case::legacy_eip155_omitted(EthLegacyEip155TxArgs::default().into(), None)] + // Typed tx with no entries → `[]`. #[case::eip1559_empty_array(EthEip1559TxArgs::default().into(), Some(0))] fn access_list_serialization(#[case] tx: ApiEthTx, #[case] expected: Option) { let json = serde_json::to_value(tx.into_lotus_json()).unwrap(); @@ -4243,9 +4246,13 @@ mod test { } #[rstest] + // `"accessList": null` → `None`. #[case::null_to_none(Some(serde_json::Value::Null), None)] + // Omitted/Missing field → `None`. #[case::missing_to_none(None, None)] - #[case::empty_array_to_some(Some(serde_json::json!([])), Some(NotNullVec(vec![])))] + // `"accessList": []` → `None`. + #[case::empty_array_to_none(Some(serde_json::json!([])), None)] + // Non-empty array → `Some(...)`. #[case::populated_array_to_some( Some(serde_json::json!([EthHash::default()])), Some(NotNullVec(vec![EthHash::default()]))