From f27db8d79f24606b8bebf47451c1c81436487b51 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 21 Feb 2025 21:05:44 +0800 Subject: [PATCH 01/14] feat(f3): manifest retrieval from smart contract --- docs/docs/users/reference/env_variables.md | 1 - f3-sidecar/README.md | 3 - f3-sidecar/api.go | 10 ++ f3-sidecar/ffi_gen.go | 7 +- f3-sidecar/ffi_impl.go | 4 +- f3-sidecar/main.go | 5 +- f3-sidecar/manifest.go | 28 +++++ f3-sidecar/run.go | 43 ++----- src/daemon/mod.rs | 2 - src/f3/go_ffi.rs | 1 - src/f3/mod.rs | 52 -------- src/networks/mod.rs | 34 +++--- src/rpc/methods/f3.rs | 84 +++++++++++-- .../methods/f3/contract_manifest_golden.json | 58 +++++++++ src/rpc/methods/f3/contract_return.hex | 1 + src/rpc/methods/f3/types.rs | 115 +++++++++++++++++- src/tool/subcommands/shed_cmd/f3.rs | 83 ++----------- 17 files changed, 325 insertions(+), 206 deletions(-) create mode 100644 f3-sidecar/manifest.go create mode 100644 src/rpc/methods/f3/contract_manifest_golden.json create mode 100644 src/rpc/methods/f3/contract_return.hex diff --git a/docs/docs/users/reference/env_variables.md b/docs/docs/users/reference/env_variables.md index d1d9bf157d73..6a8080178696 100644 --- a/docs/docs/users/reference/env_variables.md +++ b/docs/docs/users/reference/env_variables.md @@ -32,7 +32,6 @@ process. | `FOREST_F3_SIDECAR_RPC_ENDPOINT` | string | 127.0.0.1:23456 | `127.0.0.1:23456` | An RPC endpoint of F3 sidecar. | | `FOREST_F3_SIDECAR_FFI_ENABLED` | 1 or true | hard-coded per chain | 1 | Whether or not to start the F3 sidecar via FFI | | `FOREST_F3_CONSENSUS_ENABLED` | 1 or true | hard-coded per chain | 1 | Whether or not to apply the F3 consensus to the node | -| `FOREST_F3_MANIFEST_SERVER` | string | empty | `12D3KooWENMwUF9YxvQxar7uBWJtZkA6amvK4xWmKXfSiHUo2Qq7` | Set dynamic F3 manifest server | | `FOREST_F3_FINALITY` | integer | inherited from chain configuration | 900 | Set the chain finality epochs in F3 manifest | | `FOREST_F3_PERMANENT_PARTICIPATING_MINER_ADDRESSES` | comma delimited strings | empty | `t0100,t0101` | Set the miner addresses that participate in F3 permanently | | `FOREST_F3_INITIAL_POWER_TABLE` | string | empty | `bafyreicmaj5hhoy5mgqvamfhgexxyergw7hdeshizghodwkjg6qmpoco7i` | Set the F3 initial power table CID | diff --git a/f3-sidecar/README.md b/f3-sidecar/README.md index 99d40421919f..19739287ff16 100644 --- a/f3-sidecar/README.md +++ b/f3-sidecar/README.md @@ -64,6 +64,3 @@ environment variable `FOREST_F3_SIDECAR_FFI_BUILD_OPT_OUT=1` is set. F3 sidecar is not started by default, set `FOREST_F3_SIDECAR_FFI_ENABLED=1` to opt in. - -Set dynamic manifest server via `FOREST_F3_MANIFEST_SERVER`, e.g. -`FOREST_F3_MANIFEST_SERVER=12D3KooWENMwUF9YxvQxar7uBWJtZkA6amvK4xWmKXfSiHUo2Qq7` diff --git a/f3-sidecar/api.go b/f3-sidecar/api.go index 59c22743965c..f34b61adf4d5 100644 --- a/f3-sidecar/api.go +++ b/f3-sidecar/api.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "github.com/filecoin-project/go-f3" "github.com/filecoin-project/go-f3/certs" @@ -62,3 +63,12 @@ func (h *F3ServerHandler) F3GetProgress(_ context.Context) gpbft.Instant { func (h *F3ServerHandler) F3GetManifest(_ context.Context) *manifest.Manifest { return h.f3.Manifest() } + +func (h *F3ServerHandler) F3SetManifest(_ context.Context, m *manifest.Manifest) error { + if ManifestProvider != nil { + ManifestProvider.Update(m) + return nil + } else { + return errors.New("forest manifest provider is not properly initialized") + } +} diff --git a/f3-sidecar/ffi_gen.go b/f3-sidecar/ffi_gen.go index bbe3bae9aff6..edc7f5941af3 100644 --- a/f3-sidecar/ffi_gen.go +++ b/f3-sidecar/ffi_gen.go @@ -29,11 +29,11 @@ import ( var GoF3NodeImpl GoF3Node type GoF3Node interface { - run(rpc_endpoint *string, jwt *string, f3_rpc_endpoint *string, initial_power_table *string, bootstrap_epoch *int64, finality *int64, f3_root *string, manifest_server *string) bool + run(rpc_endpoint *string, jwt *string, f3_rpc_endpoint *string, initial_power_table *string, bootstrap_epoch *int64, finality *int64, f3_root *string) bool } //export CGoF3Node_run -func CGoF3Node_run(rpc_endpoint C.StringRef, jwt C.StringRef, f3_rpc_endpoint C.StringRef, initial_power_table C.StringRef, bootstrap_epoch C.int64_t, finality C.int64_t, f3_root C.StringRef, manifest_server C.StringRef, slot *C.void, cb *C.void) { +func CGoF3Node_run(rpc_endpoint C.StringRef, jwt C.StringRef, f3_rpc_endpoint C.StringRef, initial_power_table C.StringRef, bootstrap_epoch C.int64_t, finality C.int64_t, f3_root C.StringRef, slot *C.void, cb *C.void) { _new_rpc_endpoint := newString(rpc_endpoint) _new_jwt := newString(jwt) _new_f3_rpc_endpoint := newString(f3_rpc_endpoint) @@ -41,8 +41,7 @@ func CGoF3Node_run(rpc_endpoint C.StringRef, jwt C.StringRef, f3_rpc_endpoint C. _new_bootstrap_epoch := newC_int64_t(bootstrap_epoch) _new_finality := newC_int64_t(finality) _new_f3_root := newString(f3_root) - _new_manifest_server := newString(manifest_server) - resp := GoF3NodeImpl.run(&_new_rpc_endpoint, &_new_jwt, &_new_f3_rpc_endpoint, &_new_initial_power_table, &_new_bootstrap_epoch, &_new_finality, &_new_f3_root, &_new_manifest_server) + resp := GoF3NodeImpl.run(&_new_rpc_endpoint, &_new_jwt, &_new_f3_rpc_endpoint, &_new_initial_power_table, &_new_bootstrap_epoch, &_new_finality, &_new_f3_root) resp_ref, buffer := cvt_ref(cntC_bool, refC_bool)(&resp) asmcall.CallFuncG0P2(unsafe.Pointer(cb), unsafe.Pointer(&resp_ref), unsafe.Pointer(slot)) runtime.KeepAlive(resp_ref) diff --git a/f3-sidecar/ffi_impl.go b/f3-sidecar/ffi_impl.go index f62638e468eb..91c1c27ee2f8 100644 --- a/f3-sidecar/ffi_impl.go +++ b/f3-sidecar/ffi_impl.go @@ -28,12 +28,12 @@ type f3Impl struct { ctx context.Context } -func (f3 *f3Impl) run(rpc_endpoint *string, jwt *string, f3_rpc_endpoint *string, initial_power_table *string, bootstrap_epoch *int64, finality *int64, db *string, manifest_server *string) bool { +func (f3 *f3Impl) run(rpc_endpoint *string, jwt *string, f3_rpc_endpoint *string, initial_power_table *string, bootstrap_epoch *int64, finality *int64, db *string) bool { var err error = nil const MAX_RETRY int = 5 nRetry := 0 for nRetry <= MAX_RETRY { - err = run(f3.ctx, *rpc_endpoint, *jwt, *f3_rpc_endpoint, *initial_power_table, *bootstrap_epoch, *finality, *db, *manifest_server) + err = run(f3.ctx, *rpc_endpoint, *jwt, *f3_rpc_endpoint, *initial_power_table, *bootstrap_epoch, *finality, *db) if err != nil { nRetry += 1 logger.Errorf("Unexpected F3 failure, retrying(%d) in 10s... error=%s", nRetry, err) diff --git a/f3-sidecar/main.go b/f3-sidecar/main.go index 9cfeb590ab36..3fa559e69221 100644 --- a/f3-sidecar/main.go +++ b/f3-sidecar/main.go @@ -41,13 +41,12 @@ func main() { flag.Int64Var(&finality, "finality", 900, "chain finality epochs") var root string flag.StringVar(&root, "root", "f3-data", "path to the f3 data directory") - var manifestServer string - flag.StringVar(&manifestServer, "manifest-server", "", "the peer id of the dynamic manifest server") + flag.Parse() ctx := context.Background() - err := run(ctx, rpcEndpoint, jwt, f3RpcEndpoint, initialPowerTable, bootstrapEpoch, finality, root, manifestServer) + err := run(ctx, rpcEndpoint, jwt, f3RpcEndpoint, initialPowerTable, bootstrapEpoch, finality, root) if err != nil { panic(err) } diff --git a/f3-sidecar/manifest.go b/f3-sidecar/manifest.go new file mode 100644 index 000000000000..a0689c2ccd95 --- /dev/null +++ b/f3-sidecar/manifest.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + + "github.com/filecoin-project/go-f3/manifest" +) + +type ForestManifestProvider struct { + ch chan *manifest.Manifest +} + +func NewForestManifestProvider(initialValue *manifest.Manifest) (*ForestManifestProvider, error) { + if err := initialValue.Validate(); err != nil { + return nil, err + } + p := ForestManifestProvider{ch: make(chan *manifest.Manifest)} + p.Update(initialValue) + return &p, nil +} + +func (p *ForestManifestProvider) Update(m *manifest.Manifest) { + p.ch <- m +} + +func (p *ForestManifestProvider) Start(context.Context) error { return nil } +func (p *ForestManifestProvider) Stop(context.Context) error { return nil } +func (p *ForestManifestProvider) ManifestUpdates() <-chan *manifest.Manifest { return p.ch } diff --git a/f3-sidecar/run.go b/f3-sidecar/run.go index f55322d950b8..59dc972e0d67 100644 --- a/f3-sidecar/run.go +++ b/f3-sidecar/run.go @@ -15,13 +15,12 @@ import ( "github.com/filecoin-project/go-f3/manifest" "github.com/filecoin-project/go-jsonrpc" "github.com/ipfs/go-cid" - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" leveldb "github.com/ipfs/go-ds-leveldb" - "github.com/libp2p/go-libp2p/core/peer" ) -func run(ctx context.Context, rpcEndpoint string, jwt string, f3RpcEndpoint string, initialPowerTable string, bootstrapEpoch int64, finality int64, f3Root string, manifestServer string) error { +var ManifestProvider *ForestManifestProvider + +func run(ctx context.Context, rpcEndpoint string, jwt string, f3RpcEndpoint string, initialPowerTable string, bootstrapEpoch int64, finality int64, f3Root string) error { api := FilecoinApi{} isJwtProvided := len(jwt) > 0 closer, err := jsonrpc.NewClient(context.Background(), rpcEndpoint, "Filecoin", &api, nil) @@ -95,40 +94,12 @@ func run(ctx context.Context, rpcEndpoint string, jwt string, f3RpcEndpoint stri } m.CommitteeLookback = manifest.DefaultCommitteeLookback - var manifestProvider manifest.ManifestProvider - switch manifestServerID, err := peer.Decode(manifestServer); { - case err != nil: - logger.Info("Using static manifest provider") - if manifestProvider, err = manifest.NewStaticManifestProvider(m); err != nil { - return err - } - default: - logger.Infof("Using dynamic manifest provider at %s", manifestServerID) - manifestDS := namespace.Wrap(ds, datastore.NewKey("/f3-dynamic-manifest")) - primaryNetworkName := m.NetworkName - filter := func(m *manifest.Manifest) error { - if m.EC.Finalize { - return fmt.Errorf("refusing dynamic manifest that finalizes tipsets") - } - if m.NetworkName == primaryNetworkName { - return fmt.Errorf( - "refusing dynamic manifest with network name %q that clashes with initial manifest", - primaryNetworkName, - ) - } - return nil - } - if manifestProvider, err = manifest.NewDynamicManifestProvider( - p2p.PubSub, - manifestServerID, - manifest.DynamicManifestProviderWithInitialManifest(m), - manifest.DynamicManifestProviderWithDatastore(manifestDS), - manifest.DynamicManifestProviderWithFilter(filter)); err != nil { - return err - } + ManifestProvider, err = NewForestManifestProvider(m) + if err != nil { + return err } - f3Module, err := f3.New(ctx, manifestProvider, ds, + f3Module, err := f3.New(ctx, ManifestProvider, ds, p2p.Host, p2p.PubSub, verif, &ec, f3Root) if err != nil { return err diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index b1a97e2dd0bd..2f76f5d9e797 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -596,7 +596,6 @@ fn maybe_start_f3_service( chain_finality, bootstrap_epoch, initial_power_table, - manifest_server, } = crate::f3::get_f3_sidecar_params(&chain_config); move || { crate::f3::run_f3_sidecar_if_enabled( @@ -609,7 +608,6 @@ fn maybe_start_f3_service( chain_finality, std::env::var("FOREST_F3_ROOT") .unwrap_or(default_f3_root.display().to_string()), - manifest_server.map(|i| i.to_string()).unwrap_or_default(), ); Ok(()) } diff --git a/src/f3/go_ffi.rs b/src/f3/go_ffi.rs index fe3a8b125b36..7048a3838f4f 100644 --- a/src/f3/go_ffi.rs +++ b/src/f3/go_ffi.rs @@ -17,6 +17,5 @@ pub trait GoF3Node { bootstrap_epoch: i64, finality: i64, f3_root: String, - manifest_server: String, ) -> bool; } diff --git a/src/f3/mod.rs b/src/f3/mod.rs index 68ca7f446e54..a83cc5d903d4 100644 --- a/src/f3/mod.rs +++ b/src/f3/mod.rs @@ -9,7 +9,6 @@ mod go_ffi; use go_ffi::*; use cid::Cid; -use libp2p::PeerId; use crate::{networks::ChainConfig, utils::misc::env::is_env_set_and_truthy}; @@ -18,7 +17,6 @@ pub struct F3Options { pub chain_finality: i64, pub bootstrap_epoch: i64, pub initial_power_table: Cid, - pub manifest_server: Option, } pub fn get_f3_sidecar_params(chain_config: &ChainConfig) -> F3Options { @@ -55,34 +53,11 @@ pub fn get_f3_sidecar_params(chain_config: &ChainConfig) -> F3Options { tracing::info!("Using F3 bootstrap epoch {i} set by FOREST_F3_BOOTSTRAP_EPOCH") }) .unwrap_or(chain_config.f3_bootstrap_epoch); - let manifest_server = match std::env::var("FOREST_F3_MANIFEST_SERVER") { - Ok(v) => { - if v.is_empty() { - None - } else { - match v.parse() { - Ok(i) => Some(i), - _ => { - tracing::warn!( - "Invalid libp2p peer id {v} set by FOREST_F3_MANIFEST_SERVER" - ); - None - } - } - .inspect(|i| { - tracing::info!("Using F3 manifest server {i} set by FOREST_F3_MANIFEST_SERVER") - }) - .or(chain_config.f3_manifest_server) - } - } - _ => chain_config.f3_manifest_server, - }; F3Options { chain_finality, bootstrap_epoch, initial_power_table, - manifest_server, } } @@ -95,7 +70,6 @@ pub fn run_f3_sidecar_if_enabled( _bootstrap_epoch: i64, _finality: i64, _f3_root: String, - _manifest_server: String, ) { if is_sidecar_ffi_enabled(chain_config) { #[cfg(all(f3sidecar, not(feature = "no-f3-sidecar")))] @@ -109,7 +83,6 @@ pub fn run_f3_sidecar_if_enabled( _bootstrap_epoch, _finality, _f3_root, - _manifest_server, ); } } @@ -147,7 +120,6 @@ mod tests { chain_finality: chain_config.policy.chain_finality, bootstrap_epoch: chain_config.f3_bootstrap_epoch, initial_power_table: chain_config.f3_initial_power_table, - manifest_server: chain_config.f3_manifest_server, } ); @@ -158,29 +130,6 @@ mod tests { "bafyreicmaj5hhoy5mgqvamfhgexxyergw7hdeshizghodwkjg6qmpoco7i", ); std::env::set_var("FOREST_F3_BOOTSTRAP_EPOCH", "100"); - // mainnet server - std::env::set_var( - "FOREST_F3_MANIFEST_SERVER", - "12D3KooWENMwUF9YxvQxar7uBWJtZkA6amvK4xWmKXfSiHUo2Qq7", - ); - assert_eq!( - get_f3_sidecar_params(&chain_config), - F3Options { - chain_finality: 100, - bootstrap_epoch: 100, - initial_power_table: "bafyreicmaj5hhoy5mgqvamfhgexxyergw7hdeshizghodwkjg6qmpoco7i" - .parse() - .unwrap(), - manifest_server: Some( - "12D3KooWENMwUF9YxvQxar7uBWJtZkA6amvK4xWmKXfSiHUo2Qq7" - .parse() - .unwrap() - ), - } - ); - - // Unset FOREST_F3_MANIFEST_SERVER - std::env::set_var("FOREST_F3_MANIFEST_SERVER", ""); assert_eq!( get_f3_sidecar_params(&chain_config), F3Options { @@ -189,7 +138,6 @@ mod tests { initial_power_table: "bafyreicmaj5hhoy5mgqvamfhgexxyergw7hdeshizghodwkjg6qmpoco7i" .parse() .unwrap(), - manifest_server: None, } ); } diff --git a/src/networks/mod.rs b/src/networks/mod.rs index e0e518e20496..75875d38dba5 100644 --- a/src/networks/mod.rs +++ b/src/networks/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0, MIT use std::str::FromStr; +use std::time::Duration; use ahash::HashMap; use cid::Cid; @@ -16,6 +17,7 @@ use tracing::warn; use crate::beacon::{BeaconPoint, BeaconSchedule, DrandBeacon, DrandConfig}; use crate::db::SettingsStore; use crate::eth::EthChainId; +use crate::rpc::eth::types::EthAddress; use crate::shim::clock::{ChainEpoch, EPOCHS_IN_DAY, EPOCH_DURATION_SECONDS}; use crate::shim::sector::{RegisteredPoStProofV3, RegisteredSealProofV3}; use crate::shim::version::NetworkVersion; @@ -43,6 +45,7 @@ pub const NEWEST_NETWORK_VERSION: NetworkVersion = NetworkVersion::V17; const ENV_FOREST_BLOCK_DELAY_SECS: &str = "FOREST_BLOCK_DELAY_SECS"; const ENV_FOREST_PROPAGATION_DELAY_SECS: &str = "FOREST_PROPAGATION_DELAY_SECS"; const ENV_PLEDGE_RULE_RAMP: &str = "FOREST_PLEDGE_RULE_RAMP"; +const DEFAULT_F3_CONTRACT_POLL_INTERVAL: Duration = Duration::from_secs(15 * 60); /// Forest builtin `filecoin` network chains. In general only `mainnet` and its /// chain information should be considered stable. @@ -235,9 +238,9 @@ pub struct ChainConfig { pub f3_consensus: bool, pub f3_bootstrap_epoch: i64, pub f3_initial_power_table: Cid, - // This will likely be deprecated once F3 is fully bootstrapped to avoid single point network dependencies. - #[cfg_attr(test, arbitrary(gen(|_| Some(libp2p::PeerId::random()))))] - pub f3_manifest_server: Option, + #[cfg_attr(test, arbitrary(gen(|_| Some(EthAddress::from_str("0x476AC9256b9921C9C6a0fC237B7fE05fe9874F50").unwrap()))))] + pub f3_contract_address: Option, + pub f3_contract_poll_interval: Duration, } impl ChainConfig { @@ -263,11 +266,11 @@ impl ChainConfig { f3_consensus: false, f3_bootstrap_epoch: -1, f3_initial_power_table: Default::default(), - f3_manifest_server: Some( - "12D3KooWENMwUF9YxvQxar7uBWJtZkA6amvK4xWmKXfSiHUo2Qq7" - .parse() - .expect("Invalid PeerId"), + f3_contract_address: Some( + EthAddress::from_str("0x476AC9256b9921C9C6a0fC237B7fE05fe9874F50") + .expect("invalid f3 contract eth address"), ), + f3_contract_poll_interval: DEFAULT_F3_CONTRACT_POLL_INTERVAL, } } @@ -299,11 +302,8 @@ impl ChainConfig { "bafy2bzaceab236vmmb3n4q4tkvua2n4dphcbzzxerxuey3mot4g3cov5j3r2c" .parse() .expect("invalid f3_initial_power_table"), - f3_manifest_server: Some( - "12D3KooWS9vD9uwm8u2uPyJV32QBAhKAmPYwmziAgr3Xzk2FU1Mr" - .parse() - .expect("Invalid PeerId"), - ), + f3_contract_address: Default::default(), + f3_contract_poll_interval: DEFAULT_F3_CONTRACT_POLL_INTERVAL, } } @@ -326,7 +326,8 @@ impl ChainConfig { f3_consensus: false, f3_bootstrap_epoch: -1, f3_initial_power_table: Default::default(), - f3_manifest_server: None, + f3_contract_address: Default::default(), + f3_contract_poll_interval: DEFAULT_F3_CONTRACT_POLL_INTERVAL, } } @@ -355,11 +356,8 @@ impl ChainConfig { f3_consensus: true, f3_bootstrap_epoch: -1, f3_initial_power_table: Default::default(), - f3_manifest_server: Some( - "12D3KooWJr9jy4ngtJNR7JC1xgLFra3DjEtyxskRYWvBK9TC3Yn6" - .parse() - .expect("Invalid PeerId"), - ), + f3_contract_address: Default::default(), + f3_contract_poll_interval: DEFAULT_F3_CONTRACT_POLL_INTERVAL, } } diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index 55c32f085ebb..225b5264f464 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -12,35 +12,43 @@ mod util; pub use self::types::{F3Instant, F3LeaseManager, F3Manifest, F3PowerEntry, FinalityCertificate}; use self::{types::*, util::*}; -use super::wallet::WalletSign; -use crate::shim::actors::{ - convert::{ - from_policy_v13_to_v10, from_policy_v13_to_v11, from_policy_v13_to_v12, - from_policy_v13_to_v14, from_policy_v13_to_v15, from_policy_v13_to_v16, - from_policy_v13_to_v9, - }, - miner, power, -}; +use super::{eth::types::EthAddress, wallet::WalletSign}; use crate::{ blocks::Tipset, chain::index::ResolveNullTipset, chain_sync::TipsetValidator, libp2p::{NetRPCMethods, NetworkMessage}, lotus_json::HasLotusJson as _, - rpc::{types::ApiTipsetKey, ApiPaths, Ctx, Permission, RpcMethod, ServerError}, + rpc::{ + eth::types::EthBytes, state::StateCall, types::ApiTipsetKey, ApiPaths, Ctx, Permission, + RpcMethod, ServerError, + }, shim::{ address::{Address, Protocol}, clock::ChainEpoch, crypto::Signature, + message::Message, }, utils::misc::env::is_env_set_and_truthy, }; +use crate::{ + rpc::eth::types::EthCallMessage, + shim::actors::{ + convert::{ + from_policy_v13_to_v10, from_policy_v13_to_v11, from_policy_v13_to_v12, + from_policy_v13_to_v14, from_policy_v13_to_v15, from_policy_v13_to_v16, + from_policy_v13_to_v9, + }, + miner, power, + }, +}; use ahash::{HashMap, HashSet}; use anyhow::Context as _; use fvm_ipld_blockstore::Blockstore; use jsonrpsee::core::{client::ClientT as _, params::ArrayParams}; use libp2p::PeerId; use num::Signed as _; +use once_cell::sync::Lazy; use once_cell::sync::OnceCell; use parking_lot::RwLock; use std::{borrow::Cow, fmt::Display, num::NonZeroU64, str::FromStr as _, sync::Arc}; @@ -567,6 +575,54 @@ impl RpcMethod<2> for SignMessage { } } +pub enum GetManifestFromContract {} + +impl GetManifestFromContract { + pub fn create_eth_call_message(contract: EthAddress) -> EthCallMessage { + // method ID of activationInformation() + static METHOD_ID: Lazy = + Lazy::new(|| EthBytes::from_str("0x2587660d").expect("Infallible")); + EthCallMessage { + to: Some(contract), + data: METHOD_ID.clone(), + ..Default::default() + } + } + + async fn get_manifest_from_contract( + ctx: Ctx, + contract: EthAddress, + ) -> anyhow::Result { + let eth_call_message = Self::create_eth_call_message(contract); + let filecoin_message = Message::try_from(eth_call_message)?; + let api_invoc_result = StateCall::handle(ctx, (filecoin_message, None.into())).await?; + let Some(message_receipt) = api_invoc_result.msg_rct else { + anyhow::bail!("No message receipt"); + }; + message_receipt.try_into() + } +} + +impl RpcMethod<0> for GetManifestFromContract { + const NAME: &'static str = "F3.GetManifestFromContract"; + const PARAM_NAMES: [&'static str; 0] = []; + const API_PATHS: ApiPaths = ApiPaths::V1; + const PERMISSION: Permission = Permission::Read; + + type Params = (); + type Ok = F3Manifest; + + async fn handle( + ctx: Ctx, + _: Self::Params, + ) -> Result { + let Some(f3_contract_address) = ctx.chain_config().f3_contract_address.clone() else { + return Err(anyhow::anyhow!("F3 contract address is not set").into()); + }; + Ok(Self::get_manifest_from_contract(ctx, f3_contract_address).await?) + } +} + /// returns a finality certificate at given instance number pub enum F3GetCertificate {} impl RpcMethod<1> for F3GetCertificate { @@ -817,6 +873,14 @@ impl RpcMethod<1> for F3Participate { } } +pub async fn set_f3_manifest(manifest: &F3Manifest) -> anyhow::Result<()> { + let client = get_rpc_http_client()?; + let mut params = ArrayParams::new(); + params.insert(manifest)?; + let _: serde_json::Value = client.request("Filecoin.F3SetManifest", params).await?; + Ok(()) +} + pub fn get_f3_rpc_endpoint() -> Cow<'static, str> { if let Ok(host) = std::env::var("FOREST_F3_SIDECAR_RPC_ENDPOINT") { Cow::Owned(host) diff --git a/src/rpc/methods/f3/contract_manifest_golden.json b/src/rpc/methods/f3/contract_manifest_golden.json new file mode 100644 index 000000000000..bc1c1085d6b0 --- /dev/null +++ b/src/rpc/methods/f3/contract_manifest_golden.json @@ -0,0 +1,58 @@ +{ + "Pause": false, + "ProtocolVersion": 5, + "InitialInstance": 0, + "BootstrapEpoch": 5000000, + "NetworkName": "filecoin", + "ExplicitPower": null, + "IgnoreECPower": false, + "InitialPowerTable": null, + "CommitteeLookback": 10, + "CatchUpAlignment": 15000000000, + "Gpbft": { + "Delta": 6000000000, + "DeltaBackOffExponent": 2.0, + "QualityDeltaMultiplier": 1.0, + "MaxLookaheadRounds": 5, + "ChainProposedLength": 100, + "RebroadcastBackoffBase": 6000000000, + "RebroadcastBackoffExponent": 1.3, + "RebroadcastBackoffSpread": 0.1, + "RebroadcastBackoffMax": 60000000000 + }, + "EC": { + "Period": 30000000000, + "Finality": 900, + "DelayMultiplier": 2.0, + "BaseDecisionBackoffTable": [ + 1.3, + 1.69, + 2.2, + 2.86, + 3.71, + 4.83, + 6.27, + 7.5 + ], + "HeadLookback": 0, + "Finalize": true + }, + "CertificateExchange": { + "ClientRequestTimeout": 10000000000, + "ServerRequestTimeout": 60000000000, + "MinimumPollInterval": 30000000000, + "MaximumPollInterval": 120000000000 + }, + "PubSub": { + "CompressionEnabled": false + }, + "ChainExchange": { + "SubscriptionBufferSize": 32, + "MaxChainLength": 100, + "MaxInstanceLookahead": 10, + "MaxDiscoveredChainsPerInstance": 1000, + "MaxWantedChainsPerInstance": 1000, + "RebroadcastInterval": 2000000000, + "MaxTimestampAge": 8000000000 + } +} \ No newline at end of file diff --git a/src/rpc/methods/f3/contract_return.hex b/src/rpc/methods/f3/contract_return.hex new file mode 100644 index 000000000000..57e37dde3f72 --- /dev/null +++ b/src/rpc/methods/f3/contract_return.hex @@ -0,0 +1 @@ +00000000000000000000000000000000000000000000000000000000004C4B400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000023D8554C172DA3014BCF3150CE78E079B06486FC1D0363321A590B6874E0FB2FC8C35C8922BC90D6926FFDE27D9C27620539F9EB52B7977DF939F07C3E168432A0DA30FC38C700DEFDC8A924652C9BF83D24C0AC4AEDCFAAD6086117E2BB42182DA3D63B7BE90D268A348B92A25CD2D7DEC1E07DE837994EA704F0ABB6194310E54323172E0EA58724699D9C84750088B8AF3FA537B2115AC620FB4E21A110E78200987EEB65816053306E04ECA4342E801C1B0D6111343F36FE50D677B5180301669747AA99FCA24B3EBCFF882AF4BE086E0EBB4C7F2C0024FFF9265E8408AFAB8A881BF568433F3E458EB8A1B861E9D89B021ACC9D1EA233990742B2B916A1FB1D5991326B003A5D490DE81D89BDC99F01FDF42A2244929D1C64A9059B620AE7F6732CF991DB161307993B62B152AB3ED0DC2374968A2F7D131125FEAA6C66D861B504CDAA326E3D7F23E32E17242F0BA1B2D79EA85E653B52E9740999DC846826FFF4FC718B69E6C39BDF67514446D399FFA7A12CC425FBF0FE6A7ADD3209AF97A165CB9EA5723E233E6D219ADBE95BF568B51159C9288411996314A0CAC8E3427620F6D34311A14660BBF2BD0E68115202B5377FA55503B507F409D11A767C43513ACA88A8DE478470DEE22FC62F2D8BA0BBC30BAD0CB4D95ECAAA4235A16381CDA3661256CFCA9BF9CAD693BC0E776F1184D152B8DED5F9565A076756293A895E5F65E187A84FC5FE774734E57BBC6974C53893941EA0ED138799D1F55D873FF83A0EDFFF13A23DF4929BA10A6ED099E509437CEF0BC17E3E065F00F000000 \ No newline at end of file diff --git a/src/rpc/methods/f3/types.rs b/src/rpc/methods/f3/types.rs index c323197e6ed3..56eed60178f6 100644 --- a/src/rpc/methods/f3/types.rs +++ b/src/rpc/methods/f3/types.rs @@ -2,14 +2,17 @@ // SPDX-License-Identifier: Apache-2.0, MIT use super::*; -use crate::utils::multihash::prelude::*; use crate::{ blocks::{Tipset, TipsetKey}, lotus_json::{base64_standard, lotus_json_with_self, HasLotusJson, LotusJson}, networks::NetworkChain, + shim::executor::Receipt, + utils::multihash::prelude::*, }; +use byteorder::ByteOrder as _; use cid::Cid; use fil_actors_shared::fvm_ipld_bitfield::BitField; +use flate2::read::DeflateDecoder; use fvm_ipld_encoding::tuple::{Deserialize_tuple, Serialize_tuple}; use fvm_shared4::ActorID; use itertools::Itertools as _; @@ -19,6 +22,7 @@ use once_cell::sync::Lazy; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; +use std::io::Read as _; use std::{cmp::Ordering, time::Duration}; const MAX_LEASE_INSTANCES: u64 = 5; @@ -293,6 +297,101 @@ pub struct F3Manifest { } lotus_json_with_self!(F3Manifest); +impl F3Manifest { + pub fn get_eth_return_from_message_receipt(receipt: &Receipt) -> anyhow::Result> { + anyhow::ensure!( + receipt.exit_code().is_success(), + "unsuccessful exit code {}", + receipt.exit_code() + ); + let return_data = receipt.return_data(); + let eth_return = + fvm_ipld_encoding::from_slice::(&return_data)?.0; + Ok(eth_return) + } + + #[allow(clippy::indexing_slicing)] + pub fn parse_contract_return(eth_return: &[u8]) -> anyhow::Result { + const MAX_PAYLOAD_LEN: usize = 4 << 10; + const SLOT_SIZE: usize = 32; + const SLOT_COUNT: usize = 3; + + // 3*32 because there should be 3 slots minimum + anyhow::ensure!( + eth_return.len() >= SLOT_COUNT * SLOT_SIZE, + "no activation information" + ); + let (slot_activation_epoch, slot_offset, slot_payload_len, payload) = ( + ð_return[..SLOT_SIZE], + ð_return[SLOT_SIZE..SLOT_SIZE * 2], + ð_return[SLOT_SIZE * 2..SLOT_SIZE * 3], + ð_return[SLOT_SIZE * 3..], + ); + // parse activation epoch from slot 1 + let activation_epoch = byteorder::BigEndian::read_u64( + slot_activation_epoch.last_chunk::<8>().expect("infallible"), + ); + // slot 2 is the offset to variable length bytes + // it is always the same 0x00000...0040 + for (i, &v) in slot_offset[..SLOT_SIZE - 1].iter().enumerate() { + anyhow::ensure!( + v == 0, + "wrong value for offset (padding): slot[{i}] = 0x{v:x} != 0x00" + ); + } + anyhow::ensure!( + slot_offset[SLOT_SIZE - 1] == 0x40, + "wrong value for offest : slot[31] = 0x{:x} != 0x40", + slot_offset[SLOT_SIZE - 1] + ); + // parse payload length from slot 3 + let payload_len = + byteorder::BigEndian::read_u64(slot_payload_len.last_chunk::<8>().expect("infallible")) + as usize; + anyhow::ensure!( + payload_len <= MAX_PAYLOAD_LEN, + "too long declared payload: {payload_len} > {MAX_PAYLOAD_LEN}" + ); + anyhow::ensure!( + payload_len <= payload.len(), + "not enough remaining bytes: {payload_len} > {}", + payload.len() + ); + anyhow::ensure!( + activation_epoch < u64::MAX && payload_len > 0, + "no active activation" + ); + let compressed_manifest_bytes = &payload[..payload_len]; + let mut deflater = DeflateDecoder::new(compressed_manifest_bytes); + let mut manifest_bytes = vec![]; + deflater.read_to_end(&mut manifest_bytes)?; + let manifest: F3Manifest = serde_json::from_slice(&manifest_bytes)?; + anyhow::ensure!( + manifest.bootstrap_epoch >= 0 && manifest.bootstrap_epoch as u64 == activation_epoch, + "bootstrap epoch does not match: {} != {activation_epoch}", + manifest.bootstrap_epoch + ); + Ok(manifest) + } +} + +impl TryFrom<&Receipt> for F3Manifest { + type Error = anyhow::Error; + + fn try_from(receipt: &Receipt) -> Result { + let eth_return = Self::get_eth_return_from_message_receipt(receipt)?; + Self::parse_contract_return(ð_return) + } +} + +impl TryFrom for F3Manifest { + type Error = anyhow::Error; + + fn try_from(receipt: Receipt) -> Result { + Self::try_from(&receipt) + } +} + #[derive(PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "PascalCase")] pub struct SupplementalData { @@ -834,4 +933,18 @@ mod tests { let serialized = serde_json::to_value(cert.clone()).unwrap(); assert_eq!(lotus_json, serialized); } + + #[test] + fn test_f3_manifest_parse_contract_return() { + let eth_return_hex = include_str!("contract_return.hex").trim(); + let eth_return = hex::decode(eth_return_hex).unwrap(); + let manifest = F3Manifest::parse_contract_return(ð_return).unwrap(); + assert_eq!( + serde_json::to_value(&manifest).unwrap(), + serde_json::from_str::(include_str!( + "contract_manifest_golden.json" + )) + .unwrap(), + ); + } } diff --git a/src/tool/subcommands/shed_cmd/f3.rs b/src/tool/subcommands/shed_cmd/f3.rs index cfee504c7390..3c020b36d3e6 100644 --- a/src/tool/subcommands/shed_cmd/f3.rs +++ b/src/tool/subcommands/shed_cmd/f3.rs @@ -1,21 +1,14 @@ // Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use crate::{ - rpc::{ - self, - eth::types::{EthAddress, EthBytes, EthCallMessage}, - f3::F3Manifest, - state::StateCall, - RpcMethodExt, - }, - shim::message::Message, +use crate::rpc::{ + self, + eth::types::EthAddress, + f3::{F3Manifest, GetManifestFromContract}, + state::StateCall, + RpcMethodExt, }; use clap::Subcommand; -use flate2::read::DeflateDecoder; -use std::{io::Read, str::FromStr as _}; - -const MAX_PAYLOAD_LEN: usize = 4 << 10; #[derive(Debug, Subcommand)] pub enum F3Commands { @@ -33,77 +26,21 @@ pub enum F3Commands { } impl F3Commands { - #[allow(clippy::indexing_slicing)] pub async fn run(self, client: rpc::Client) -> anyhow::Result<()> { match self { Self::CheckActivation { contract: _ } => { unimplemented!("Will be done in a subsequent PR"); } Self::CheckActivationRaw { contract } => { - let eth_call_message = EthCallMessage { - to: Some(contract), - data: EthBytes::from_str("0x2587660d")?, // method ID of activationInformation() - ..Default::default() - }; - let filecoin_message = Message::try_from(eth_call_message)?; + let eth_call_message = GetManifestFromContract::create_eth_call_message(contract); let api_invoc_result = - StateCall::call(&client, (filecoin_message, None.into())).await?; + StateCall::call(&client, (eth_call_message.try_into()?, None.into())).await?; let Some(message_receipt) = api_invoc_result.msg_rct else { anyhow::bail!("No message receipt"); }; - anyhow::ensure!( - message_receipt.exit_code().is_success(), - "unsuccessful exit code {}", - message_receipt.exit_code() - ); - let return_data = message_receipt.return_data(); - let eth_return = - fvm_ipld_encoding::from_slice::(&return_data)?.0; + let eth_return = F3Manifest::get_eth_return_from_message_receipt(&message_receipt)?; println!("Raw data: {}", hex::encode(eth_return.as_slice())); - // 3*32 because there should be 3 slots minimum - anyhow::ensure!(eth_return.len() >= 3 * 32, "no activation information"); - let mut activation_epoch_bytes: [u8; 8] = [0; 8]; - activation_epoch_bytes.copy_from_slice(ð_return[24..32]); - let activation_epoch = u64::from_be_bytes(activation_epoch_bytes); - for (i, &v) in eth_return[32..63].iter().enumerate() { - anyhow::ensure!( - v == 0, - "wrong value for offset (padding): slot[{i}] = 0x{v:x} != 0x00" - ); - } - anyhow::ensure!( - eth_return[63] == 0x40, - "wrong value for offest : slot[31] = 0x{:x} != 0x40", - eth_return[63] - ); - let mut payload_len_bytes: [u8; 8] = [0; 8]; - payload_len_bytes.copy_from_slice(ð_return[88..96]); - let payload_len = u64::from_be_bytes(payload_len_bytes) as usize; - anyhow::ensure!( - payload_len <= MAX_PAYLOAD_LEN, - "too long declared payload: {payload_len} > {MAX_PAYLOAD_LEN}" - ); - let payload_bytes = ð_return[96..]; - anyhow::ensure!( - payload_len <= payload_bytes.len(), - "not enough remaining bytes: {payload_len} > {}", - payload_bytes.len() - ); - anyhow::ensure!( - activation_epoch < u64::MAX && payload_len > 0, - "no active activation" - ); - let compressed_manifest_bytes = &payload_bytes[..payload_len]; - let mut deflater = DeflateDecoder::new(compressed_manifest_bytes); - let mut manifest_bytes = vec![]; - deflater.read_to_end(&mut manifest_bytes)?; - let manifest: F3Manifest = serde_json::from_slice(&manifest_bytes)?; - anyhow::ensure!( - manifest.bootstrap_epoch >= 0 - && manifest.bootstrap_epoch as u64 == activation_epoch, - "bootstrap epoch does not match: {} != {activation_epoch}", - manifest.bootstrap_epoch - ); + let manifest = F3Manifest::parse_contract_return(ð_return)?; println!("{}", serde_json::to_string_pretty(&manifest)?); } } From e6587af196c4fab1f84a2942a641485afca99fa4 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 24 Feb 2025 16:56:16 +0800 Subject: [PATCH 02/14] refactor --- src/rpc/methods/f3.rs | 12 ++++++++---- src/rpc/methods/state.rs | 25 +++++++++++++++++++------ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index 225b5264f464..045b5e62e76e 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -29,6 +29,7 @@ use crate::{ crypto::Signature, message::Message, }, + state_manager::StateManager, utils::misc::env::is_env_set_and_truthy, }; use crate::{ @@ -589,13 +590,13 @@ impl GetManifestFromContract { } } - async fn get_manifest_from_contract( - ctx: Ctx, + fn get_manifest_from_contract( + state_manager: &Arc>, contract: EthAddress, ) -> anyhow::Result { let eth_call_message = Self::create_eth_call_message(contract); let filecoin_message = Message::try_from(eth_call_message)?; - let api_invoc_result = StateCall::handle(ctx, (filecoin_message, None.into())).await?; + let api_invoc_result = StateCall::run(state_manager, &filecoin_message, None)?; let Some(message_receipt) = api_invoc_result.msg_rct else { anyhow::bail!("No message receipt"); }; @@ -619,7 +620,10 @@ impl RpcMethod<0> for GetManifestFromContract { let Some(f3_contract_address) = ctx.chain_config().f3_contract_address.clone() else { return Err(anyhow::anyhow!("F3 contract address is not set").into()); }; - Ok(Self::get_manifest_from_contract(ctx, f3_contract_address).await?) + Ok(Self::get_manifest_from_contract( + &ctx.state_manager, + f3_contract_address, + )?) } } diff --git a/src/rpc/methods/state.rs b/src/rpc/methods/state.rs index 09d237ca027a..354b51cfff25 100644 --- a/src/rpc/methods/state.rs +++ b/src/rpc/methods/state.rs @@ -9,7 +9,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; pub use types::*; -use crate::blocks::Tipset; +use crate::blocks::{Tipset, TipsetKey}; use crate::chain::index::ResolveNullTipset; use crate::cid_collections::CidHashSet; use crate::eth::EthChainId; @@ -42,7 +42,7 @@ use crate::shim::{ state_tree::ActorState, version::NetworkVersion, }; use crate::state_manager::circulating_supply::GenesisInfo; -use crate::state_manager::{MarketBalance, StateOutput}; +use crate::state_manager::{MarketBalance, StateManager, StateOutput}; use crate::utils::db::{ car_stream::{CarBlock, CarWriter}, BlockstoreExt as _, @@ -77,6 +77,22 @@ const INITIAL_PLEDGE_NUM: u64 = 110; const INITIAL_PLEDGE_DEN: u64 = 100; pub enum StateCall {} + +impl StateCall { + pub fn run( + state_manager: &Arc>, + message: &Message, + tsk: Option, + ) -> anyhow::Result { + let tipset = state_manager + .chain_store() + .load_required_tipset_or_heaviest(&tsk)?; + // Handle expensive fork error? + // TODO(elmattic): https://github.com/ChainSafe/forest/issues/3733 + Ok(state_manager.call(message, Some(tipset))?) + } +} + impl RpcMethod<2> for StateCall { const NAME: &'static str = "Filecoin.StateCall"; const PARAM_NAMES: [&'static str; 2] = ["message", "tipsetKey"]; @@ -91,10 +107,7 @@ impl RpcMethod<2> for StateCall { ctx: Ctx, (message, ApiTipsetKey(tsk)): Self::Params, ) -> Result { - let tipset = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?; - // Handle expensive fork error? - // TODO(elmattic): https://github.com/ChainSafe/forest/issues/3733 - Ok(ctx.state_manager.call(&message, Some(tipset))?) + Ok(Self::run(&ctx.state_manager, &message, tsk)?) } } From 92155c665f9351bdfa00274deabc2d20930504a6 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 24 Feb 2025 18:36:43 +0800 Subject: [PATCH 03/14] ContractManifestProvider --- docs/docs/users/reference/env_variables.md | 1 + f3-sidecar/api.go | 11 +---- f3-sidecar/ffi_gen.go | 7 +-- f3-sidecar/ffi_impl.go | 4 +- f3-sidecar/main.go | 4 +- f3-sidecar/manifest.go | 57 +++++++++++++++++++--- f3-sidecar/run.go | 6 +-- src/f3/go_ffi.rs | 1 + src/f3/mod.rs | 1 + src/networks/mod.rs | 16 +++++- src/rpc/methods/f3.rs | 24 +++------ 11 files changed, 88 insertions(+), 44 deletions(-) diff --git a/docs/docs/users/reference/env_variables.md b/docs/docs/users/reference/env_variables.md index 6a8080178696..d01c6d9f2793 100644 --- a/docs/docs/users/reference/env_variables.md +++ b/docs/docs/users/reference/env_variables.md @@ -37,6 +37,7 @@ process. | `FOREST_F3_INITIAL_POWER_TABLE` | string | empty | `bafyreicmaj5hhoy5mgqvamfhgexxyergw7hdeshizghodwkjg6qmpoco7i` | Set the F3 initial power table CID | | `FOREST_F3_ROOT` | string | [FOREST_DATA_ROOT]/f3 | `/var/tmp/f3` | Set the data directory for F3 | | `FOREST_F3_BOOTSTRAP_EPOCH` | integer | -1 | 100 | Set the bootstrap epoch for F3 | +| `FOREST_F3_MANIFEST_POLL_INTERVAL` | string | empty | `15m` | Set the contract manifest poll interval for F3 | | `FOREST_DRAND_MAINNET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_MAINNET` config | | `FOREST_DRAND_QUICKNET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_QUICKNET` config | diff --git a/f3-sidecar/api.go b/f3-sidecar/api.go index f34b61adf4d5..1f116f698ffb 100644 --- a/f3-sidecar/api.go +++ b/f3-sidecar/api.go @@ -2,7 +2,6 @@ package main import ( "context" - "errors" "github.com/filecoin-project/go-f3" "github.com/filecoin-project/go-f3/certs" @@ -22,6 +21,7 @@ type F3Api struct { GetParticipatingMinerIDs func(context.Context) ([]uint64, error) SignMessage func(context.Context, []byte, []byte) (*crypto.Signature, error) Finalize func(context.Context, gpbft.TipSetKey) error + GetManifestFromContract func(context.Context) (*manifest.Manifest, error) } type FilecoinApi struct { @@ -63,12 +63,3 @@ func (h *F3ServerHandler) F3GetProgress(_ context.Context) gpbft.Instant { func (h *F3ServerHandler) F3GetManifest(_ context.Context) *manifest.Manifest { return h.f3.Manifest() } - -func (h *F3ServerHandler) F3SetManifest(_ context.Context, m *manifest.Manifest) error { - if ManifestProvider != nil { - ManifestProvider.Update(m) - return nil - } else { - return errors.New("forest manifest provider is not properly initialized") - } -} diff --git a/f3-sidecar/ffi_gen.go b/f3-sidecar/ffi_gen.go index edc7f5941af3..62001d50db17 100644 --- a/f3-sidecar/ffi_gen.go +++ b/f3-sidecar/ffi_gen.go @@ -29,11 +29,11 @@ import ( var GoF3NodeImpl GoF3Node type GoF3Node interface { - run(rpc_endpoint *string, jwt *string, f3_rpc_endpoint *string, initial_power_table *string, bootstrap_epoch *int64, finality *int64, f3_root *string) bool + run(rpc_endpoint *string, jwt *string, f3_rpc_endpoint *string, initial_power_table *string, bootstrap_epoch *int64, finality *int64, f3_root *string, contract_manifest_poll_interval_seconds *uint64) bool } //export CGoF3Node_run -func CGoF3Node_run(rpc_endpoint C.StringRef, jwt C.StringRef, f3_rpc_endpoint C.StringRef, initial_power_table C.StringRef, bootstrap_epoch C.int64_t, finality C.int64_t, f3_root C.StringRef, slot *C.void, cb *C.void) { +func CGoF3Node_run(rpc_endpoint C.StringRef, jwt C.StringRef, f3_rpc_endpoint C.StringRef, initial_power_table C.StringRef, bootstrap_epoch C.int64_t, finality C.int64_t, f3_root C.StringRef, contract_manifest_poll_interval_seconds C.uint64_t, slot *C.void, cb *C.void) { _new_rpc_endpoint := newString(rpc_endpoint) _new_jwt := newString(jwt) _new_f3_rpc_endpoint := newString(f3_rpc_endpoint) @@ -41,7 +41,8 @@ func CGoF3Node_run(rpc_endpoint C.StringRef, jwt C.StringRef, f3_rpc_endpoint C. _new_bootstrap_epoch := newC_int64_t(bootstrap_epoch) _new_finality := newC_int64_t(finality) _new_f3_root := newString(f3_root) - resp := GoF3NodeImpl.run(&_new_rpc_endpoint, &_new_jwt, &_new_f3_rpc_endpoint, &_new_initial_power_table, &_new_bootstrap_epoch, &_new_finality, &_new_f3_root) + _new_contract_manifest_poll_interval_seconds := newC_uint64_t(contract_manifest_poll_interval_seconds) + resp := GoF3NodeImpl.run(&_new_rpc_endpoint, &_new_jwt, &_new_f3_rpc_endpoint, &_new_initial_power_table, &_new_bootstrap_epoch, &_new_finality, &_new_f3_root, &_new_contract_manifest_poll_interval_seconds) resp_ref, buffer := cvt_ref(cntC_bool, refC_bool)(&resp) asmcall.CallFuncG0P2(unsafe.Pointer(cb), unsafe.Pointer(&resp_ref), unsafe.Pointer(slot)) runtime.KeepAlive(resp_ref) diff --git a/f3-sidecar/ffi_impl.go b/f3-sidecar/ffi_impl.go index 91c1c27ee2f8..a7b179d75046 100644 --- a/f3-sidecar/ffi_impl.go +++ b/f3-sidecar/ffi_impl.go @@ -28,12 +28,12 @@ type f3Impl struct { ctx context.Context } -func (f3 *f3Impl) run(rpc_endpoint *string, jwt *string, f3_rpc_endpoint *string, initial_power_table *string, bootstrap_epoch *int64, finality *int64, db *string) bool { +func (f3 *f3Impl) run(rpc_endpoint *string, jwt *string, f3_rpc_endpoint *string, initial_power_table *string, bootstrap_epoch *int64, finality *int64, db *string, contract_manifest_poll_interval_seconds *uint64) bool { var err error = nil const MAX_RETRY int = 5 nRetry := 0 for nRetry <= MAX_RETRY { - err = run(f3.ctx, *rpc_endpoint, *jwt, *f3_rpc_endpoint, *initial_power_table, *bootstrap_epoch, *finality, *db) + err = run(f3.ctx, *rpc_endpoint, *jwt, *f3_rpc_endpoint, *initial_power_table, *bootstrap_epoch, *finality, *db, *contract_manifest_poll_interval_seconds) if err != nil { nRetry += 1 logger.Errorf("Unexpected F3 failure, retrying(%d) in 10s... error=%s", nRetry, err) diff --git a/f3-sidecar/main.go b/f3-sidecar/main.go index 3fa559e69221..57520c443d99 100644 --- a/f3-sidecar/main.go +++ b/f3-sidecar/main.go @@ -41,12 +41,14 @@ func main() { flag.Int64Var(&finality, "finality", 900, "chain finality epochs") var root string flag.StringVar(&root, "root", "f3-data", "path to the f3 data directory") + var contract_poll_interval uint64 + flag.Uint64Var(&contract_poll_interval, "contract-poll-interval", 900, "contract manifest poll interval seconds") flag.Parse() ctx := context.Background() - err := run(ctx, rpcEndpoint, jwt, f3RpcEndpoint, initialPowerTable, bootstrapEpoch, finality, root) + err := run(ctx, rpcEndpoint, jwt, f3RpcEndpoint, initialPowerTable, bootstrapEpoch, finality, root, contract_poll_interval) if err != nil { panic(err) } diff --git a/f3-sidecar/manifest.go b/f3-sidecar/manifest.go index a0689c2ccd95..6e6a936e3a1c 100644 --- a/f3-sidecar/manifest.go +++ b/f3-sidecar/manifest.go @@ -2,27 +2,68 @@ package main import ( "context" + "time" "github.com/filecoin-project/go-f3/manifest" ) -type ForestManifestProvider struct { - ch chan *manifest.Manifest +type ContractManifestProvider struct { + started *bool + pollInterval time.Duration + currentManifest *manifest.Manifest + f3Api *F3Api + ch chan *manifest.Manifest } -func NewForestManifestProvider(initialValue *manifest.Manifest) (*ForestManifestProvider, error) { +func NewContractManifestProvider(initialValue *manifest.Manifest, contract_manifest_poll_interval_seconds uint64, f3Api *F3Api) (*ContractManifestProvider, error) { if err := initialValue.Validate(); err != nil { return nil, err } - p := ForestManifestProvider{ch: make(chan *manifest.Manifest)} + started := false + pollInterval := time.Duration(contract_manifest_poll_interval_seconds) * time.Second + p := ContractManifestProvider{ + started: &started, + pollInterval: pollInterval, + currentManifest: initialValue, + f3Api: f3Api, + ch: make(chan *manifest.Manifest), + } p.Update(initialValue) return &p, nil } -func (p *ForestManifestProvider) Update(m *manifest.Manifest) { +func (p *ContractManifestProvider) Update(m *manifest.Manifest) { + p.currentManifest = m p.ch <- m } -func (p *ForestManifestProvider) Start(context.Context) error { return nil } -func (p *ForestManifestProvider) Stop(context.Context) error { return nil } -func (p *ForestManifestProvider) ManifestUpdates() <-chan *manifest.Manifest { return p.ch } +func (p *ContractManifestProvider) Start(ctx context.Context) error { + started := true + p.started = &started + go func() { + for started && ctx.Err() == nil { + logger.Infof("Polling manifest from contract...\n") + m, err := p.f3Api.GetManifestFromContract(ctx) + if err == nil { + if m != nil { + if !m.Equal(p.currentManifest) { + logger.Infof("Successfully polled manifest from contract, updating...\n") + p.Update(m) + } else { + logger.Infof("Successfully polled unchanged manifest from contract\n") + } + } + } else { + logger.Warnf("failed to get manifest from contract: %s\n", err) + } + time.Sleep(p.pollInterval) + } + }() + + return nil +} +func (p *ContractManifestProvider) Stop(context.Context) error { + *p.started = false + return nil +} +func (p *ContractManifestProvider) ManifestUpdates() <-chan *manifest.Manifest { return p.ch } diff --git a/f3-sidecar/run.go b/f3-sidecar/run.go index 59dc972e0d67..984f389672eb 100644 --- a/f3-sidecar/run.go +++ b/f3-sidecar/run.go @@ -18,9 +18,9 @@ import ( leveldb "github.com/ipfs/go-ds-leveldb" ) -var ManifestProvider *ForestManifestProvider +var ManifestProvider *ContractManifestProvider -func run(ctx context.Context, rpcEndpoint string, jwt string, f3RpcEndpoint string, initialPowerTable string, bootstrapEpoch int64, finality int64, f3Root string) error { +func run(ctx context.Context, rpcEndpoint string, jwt string, f3RpcEndpoint string, initialPowerTable string, bootstrapEpoch int64, finality int64, f3Root string, contract_manifest_poll_interval_seconds uint64) error { api := FilecoinApi{} isJwtProvided := len(jwt) > 0 closer, err := jsonrpc.NewClient(context.Background(), rpcEndpoint, "Filecoin", &api, nil) @@ -94,7 +94,7 @@ func run(ctx context.Context, rpcEndpoint string, jwt string, f3RpcEndpoint stri } m.CommitteeLookback = manifest.DefaultCommitteeLookback - ManifestProvider, err = NewForestManifestProvider(m) + ManifestProvider, err = NewContractManifestProvider(m, contract_manifest_poll_interval_seconds, &ec.f3api) if err != nil { return err } diff --git a/src/f3/go_ffi.rs b/src/f3/go_ffi.rs index 7048a3838f4f..1df539a999b8 100644 --- a/src/f3/go_ffi.rs +++ b/src/f3/go_ffi.rs @@ -17,5 +17,6 @@ pub trait GoF3Node { bootstrap_epoch: i64, finality: i64, f3_root: String, + contract_manifest_poll_interval_seconds: u64, ) -> bool; } diff --git a/src/f3/mod.rs b/src/f3/mod.rs index a83cc5d903d4..eba72dc7659a 100644 --- a/src/f3/mod.rs +++ b/src/f3/mod.rs @@ -83,6 +83,7 @@ pub fn run_f3_sidecar_if_enabled( _bootstrap_epoch, _finality, _f3_root, + chain_config.f3_contract_poll_interval().as_secs(), ); } } diff --git a/src/networks/mod.rs b/src/networks/mod.rs index 75875d38dba5..127f323579ef 100644 --- a/src/networks/mod.rs +++ b/src/networks/mod.rs @@ -240,7 +240,7 @@ pub struct ChainConfig { pub f3_initial_power_table: Cid, #[cfg_attr(test, arbitrary(gen(|_| Some(EthAddress::from_str("0x476AC9256b9921C9C6a0fC237B7fE05fe9874F50").unwrap()))))] pub f3_contract_address: Option, - pub f3_contract_poll_interval: Duration, + f3_contract_poll_interval: Duration, } impl ChainConfig { @@ -447,6 +447,20 @@ impl ChainConfig { pub fn genesis_network_version(&self) -> NetworkVersion { self.genesis_network } + + #[allow(dead_code)] + pub fn f3_contract_poll_interval(&self) -> Duration { + std::env::var("FOREST_F3_MANIFEST_POLL_INTERVAL") + .ok() + .and_then(|i| humantime::Duration::from_str(&i).ok()) + .inspect(|i| { + tracing::info!( + "Using F3 contract manifest poll interval {i} set by FOREST_F3_MANIFEST_POLL_INTERVAL" + ) + }) + .map(Into::into) + .unwrap_or(self.f3_contract_poll_interval) + } } impl Default for ChainConfig { diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index 045b5e62e76e..c583f1e863a4 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -611,19 +611,19 @@ impl RpcMethod<0> for GetManifestFromContract { const PERMISSION: Permission = Permission::Read; type Params = (); - type Ok = F3Manifest; + type Ok = Option; async fn handle( ctx: Ctx, _: Self::Params, ) -> Result { - let Some(f3_contract_address) = ctx.chain_config().f3_contract_address.clone() else { - return Err(anyhow::anyhow!("F3 contract address is not set").into()); - }; - Ok(Self::get_manifest_from_contract( - &ctx.state_manager, - f3_contract_address, - )?) + Ok(match &ctx.chain_config().f3_contract_address { + Some(f3_contract_address) => Some(Self::get_manifest_from_contract( + &ctx.state_manager, + f3_contract_address.clone(), + )?), + _ => None, + }) } } @@ -877,14 +877,6 @@ impl RpcMethod<1> for F3Participate { } } -pub async fn set_f3_manifest(manifest: &F3Manifest) -> anyhow::Result<()> { - let client = get_rpc_http_client()?; - let mut params = ArrayParams::new(); - params.insert(manifest)?; - let _: serde_json::Value = client.request("Filecoin.F3SetManifest", params).await?; - Ok(()) -} - pub fn get_f3_rpc_endpoint() -> Cow<'static, str> { if let Ok(host) = std::env::var("FOREST_F3_SIDECAR_RPC_ENDPOINT") { Cow::Owned(host) From 3425568e921eba8ebdf68a490065a087e4c962fb Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 24 Feb 2025 20:02:38 +0800 Subject: [PATCH 04/14] fix --- f3-sidecar/manifest.go | 15 ++++++++++++--- f3-sidecar/run.go | 23 +++++++++++++++-------- src/rpc/mod.rs | 1 + 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/f3-sidecar/manifest.go b/f3-sidecar/manifest.go index 6e6a936e3a1c..44fb15d5899d 100644 --- a/f3-sidecar/manifest.go +++ b/f3-sidecar/manifest.go @@ -10,6 +10,7 @@ import ( type ContractManifestProvider struct { started *bool pollInterval time.Duration + initialManifest *manifest.Manifest currentManifest *manifest.Manifest f3Api *F3Api ch chan *manifest.Manifest @@ -24,11 +25,11 @@ func NewContractManifestProvider(initialValue *manifest.Manifest, contract_manif p := ContractManifestProvider{ started: &started, pollInterval: pollInterval, - currentManifest: initialValue, + initialManifest: initialValue, + currentManifest: nil, f3Api: f3Api, ch: make(chan *manifest.Manifest), } - p.Update(initialValue) return &p, nil } @@ -38,11 +39,19 @@ func (p *ContractManifestProvider) Update(m *manifest.Manifest) { } func (p *ContractManifestProvider) Start(ctx context.Context) error { + if *p.started { + logger.Warnf("ContractManifestProvider has already been started\n") + return nil + } + started := true p.started = &started go func() { for started && ctx.Err() == nil { - logger.Infof("Polling manifest from contract...\n") + if p.currentManifest == nil { + p.Update(p.initialManifest) + } + logger.Debugf("Polling manifest from contract...\n") m, err := p.f3Api.GetManifestFromContract(ctx) if err == nil { if m != nil { diff --git a/f3-sidecar/run.go b/f3-sidecar/run.go index 984f389672eb..82289649e1e2 100644 --- a/f3-sidecar/run.go +++ b/f3-sidecar/run.go @@ -18,8 +18,6 @@ import ( leveldb "github.com/ipfs/go-ds-leveldb" ) -var ManifestProvider *ContractManifestProvider - func run(ctx context.Context, rpcEndpoint string, jwt string, f3RpcEndpoint string, initialPowerTable string, bootstrapEpoch int64, finality int64, f3Root string, contract_manifest_poll_interval_seconds uint64) error { api := FilecoinApi{} isJwtProvided := len(jwt) > 0 @@ -28,9 +26,16 @@ func run(ctx context.Context, rpcEndpoint string, jwt string, f3RpcEndpoint stri return err } defer closer() - network, err := api.StateNetworkName(ctx) - if err != nil { - return err + var network string + for { + network, err = api.StateNetworkName(ctx) + if err == nil { + logger.Infoln("Forest RPC server is online") + break + } else { + logger.Warnln("waiting for Forest RPC server") + time.Sleep(5 * time.Second) + } } listenAddrs, err := api.NetAddrsListen(ctx) if err != nil { @@ -94,12 +99,14 @@ func run(ctx context.Context, rpcEndpoint string, jwt string, f3RpcEndpoint stri } m.CommitteeLookback = manifest.DefaultCommitteeLookback - ManifestProvider, err = NewContractManifestProvider(m, contract_manifest_poll_interval_seconds, &ec.f3api) + manifestProvider, err := NewContractManifestProvider(m, contract_manifest_poll_interval_seconds, &ec.f3api) if err != nil { return err } - - f3Module, err := f3.New(ctx, ManifestProvider, ds, + if err := manifestProvider.Start(ctx); err != nil { + return err + } + f3Module, err := f3.New(ctx, manifestProvider, ds, p2p.Host, p2p.PubSub, verif, &ec, f3Root) if err != nil { return err diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index 237cbb616b5f..c6caa8614ff6 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -268,6 +268,7 @@ macro_rules! for_each_rpc_method { $callback!($crate::rpc::f3::Finalize); $callback!($crate::rpc::f3::ProtectPeer); $callback!($crate::rpc::f3::SignMessage); + $callback!($crate::rpc::f3::GetManifestFromContract); // misc $callback!($crate::rpc::misc::GetActorEventsRaw); From b81c4b3636bb540d58f0c2c01e1d885b071800d8 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 26 Feb 2025 07:21:21 +0800 Subject: [PATCH 05/14] Update src/rpc/methods/f3.rs Co-authored-by: Hubert --- src/rpc/methods/f3.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index c583f1e863a4..3b714d6cf007 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -609,6 +609,7 @@ impl RpcMethod<0> for GetManifestFromContract { const PARAM_NAMES: [&'static str; 0] = []; const API_PATHS: ApiPaths = ApiPaths::V1; const PERMISSION: Permission = Permission::Read; + const DESCRIPTION: Option<&'static str> = Some("Retrieves the manifest with all F3 parameters from a smart contract. The address of the contract is defined by the node."); type Params = (); type Ok = Option; From edb93e44f832f40d0e92723fe9e270be66b0663a Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 26 Feb 2025 07:35:27 +0800 Subject: [PATCH 06/14] FOREST_F3_CONTRACT_ADDRESS env var --- docs/docs/users/reference/env_variables.md | 1 + src/networks/mod.rs | 28 ++++++++++++++++++---- src/rpc/methods/f3.rs | 4 ++-- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/docs/docs/users/reference/env_variables.md b/docs/docs/users/reference/env_variables.md index d01c6d9f2793..81865987f100 100644 --- a/docs/docs/users/reference/env_variables.md +++ b/docs/docs/users/reference/env_variables.md @@ -37,6 +37,7 @@ process. | `FOREST_F3_INITIAL_POWER_TABLE` | string | empty | `bafyreicmaj5hhoy5mgqvamfhgexxyergw7hdeshizghodwkjg6qmpoco7i` | Set the F3 initial power table CID | | `FOREST_F3_ROOT` | string | [FOREST_DATA_ROOT]/f3 | `/var/tmp/f3` | Set the data directory for F3 | | `FOREST_F3_BOOTSTRAP_EPOCH` | integer | -1 | 100 | Set the bootstrap epoch for F3 | +| `FOREST_F3_CONTRACT_ADDRESS` | string | empty | `0x476AC9256b9921C9C6a0fC237B7fE05fe9874F50` | Set the manifest contract eth address for F3 | | `FOREST_F3_MANIFEST_POLL_INTERVAL` | string | empty | `15m` | Set the contract manifest poll interval for F3 | | `FOREST_DRAND_MAINNET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_MAINNET` config | | `FOREST_DRAND_QUICKNET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_QUICKNET` config | diff --git a/src/networks/mod.rs b/src/networks/mod.rs index 127f323579ef..c7ce0376d356 100644 --- a/src/networks/mod.rs +++ b/src/networks/mod.rs @@ -239,7 +239,7 @@ pub struct ChainConfig { pub f3_bootstrap_epoch: i64, pub f3_initial_power_table: Cid, #[cfg_attr(test, arbitrary(gen(|_| Some(EthAddress::from_str("0x476AC9256b9921C9C6a0fC237B7fE05fe9874F50").unwrap()))))] - pub f3_contract_address: Option, + f3_contract_address: Option, f3_contract_poll_interval: Duration, } @@ -450,17 +450,35 @@ impl ChainConfig { #[allow(dead_code)] pub fn f3_contract_poll_interval(&self) -> Duration { - std::env::var("FOREST_F3_MANIFEST_POLL_INTERVAL") + const ENV_KEY: &str = "FOREST_F3_MANIFEST_POLL_INTERVAL"; + std::env::var(ENV_KEY) .ok() .and_then(|i| humantime::Duration::from_str(&i).ok()) .inspect(|i| { - tracing::info!( - "Using F3 contract manifest poll interval {i} set by FOREST_F3_MANIFEST_POLL_INTERVAL" - ) + tracing::info!("Using F3 contract manifest poll interval {i} set by {ENV_KEY}") }) .map(Into::into) .unwrap_or(self.f3_contract_poll_interval) } + + pub fn f3_contract_address(&self) -> Option { + const ENV_KEY: &str = "FOREST_F3_CONTRACT_ADDRESS"; + std::env::var(ENV_KEY) + .ok() + .and_then(|i| { + if i.is_empty() { + tracing::info!("F3 contract is disabled by {ENV_KEY}"); + None + } else if let Ok(addr) = EthAddress::from_str(&i) { + tracing::info!("Using F3 contract address {i} set by {ENV_KEY}"); + Some(addr) + } else { + tracing::warn!("Failed to parse F3 contract address {i}"); + None + } + }) + .or_else(|| self.f3_contract_address.clone()) + } } impl Default for ChainConfig { diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index 3b714d6cf007..3bc4b3e9020e 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -618,10 +618,10 @@ impl RpcMethod<0> for GetManifestFromContract { ctx: Ctx, _: Self::Params, ) -> Result { - Ok(match &ctx.chain_config().f3_contract_address { + Ok(match ctx.chain_config().f3_contract_address() { Some(f3_contract_address) => Some(Self::get_manifest_from_contract( &ctx.state_manager, - f3_contract_address.clone(), + f3_contract_address, )?), _ => None, }) From b4b9b45d85b417c1ebda9b4e7d0149ba9c1f5804 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 26 Feb 2025 20:22:15 +0800 Subject: [PATCH 07/14] fix spellcheck errors --- docs/docs/users/reference/env_variables.md | 2 +- f3-sidecar/ffi_impl.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/docs/users/reference/env_variables.md b/docs/docs/users/reference/env_variables.md index 81865987f100..2942270a0bfb 100644 --- a/docs/docs/users/reference/env_variables.md +++ b/docs/docs/users/reference/env_variables.md @@ -37,7 +37,7 @@ process. | `FOREST_F3_INITIAL_POWER_TABLE` | string | empty | `bafyreicmaj5hhoy5mgqvamfhgexxyergw7hdeshizghodwkjg6qmpoco7i` | Set the F3 initial power table CID | | `FOREST_F3_ROOT` | string | [FOREST_DATA_ROOT]/f3 | `/var/tmp/f3` | Set the data directory for F3 | | `FOREST_F3_BOOTSTRAP_EPOCH` | integer | -1 | 100 | Set the bootstrap epoch for F3 | -| `FOREST_F3_CONTRACT_ADDRESS` | string | empty | `0x476AC9256b9921C9C6a0fC237B7fE05fe9874F50` | Set the manifest contract eth address for F3 | +| `FOREST_F3_CONTRACT_ADDRESS` | string | empty | `0x476AC9256b9921C9C6a0fC237B7fE05fe9874F50` | Set the manifest contract Ethereum address for F3 | | `FOREST_F3_MANIFEST_POLL_INTERVAL` | string | empty | `15m` | Set the contract manifest poll interval for F3 | | `FOREST_DRAND_MAINNET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_MAINNET` config | | `FOREST_DRAND_QUICKNET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_QUICKNET` config | diff --git a/f3-sidecar/ffi_impl.go b/f3-sidecar/ffi_impl.go index a7b179d75046..66199520dfe2 100644 --- a/f3-sidecar/ffi_impl.go +++ b/f3-sidecar/ffi_impl.go @@ -28,6 +28,8 @@ type f3Impl struct { ctx context.Context } +// The nil checks of the parameters are ommitted because they are passed from Rust code which are not nil. +// The signature pointer types that are generated by rust2go to avoid lifetime issues func (f3 *f3Impl) run(rpc_endpoint *string, jwt *string, f3_rpc_endpoint *string, initial_power_table *string, bootstrap_epoch *int64, finality *int64, db *string, contract_manifest_poll_interval_seconds *uint64) bool { var err error = nil const MAX_RETRY int = 5 From cc8e25eedc6ba86836c305072274f934c8b7e005 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 26 Feb 2025 21:10:53 +0800 Subject: [PATCH 08/14] link to solidity contract --- src/rpc/methods/f3/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rpc/methods/f3/types.rs b/src/rpc/methods/f3/types.rs index 56eed60178f6..0a7be531b844 100644 --- a/src/rpc/methods/f3/types.rs +++ b/src/rpc/methods/f3/types.rs @@ -936,6 +936,7 @@ mod tests { #[test] fn test_f3_manifest_parse_contract_return() { + // The solidity contract: https://github.com/filecoin-project/f3-activation-contract/blob/master/contracts/F3Parameters.sol let eth_return_hex = include_str!("contract_return.hex").trim(); let eth_return = hex::decode(eth_return_hex).unwrap(); let manifest = F3Manifest::parse_contract_return(ð_return).unwrap(); From 7de05ce2fca6d2d343db163f97cdca225e5881ed Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 26 Feb 2025 21:19:40 +0800 Subject: [PATCH 09/14] link to activationInformation method ID --- src/rpc/methods/f3.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index 3bc4b3e9020e..5033618652ae 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -580,7 +580,8 @@ pub enum GetManifestFromContract {} impl GetManifestFromContract { pub fn create_eth_call_message(contract: EthAddress) -> EthCallMessage { - // method ID of activationInformation() + // method ID of activationInformation(), + // see static METHOD_ID: Lazy = Lazy::new(|| EthBytes::from_str("0x2587660d").expect("Infallible")); EthCallMessage { From 76edefb4152f6d90a13a3b4919fed1993b5600f1 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 26 Feb 2025 21:22:07 +0800 Subject: [PATCH 10/14] Update src/rpc/methods/f3/types.rs Co-authored-by: Hubert --- src/rpc/methods/f3/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/methods/f3/types.rs b/src/rpc/methods/f3/types.rs index 0a7be531b844..69cf5aa871f1 100644 --- a/src/rpc/methods/f3/types.rs +++ b/src/rpc/methods/f3/types.rs @@ -936,7 +936,7 @@ mod tests { #[test] fn test_f3_manifest_parse_contract_return() { - // The solidity contract: https://github.com/filecoin-project/f3-activation-contract/blob/master/contracts/F3Parameters.sol + // The solidity contract: https://github.com/filecoin-project/f3-activation-contract/blob/063cd51a46f61b717375fe5675a6ddc73f4d8626/contracts/F3Parameters.sol let eth_return_hex = include_str!("contract_return.hex").trim(); let eth_return = hex::decode(eth_return_hex).unwrap(); let manifest = F3Manifest::parse_contract_return(ð_return).unwrap(); From 678991629393194745d51bc43a8a46a43c7933e9 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 26 Feb 2025 21:30:22 +0800 Subject: [PATCH 11/14] no indexing_slicing --- src/rpc/methods/f3/types.rs | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/rpc/methods/f3/types.rs b/src/rpc/methods/f3/types.rs index 69cf5aa871f1..5f9e7f906cf3 100644 --- a/src/rpc/methods/f3/types.rs +++ b/src/rpc/methods/f3/types.rs @@ -310,8 +310,8 @@ impl F3Manifest { Ok(eth_return) } - #[allow(clippy::indexing_slicing)] pub fn parse_contract_return(eth_return: &[u8]) -> anyhow::Result { + const INDEXING_SLICING_ERROR: &str = "unexpected overflow in indexing slicling"; const MAX_PAYLOAD_LEN: usize = 4 << 10; const SLOT_SIZE: usize = 32; const SLOT_COUNT: usize = 3; @@ -322,10 +322,18 @@ impl F3Manifest { "no activation information" ); let (slot_activation_epoch, slot_offset, slot_payload_len, payload) = ( - ð_return[..SLOT_SIZE], - ð_return[SLOT_SIZE..SLOT_SIZE * 2], - ð_return[SLOT_SIZE * 2..SLOT_SIZE * 3], - ð_return[SLOT_SIZE * 3..], + eth_return + .get(..SLOT_SIZE) + .context(INDEXING_SLICING_ERROR)?, + ð_return + .get(SLOT_SIZE..SLOT_SIZE * 2) + .context(INDEXING_SLICING_ERROR)?, + ð_return + .get(SLOT_SIZE * 2..SLOT_SIZE * 3) + .context(INDEXING_SLICING_ERROR)?, + ð_return + .get(SLOT_SIZE * 3..) + .context(INDEXING_SLICING_ERROR)?, ); // parse activation epoch from slot 1 let activation_epoch = byteorder::BigEndian::read_u64( @@ -333,16 +341,21 @@ impl F3Manifest { ); // slot 2 is the offset to variable length bytes // it is always the same 0x00000...0040 - for (i, &v) in slot_offset[..SLOT_SIZE - 1].iter().enumerate() { + for (i, &v) in slot_offset + .get(..SLOT_SIZE - 1) + .context(INDEXING_SLICING_ERROR)? + .iter() + .enumerate() + { anyhow::ensure!( v == 0, "wrong value for offset (padding): slot[{i}] = 0x{v:x} != 0x00" ); } + let slot_offset_last = *slot_offset.last().context("unexpected empty slot_offset")?; anyhow::ensure!( - slot_offset[SLOT_SIZE - 1] == 0x40, - "wrong value for offest : slot[31] = 0x{:x} != 0x40", - slot_offset[SLOT_SIZE - 1] + slot_offset_last == 0x40, + "wrong value for offest : slot[31] = 0x{slot_offset_last:x} != 0x40", ); // parse payload length from slot 3 let payload_len = @@ -361,7 +374,9 @@ impl F3Manifest { activation_epoch < u64::MAX && payload_len > 0, "no active activation" ); - let compressed_manifest_bytes = &payload[..payload_len]; + let compressed_manifest_bytes = payload + .get(..payload_len) + .context("not enough remaining bytes in payload")?; let mut deflater = DeflateDecoder::new(compressed_manifest_bytes); let mut manifest_bytes = vec![]; deflater.read_to_end(&mut manifest_bytes)?; From c94730c35437f27fc96877da8d1ff95b39cfabb9 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 26 Feb 2025 21:36:03 +0800 Subject: [PATCH 12/14] go mod tidy --- f3-sidecar/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/f3-sidecar/go.mod b/f3-sidecar/go.mod index 26d51d407737..06de75a98483 100644 --- a/f3-sidecar/go.mod +++ b/f3-sidecar/go.mod @@ -8,7 +8,6 @@ require ( github.com/filecoin-project/go-state-types v0.15.0 github.com/ihciah/rust2go v0.0.0-20250125181647-c5957947a3c0 github.com/ipfs/go-cid v0.5.0 - github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-leveldb v0.5.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/libp2p/go-libp2p v0.40.0 @@ -52,6 +51,7 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/ipfs/boxo v0.27.4 // indirect + github.com/ipfs/go-datastore v0.6.0 // indirect github.com/ipld/go-ipld-prime v0.21.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect From 83ca6db2dfc97211825adfd5a061e311fcc9d83a Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Thu, 27 Feb 2025 19:42:22 +0800 Subject: [PATCH 13/14] enable F3 on mainnet --- f3-sidecar/manifest.go | 22 +++++++++++----------- f3-sidecar/run.go | 13 +------------ src/networks/mod.rs | 4 ++-- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/f3-sidecar/manifest.go b/f3-sidecar/manifest.go index 44fb15d5899d..900f1e23d9e7 100644 --- a/f3-sidecar/manifest.go +++ b/f3-sidecar/manifest.go @@ -17,9 +17,6 @@ type ContractManifestProvider struct { } func NewContractManifestProvider(initialValue *manifest.Manifest, contract_manifest_poll_interval_seconds uint64, f3Api *F3Api) (*ContractManifestProvider, error) { - if err := initialValue.Validate(); err != nil { - return nil, err - } started := false pollInterval := time.Duration(contract_manifest_poll_interval_seconds) * time.Second p := ContractManifestProvider{ @@ -30,17 +27,23 @@ func NewContractManifestProvider(initialValue *manifest.Manifest, contract_manif f3Api: f3Api, ch: make(chan *manifest.Manifest), } + p.Update(p.initialManifest) return &p, nil } func (p *ContractManifestProvider) Update(m *manifest.Manifest) { - p.currentManifest = m - p.ch <- m + err := m.Validate() + if err == nil { + p.currentManifest = m + p.ch <- m + } else { + logger.Warnf("Invalid manifest, skip updating, %s\n", err) + } } func (p *ContractManifestProvider) Start(ctx context.Context) error { if *p.started { - logger.Warnf("ContractManifestProvider has already been started\n") + logger.Warnln("ContractManifestProvider has already been started") return nil } @@ -48,18 +51,15 @@ func (p *ContractManifestProvider) Start(ctx context.Context) error { p.started = &started go func() { for started && ctx.Err() == nil { - if p.currentManifest == nil { - p.Update(p.initialManifest) - } logger.Debugf("Polling manifest from contract...\n") m, err := p.f3Api.GetManifestFromContract(ctx) if err == nil { if m != nil { if !m.Equal(p.currentManifest) { - logger.Infof("Successfully polled manifest from contract, updating...\n") + logger.Infoln("Successfully polled manifest from contract, updating...") p.Update(m) } else { - logger.Infof("Successfully polled unchanged manifest from contract\n") + logger.Infoln("Successfully polled unchanged manifest from contract") } } } else { diff --git a/f3-sidecar/run.go b/f3-sidecar/run.go index 82289649e1e2..651252bdb86a 100644 --- a/f3-sidecar/run.go +++ b/f3-sidecar/run.go @@ -84,19 +84,8 @@ func run(ctx context.Context, rpcEndpoint string, jwt string, f3RpcEndpoint stri m.CatchUpAlignment = blockDelay / 2 m.CertificateExchange.MinimumPollInterval = blockDelay m.CertificateExchange.MaximumPollInterval = 4 * blockDelay - - head, err := ec.GetHead(ctx) - if err != nil { - return err - } m.EC.Finality = finality - if bootstrapEpoch < 0 { - // This is temporary logic to make the dummy bootstrap epoch work locally. - // It should be removed once bootstrapEpochs are determinted. - m.BootstrapEpoch = max(m.EC.Finality+1, head.Epoch()-m.EC.Finality+1) - } else { - m.BootstrapEpoch = bootstrapEpoch - } + m.BootstrapEpoch = bootstrapEpoch m.CommitteeLookback = manifest.DefaultCommitteeLookback manifestProvider, err := NewContractManifestProvider(m, contract_manifest_poll_interval_seconds, &ec.f3api) diff --git a/src/networks/mod.rs b/src/networks/mod.rs index c7ce0376d356..916b494032b2 100644 --- a/src/networks/mod.rs +++ b/src/networks/mod.rs @@ -262,8 +262,8 @@ impl ChainConfig { breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION, // 1 year on mainnet fip0081_ramp_duration_epochs: 365 * EPOCHS_IN_DAY as u64, - f3_enabled: false, - f3_consensus: false, + f3_enabled: true, + f3_consensus: true, f3_bootstrap_epoch: -1, f3_initial_power_table: Default::default(), f3_contract_address: Some( From cd97d98aebe7832feb8a73638862da33f7fef032 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 28 Feb 2025 07:29:02 +0800 Subject: [PATCH 14/14] fix --- f3-sidecar/manifest.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/f3-sidecar/manifest.go b/f3-sidecar/manifest.go index 900f1e23d9e7..2e1ba3447c0b 100644 --- a/f3-sidecar/manifest.go +++ b/f3-sidecar/manifest.go @@ -27,7 +27,6 @@ func NewContractManifestProvider(initialValue *manifest.Manifest, contract_manif f3Api: f3Api, ch: make(chan *manifest.Manifest), } - p.Update(p.initialManifest) return &p, nil } @@ -51,6 +50,10 @@ func (p *ContractManifestProvider) Start(ctx context.Context) error { p.started = &started go func() { for started && ctx.Err() == nil { + if p.currentManifest == nil && p.initialManifest != nil { + p.Update(p.initialManifest) + p.initialManifest = nil + } logger.Debugf("Polling manifest from contract...\n") m, err := p.f3Api.GetManifestFromContract(ctx) if err == nil {