diff --git a/blockchain/blocks/Cargo.toml b/blockchain/blocks/Cargo.toml index 6a775a15db95..be72770bc8a2 100644 --- a/blockchain/blocks/Cargo.toml +++ b/blockchain/blocks/Cargo.toml @@ -14,6 +14,7 @@ derive_builder = "0.9" serde = { version = "1.0", features = ["derive"] } encoding = { package = "forest_encoding", path = "../../encoding" } num-bigint = { path = "../../utils/bigint", package = "forest_bigint" } +sha2 = { version = "0.8", default-features = false } thiserror = "1.0" vm = { path = "../../vm" } diff --git a/blockchain/blocks/src/header.rs b/blockchain/blocks/src/header.rs index ff084a9a686e..7468288d63d6 100644 --- a/blockchain/blocks/src/header.rs +++ b/blockchain/blocks/src/header.rs @@ -17,9 +17,13 @@ use num_bigint::{ BigUint, }; use serde::Deserialize; +use sha2::Digest; use std::cmp::Ordering; use std::fmt; use std::time::{SystemTime, UNIX_EPOCH}; +// TODO should probably have a central place for constants +const SHA_256_BITS: usize = 256; +const BLOCKS_PER_EPOCH: u64 = 5; /// Header of a block /// @@ -301,6 +305,31 @@ impl BlockHeader { Ok(()) } + /// Returns true if (h(vrfout) * totalPower) < (e * sectorSize * 2^256) + pub fn is_ticket_winner(&self, mpow: BigUint, net_pow: BigUint) -> bool { + /* + Need to check that + (h(vrfout) + 1) / (max(h) + 1) <= e * myPower / totalPower + max(h) == 2^256-1 + which in terms of integer math means: + (h(vrfout) + 1) * totalPower <= e * myPower * 2^256 + in 2^256 space, it is equivalent to: + h(vrfout) * totalPower < e * myPower * 2^256 + */ + + // TODO switch ticket for election_proof + let h = sha2::Sha256::digest(self.ticket.vrfproof.bytes()); + let mut lhs = BigUint::from_bytes_le(&h); + lhs *= net_pow; + + // rhs = sectorSize * 2^256 + // rhs = sectorSize << 256 + let mut rhs = mpow << SHA_256_BITS; + rhs *= BigUint::from(BLOCKS_PER_EPOCH); + + // h(vrfout) * totalPower < e * sectorSize * 2^256 + lhs < rhs + } } /// human-readable string representation of a block CID diff --git a/blockchain/chain_sync/src/sync.rs b/blockchain/chain_sync/src/sync.rs index fbb04c108076..a45ea33ba745 100644 --- a/blockchain/chain_sync/src/sync.rs +++ b/blockchain/chain_sync/src/sync.rs @@ -596,7 +596,6 @@ where /// Validates block semantically according to https://github.com/filecoin-project/specs/blob/6ab401c0b92efb6420c6e198ec387cf56dc86057/validation.md fn validate(&self, block: &Block) -> Result<(), Error> { - // get header from full block let header = block.header(); // check if block has been signed @@ -613,23 +612,32 @@ where // check messages to ensure valid state transitions self.check_blk_msgs(block.clone(), &parent_tipset)?; + // TODO use computed state_root instead of parent_tipset.parent_state() + let work_addr = self + .state_manager + .get_miner_work_addr(&parent_tipset.parent_state(), header.miner_address())?; // block signature check - // TODO need to pass in raw miner address; temp using header miner address - // see https://github.com/filecoin-project/lotus/blob/master/chain/sync.go#L611 - header.check_block_signature(header.miner_address())?; + header.check_block_signature(&work_addr)?; - // TODO: incomplete, still need to retrieve power in order to ensure ticket is the winner - let _slash = self - .state_manager - .miner_slashed(header.miner_address(), &parent_tipset)?; - let _sector_size = self + let slash = self .state_manager - .miner_sector_size(header.miner_address(), &parent_tipset)?; + .is_miner_slashed(header.miner_address(), &parent_tipset.parent_state())?; + if slash { + return Err(Error::Validation( + "Received block was from slashed or invalid miner".to_owned(), + )); + } - // TODO winner_check - // TODO miner_check + let (c_pow, net_pow) = self + .state_manager + .get_power(&parent_tipset.parent_state(), header.miner_address())?; + // ticket winner check + if !header.is_ticket_winner(c_pow, net_pow) { + return Err(Error::Validation( + "Miner created a block but was not a winner".to_owned(), + )); + } // TODO verify_ticket_vrf - // TODO verify_election_proof_check Ok(()) } diff --git a/blockchain/state_manager/Cargo.toml b/blockchain/state_manager/Cargo.toml index 7a3e8ccbf639..1ace9f79d971 100644 --- a/blockchain/state_manager/Cargo.toml +++ b/blockchain/state_manager/Cargo.toml @@ -7,10 +7,12 @@ edition = "2018" [dependencies] address = { package = "forest_address", path = "../../vm/address/" } actor = { path = "../../vm/actor/" } +cid = { package = "forest_cid", path = "../../ipld/cid" } db = { path = "../../node/db/" } encoding = { package = "forest_encoding", path = "../../encoding/" } +num-bigint = { path = "../../utils/bigint", package = "forest_bigint" } state_tree = { path = "../../vm/state_tree/" } -vm = { path = "../../vm" } +default_runtime = { path = "../../vm/default_runtime/" } blockstore = { package = "ipld_blockstore", path = "../../ipld/blockstore/" } forest_blocks = { path = "../../blockchain/blocks" } thiserror = "1.0" \ No newline at end of file diff --git a/blockchain/state_manager/src/errors.rs b/blockchain/state_manager/src/errors.rs index 685cba04ebad..090eacb2915b 100644 --- a/blockchain/state_manager/src/errors.rs +++ b/blockchain/state_manager/src/errors.rs @@ -19,4 +19,13 @@ pub enum Error { /// Error originating from key-value store #[error(transparent)] DB(#[from] DbErr), + /// Other state manager error + #[error("{0}")] + Other(String), +} + +impl From for Error { + fn from(e: String) -> Self { + Error::Other(e) + } } diff --git a/blockchain/state_manager/src/lib.rs b/blockchain/state_manager/src/lib.rs index 38a4a2ef96b5..5a68f7751b6e 100644 --- a/blockchain/state_manager/src/lib.rs +++ b/blockchain/state_manager/src/lib.rs @@ -4,14 +4,15 @@ mod errors; pub use self::errors::*; -use actor::{miner, ActorState}; +use actor::{miner, power, ActorState, STORAGE_POWER_ACTOR_ADDR}; use address::Address; use blockstore::BlockStore; +use cid::Cid; +use default_runtime::resolve_to_key_addr; use encoding::de::DeserializeOwned; -use forest_blocks::Tipset; +use num_bigint::BigUint; use state_tree::{HamtStateTree, StateTree}; use std::sync::Arc; -use vm::SectorSize; /// Intermediary for retrieving state objects and updating actor states pub struct StateManager { @@ -27,12 +28,12 @@ where Self { bs } } /// Loads actor state from IPLD Store - fn load_actor_state(&self, addr: &Address, ts: &Tipset) -> Result + fn load_actor_state(&self, addr: &Address, state_cid: &Cid) -> Result where D: DeserializeOwned, { let actor = self - .get_actor(addr, ts)? + .get_actor(addr, state_cid)? .ok_or_else(|| Error::ActorNotFound(addr.to_string()))?; let act: D = self .bs @@ -41,23 +42,45 @@ where .ok_or_else(|| Error::ActorStateNotFound(actor.state.to_string()))?; Ok(act) } - /// Returns the epoch at which the miner was slashed at - pub fn miner_slashed(&self, _addr: &Address, _ts: &Tipset) -> Result { - // TODO update to use power actor if needed - todo!() + fn get_actor(&self, addr: &Address, state_cid: &Cid) -> Result, Error> { + let state = + HamtStateTree::new_from_root(self.bs.as_ref(), state_cid).map_err(Error::State)?; + state.get_actor(addr).map_err(Error::State) } - /// Returns the amount of space in each sector committed to the network by this miner - pub fn miner_sector_size(&self, addr: &Address, ts: &Tipset) -> Result { - let act: miner::State = self.load_actor_state(addr, ts)?; - // * Switch back to retrieving from Cid if/when changed in actors - // let info: miner::MinerInfo = self.bs.get(&act.info)?.ok_or_else(|| { - // Error::State("Could not retrieve miner info from IPLD store".to_owned()) - // })?; - Ok(act.info.sector_size) + /// Returns true if miner has been slashed or is considered invalid + pub fn is_miner_slashed(&self, addr: &Address, state_cid: &Cid) -> Result { + let ms: miner::State = self.load_actor_state(addr, state_cid)?; + if ms.post_state.has_failed_post() { + return Ok(true); + } + + let ps: power::State = self.load_actor_state(&*STORAGE_POWER_ACTOR_ADDR, state_cid)?; + match ps.get_claim(self.bs.as_ref(), addr)? { + Some(_) => Ok(false), + None => Ok(true), + } } - pub fn get_actor(&self, addr: &Address, ts: &Tipset) -> Result, Error> { - let state = HamtStateTree::new_from_root(self.bs.as_ref(), ts.parent_state()) - .map_err(Error::State)?; - state.get_actor(addr).map_err(Error::State) + /// Returns raw work address of a miner + pub fn get_miner_work_addr(&self, state_cid: &Cid, addr: &Address) -> Result { + let ms: miner::State = self.load_actor_state(addr, state_cid)?; + + let state = + HamtStateTree::new_from_root(self.bs.as_ref(), state_cid).map_err(Error::State)?; + // Note: miner::State info likely to be changed to CID + let addr = resolve_to_key_addr(&state, self.bs.as_ref(), &ms.info.worker) + .map_err(|e| Error::Other(format!("Failed to resolve key address; error: {}", e)))?; + Ok(addr) + } + /// Returns specified actor's claimed power and total network power as a tuple + pub fn get_power(&self, state_cid: &Cid, addr: &Address) -> Result<(BigUint, BigUint), Error> { + let ps: power::State = self.load_actor_state(&*STORAGE_POWER_ACTOR_ADDR, state_cid)?; + + if let Some(claim) = ps.get_claim(self.bs.as_ref(), addr)? { + Ok((claim.power, ps.total_network_power)) + } else { + Err(Error::State( + "Failed to retrieve claimed power from actor state".to_owned(), + )) + } } } diff --git a/vm/actor/src/builtin/power/state.rs b/vm/actor/src/builtin/power/state.rs index 54840ea9d486..d19628742ddf 100644 --- a/vm/actor/src/builtin/power/state.rs +++ b/vm/actor/src/builtin/power/state.rs @@ -191,7 +191,7 @@ impl State { } /// Gets claim from claims map by address - pub(super) fn get_claim( + pub fn get_claim( &self, store: &BS, a: &Address, diff --git a/vm/default_runtime/src/lib.rs b/vm/default_runtime/src/lib.rs index 5143de48ca09..392f90690ce2 100644 --- a/vm/default_runtime/src/lib.rs +++ b/vm/default_runtime/src/lib.rs @@ -7,9 +7,9 @@ mod gas_syscalls; use self::gas_block_store::GasBlockStore; use self::gas_syscalls::GasSyscalls; use actor::{ - self, ACCOUNT_ACTOR_CODE_ID, CRON_ACTOR_CODE_ID, INIT_ACTOR_CODE_ID, MARKET_ACTOR_CODE_ID, - MINER_ACTOR_CODE_ID, MULTISIG_ACTOR_CODE_ID, PAYCH_ACTOR_CODE_ID, POWER_ACTOR_CODE_ID, - REWARD_ACTOR_CODE_ID, SYSTEM_ACTOR_CODE_ID, + self, account, ACCOUNT_ACTOR_CODE_ID, CRON_ACTOR_CODE_ID, INIT_ACTOR_CODE_ID, + MARKET_ACTOR_CODE_ID, MINER_ACTOR_CODE_ID, MULTISIG_ACTOR_CODE_ID, PAYCH_ACTOR_CODE_ID, + POWER_ACTOR_CODE_ID, REWARD_ACTOR_CODE_ID, SYSTEM_ACTOR_CODE_ID, }; use address::{Address, Protocol}; use byteorder::{BigEndian, WriteBytesExt}; @@ -156,39 +156,6 @@ where Ok(()) } - fn resolve_to_key_addr(&self, addr: &Address) -> Result { - if addr.protocol() == Protocol::BLS || addr.protocol() == Protocol::Secp256k1 { - return Ok(addr.clone()); - } - let act = self.get_actor(&addr)?; - if act.code != *ACCOUNT_ACTOR_CODE_ID { - return Err(self.abort( - ExitCode::SysErrInternal, - format!("address {:?} was not for an account actor", addr), - )); - } - let actor_state: actor::account::State = self - .store - .get(&act.state) - // TODO this needs to be a fatal error - .map_err(|e| { - self.abort( - ExitCode::SysErrInternal, - format!( - "Could not get actor state in resolve_to_key_addr: {:?}", - e.to_string() - ), - ) - })? - .ok_or_else(|| { - self.abort( - ExitCode::SysErrInternal, - "Actor state not found in resolve_to_key_addr", - ) - })?; - - Ok(actor_state.address) - } } impl Runtime for DefaultRuntime<'_, '_, '_, ST, BS, SYS> @@ -378,7 +345,7 @@ where ActorError::new(exit_code, msg.as_ref().to_owned()) } fn new_actor_address(&mut self) -> Result { - let oa = self.resolve_to_key_addr(&self.origin)?; + let oa = resolve_to_key_addr(self.state, &self.store, &self.origin)?; let mut b = to_vec(&oa).map_err(|e| { self.abort( ExitCode::ErrSerialization, @@ -546,3 +513,51 @@ fn transfer( Ok(()) } + +/// Returns public address of the specified actor address +pub fn resolve_to_key_addr<'st, 'bs, ST, BS>( + st: &'st ST, + store: &'bs BS, + addr: &Address, +) -> Result +where + ST: StateTree, + BS: BlockStore, +{ + if addr.protocol() == Protocol::BLS || addr.protocol() == Protocol::Secp256k1 { + return Ok(addr.clone()); + } + + let act = st + .get_actor(&addr) + .map_err(|e| ActorError::new(ExitCode::SysErrInternal, e))? + .ok_or_else(|| { + ActorError::new( + ExitCode::SysErrInternal, + format!("Failed to retrieve actor: {}", addr), + ) + })?; + + if act.code != *ACCOUNT_ACTOR_CODE_ID { + return Err(ActorError::new_fatal(format!( + "Address was not found for an account actor: {}", + addr + ))); + } + let acc_st: account::State = store + .get(&act.state) + .map_err(|e| { + ActorError::new_fatal(format!( + "Failed to get account actor state for: {}, e: {}", + addr, e + )) + })? + .ok_or_else(|| { + ActorError::new_fatal(format!( + "Address was not found for an account actor: {}", + addr + )) + })?; + + Ok(acc_st.address) +}