From 54031af754f326cac0efd523d599442e6baf92e1 Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 4 Feb 2026 15:14:57 +0530 Subject: [PATCH 1/6] Impl EthSendRawTransactionUntrusted --- src/message_pool/msgpool/mod.rs | 2 +- src/message_pool/msgpool/msg_pool.rs | 48 ++++++++++++------- src/rpc/methods/eth.rs | 22 +++++++++ src/rpc/methods/mpool.rs | 3 +- src/rpc/mod.rs | 1 + .../api_cmd/test_snapshots_ignored.txt | 1 + 6 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/message_pool/msgpool/mod.rs b/src/message_pool/msgpool/mod.rs index d50d86e3d6e3..d1219c04aad6 100644 --- a/src/message_pool/msgpool/mod.rs +++ b/src/message_pool/msgpool/mod.rs @@ -277,7 +277,7 @@ where for (_, hm) in rmsgs { for (_, msg) in hm { let sequence = get_state_sequence(api, &msg.from(), &cur_tipset.read().clone())?; - if let Err(e) = add_helper(api, bls_sig_cache, pending, msg, sequence) { + if let Err(e) = add_helper(api, bls_sig_cache, pending, msg, sequence, false) { error!("Failed to read message from reorg to mpool: {}", e); } } diff --git a/src/message_pool/msgpool/msg_pool.rs b/src/message_pool/msgpool/msg_pool.rs index 19a54e93aee4..443ff3b510c9 100644 --- a/src/message_pool/msgpool/msg_pool.rs +++ b/src/message_pool/msgpool/msg_pool.rs @@ -89,7 +89,6 @@ impl MsgSet { /// Add a signed message to the `MsgSet`. Increase `next_sequence` if the /// message has a sequence greater than any existing message sequence. /// Use this method when pushing a message coming from untrusted sources. - #[allow(dead_code)] pub fn add_untrusted(&mut self, api: &T, m: SignedMessage) -> Result<(), Error> where T: Provider, @@ -216,11 +215,11 @@ where /// Push a signed message to the `MessagePool`. Additionally performs basic /// checks on the validity of a message. - pub async fn push(&self, msg: SignedMessage) -> Result { + pub async fn push_internal(&self, msg: SignedMessage, untrusted: bool) -> Result { self.check_message(&msg)?; let cid = msg.cid(); let cur_ts = self.current_tipset(); - let publish = self.add_tipset(msg.clone(), &cur_ts, true)?; + let publish = self.add_tipset(msg.clone(), &cur_ts, true, untrusted)?; let msg_ser = to_vec(&msg)?; let network_name = self.chain_config.network.genesis_name(); self.add_local(msg)?; @@ -236,6 +235,16 @@ where Ok(cid) } + /// Push a signed message to the `MessagePool` from an trusted source. + pub async fn push(&self, msg: SignedMessage) -> Result { + self.push_internal(msg, false).await + } + + /// Push a signed message to the `MessagePool` from an untrusted source. + pub async fn push_untrusted(&self, msg: SignedMessage) -> Result { + self.push_internal(msg, true).await + } + fn check_message(&self, msg: &SignedMessage) -> Result<(), Error> { if to_vec(msg)?.len() > MAX_MESSAGE_SIZE { return Err(Error::MessageTooBig); @@ -255,7 +264,7 @@ where pub fn add(&self, msg: SignedMessage) -> Result<(), Error> { self.check_message(&msg)?; let ts = self.current_tipset(); - self.add_tipset(msg, &ts, false)?; + self.add_tipset(msg, &ts, false, false)?; Ok(()) } @@ -280,7 +289,13 @@ where /// Verify the `state_sequence` and balance for the sender of the message /// given then call `add_locked` to finish adding the `signed_message` /// to pending. - fn add_tipset(&self, msg: SignedMessage, cur_ts: &Tipset, local: bool) -> Result { + fn add_tipset( + &self, + msg: SignedMessage, + cur_ts: &Tipset, + local: bool, + untrusted: bool, + ) -> Result { let sequence = self.get_state_sequence(&msg.from(), cur_ts)?; if sequence > msg.message().sequence { @@ -313,7 +328,7 @@ where if balance < msg_balance { return Err(Error::NotEnoughFunds); } - self.add_helper(msg)?; + self.add_helper(msg, untrusted)?; Ok(publish) } @@ -321,7 +336,7 @@ where /// hash-map. If an entry in the hash-map does not yet exist, create a /// new `mset` that will correspond to the from message and push it to /// the pending hash-map. - fn add_helper(&self, msg: SignedMessage) -> Result<(), Error> { + fn add_helper(&self, msg: SignedMessage, untrusted: bool) -> Result<(), Error> { let from = msg.from(); let cur_ts = self.current_tipset(); add_helper( @@ -330,6 +345,7 @@ where self.pending.as_ref(), msg, self.get_state_sequence(&from, &cur_ts)?, + untrusted, ) } @@ -595,6 +611,7 @@ pub(in crate::message_pool) fn add_helper( pending: &SyncRwLock>, msg: SignedMessage, sequence: u64, + untrusted: bool, ) -> Result<(), Error> where T: Provider, @@ -607,15 +624,12 @@ where api.put_message(&ChainMessage::Unsigned(msg.message().clone()))?; let mut pending = pending.write(); - let msett = pending.get_mut(&msg.from()); - match msett { - Some(mset) => mset.add_trusted(api, msg)?, - None => { - let mut mset = MsgSet::new(sequence); - let from = msg.from(); - mset.add_trusted(api, msg)?; - pending.insert(from, mset); - } + let from = msg.from(); + let mset = pending.entry(from).or_insert_with(|| MsgSet::new(sequence)); + if untrusted { + mset.add_untrusted(api, msg)?; + } else { + mset.add_trusted(api, msg)?; } Ok(()) @@ -697,7 +711,7 @@ mod tests { }; let msg = SignedMessage::mock_bls_signed_message(message); let sequence = msg.message().sequence; - let res = add_helper(&api, &bls_sig_cache, &pending, msg, sequence); + let res = add_helper(&api, &bls_sig_cache, &pending, msg, sequence, false); assert!(res.is_ok()); } } diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 8b219bf132cb..5c911c002273 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -3412,6 +3412,28 @@ impl RpcMethod<1> for EthSendRawTransaction { } } +pub enum EthSendRawTransactionUntrusted {} +impl RpcMethod<1> for EthSendRawTransactionUntrusted { + const NAME: &'static str = "Filecoin.EthSendRawTransactionUntrusted"; + const NAME_ALIAS: Option<&'static str> = Some("eth_sendRawTransactionUntrusted"); + const PARAM_NAMES: [&'static str; 1] = ["rawTx"]; + const API_PATHS: BitFlags = ApiPaths::all_with_v2(); + const PERMISSION: Permission = Permission::Read; + + type Params = (EthBytes,); + type Ok = EthHash; + + async fn handle( + ctx: Ctx, + (raw_tx,): Self::Params, + ) -> Result { + let tx_args = parse_eth_transaction(&raw_tx.0)?; + let smsg = tx_args.get_signed_message(ctx.chain_config().eth_chain_id)?; + let cid = ctx.mpool.as_ref().push_untrusted(smsg).await?; + Ok(cid.into()) + } +} + #[derive(Clone, Debug, PartialEq)] pub struct CollectedEvent { pub(crate) entries: Vec, diff --git a/src/rpc/methods/mpool.rs b/src/rpc/methods/mpool.rs index 58a9298544dc..9e45ccdeec28 100644 --- a/src/rpc/methods/mpool.rs +++ b/src/rpc/methods/mpool.rs @@ -200,7 +200,8 @@ impl RpcMethod<1> for MpoolPushUntrusted { // Lotus implements a few extra sanity checks that we skip. We skip them // because those checks aren't used for messages received from peers and // therefore aren't safety critical. - MpoolPush::handle(ctx, (message,)).await + let cid = ctx.mpool.as_ref().push_untrusted(message).await?; + Ok(cid) } } diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index 6986ae7e05aa..2d07ec780dcc 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -159,6 +159,7 @@ macro_rules! for_each_rpc_method { $callback!($crate::rpc::eth::EthTraceReplayBlockTransactionsV2); $callback!($crate::rpc::eth::Web3ClientVersion); $callback!($crate::rpc::eth::EthSendRawTransaction); + $callback!($crate::rpc::eth::EthSendRawTransactionUntrusted); // gas vertical $callback!($crate::rpc::gas::GasEstimateFeeCap); diff --git a/src/tool/subcommands/api_cmd/test_snapshots_ignored.txt b/src/tool/subcommands/api_cmd/test_snapshots_ignored.txt index e56c80c338f1..763b25dce701 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots_ignored.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots_ignored.txt @@ -19,6 +19,7 @@ Filecoin.EthEstimateGas Filecoin.EthGetFilterChanges Filecoin.EthGetFilterLogs Filecoin.EthSendRawTransaction +Filecoin.EthSendRawTransactionUntrusted Filecoin.EthSubscribe Filecoin.EthSyncing Filecoin.EthUnsubscribe From afed53f2080a3105aa14fbd3b0c4fa1e9da8d35c Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 4 Feb 2026 15:26:38 +0530 Subject: [PATCH 2/6] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f50121804636..9df86ea78d08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ - [#6498](https://github.com/ChainSafe/forest/pull/6498): Implemented `Filecoin.EthGetBlockReceiptsLimited` for API v2. +- [#6524](https://github.com/ChainSafe/forest/pull/6524): Implemented `Filecoin.EthSendRawTransactionUntrusted` for API v2. + ### Changed - [#6471](https://github.com/ChainSafe/forest/pull/6471): Moved `forest-tool state` subcommand to `forest-dev`. From f4d7838635e3b0fdc0a89a45016f12ba39281b84 Mon Sep 17 00:00:00 2001 From: Shashank Date: Sat, 7 Feb 2026 06:50:57 +0530 Subject: [PATCH 3/6] fix open rpc test --- .../forest__rpc__tests__rpc__v0.snap | 24 +++++++++++++++++++ .../forest__rpc__tests__rpc__v1.snap | 24 +++++++++++++++++++ .../forest__rpc__tests__rpc__v2.snap | 24 +++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap index 8be4b233eee0..64774277d9d7 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap @@ -1717,6 +1717,30 @@ methods: schema: $ref: "#/components/schemas/EthHash" paramStructure: by-position + - name: Filecoin.EthSendRawTransactionUntrusted + params: + - name: rawTx + required: true + schema: + $ref: "#/components/schemas/EthBytes" + result: + name: Filecoin.EthSendRawTransactionUntrusted.Result + required: true + schema: + $ref: "#/components/schemas/EthHash" + paramStructure: by-position + - name: eth_sendRawTransactionUntrusted + params: + - name: rawTx + required: true + schema: + $ref: "#/components/schemas/EthBytes" + result: + name: eth_sendRawTransactionUntrusted.Result + required: true + schema: + $ref: "#/components/schemas/EthHash" + paramStructure: by-position - name: Filecoin.GasEstimateFeeCap description: Returns the estimated fee cap for the given parameters. params: diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap index 7ff3ee0b564f..63f4d8d516ac 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v1.snap @@ -1713,6 +1713,30 @@ methods: schema: $ref: "#/components/schemas/EthHash" paramStructure: by-position + - name: Filecoin.EthSendRawTransactionUntrusted + params: + - name: rawTx + required: true + schema: + $ref: "#/components/schemas/EthBytes" + result: + name: Filecoin.EthSendRawTransactionUntrusted.Result + required: true + schema: + $ref: "#/components/schemas/EthHash" + paramStructure: by-position + - name: eth_sendRawTransactionUntrusted + params: + - name: rawTx + required: true + schema: + $ref: "#/components/schemas/EthBytes" + result: + name: eth_sendRawTransactionUntrusted.Result + required: true + schema: + $ref: "#/components/schemas/EthHash" + paramStructure: by-position - name: Filecoin.GasEstimateFeeCap description: Returns the estimated fee cap for the given parameters. params: diff --git a/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap index 8c05524bd277..9e1b38c49ea8 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap @@ -1212,6 +1212,30 @@ methods: schema: $ref: "#/components/schemas/EthHash" paramStructure: by-position + - name: Filecoin.EthSendRawTransactionUntrusted + params: + - name: rawTx + required: true + schema: + $ref: "#/components/schemas/EthBytes" + result: + name: Filecoin.EthSendRawTransactionUntrusted.Result + required: true + schema: + $ref: "#/components/schemas/EthHash" + paramStructure: by-position + - name: eth_sendRawTransactionUntrusted + params: + - name: rawTx + required: true + schema: + $ref: "#/components/schemas/EthBytes" + result: + name: eth_sendRawTransactionUntrusted.Result + required: true + schema: + $ref: "#/components/schemas/EthHash" + paramStructure: by-position - name: Filecoin.NetListening params: [] result: From e5edbe496a160371730101e315cfc61461c8368a Mon Sep 17 00:00:00 2001 From: Shashank Date: Mon, 9 Feb 2026 22:17:54 +0530 Subject: [PATCH 4/6] fix changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3025ba8eb7a6..d20795c0b20c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ ### Added +- [#6524](https://github.com/ChainSafe/forest/pull/6524): Implemented `Filecoin.EthSendRawTransactionUntrusted` for API v2. + ### Changed ### Removed @@ -63,8 +65,6 @@ This is a non-mandatory release for all node operators. It resets F3 on calibnet - [#6498](https://github.com/ChainSafe/forest/pull/6498): Implemented `Filecoin.EthGetBlockReceiptsLimited` for API v2. -- [#6524](https://github.com/ChainSafe/forest/pull/6524): Implemented `Filecoin.EthSendRawTransactionUntrusted` for API v2. - ### Changed - [#6471](https://github.com/ChainSafe/forest/pull/6471): Moved `forest-tool state` subcommand to `forest-dev`. From 69b49f7202e464b6cc6caab942d645dc469e2b6b Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 11 Feb 2026 01:47:37 +0530 Subject: [PATCH 5/6] use TrustPolicy --- src/message_pool/msgpool/mod.rs | 4 +-- src/message_pool/msgpool/msg_pool.rs | 41 ++++++++++++++++++---------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/message_pool/msgpool/mod.rs b/src/message_pool/msgpool/mod.rs index d1219c04aad6..f61885d92369 100644 --- a/src/message_pool/msgpool/mod.rs +++ b/src/message_pool/msgpool/mod.rs @@ -28,7 +28,7 @@ use utils::{get_base_fee_lower_bound, recover_sig}; use super::errors::Error; use crate::message_pool::{ msg_chain::{Chains, create_message_chains}, - msg_pool::{MsgSet, add_helper, remove}, + msg_pool::{add_helper, MsgSet, remove, TrustPolicy}, provider::Provider, }; @@ -277,7 +277,7 @@ where for (_, hm) in rmsgs { for (_, msg) in hm { let sequence = get_state_sequence(api, &msg.from(), &cur_tipset.read().clone())?; - if let Err(e) = add_helper(api, bls_sig_cache, pending, msg, sequence, false) { + if let Err(e) = add_helper(api, bls_sig_cache, pending, msg, sequence, TrustPolicy::Trusted) { error!("Failed to read message from reorg to mpool: {}", e); } } diff --git a/src/message_pool/msgpool/msg_pool.rs b/src/message_pool/msgpool/msg_pool.rs index 0458946fa317..900eafed9d5c 100644 --- a/src/message_pool/msgpool/msg_pool.rs +++ b/src/message_pool/msgpool/msg_pool.rs @@ -58,6 +58,14 @@ pub const MAX_UNTRUSTED_ACTOR_PENDING_MESSAGES: u64 = 10; /// large messages from being added to the message pool. const MAX_MESSAGE_SIZE: usize = 64 << 10; // 64 KiB +/// Trust policy for whether a message is from a trusted or untrusted source. +/// Untrusted sources are subject to stricter limits. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TrustPolicy { + Trusted, + Untrusted, +} + /// Simple structure that contains a hash-map of messages where k: a message /// from address, v: a message which corresponds to that address. #[derive(Clone, Default, Debug)] @@ -219,11 +227,15 @@ where /// Push a signed message to the `MessagePool`. Additionally performs basic /// checks on the validity of a message. - pub async fn push_internal(&self, msg: SignedMessage, untrusted: bool) -> Result { + pub async fn push_internal( + &self, + msg: SignedMessage, + trust_policy: TrustPolicy, + ) -> Result { self.check_message(&msg)?; let cid = msg.cid(); let cur_ts = self.current_tipset(); - let publish = self.add_tipset(msg.clone(), &cur_ts, true, untrusted)?; + let publish = self.add_tipset(msg.clone(), &cur_ts, true, trust_policy)?; let msg_ser = to_vec(&msg)?; let network_name = self.chain_config.network.genesis_name(); self.add_local(msg)?; @@ -241,12 +253,12 @@ where /// Push a signed message to the `MessagePool` from an trusted source. pub async fn push(&self, msg: SignedMessage) -> Result { - self.push_internal(msg, false).await + self.push_internal(msg, TrustPolicy::Trusted).await } /// Push a signed message to the `MessagePool` from an untrusted source. pub async fn push_untrusted(&self, msg: SignedMessage) -> Result { - self.push_internal(msg, true).await + self.push_internal(msg, TrustPolicy::Untrusted).await } fn check_message(&self, msg: &SignedMessage) -> Result<(), Error> { @@ -268,7 +280,7 @@ where pub fn add(&self, msg: SignedMessage) -> Result<(), Error> { self.check_message(&msg)?; let ts = self.current_tipset(); - self.add_tipset(msg, &ts, false, false)?; + self.add_tipset(msg, &ts, false, TrustPolicy::Trusted)?; Ok(()) } @@ -298,7 +310,7 @@ where msg: SignedMessage, cur_ts: &Tipset, local: bool, - untrusted: bool, + trust_policy: TrustPolicy, ) -> Result { let sequence = self.get_state_sequence(&msg.from(), cur_ts)?; @@ -332,7 +344,7 @@ where if balance < msg_balance { return Err(Error::NotEnoughFunds); } - self.add_helper(msg, untrusted)?; + self.add_helper(msg, trust_policy)?; Ok(publish) } @@ -340,7 +352,7 @@ where /// hash-map. If an entry in the hash-map does not yet exist, create a /// new `mset` that will correspond to the from message and push it to /// the pending hash-map. - fn add_helper(&self, msg: SignedMessage, untrusted: bool) -> Result<(), Error> { + fn add_helper(&self, msg: SignedMessage, trust_policy: TrustPolicy) -> Result<(), Error> { let from = msg.from(); let cur_ts = self.current_tipset(); add_helper( @@ -349,7 +361,7 @@ where self.pending.as_ref(), msg, self.get_state_sequence(&from, &cur_ts)?, - untrusted, + trust_policy, ) } @@ -615,7 +627,7 @@ pub(in crate::message_pool) fn add_helper( pending: &SyncRwLock>, msg: SignedMessage, sequence: u64, - untrusted: bool, + trust_policy: TrustPolicy, ) -> Result<(), Error> where T: Provider, @@ -630,10 +642,9 @@ where let mut pending = pending.write(); let from = msg.from(); let mset = pending.entry(from).or_insert_with(|| MsgSet::new(sequence)); - if untrusted { - mset.add_untrusted(api, msg)?; - } else { - mset.add_trusted(api, msg)?; + match trust_policy { + TrustPolicy::Untrusted => mset.add_untrusted(api, msg)?, + TrustPolicy::Trusted => mset.add_trusted(api, msg)?, } Ok(()) @@ -715,7 +726,7 @@ mod tests { }; let msg = SignedMessage::mock_bls_signed_message(message); let sequence = msg.message().sequence; - let res = add_helper(&api, &bls_sig_cache, &pending, msg, sequence, false); + let res = add_helper(&api, &bls_sig_cache, &pending, msg, sequence, TrustPolicy::Trusted); assert!(res.is_ok()); } From b972b9a0355908fabe4fd908b5c2cb2679a2d853 Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 11 Feb 2026 01:48:27 +0530 Subject: [PATCH 6/6] fmt --- src/message_pool/msgpool/mod.rs | 11 +++++++++-- src/message_pool/msgpool/msg_pool.rs | 9 ++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/message_pool/msgpool/mod.rs b/src/message_pool/msgpool/mod.rs index f61885d92369..720d39c36154 100644 --- a/src/message_pool/msgpool/mod.rs +++ b/src/message_pool/msgpool/mod.rs @@ -28,7 +28,7 @@ use utils::{get_base_fee_lower_bound, recover_sig}; use super::errors::Error; use crate::message_pool::{ msg_chain::{Chains, create_message_chains}, - msg_pool::{add_helper, MsgSet, remove, TrustPolicy}, + msg_pool::{MsgSet, TrustPolicy, add_helper, remove}, provider::Provider, }; @@ -277,7 +277,14 @@ where for (_, hm) in rmsgs { for (_, msg) in hm { let sequence = get_state_sequence(api, &msg.from(), &cur_tipset.read().clone())?; - if let Err(e) = add_helper(api, bls_sig_cache, pending, msg, sequence, TrustPolicy::Trusted) { + if let Err(e) = add_helper( + api, + bls_sig_cache, + pending, + msg, + sequence, + TrustPolicy::Trusted, + ) { error!("Failed to read message from reorg to mpool: {}", e); } } diff --git a/src/message_pool/msgpool/msg_pool.rs b/src/message_pool/msgpool/msg_pool.rs index 900eafed9d5c..48c3c9caa69f 100644 --- a/src/message_pool/msgpool/msg_pool.rs +++ b/src/message_pool/msgpool/msg_pool.rs @@ -726,7 +726,14 @@ mod tests { }; let msg = SignedMessage::mock_bls_signed_message(message); let sequence = msg.message().sequence; - let res = add_helper(&api, &bls_sig_cache, &pending, msg, sequence, TrustPolicy::Trusted); + let res = add_helper( + &api, + &bls_sig_cache, + &pending, + msg, + sequence, + TrustPolicy::Trusted, + ); assert!(res.is_ok()); }