From 0683175522bdbfe0d994d1ee88b8b855b438b6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 1 May 2025 12:38:37 +0200 Subject: [PATCH 1/6] initial sealer actor structure --- Cargo.lock | 17 ++++++++ Cargo.toml | 2 + README.md | 1 + actors/sealer/Cargo.toml | 32 ++++++++++++++ actors/sealer/src/lib.rs | 83 ++++++++++++++++++++++++++++++++++++ actors/sealer/src/state.rs | 13 ++++++ actors/sealer/src/testing.rs | 17 ++++++++ actors/sealer/src/types.rs | 16 +++++++ build.rs | 1 + runtime/src/test_utils.rs | 4 ++ vm_api/src/builtin.rs | 2 + 11 files changed, 188 insertions(+) create mode 100644 actors/sealer/Cargo.toml create mode 100644 actors/sealer/src/lib.rs create mode 100644 actors/sealer/src/state.rs create mode 100644 actors/sealer/src/testing.rs create mode 100644 actors/sealer/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 4a6e8761ed..b4257774cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -954,6 +954,22 @@ dependencies = [ "serde", ] +[[package]] +name = "fil_actor_sealer" +version = "16.0.1" +dependencies = [ + "fil_actors_runtime", + "fvm_ipld_blockstore", + "fvm_ipld_encoding", + "fvm_shared", + "lazy_static", + "log", + "num", + "num-derive", + "num-traits", + "serde", +] + [[package]] name = "fil_actor_system" version = "16.0.1" @@ -1123,6 +1139,7 @@ dependencies = [ "fil_actor_placeholder", "fil_actor_power", "fil_actor_reward", + "fil_actor_sealer", "fil_actor_system", "fil_actor_verifreg", "fil_actors_runtime", diff --git a/Cargo.toml b/Cargo.toml index 949fc932df..403f505fc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ fil_actor_power = { workspace = true, features = ["fil-actor"] } fil_actor_reward = { workspace = true, features = ["fil-actor"] } fil_actor_system = { workspace = true, features = ["fil-actor"] } fil_actor_verifreg = { workspace = true, features = ["fil-actor"] } +fil_actor_sealer = { workspace = true, features = ["fil-actor"] } [build-dependencies] fil_actor_bundler = "8.0.0" @@ -156,6 +157,7 @@ fil_actor_power = { path = "actors/power" } fil_actor_reward = { path = "actors/reward" } fil_actor_system = { path = "actors/system" } fil_actor_verifreg = { path = "actors/verifreg" } +fil_actor_sealer = { path = "actors/sealer" } fil_actors_evm_shared = { path = "actors/evm/shared" } fil_actors_runtime = { path = "runtime" } fil_builtin_actors_state = { path = "state" } diff --git a/README.md b/README.md index 19af554a69..21e7f8cff4 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ type ManifestPayload struct { evm &ActorBytecode eam &ActorBytecode ethaccount &ActorBytecode + sealer &ActorBytecode } representation listpairs # RAW block diff --git a/actors/sealer/Cargo.toml b/actors/sealer/Cargo.toml new file mode 100644 index 0000000000..05f84d5ede --- /dev/null +++ b/actors/sealer/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "fil_actor_sealer" +description = "Builtin sealer actor for Filecoin" +version.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +authors = ["Curio Storage Inc. "] +keywords = ["filecoin", "web3", "wasm"] + +[lib] +## lib is necessary for integration tests +## cdylib is necessary for Wasm build +crate-type = ["cdylib", "lib"] + +[dependencies] +fil_actors_runtime = { workspace = true } +fvm_shared = { workspace = true } +num-traits = { workspace = true } +num-derive = { workspace = true } +log = { workspace = true } +lazy_static = { workspace = true } +serde = { workspace = true } +fvm_ipld_blockstore = { workspace = true } +fvm_ipld_encoding = { workspace = true } + +[dev-dependencies] +fil_actors_runtime = { workspace = true, features = ["test_utils", "sector-default"] } +num = { workspace = true } + +[features] +fil-actor = ["fil_actors_runtime/fil-actor"] diff --git a/actors/sealer/src/lib.rs b/actors/sealer/src/lib.rs new file mode 100644 index 0000000000..fbf878991d --- /dev/null +++ b/actors/sealer/src/lib.rs @@ -0,0 +1,83 @@ +// Copyright 2024 Curio Storage Inc. +// SPDX-License-Identifier: Apache-2.0, MIT + +use fvm_ipld_encoding::ipld_block::IpldBlock; +use fvm_shared::{METHOD_CONSTRUCTOR, MethodNum}; +use num_derive::FromPrimitive; + +use fil_actors_runtime::builtin::singletons::SYSTEM_ACTOR_ADDR; +use fil_actors_runtime::runtime::{ActorCode, Runtime}; +use fil_actors_runtime::{FIRST_EXPORTED_METHOD_NUMBER, actor_dispatch}; +use fil_actors_runtime::{ActorError, actor_error}; + +use crate::types::{ConstructorParams, SealerIDReturn}; + +pub use self::state::State; + +mod state; +pub mod types; +pub mod testing; + +#[cfg(feature = "fil-actor")] +fil_actors_runtime::wasm_trampoline!(Actor); + +/// Sealer actor methods available +#[derive(FromPrimitive)] +#[repr(u64)] +pub enum Method { + Constructor = METHOD_CONSTRUCTOR, + SealerID = 2, + // TODO: Add more methods as needed +} + +/// Sealer Actor +pub struct Actor; + +impl Actor { + /// Constructor for Sealer actor + pub fn constructor(rt: &impl Runtime, _params: ConstructorParams) -> Result<(), ActorError> { + rt.validate_immediate_caller_is(std::iter::once(&SYSTEM_ACTOR_ADDR))?; + let id_addr = rt.message().receiver(); + let state = State { + id_addr, + // TODO: initialize sector bitfield, acl, etc. + }; + rt.create(&state)?; + Ok(()) + } + + /// Returns the SealerID (the actor's ID address) + pub fn sealer_id(rt: &impl Runtime) -> Result { + rt.validate_immediate_caller_accept_any()?; + let st: State = rt.state()?; + Ok(SealerIDReturn { id_addr: st.id_addr }) + } + + /// Fallback method for unimplemented method numbers. + pub fn fallback( + rt: &impl Runtime, + method: MethodNum, + _: Option, + ) -> Result, ActorError> { + rt.validate_immediate_caller_accept_any()?; + if method >= FIRST_EXPORTED_METHOD_NUMBER { + Ok(None) + } else { + Err(actor_error!(unhandled_message; "invalid method: {}", method)) + } + } +} + +impl ActorCode for Actor { + type Methods = Method; + + fn name() -> &'static str { + "Sealer" + } + + actor_dispatch! { + Constructor => constructor, + SealerID => sealer_id, + _ => fallback, + } +} diff --git a/actors/sealer/src/state.rs b/actors/sealer/src/state.rs new file mode 100644 index 0000000000..14f4f15708 --- /dev/null +++ b/actors/sealer/src/state.rs @@ -0,0 +1,13 @@ +// Copyright 2024 Curio Storage Inc. +// SPDX-License-Identifier: Apache-2.0, MIT + +use fvm_ipld_encoding::tuple::*; +use fvm_shared::address::Address; + +/// State for the Sealer actor +#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone)] +pub struct State { + /// The ID address of this sealer actor + pub id_addr: Address, + // TODO: Add sector bitfield, ACL/proxy address, etc. +} \ No newline at end of file diff --git a/actors/sealer/src/testing.rs b/actors/sealer/src/testing.rs new file mode 100644 index 0000000000..b1d927e00c --- /dev/null +++ b/actors/sealer/src/testing.rs @@ -0,0 +1,17 @@ +use fil_actors_runtime::MessageAccumulator; +use fvm_shared::address::Address; + +use crate::State; + +pub struct StateSummary { + pub id_addr: Address, +} + +pub fn check_state_invariants( + state: &State, + _id_address: &Address, +) -> (StateSummary, MessageAccumulator) { + let acc = MessageAccumulator::default(); + // TODO: Add invariants as needed + (StateSummary { id_addr: state.id_addr }, acc) +} \ No newline at end of file diff --git a/actors/sealer/src/types.rs b/actors/sealer/src/types.rs new file mode 100644 index 0000000000..c96f74ea44 --- /dev/null +++ b/actors/sealer/src/types.rs @@ -0,0 +1,16 @@ +use fvm_ipld_encoding::tuple::*; +use fvm_shared::address::Address; +use std::marker::PhantomData; + +#[derive(Debug, Serialize_tuple, Deserialize_tuple)] +pub struct ConstructorParams { + // No real parameters yet; this dummy field stops the derive macro from + // generating an unused-lifetime error. + #[serde(skip)] + _phantom: PhantomData<()>, +} + +#[derive(Debug, Serialize_tuple, Deserialize_tuple)] +pub struct SealerIDReturn { + pub id_addr: Address, +} \ No newline at end of file diff --git a/build.rs b/build.rs index 7dadee3c6f..e4109c72eb 100644 --- a/build.rs +++ b/build.rs @@ -30,6 +30,7 @@ const ACTORS: &[(&Package, &ID)] = &[ ("evm", "evm"), ("eam", "eam"), ("ethaccount", "ethaccount"), + ("sealer", "sealer"), ]; const NETWORK_ENV: &str = "BUILD_FIL_NETWORK"; diff --git a/runtime/src/test_utils.rs b/runtime/src/test_utils.rs index ca6cf046fc..35c5f6a938 100644 --- a/runtime/src/test_utils.rs +++ b/runtime/src/test_utils.rs @@ -67,6 +67,7 @@ lazy_static::lazy_static! { pub static ref EVM_ACTOR_CODE_ID: Cid = make_identity_cid(b"fil/test/evm"); pub static ref EAM_ACTOR_CODE_ID: Cid = make_identity_cid(b"fil/test/eam"); pub static ref ETHACCOUNT_ACTOR_CODE_ID: Cid = make_identity_cid(b"fil/test/ethaccount"); + pub static ref SEALER_ACTOR_CODE_ID: Cid = make_identity_cid(b"fil/test/sealer"); pub static ref ACTOR_TYPES: BTreeMap = { let mut map = BTreeMap::new(); @@ -86,6 +87,7 @@ lazy_static::lazy_static! { map.insert(*EVM_ACTOR_CODE_ID, Type::EVM); map.insert(*EAM_ACTOR_CODE_ID, Type::EAM); map.insert(*ETHACCOUNT_ACTOR_CODE_ID, Type::EthAccount); + map.insert(*SEALER_ACTOR_CODE_ID, Type::Sealer); map }; pub static ref ACTOR_CODES: BTreeMap = [ @@ -105,6 +107,7 @@ lazy_static::lazy_static! { (Type::EVM, *EVM_ACTOR_CODE_ID), (Type::EAM, *EAM_ACTOR_CODE_ID), (Type::EthAccount, *ETHACCOUNT_ACTOR_CODE_ID), + (Type::Sealer, *SEALER_ACTOR_CODE_ID), ] .into_iter() .collect(); @@ -117,6 +120,7 @@ lazy_static::lazy_static! { map.insert(*PLACEHOLDER_ACTOR_CODE_ID, ()); map.insert(*EVM_ACTOR_CODE_ID, ()); map.insert(*ETHACCOUNT_ACTOR_CODE_ID, ()); + map.insert(*SEALER_ACTOR_CODE_ID, ()); map }; } diff --git a/vm_api/src/builtin.rs b/vm_api/src/builtin.rs index 0279349e69..b7371c52c7 100644 --- a/vm_api/src/builtin.rs +++ b/vm_api/src/builtin.rs @@ -24,6 +24,7 @@ pub enum Type { EVM = 14, EAM = 15, EthAccount = 16, + Sealer = 17, } impl Type { @@ -45,6 +46,7 @@ impl Type { Type::EVM => "evm", Type::EAM => "eam", Type::EthAccount => "ethaccount", + Type::Sealer => "sealer", } } } From 81c1a5d140f835ef22521b5f0bd7a1d9288ddd87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 1 May 2025 13:49:25 +0200 Subject: [PATCH 2/6] mostly complete sealerid actor --- Cargo.lock | 5 ++ actors/sealer/Cargo.toml | 6 ++ actors/sealer/src/lib.rs | 127 ++++++++++++++++++++++++++++++----- actors/sealer/src/state.rs | 68 +++++++++++++++++-- actors/sealer/src/testing.rs | 4 +- actors/sealer/src/types.rs | 53 ++++++++++++--- 6 files changed, 231 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4257774cc..6d1f7decc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -958,12 +958,17 @@ dependencies = [ name = "fil_actor_sealer" version = "16.0.1" dependencies = [ + "anyhow", + "cid", "fil_actors_runtime", + "frc42_dispatch", + "fvm_ipld_bitfield", "fvm_ipld_blockstore", "fvm_ipld_encoding", "fvm_shared", "lazy_static", "log", + "multihash-codetable", "num", "num-derive", "num-traits", diff --git a/actors/sealer/Cargo.toml b/actors/sealer/Cargo.toml index 05f84d5ede..8d708d7932 100644 --- a/actors/sealer/Cargo.toml +++ b/actors/sealer/Cargo.toml @@ -23,6 +23,12 @@ lazy_static = { workspace = true } serde = { workspace = true } fvm_ipld_blockstore = { workspace = true } fvm_ipld_encoding = { workspace = true } +cid = { workspace = true } +fvm_ipld_bitfield = { workspace = true } +multihash-codetable = { workspace = true } +anyhow = { workspace = true } +frc42_dispatch = { workspace = true } + [dev-dependencies] fil_actors_runtime = { workspace = true, features = ["test_utils", "sector-default"] } diff --git a/actors/sealer/src/lib.rs b/actors/sealer/src/lib.rs index fbf878991d..f06c0074e6 100644 --- a/actors/sealer/src/lib.rs +++ b/actors/sealer/src/lib.rs @@ -1,19 +1,31 @@ -// Copyright 2024 Curio Storage Inc. +// Copyright 2025 Curio Storage Inc. // SPDX-License-Identifier: Apache-2.0, MIT use fvm_ipld_encoding::ipld_block::IpldBlock; use fvm_shared::{METHOD_CONSTRUCTOR, MethodNum}; + use num_derive::FromPrimitive; -use fil_actors_runtime::builtin::singletons::SYSTEM_ACTOR_ADDR; +use fil_actors_runtime::builtin::singletons::INIT_ACTOR_ADDR; use fil_actors_runtime::runtime::{ActorCode, Runtime}; use fil_actors_runtime::{FIRST_EXPORTED_METHOD_NUMBER, actor_dispatch}; -use fil_actors_runtime::{ActorError, actor_error}; - -use crate::types::{ConstructorParams, SealerIDReturn}; +use fil_actors_runtime::{ActorError, ActorDowncast, actor_error}; +use fvm_ipld_bitfield::{BitField, Validate}; +use fvm_ipld_encoding::{CborStore}; +use multihash_codetable::Code; +use fvm_shared::error::ExitCode; +use fil_actors_runtime::runtime::builtins::Type; +use fvm_shared::sector::SectorNumber; +use fil_actors_runtime::runtime::policy_constants::MAX_SECTOR_NUMBER; +use fvm_shared::econ::TokenAmount; +use num_traits::Zero; +use fvm_shared::sys::SendFlags; -pub use self::state::State; +use crate::types::{ConstructorParams, ActivateSectorParams, CompactSectorNumbersParams, ActivateSectorReturn}; +use crate::ext::account; +pub use self::state::{State, CollisionPolicy}; +pub mod ext; mod state; pub mod types; pub mod testing; @@ -26,8 +38,9 @@ fil_actors_runtime::wasm_trampoline!(Actor); #[repr(u64)] pub enum Method { Constructor = METHOD_CONSTRUCTOR, - SealerID = 2, - // TODO: Add more methods as needed + + ActivateSector = frc42_dispatch::method_hash!("ActivateSector"), + CompactSectorNumbers = frc42_dispatch::method_hash!("CompactSectorNumbers"), } /// Sealer Actor @@ -36,23 +49,100 @@ pub struct Actor; impl Actor { /// Constructor for Sealer actor pub fn constructor(rt: &impl Runtime, _params: ConstructorParams) -> Result<(), ActorError> { - rt.validate_immediate_caller_is(std::iter::once(&SYSTEM_ACTOR_ADDR))?; - let id_addr = rt.message().receiver(); + rt.validate_immediate_caller_is(std::iter::once(&INIT_ACTOR_ADDR))?; + + let empty_bitfield = rt.store().put_cbor(&BitField::new(), Code::Blake2b256).map_err(|e| { + e.downcast_default(ExitCode::USR_ILLEGAL_STATE, "failed to construct empty bitfield") + })?; + + let validator = _params.validator; let state = State { - id_addr, - // TODO: initialize sector bitfield, acl, etc. + validator, + allocated_sectors: empty_bitfield, }; rt.create(&state)?; Ok(()) } - /// Returns the SealerID (the actor's ID address) - pub fn sealer_id(rt: &impl Runtime) -> Result { - rt.validate_immediate_caller_accept_any()?; - let st: State = rt.state()?; - Ok(SealerIDReturn { id_addr: st.id_addr }) + pub fn activate_sector(rt: &impl Runtime, params: ActivateSectorParams) -> Result { + rt.validate_immediate_caller_type(std::iter::once(&Type::Miner))?; + + rt.transaction(|state: &mut State, rt| { + + // Call the validator with the sector numbers + let payload = types::VerifierSignaturePayload::new( + params.sector_numbers.clone(), + rt.message().receiver(), + rt.message().caller(), + ); + + let serialized_payload = payload.serialize() + .map_err(|e| actor_error!(illegal_state, "failed to serialize payload: {}", e))?; + + // We're not actually signing anything here, just passing the payload to the validator + // The validator will verify the sector numbers are valid + let auth_params = account::AuthenticateMessageParams { + signature: params.verifier_signature, + message: serialized_payload, + }; + + // Call the validator actor to authenticate the sector numbers + let send_flags = SendFlags::default(); + + rt.send( + &state.validator, + account::AUTHENTICATE_MESSAGE_METHOD, + IpldBlock::serialize_cbor(&auth_params)?, + TokenAmount::zero(), + None, + send_flags, + )?; + + // Allocate the sector numbers after validation + state.allocate_sector_numbers( + rt.store(), + ¶ms.sector_numbers, + CollisionPolicy::DenyCollisions, + ) + })?; + + Ok(ActivateSectorReturn { + sector_numbers: params.sector_numbers, + }) } + + pub fn compact_sector_numbers(rt: &impl Runtime, params: CompactSectorNumbersParams) -> Result<(), ActorError> { + let mask_sector_numbers = params + .mask_sector_numbers + .validate() + .map_err(|e| actor_error!(illegal_argument, "invalid mask bitfield: {}", e))?; + + let last_sector_number = mask_sector_numbers + .last() + .ok_or_else(|| actor_error!(illegal_argument, "invalid mask bitfield"))? + as SectorNumber; + + if last_sector_number > MAX_SECTOR_NUMBER { + return Err(actor_error!( + illegal_argument, + "masked sector number {} exceeded max sector number", + last_sector_number + )); + } + rt.transaction(|state: &mut State, rt| { + rt.validate_immediate_caller_is([state.validator].iter())?; + + state.allocate_sector_numbers( + rt.store(), + mask_sector_numbers, + CollisionPolicy::AllowCollisions, + ) + })?; + + Ok(()) + } + /// Fallback method for unimplemented method numbers. pub fn fallback( rt: &impl Runtime, @@ -77,7 +167,8 @@ impl ActorCode for Actor { actor_dispatch! { Constructor => constructor, - SealerID => sealer_id, + ActivateSector => activate_sector, + CompactSectorNumbers => compact_sector_numbers, _ => fallback, } } diff --git a/actors/sealer/src/state.rs b/actors/sealer/src/state.rs index 14f4f15708..72c9cf1168 100644 --- a/actors/sealer/src/state.rs +++ b/actors/sealer/src/state.rs @@ -1,13 +1,73 @@ // Copyright 2024 Curio Storage Inc. // SPDX-License-Identifier: Apache-2.0, MIT +use fil_actors_runtime::{ActorError, ActorDowncast, actor_error}; use fvm_ipld_encoding::tuple::*; use fvm_shared::address::Address; +use cid::Cid; +use fvm_ipld_bitfield::BitField; +use fvm_ipld_blockstore::Blockstore; +use fvm_shared::error::ExitCode; +use multihash_codetable::Code; +use fvm_ipld_encoding::CborStore; /// State for the Sealer actor #[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone)] pub struct State { - /// The ID address of this sealer actor - pub id_addr: Address, - // TODO: Add sector bitfield, ACL/proxy address, etc. -} \ No newline at end of file + pub allocated_sectors: Cid, // BitField + + // Address of a validator actor which learns about all consumed sector numbers with the ability to veto a transaction + pub validator: Address, +} + +#[derive(PartialEq, Eq)] +pub enum CollisionPolicy { + AllowCollisions, + DenyCollisions, +} + +impl State { + /// Marks a set of sector numbers as having been allocated. + /// If policy is `DenyCollisions`, fails if the set intersects with the sector numbers already allocated. + pub fn allocate_sector_numbers( + &mut self, + store: &BS, + sector_numbers: &BitField, + policy: CollisionPolicy, + ) -> Result<(), ActorError> { + let prior_allocation = store + .get_cbor(&self.allocated_sectors) + .map_err(|e| { + e.downcast_default( + ExitCode::USR_ILLEGAL_STATE, + "failed to load allocated sectors bitfield", + ) + })? + .ok_or_else(|| actor_error!(illegal_state, "allocated sectors bitfield not found"))?; + + if policy != CollisionPolicy::AllowCollisions { + // NOTE: A fancy merge algorithm could extract this intersection while merging, below, saving + // one iteration of the runs + let collisions = &prior_allocation & sector_numbers; + if !collisions.is_empty() { + return Err(actor_error!( + illegal_argument, + "sector numbers {:?} already allocated", + collisions + )); + } + } + let new_allocation = &prior_allocation | sector_numbers; + self.allocated_sectors = + store.put_cbor(&new_allocation, Code::Blake2b256).map_err(|e| { + e.downcast_default( + ExitCode::USR_ILLEGAL_ARGUMENT, + format!( + "failed to store allocated sectors bitfield after adding {:?}", + sector_numbers, + ), + ) + })?; + Ok(()) + } +} \ No newline at end of file diff --git a/actors/sealer/src/testing.rs b/actors/sealer/src/testing.rs index b1d927e00c..dc9a92f3ab 100644 --- a/actors/sealer/src/testing.rs +++ b/actors/sealer/src/testing.rs @@ -4,7 +4,7 @@ use fvm_shared::address::Address; use crate::State; pub struct StateSummary { - pub id_addr: Address, + pub validator: Address, } pub fn check_state_invariants( @@ -13,5 +13,5 @@ pub fn check_state_invariants( ) -> (StateSummary, MessageAccumulator) { let acc = MessageAccumulator::default(); // TODO: Add invariants as needed - (StateSummary { id_addr: state.id_addr }, acc) + (StateSummary { validator: state.validator }, acc) } \ No newline at end of file diff --git a/actors/sealer/src/types.rs b/actors/sealer/src/types.rs index c96f74ea44..9c937a2bcd 100644 --- a/actors/sealer/src/types.rs +++ b/actors/sealer/src/types.rs @@ -1,16 +1,53 @@ use fvm_ipld_encoding::tuple::*; use fvm_shared::address::Address; -use std::marker::PhantomData; +use fvm_ipld_bitfield::BitField; +use fvm_ipld_encoding::strict_bytes; +use fvm_ipld_encoding::Error; #[derive(Debug, Serialize_tuple, Deserialize_tuple)] pub struct ConstructorParams { - // No real parameters yet; this dummy field stops the derive macro from - // generating an unused-lifetime error. - #[serde(skip)] - _phantom: PhantomData<()>, + pub validator: Address, } #[derive(Debug, Serialize_tuple, Deserialize_tuple)] -pub struct SealerIDReturn { - pub id_addr: Address, -} \ No newline at end of file +pub struct ActivateSectorParams { + pub sector_numbers: BitField, + pub verifier_signature: Vec, +} + +#[derive(Debug, Serialize_tuple, Deserialize_tuple)] +pub struct ActivateSectorReturn { + pub sector_numbers: BitField, +} + +#[derive(Debug, Serialize_tuple, Deserialize_tuple)] +pub struct CompactSectorNumbersParams { + pub mask_sector_numbers: BitField, +} + +pub const SIGNATURE_DOMAIN_SEPARATION_SEALER_NUMBERS: &[u8] = b"fil_sealernumbers:"; + +#[derive(Debug, Serialize_tuple, Deserialize_tuple)] +pub struct VerifierSignaturePayload { + #[serde(with = "strict_bytes")] + pub domain: Vec, + pub sector_numbers: BitField, + pub sealer_id_actor: Address, + pub miner_actor: Address, +} + +impl VerifierSignaturePayload { + pub fn new(sector_numbers: BitField, sealer_id_actor: Address, miner_actor: Address) -> Self { + Self { + domain: SIGNATURE_DOMAIN_SEPARATION_SEALER_NUMBERS.to_vec(), + sector_numbers, + sealer_id_actor, + miner_actor, + } + } + + pub fn serialize(&self) -> Result, Error> { + fvm_ipld_encoding::to_vec(self) + } +} + From 9281fd674d6e1bba67bc7bc6f677ef9e8d14c9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 1 May 2025 14:54:30 +0200 Subject: [PATCH 3/6] plumb-in sealerid to the miner actor --- actors/miner/src/ext.rs | 14 +++++ actors/miner/src/lib.rs | 98 +++++++++++++++++++++++++++----- actors/miner/src/types.rs | 3 + actors/miner/tests/types_test.rs | 24 ++++++-- actors/miner/tests/util.rs | 4 ++ actors/sealer/src/lib.rs | 6 +- 6 files changed, 126 insertions(+), 23 deletions(-) diff --git a/actors/miner/src/ext.rs b/actors/miner/src/ext.rs index f91276d66c..33a4b0673e 100644 --- a/actors/miner/src/ext.rs +++ b/actors/miner/src/ext.rs @@ -199,3 +199,17 @@ pub mod verifreg { pub sector_claims: Vec, } } + +pub mod sealer { + use super::*; + use fvm_ipld_bitfield::BitField; + + pub const ACTIVATE_SECTORS_METHOD: u64 = + frc42_dispatch::method_hash!("ActivateSectors"); + + #[derive(Debug, Serialize_tuple, Deserialize_tuple)] + pub struct ActivateSectorParams { + pub sector_numbers: BitField, + pub verifier_signature: Vec, + } +} diff --git a/actors/miner/src/lib.rs b/actors/miner/src/lib.rs index 7a5587b7af..56670cadc7 100644 --- a/actors/miner/src/lib.rs +++ b/actors/miner/src/lib.rs @@ -2033,11 +2033,12 @@ impl Actor { return Err(actor_error!(illegal_argument, "aggregate proof type must be SnarkPackV2")); } - let (validation_batch, proof_inputs, sector_numbers) = validate_ni_sectors( + let (validation_batch, proof_inputs, sector_numbers, sealer_numbers) = validate_ni_sectors( rt, ¶ms.sectors, params.seal_proof_type, params.require_activation_success, + params.sealer_id_actor, )?; if validation_batch.success_count == 0 { @@ -2056,6 +2057,17 @@ impl Actor { ¶ms.aggregate_proof, )?; + if params.sealer_id_actor.is_some() { + let Some(sig) = params.sealer_id_verifier_signature else { + return Err(actor_error!(illegal_argument, "sealer id verifier signature is required when using Sealer ID")); + }; + let Some(sealer_numbers) = sealer_numbers else { + return Err(actor_error!(illegal_argument, "sealer numbers are required when using Sealer ID")); + }; + + validate_sealer_id_numbers(rt, ¶ms.sealer_id_actor.unwrap(), sig, &sealer_numbers)?; + } + // With no data, QA power = raw power let qa_sector_power = raw_power_for_sector(info.sector_size); @@ -4952,22 +4964,29 @@ fn validate_ni_sectors( sectors: &[SectorNIActivationInfo], seal_proof_type: RegisteredSealProof, all_or_nothing: bool, -) -> Result<(BatchReturn, Vec, BitField), ActorError> { + sealer_id_actor: Option
, +) -> Result<(BatchReturn, Vec, BitField, Option), ActorError> { let receiver = rt.message().receiver(); - let miner_id = receiver.id().unwrap(); let curr_epoch = rt.curr_epoch(); let activation_epoch = curr_epoch; let challenge_earliest = curr_epoch - rt.policy().max_prove_commit_ni_randomness_lookback; let unsealed_cid = CompactCommD::empty().get_cid(seal_proof_type).unwrap(); let entropy = serialize(&receiver, "address for get verify info")?; + let expected_sealer_id = match sealer_id_actor { + Some(sealer_id) => sealer_id.id(), + None => receiver.id(), + }.unwrap(); + if sectors.is_empty() { - return Ok((BatchReturn::empty(), vec![], BitField::new())); + return Ok((BatchReturn::empty(), vec![], BitField::new(), None)); } let mut batch = BatchReturnGen::new(sectors.len()); let mut verify_infos = vec![]; let mut sector_numbers = BitField::new(); + let mut sealing_numbers = BitField::new(); + for (i, sector) in sectors.iter().enumerate() { let mut fail_validation = false; @@ -4986,6 +5005,28 @@ fn validate_ni_sectors( sector_numbers.set(sector.sector_number); + if sealer_id_actor.is_none() && sector.sector_number != sector.sealing_number { + warn!("sealing number must be same as sector number for all sectors"); + fail_validation = true; + } + + if sealer_id_actor.is_some() { + if sealing_numbers.get(sector.sealing_number) { + return Err(actor_error!( + illegal_argument, + "duplicate sealing number {}", + sector.sealing_number + )); + } + + if sector.sealing_number > MAX_SECTOR_NUMBER { + warn!("sealing number {} out of range 0..(2^63-1)", sector.sealing_number); + fail_validation = true; + } + + sealing_numbers.set(sector.sealing_number); + } + if let Err(err) = validate_expiration( rt.policy(), curr_epoch, @@ -4997,13 +5038,8 @@ fn validate_ni_sectors( fail_validation = true; } - if sector.sealer_id != miner_id { - warn!("sealer must be the same as the receiver actor for all sectors"); - fail_validation = true; - } - - if sector.sector_number != sector.sealing_number { - warn!("sealing number must be same as sector number for all sectors"); + if sector.sealer_id != expected_sealer_id { + warn!("sealer must be the same as the expected sealer ID for all sectors"); fail_validation = true; } @@ -5061,7 +5097,12 @@ fn validate_ni_sectors( } } - Ok((batch.generate(), verify_infos, sector_numbers)) + let out_sealing_numbers = match sealer_id_actor { + Some(_) => Some(sealing_numbers), + None => None, + }; + + Ok((batch.generate(), verify_infos, sector_numbers, out_sealing_numbers)) } // Validates a batch of sector sealing proofs. @@ -5126,7 +5167,7 @@ fn validate_seal_aggregate_proof( fn verify_aggregate_seal( rt: &impl Runtime, proof_inputs: &[SectorSealProofInput], - miner_actor_id: ActorID, + sealer_id: ActorID, seal_proof: RegisteredSealProof, aggregate_proof: RegisteredAggregateProof, proof_bytes: &RawBytes, @@ -5135,7 +5176,7 @@ fn verify_aggregate_seal( proof_inputs.iter().map(|pi| pi.to_aggregate_seal_verify_info()).collect(); rt.verify_aggregate_seals(&AggregateSealVerifyProofAndInfos { - miner: miner_actor_id, + miner: sealer_id, seal_proof, aggregate_proof, proof: proof_bytes.clone().into(), @@ -5144,6 +5185,35 @@ fn verify_aggregate_seal( .context_code(ExitCode::USR_ILLEGAL_ARGUMENT, "aggregate seal verify failed") } +fn validate_sealer_id_numbers( + rt: &impl Runtime, + sealer_id_actor: &Address, + sealer_id_verifier_signature: Vec, + sector_numbers: &BitField, +) -> Result<(), ActorError> { + let params = ext::sealer::ActivateSectorParams { + sector_numbers: sector_numbers.clone(), + verifier_signature: sealer_id_verifier_signature, + }; + + let result = rt.send_simple( + &sealer_id_actor, + ext::sealer::ACTIVATE_SECTORS_METHOD, + IpldBlock::serialize_cbor(¶ms)?, + TokenAmount::zero(), + )?; + + if !result.exit_code.is_success() { + return Err(ActorError::checked( + result.exit_code, + "failed to verify Sealer ID sector numbers".to_string(), + None, + )); + } + + Ok(()) +} + fn verify_deals( rt: &impl Runtime, sectors: &[ext::market::SectorDeals], diff --git a/actors/miner/src/types.rs b/actors/miner/src/types.rs index e8e2d24f8d..ab58ceff2e 100644 --- a/actors/miner/src/types.rs +++ b/actors/miner/src/types.rs @@ -155,6 +155,9 @@ pub struct ProveCommitSectorsNIParams { pub aggregate_proof_type: RegisteredAggregateProof, // Proof type for aggregation pub proving_deadline: u64, // The Window PoST deadline index at which to schedule the new sectors pub require_activation_success: bool, // Whether to abort if any sector activation fails + + pub sealer_id_actor: Option
, // Optional sealer ID actor from which the sector numbers are derived from + pub sealer_id_verifier_signature: Option>, // Verifier signature } #[derive(Clone, Debug, Eq, PartialEq, Serialize_tuple, Deserialize_tuple)] diff --git a/actors/miner/tests/types_test.rs b/actors/miner/tests/types_test.rs index 221a1a2bbb..055a191041 100644 --- a/actors/miner/tests/types_test.rs +++ b/actors/miner/tests/types_test.rs @@ -30,9 +30,13 @@ mod serialization { aggregate_proof_type: RegisteredAggregateProof::SnarkPackV2, proving_deadline: 2, require_activation_success: false, + + sealer_id_actor: None, + sealer_id_verifier_signature: None, + final_sector_numbers: None, }, - // [[],byte[],8,1,2,false] - &hex!("868040080102f4")[..], + // [[],byte[],8,1,2,false,null,null,null] + &hex!("898040080102f4f6f6f6")[..], ), ( ProveCommitSectorsNIParams { @@ -49,9 +53,13 @@ mod serialization { aggregate_proof_type: RegisteredAggregateProof::SnarkPackV2, proving_deadline: 6, require_activation_success: true, + + sealer_id_actor: None, + sealer_id_verifier_signature: None, + final_sector_numbers: None, }, - // [[[1,2,bagboea4seaaqa,3,4,5]],byte[deadbeef],18,1,6,true] - &hex!("8681860102d82a49000182e2039220010003040544deadbeef120106f5"), + // [[[1,2,bagboea4seaaqa,3,4,5]],byte[deadbeef],18,1,6,true,null,null,null] + &hex!("8981860102d82a49000182e2039220010003040544deadbeef120106f5f6f6f6"), ), ( ProveCommitSectorsNIParams { @@ -78,10 +86,14 @@ mod serialization { aggregate_proof_type: RegisteredAggregateProof::SnarkPackV2, proving_deadline: 11, require_activation_success: false, + + sealer_id_actor: None, + sealer_id_verifier_signature: None, + final_sector_numbers: None, }, - // [[[1,2,bagboea4seaaqa,3,4,5],[6,7,bagboea4seaaqc,8,9,10]],byte[deadbeef],18,1,11,false] + // [[[1,2,bagboea4seaaqa,3,4,5],[6,7,bagboea4seaaqc,8,9,10]],byte[deadbeef],18,1,11,false,null,null,null] &hex!( - "8682860102d82a49000182e20392200100030405860607d82a49000182e2039220010108090a44deadbeef12010bf4" + "8982860102d82a49000182e20392200100030405860607d82a49000182e2039220010108090a44deadbeef12010bf4f6f6f6" ), ), ]; diff --git a/actors/miner/tests/util.rs b/actors/miner/tests/util.rs index 842dde8bb9..90a9b9b5a4 100644 --- a/actors/miner/tests/util.rs +++ b/actors/miner/tests/util.rs @@ -566,6 +566,10 @@ impl ActorHarness { aggregate_proof_type: RegisteredAggregateProof::SnarkPackV2, proving_deadline, require_activation_success: true, + + sealer_id_actor: None, + sealer_id_verifier_signature: None, + final_sector_numbers: None, } } diff --git a/actors/sealer/src/lib.rs b/actors/sealer/src/lib.rs index f06c0074e6..08a484fd81 100644 --- a/actors/sealer/src/lib.rs +++ b/actors/sealer/src/lib.rs @@ -39,7 +39,7 @@ fil_actors_runtime::wasm_trampoline!(Actor); pub enum Method { Constructor = METHOD_CONSTRUCTOR, - ActivateSector = frc42_dispatch::method_hash!("ActivateSector"), + ActivateSectors = frc42_dispatch::method_hash!("ActivateSectors"), CompactSectorNumbers = frc42_dispatch::method_hash!("CompactSectorNumbers"), } @@ -64,7 +64,7 @@ impl Actor { Ok(()) } - pub fn activate_sector(rt: &impl Runtime, params: ActivateSectorParams) -> Result { + pub fn activate_sectors(rt: &impl Runtime, params: ActivateSectorParams) -> Result { rt.validate_immediate_caller_type(std::iter::once(&Type::Miner))?; rt.transaction(|state: &mut State, rt| { @@ -167,7 +167,7 @@ impl ActorCode for Actor { actor_dispatch! { Constructor => constructor, - ActivateSector => activate_sector, + ActivateSectors => activate_sectors, CompactSectorNumbers => compact_sector_numbers, _ => fallback, } From a3026b6dbe8e43679f83801954004f6deae4b2ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 1 May 2025 15:25:38 +0200 Subject: [PATCH 4/6] change sealer_id_actor type to ActorID --- actors/miner/src/lib.rs | 16 ++++++++-------- actors/miner/src/types.rs | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/actors/miner/src/lib.rs b/actors/miner/src/lib.rs index 56670cadc7..4ba6696a11 100644 --- a/actors/miner/src/lib.rs +++ b/actors/miner/src/lib.rs @@ -2057,7 +2057,7 @@ impl Actor { ¶ms.aggregate_proof, )?; - if params.sealer_id_actor.is_some() { + if let Some(sealer_id_actor) = params.sealer_id_actor { let Some(sig) = params.sealer_id_verifier_signature else { return Err(actor_error!(illegal_argument, "sealer id verifier signature is required when using Sealer ID")); }; @@ -2065,7 +2065,7 @@ impl Actor { return Err(actor_error!(illegal_argument, "sealer numbers are required when using Sealer ID")); }; - validate_sealer_id_numbers(rt, ¶ms.sealer_id_actor.unwrap(), sig, &sealer_numbers)?; + validate_sealer_id_numbers(rt, sealer_id_actor, sig, &sealer_numbers)?; } // With no data, QA power = raw power @@ -4964,7 +4964,7 @@ fn validate_ni_sectors( sectors: &[SectorNIActivationInfo], seal_proof_type: RegisteredSealProof, all_or_nothing: bool, - sealer_id_actor: Option
, + sealer_id_actor: Option, ) -> Result<(BatchReturn, Vec, BitField, Option), ActorError> { let receiver = rt.message().receiver(); let curr_epoch = rt.curr_epoch(); @@ -4974,9 +4974,9 @@ fn validate_ni_sectors( let entropy = serialize(&receiver, "address for get verify info")?; let expected_sealer_id = match sealer_id_actor { - Some(sealer_id) => sealer_id.id(), - None => receiver.id(), - }.unwrap(); + Some(sealer_id) => sealer_id, + None => receiver.id().unwrap(), + }; if sectors.is_empty() { return Ok((BatchReturn::empty(), vec![], BitField::new(), None)); @@ -5187,7 +5187,7 @@ fn verify_aggregate_seal( fn validate_sealer_id_numbers( rt: &impl Runtime, - sealer_id_actor: &Address, + sealer_id_actor: ActorID, sealer_id_verifier_signature: Vec, sector_numbers: &BitField, ) -> Result<(), ActorError> { @@ -5197,7 +5197,7 @@ fn validate_sealer_id_numbers( }; let result = rt.send_simple( - &sealer_id_actor, + &Address::new_id(sealer_id_actor), ext::sealer::ACTIVATE_SECTORS_METHOD, IpldBlock::serialize_cbor(¶ms)?, TokenAmount::zero(), diff --git a/actors/miner/src/types.rs b/actors/miner/src/types.rs index ab58ceff2e..b7b8d4ba52 100644 --- a/actors/miner/src/types.rs +++ b/actors/miner/src/types.rs @@ -156,7 +156,7 @@ pub struct ProveCommitSectorsNIParams { pub proving_deadline: u64, // The Window PoST deadline index at which to schedule the new sectors pub require_activation_success: bool, // Whether to abort if any sector activation fails - pub sealer_id_actor: Option
, // Optional sealer ID actor from which the sector numbers are derived from + pub sealer_id_actor: Option, // Optional sealer ID actor from which the sector numbers are derived from pub sealer_id_verifier_signature: Option>, // Verifier signature } From fe2ff641f06f6df9a4d81f1af8c81175abf824db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 1 May 2025 15:28:26 +0200 Subject: [PATCH 5/6] missing file --- actors/sealer/src/ext.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 actors/sealer/src/ext.rs diff --git a/actors/sealer/src/ext.rs b/actors/sealer/src/ext.rs new file mode 100644 index 0000000000..9f2a14e67a --- /dev/null +++ b/actors/sealer/src/ext.rs @@ -0,0 +1,17 @@ +use fvm_ipld_encoding::strict_bytes; +use fvm_ipld_encoding::tuple::*; + +pub mod account { + use super::*; + + pub const AUTHENTICATE_MESSAGE_METHOD: u64 = + frc42_dispatch::method_hash!("AuthenticateMessage"); + + #[derive(Serialize_tuple, Deserialize_tuple)] + pub struct AuthenticateMessageParams { + #[serde(with = "strict_bytes")] + pub signature: Vec, + #[serde(with = "strict_bytes")] + pub message: Vec, + } +} From d8455a50f230203635eaecc40b46d7c6f91d5975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 2 May 2025 07:00:30 +0200 Subject: [PATCH 6/6] address review --- actors/miner/src/lib.rs | 6 ++++-- actors/sealer/src/testing.rs | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/actors/miner/src/lib.rs b/actors/miner/src/lib.rs index 4ba6696a11..6da0db9b83 100644 --- a/actors/miner/src/lib.rs +++ b/actors/miner/src/lib.rs @@ -5189,13 +5189,15 @@ fn validate_sealer_id_numbers( rt: &impl Runtime, sealer_id_actor: ActorID, sealer_id_verifier_signature: Vec, - sector_numbers: &BitField, + sealer_numbers: &BitField, ) -> Result<(), ActorError> { let params = ext::sealer::ActivateSectorParams { - sector_numbers: sector_numbers.clone(), + sector_numbers: sealer_numbers.clone(), verifier_signature: sealer_id_verifier_signature, }; + assert_eq(rt.get_code_cid_for_type(Type::Sealer), rt.get_actor_code_cid(sealer_id_actor)). + let result = rt.send_simple( &Address::new_id(sealer_id_actor), ext::sealer::ACTIVATE_SECTORS_METHOD, diff --git a/actors/sealer/src/testing.rs b/actors/sealer/src/testing.rs index dc9a92f3ab..96db2ba6ce 100644 --- a/actors/sealer/src/testing.rs +++ b/actors/sealer/src/testing.rs @@ -12,6 +12,10 @@ pub fn check_state_invariants( _id_address: &Address, ) -> (StateSummary, MessageAccumulator) { let acc = MessageAccumulator::default(); - // TODO: Add invariants as needed + + // TODO: Add invariants + // check allocated sectors bitfield + // check verifier actor exists + (StateSummary { validator: state.validator }, acc) } \ No newline at end of file