diff --git a/CHANGELOG.md b/CHANGELOG.md index 680303a97070..e7dbc5b8e74e 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. + - [#6513](https://github.com/ChainSafe/forest/pull/6513): Enabled `Filecoin.EthNewFilter` for API v2. ### Changed diff --git a/src/message_pool/msgpool/mod.rs b/src/message_pool/msgpool/mod.rs index d50d86e3d6e3..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::{MsgSet, add_helper, remove}, + 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) { + 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 901091d92b20..48c3c9caa69f 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)] @@ -89,7 +97,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, @@ -220,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(&self, msg: SignedMessage) -> 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)?; + 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)?; @@ -240,6 +251,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, 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, TrustPolicy::Untrusted).await + } + fn check_message(&self, msg: &SignedMessage) -> Result<(), Error> { if to_vec(msg)?.len() > MAX_MESSAGE_SIZE { return Err(Error::MessageTooBig); @@ -259,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)?; + self.add_tipset(msg, &ts, false, TrustPolicy::Trusted)?; Ok(()) } @@ -284,7 +305,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, + trust_policy: TrustPolicy, + ) -> Result { let sequence = self.get_state_sequence(&msg.from(), cur_ts)?; if sequence > msg.message().sequence { @@ -317,7 +344,7 @@ where if balance < msg_balance { return Err(Error::NotEnoughFunds); } - self.add_helper(msg)?; + self.add_helper(msg, trust_policy)?; Ok(publish) } @@ -325,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) -> 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( @@ -334,6 +361,7 @@ where self.pending.as_ref(), msg, self.get_state_sequence(&from, &cur_ts)?, + trust_policy, ) } @@ -599,6 +627,7 @@ pub(in crate::message_pool) fn add_helper( pending: &SyncRwLock>, msg: SignedMessage, sequence: u64, + trust_policy: TrustPolicy, ) -> Result<(), Error> where T: Provider, @@ -611,15 +640,11 @@ 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)); + match trust_policy { + TrustPolicy::Untrusted => mset.add_untrusted(api, msg)?, + TrustPolicy::Trusted => mset.add_trusted(api, msg)?, } Ok(()) @@ -701,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); + let res = add_helper( + &api, + &bls_sig_cache, + &pending, + msg, + sequence, + TrustPolicy::Trusted, + ); assert!(res.is_ok()); } diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 81cf86595bba..a259d0cfd26f 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -3433,6 +3433,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 4bb2fff95563..75efa377d0ef 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/rpc/snapshots/forest__rpc__tests__rpc__v0.snap b/src/rpc/snapshots/forest__rpc__tests__rpc__v0.snap index 21d077d0f360..38ab63d1db6a 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 a0edccc39513..2fd9e29c794f 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 38a798b8d842..24d7dd712c67 100644 --- a/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap +++ b/src/rpc/snapshots/forest__rpc__tests__rpc__v2.snap @@ -1236,6 +1236,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: 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