Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,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)
Expand Down
3 changes: 2 additions & 1 deletion src/lotus_json/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0, MIT

use super::*;
use get_size2::GetSize;

impl<T> HasLotusJson for Vec<T>
// TODO(forest): https://github.com/ChainSafe/forest/issues/4032
Expand Down Expand Up @@ -37,7 +38,7 @@ where
// while an empty `NotNullVec<T>` serializes into `[]`
// this is a temporary workaround and will likely be deprecated once
// other issues on serde of `Vec<T>` are resolved.
#[derive(Debug, Clone, PartialEq, JsonSchema)]
#[derive(Debug, Clone, Default, PartialEq, JsonSchema, GetSize)]
pub struct NotNullVec<T>(pub Vec<T>);

impl<T> HasLotusJson for NotNullVec<T>
Expand Down
61 changes: 45 additions & 16 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -644,7 +644,7 @@ pub struct ApiEthTx {
pub gas_price: Option<EthBigInt>,
#[schemars(with = "Option<Vec<EthHash>>")]
#[serde(with = "crate::lotus_json")]
pub access_list: Vec<EthHash>,
pub access_list: Option<NotNullVec<EthHash>>,
pub v: EthBigInt,
pub r: EthBigInt,
pub s: EthBigInt,
Expand Down Expand Up @@ -846,15 +846,15 @@ impl RpcMethod<0> for EthAccounts {
);

type Params = ();
type Ok = Vec<String>;
type Ok = NotNullVec<String>;

async fn handle(
_: Ctx,
(): Self::Params,
_: &http::Extensions,
) -> Result<Self::Ok, ServerError> {
// EthAccounts will always return [] since we don't expect Forest to manage private keys
Ok(vec![])
Ok(NotNullVec(vec![]))
}
}

Expand Down Expand Up @@ -1263,7 +1263,7 @@ fn eth_tx_from_native_message<DB: Blockstore>(
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: Some(NotNullVec(vec![])),
..ApiEthTx::default()
})
}
Expand Down Expand Up @@ -1602,7 +1602,7 @@ impl RpcMethod<1> for EthGetBlockReceipts {
);

type Params = (BlockNumberOrHash,);
type Ok = Vec<EthTxReceipt>;
type Ok = NotNullVec<EthTxReceipt>;

async fn handle(
ctx: Ctx,
Expand All @@ -1615,6 +1615,7 @@ impl RpcMethod<1> for EthGetBlockReceipts {
.await?;
get_block_receipts(&ctx, ts, None)
.await
.map(NotNullVec)
.map_err(ServerError::from)
}
}
Expand All @@ -1631,7 +1632,7 @@ impl RpcMethod<2> for EthGetBlockReceiptsLimited {
);

type Params = (BlockNumberOrHash, ChainEpoch);
type Ok = Vec<EthTxReceipt>;
type Ok = NotNullVec<EthTxReceipt>;

async fn handle(
ctx: Ctx,
Expand All @@ -1644,6 +1645,7 @@ impl RpcMethod<2> for EthGetBlockReceiptsLimited {
.await?;
get_block_receipts(&ctx, ts, Some(limit))
.await
.map(NotNullVec)
.map_err(ServerError::from)
}
}
Expand Down Expand Up @@ -3572,7 +3574,7 @@ impl RpcMethod<1> for EthTraceBlock {
const DESCRIPTION: Option<&'static str> = Some("Returns traces created at given block.");

type Params = (BlockNumberOrHash,);
type Ok = Vec<EthBlockTrace>;
type Ok = NotNullVec<EthBlockTrace>;
async fn handle(
ctx: Ctx,
(block_param,): Self::Params,
Expand All @@ -3582,7 +3584,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)
}
}

Expand Down Expand Up @@ -3971,7 +3975,7 @@ impl RpcMethod<1> for EthTraceTransaction {
Some("Returns the traces for a specific transaction.");

type Params = (String,);
type Ok = Vec<EthBlockTrace>;
type Ok = NotNullVec<EthBlockTrace>;
async fn handle(
ctx: Ctx,
(tx_hash,): Self::Params,
Expand All @@ -3992,7 +3996,7 @@ impl RpcMethod<1> for EthTraceTransaction {
.into_iter()
.filter(|trace| trace.transaction_hash == eth_hash)
.collect();
Ok(traces)
Ok(NotNullVec(traces))
}
}

Expand All @@ -4009,7 +4013,7 @@ impl RpcMethod<2> for EthTraceReplayBlockTransactions {
);

type Params = (BlockNumberOrHash, Vec<String>);
type Ok = Vec<EthReplayBlockTransactionTrace>;
type Ok = NotNullVec<EthReplayBlockTransactionTrace>;

async fn handle(
ctx: Ctx,
Expand All @@ -4028,7 +4032,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)
}
}

Expand Down Expand Up @@ -4081,7 +4087,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<EthBlockTrace>;
type Ok = NotNullVec<EthBlockTrace>;

async fn handle(
ctx: Ctx,
Expand Down Expand Up @@ -4115,7 +4121,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?,
))
}
}

Expand Down Expand Up @@ -4147,7 +4155,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())?
Expand Down Expand Up @@ -4201,6 +4209,27 @@ mod test {
}
}

#[test]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To reproduce reth/geth and ensure no regressions, we should test the legacy transactions have the field omitted.

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);
}

#[quickcheck]
fn gas_price_result_serde_roundtrip(i: u128) {
let r = EthBigInt(ethereum_types::U256::from(i));
Expand Down
Loading
Loading