forest-filecoin 0.38.0

Rust Filecoin implementation.
// Copyright 2019-2026 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use super::*;
use crate::message::signed_message::SignedMessage;
use crate::shim::address::Protocol;
use crate::shim::crypto::{SECP_SIG_LEN, Signature, SignatureType};
use crate::shim::{address::Address, econ::TokenAmount, message::Message};
use ambassador::Delegate;
use get_size2::GetSize;
use serde::{Deserialize, Serialize};
use spire_enum::prelude::delegated_enum;
use std::sync::Arc;

/// `Enum` to encapsulate signed and unsigned messages. Useful when working with
/// both types
#[delegated_enum]
#[derive(
    Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq, GetSize, derive_more::From, Delegate,
)]
#[delegate(MessageRead)]
#[serde(untagged)]
pub enum ChainMessage {
    Unsigned(Arc<Message>),
    Signed(Arc<SignedMessage>),
}

impl From<Message> for ChainMessage {
    fn from(msg: Message) -> Self {
        Arc::new(msg).into()
    }
}

impl From<SignedMessage> for ChainMessage {
    fn from(msg: SignedMessage) -> Self {
        Arc::new(msg).into()
    }
}

impl ChainMessage {
    pub fn message(&self) -> &Message {
        match self {
            Self::Unsigned(m) => m,
            Self::Signed(sm) => sm.message(),
        }
    }

    pub fn cid(&self) -> cid::Cid {
        delegate_chain_message!(self.cid())
    }

    /// The length of this message's own encoding, signature included. Not
    /// [`MessageRead::chain_length`], which drops a BLS signature.
    pub fn encoded_len(&self) -> usize {
        match self {
            Self::Unsigned(msg) => msg.encoded_len(),
            Self::Signed(msg) => msg.signed_encoded_len(),
        }
    }

    /// Tests if a message is equivalent to another replacing message.
    /// A replacing message is a message with a different CID,
    /// any of Gas values, and different signature, but with all
    /// other parameters matching (source/destination, nonce, parameters, etc.)
    /// See <https://github.com/filecoin-project/lotus/blob/813d133c24295629ef442fc3aa60e6e6b2101226/chain/types/message.go#L138>
    pub fn equal_call(&self, other: &Self) -> bool {
        self.message().equal_call(other.message())
    }

    /// Wrap `msg` for gas estimation so its on-chain size approximates a
    /// real submission — the FVM charges per-byte gas based on the wire
    /// encoding including the signature. Mirrors Lotus's
    /// [`CallWithGas`](https://github.com/filecoin-project/lotus/blob/4a72a61c704d64228b4d91c497948442a9bfd235/chain/stmgr/call.go#L247-L267):
    /// SECP256k1 / Delegated get a 65-byte zero-filled signature; BLS and
    /// all other protocols (ID, Actor) get the unsigned message.
    pub fn for_gas_estimation(msg: Message, from_protocol: Protocol) -> Self {
        match from_protocol {
            Protocol::Secp256k1 => {
                SignedMessage::new_unchecked(msg, Signature::new_secp256k1(vec![0; SECP_SIG_LEN]))
                    .into()
            }
            // Lotus uses the same signature length as Secp256k1 for Delegated.
            Protocol::Delegated => SignedMessage::new_unchecked(
                msg,
                Signature::new(SignatureType::Delegated, vec![0; SECP_SIG_LEN]),
            )
            .into(),
            _ => msg.into(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::shim::crypto::BLS_SIG_LEN;
    use crate::utils::encoding::calc_encoded_len;
    use quickcheck_macros::quickcheck;

    /// The memo travels with the `Arc`, so the derived `PartialEq`/`Hash` must stay blind to it
    /// here too.
    #[quickcheck]
    fn computing_the_memo_is_invisible(msg: SignedMessage) -> bool {
        use std::hash::Hasher as _;
        let hash_of = |value: &ChainMessage| {
            let mut hasher = std::collections::hash_map::DefaultHasher::new();
            std::hash::Hash::hash(value, &mut hasher);
            hasher.finish()
        };
        // Two independent `Arc`s: cloning a `ChainMessage` would alias one memo and warm both.
        let cold: ChainMessage = msg.clone().into();
        let warm: ChainMessage = msg.into();
        let _ = warm.encoded_len();
        warm == cold && hash_of(&warm) == hash_of(&cold)
    }

    /// `encoded_len` must measure the bytes this value serializes to, for every variant: the
    /// `#[serde(untagged)]` encoding is the inner message's, signature included.
    #[quickcheck]
    fn encoded_len_measures_the_serialized_bytes(msg: Message, sig_bytes: Vec<u8>) -> bool {
        let signatures = [
            Signature::new_secp256k1(sig_bytes.clone()),
            Signature::new(SignatureType::Delegated, sig_bytes),
            Signature::new_bls(vec![0; BLS_SIG_LEN]),
        ];
        let unsigned: ChainMessage = msg.clone().into();
        let mut candidates = std::iter::once(unsigned).chain(
            signatures
                .into_iter()
                .map(|sig| SignedMessage::new_unchecked(msg.clone(), sig).into()),
        );
        candidates.all(|chain_msg: ChainMessage| {
            chain_msg.encoded_len() == calc_encoded_len(&chain_msg).unwrap()
        })
    }

    fn dummy_msg() -> Message {
        Message::builder()
            .from(Address::new_id(2))
            .to(Address::new_id(1))
            .build()
    }

    #[track_caller]
    fn assert_signed_with_zero_sig(
        chain_msg: ChainMessage,
        sig_type: SignatureType,
        msg: &Message,
    ) {
        let ChainMessage::Signed(signed) = chain_msg else {
            panic!("expected Signed variant");
        };
        assert_eq!(signed.signature().signature_type(), sig_type);
        assert_eq!(signed.signature().bytes(), &vec![0; SECP_SIG_LEN]);
        assert_eq!(signed.message(), msg);
    }

    #[track_caller]
    fn assert_unsigned(chain_msg: ChainMessage, msg: &Message) {
        let ChainMessage::Unsigned(m) = chain_msg else {
            panic!("expected Unsigned variant");
        };
        assert_eq!(&*m, msg);
    }

    #[test]
    fn test_for_gas_estimation_secp256k1() {
        let msg = dummy_msg();
        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), Protocol::Secp256k1);
        assert_signed_with_zero_sig(chain_msg, SignatureType::Secp256k1, &msg);
    }

    #[test]
    fn test_for_gas_estimation_delegated() {
        let msg = dummy_msg();
        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), Protocol::Delegated);
        // Lotus uses the same signature length as Secp256k1 for delegated; this
        // lock-in prevents an accidental length change from regressing parity.
        assert_signed_with_zero_sig(chain_msg, SignatureType::Delegated, &msg);
    }

    #[test]
    fn test_for_gas_estimation_bls() {
        let msg = dummy_msg();
        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), Protocol::BLS);
        // Lotus passes BLS senders to ApplyMessage as the raw unsigned message
        // (see chain/stmgr/call.go); matching that here.
        assert_unsigned(chain_msg, &msg);
    }

    #[test]
    fn test_for_gas_estimation_id() {
        let msg = dummy_msg();
        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), Protocol::ID);
        assert_unsigned(chain_msg, &msg);
    }

    #[test]
    fn test_for_gas_estimation_actor() {
        let msg = dummy_msg();
        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), Protocol::Actor);
        assert_unsigned(chain_msg, &msg);
    }
}