Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements verify consensus fault syscall and reorg vm crates #386

Merged
merged 10 commits into from
Apr 30, 2020
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ members = [
"vm/runtime",
"vm/state_tree",
"vm/interpreter",
"vm/default_runtime",
"node",
"node/clock",
"crypto",
Expand Down
2 changes: 0 additions & 2 deletions blockchain/state_manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ db = { path = "../../node/db/" }
encoding = { package = "forest_encoding", path = "../../encoding/" }
num-bigint = { path = "../../utils/bigint", package = "forest_bigint" }
state_tree = { path = "../../vm/state_tree/" }
default_runtime = { path = "../../vm/default_runtime/" }
blockstore = { package = "ipld_blockstore", path = "../../ipld/blockstore/" }
forest_blocks = { path = "../../blockchain/blocks" }
thiserror = "1.0"
interpreter = { path = "../../vm/interpreter/" }
runtime = { path = "../../vm/runtime/" }
ipld_amt = { path = "../../ipld/amt/" }
11 changes: 7 additions & 4 deletions blockchain/state_manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ use address::{Address, Protocol};
use blockstore::BlockStore;
use blockstore::BufferedBlockStore;
use cid::Cid;
use default_runtime::resolve_to_key_addr;
use encoding::de::DeserializeOwned;
use forest_blocks::FullTipset;
use interpreter::VM;
use interpreter::{resolve_to_key_addr, DefaultSyscalls, VM};
use ipld_amt::Amt;
use num_bigint::BigUint;
use runtime::DefaultSyscalls;
use state_tree::StateTree;
use std::error::Error as StdError;
use std::sync::Arc;
Expand Down Expand Up @@ -93,7 +91,12 @@ where
pub fn apply_blocks(&self, ts: &FullTipset) -> Result<(Cid, Cid), Box<dyn StdError>> {
let mut buf_store = BufferedBlockStore::new(self.bs.as_ref());
// TODO possibly switch out syscalls to be saved at state manager level
let mut vm = VM::new(ts.parent_state(), &buf_store, ts.epoch(), DefaultSyscalls)?;
let mut vm = VM::new(
ts.parent_state(),
&buf_store,
ts.epoch(),
DefaultSyscalls::new(&buf_store),
)?;

// Apply tipset messages
let receipts = vm.apply_tip_set_messages(ts)?;
Expand Down
4 changes: 2 additions & 2 deletions vm/actor/src/builtin/market/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ impl Actor {
.map_err(|e| {
ActorError::new(
ExitCode::SysErrorIllegalArgument,
format!("failed to compute unsealed sector CID: {}", e.msg()),
format!("failed to compute unsealed sector CID: {}", e),
)
})?;

Expand Down Expand Up @@ -610,7 +610,7 @@ where
.map_err(|e| {
ActorError::new(
ExitCode::ErrIllegalArgument,
format!("signature proposal invalid: {}", e.msg()),
format!("signature proposal invalid: {}", e),
)
})?;

Expand Down
4 changes: 2 additions & 2 deletions vm/actor/src/builtin/paych/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl Actor {
.map_err(|e| {
ActorError::new(
ExitCode::ErrIllegalArgument,
format!("voucher signature invalid: {}", e.msg()),
format!("voucher signature invalid: {}", e),
)
})?;

Expand All @@ -128,7 +128,7 @@ impl Actor {
}

if !sv.secret_pre_image.is_empty() {
let hashed_secret: &[u8] = &rt.syscalls().hash_blake2b(&params.secret)?;
let hashed_secret: &[u8] = &rt.syscalls().hash_blake2b(&params.secret).unwrap();
dutterbutter marked this conversation as resolved.
Show resolved Hide resolved
if hashed_secret != sv.secret_pre_image.as_slice() {
return Err(ActorError::new(
ExitCode::ErrIllegalArgument,
Expand Down
9 changes: 7 additions & 2 deletions vm/actor/src/builtin/power/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,10 +482,15 @@ impl Actor {
.map_err(|e| {
ActorError::new(
ExitCode::ErrIllegalArgument,
format!("fault not verified: {}", e.msg()),
format!("fault not verified: {}", e),
)
})?
.ok_or_else(|| {
ActorError::new(
ExitCode::ErrIllegalArgument,
"fault not verified".to_owned(),
dutterbutter marked this conversation as resolved.
Show resolved Hide resolved
)
})?;

let reporter = *rt.message().from();
let reward = rt.transaction(|st: &mut State, rt| {
let claim = Self::get_claim_or_abort(st, rt.store(), &fault.target)?;
Expand Down
27 changes: 0 additions & 27 deletions vm/default_runtime/Cargo.toml

This file was deleted.

8 changes: 6 additions & 2 deletions vm/interpreter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ forest_encoding = { path = "../../encoding" }
cid = { package = "forest_cid", path = "../../ipld/cid" }
crypto = { path = "../../crypto" }
num-traits = "0.2.11"
default_runtime = { path = "../default_runtime" }
byteorder = "1.3.4"
state_tree = { path = "../state_tree" }
log = "0.4.8"
log = "0.4.8"
db = { path = "../../node/db" }

[dev-dependencies]
ipld_hamt = { path = "../../ipld/hamt" }
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

mod gas_block_store;
mod gas_syscalls;

use self::gas_block_store::GasBlockStore;
use self::gas_syscalls::GasSyscalls;
use super::gas_block_store::GasBlockStore;
use super::gas_syscalls::GasSyscalls;
use actor::{
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,
Expand All @@ -31,13 +28,10 @@ use vm::{
};

/// Implementation of the Runtime trait.
pub struct DefaultRuntime<'db, 'msg, 'st, BS, SYS>
where
SYS: Copy,
{
pub struct DefaultRuntime<'db, 'msg, 'st, 'sys, BS, SYS> {
state: &'st mut StateTree<'db, BS>,
store: GasBlockStore<'db, BS>,
syscalls: GasSyscalls<SYS>,
syscalls: GasSyscalls<'sys, SYS>,
gas_tracker: Rc<RefCell<GasTracker>>,
message: &'msg UnsignedMessage,
epoch: ChainEpoch,
Expand All @@ -47,17 +41,17 @@ where
price_list: PriceList,
}

impl<'db, 'msg, 'st, BS, SYS> DefaultRuntime<'db, 'msg, 'st, BS, SYS>
impl<'db, 'msg, 'st, 'sys, BS, SYS> DefaultRuntime<'db, 'msg, 'st, 'sys, BS, SYS>
where
BS: BlockStore,
SYS: Syscalls + Copy,
SYS: Syscalls,
{
/// Constructs a new Runtime
#[allow(clippy::too_many_arguments)]
pub fn new(
state: &'st mut StateTree<'db, BS>,
store: &'db BS,
syscalls: SYS,
syscalls: &'sys SYS,
gas_used: i64,
message: &'msg UnsignedMessage,
epoch: ChainEpoch,
Expand Down Expand Up @@ -158,10 +152,10 @@ where
}
}

impl<BS, SYS> Runtime<BS> for DefaultRuntime<'_, '_, '_, BS, SYS>
impl<BS, SYS> Runtime<BS> for DefaultRuntime<'_, '_, '_, '_, BS, SYS>
where
BS: BlockStore,
SYS: Syscalls + Copy,
SYS: Syscalls,
{
fn message(&self) -> &UnsignedMessage {
&self.message
Expand Down Expand Up @@ -408,13 +402,13 @@ where
/// Shared logic between the DefaultRuntime and the Interpreter.
/// It invokes methods on different Actors based on the Message.
pub fn internal_send<BS, SYS>(
runtime: &mut DefaultRuntime<'_, '_, '_, BS, SYS>,
runtime: &mut DefaultRuntime<'_, '_, '_, '_, BS, SYS>,
msg: &UnsignedMessage,
_gas_cost: i64,
) -> Result<Serialized, ActorError>
where
BS: BlockStore,
SYS: Syscalls + Copy,
SYS: Syscalls,
{
runtime.charge_gas(
runtime
Expand Down
153 changes: 153 additions & 0 deletions vm/interpreter/src/default_syscalls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use crate::resolve_to_key_addr;
use actor::miner;
use blocks::BlockHeader;
use clock::ChainEpoch;
use forest_encoding::from_slice;
use ipld_blockstore::BlockStore;
use runtime::{ConsensusFault, ConsensusFaultType, Syscalls};
use state_tree::StateTree;
use std::error::Error as StdError;

/// Default syscalls information
pub struct DefaultSyscalls<'bs, BS> {
store: &'bs BS,
}

impl<'bs, BS> DefaultSyscalls<'bs, BS> {
/// DefaultSyscalls constuctor
pub fn new(store: &'bs BS) -> Self {
Self { store }
}
}

impl<'bs, BS> Syscalls for DefaultSyscalls<'bs, BS>
where
BS: BlockStore,
{
/// Verifies that two block headers provide proof of a consensus fault:
/// - both headers mined by the same actor
/// - headers are different
/// - first header is of the same or lower epoch as the second
/// - at least one of the headers appears in the current chain at or after epoch `earliest`
/// - the headers provide evidence of a fault (see the spec for the different fault types).
/// The parameters are all serialized block headers. The third "extra" parameter is consulted only for
/// the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the
/// blocks in the parent of h2 (i.e. h2's grandparent).
/// Returns an error if the headers don't prove a fault.
fn verify_consensus_fault(
&self,
h1: &[u8],
h2: &[u8],
extra: &[u8],
_earliest: ChainEpoch, // unused in lotus
) -> Result<Option<ConsensusFault>, Box<dyn StdError>> {
// Note that block syntax is not validated. Any validly signed block will be accepted pursuant to the below conditions.
// Whether or not it could ever have been accepted in a chain is not checked/does not matter here.
// for that reason when checking block parent relationships, rather than instantiating a Tipset to do so
// (which runs a syntactic check), we do it directly on the CIDs.

// (0) cheap preliminary checks

if h1 == h2 {
return Err(format!(
"no consensus fault: submitted blocks are the same: {:?}, {:?}",
h1, h2
)
.into());
};
let bh_1: BlockHeader = from_slice(h1)?;
let bh_2: BlockHeader = from_slice(h2)?;

// (1) check conditions necessary to any consensus fault

if bh_1.miner_address() != bh_2.miner_address() {
return Err(format!(
"no consensus fault: blocks not mined by same miner: {:?}, {:?}",
bh_1.miner_address(),
bh_2.miner_address()
)
.into());
};
// block a must be earlier or equal to block b, epoch wise (ie at least as early in the chain).
if bh_1.epoch() < bh_2.epoch() {
return Err(format!(
"first block must not be of higher height than second: {:?}, {:?}",
bh_1.epoch(),
bh_2.epoch()
)
.into());
};
let mut cf: Option<ConsensusFault> = None;
// (a) double-fork mining fault
if bh_1.epoch() == bh_2.epoch() {
cf = Some(ConsensusFault {
target: *bh_1.miner_address(),
epoch: bh_2.epoch(),
fault_type: ConsensusFaultType::DoubleForkMining,
})
};
// (b) time-offset mining fault
// strictly speaking no need to compare heights based on double fork mining check above,
// but at same height this would be a different fault.
if bh_1.parents() != bh_2.parents() && bh_1.epoch() != bh_2.epoch() {
cf = Some(ConsensusFault {
target: *bh_1.miner_address(),
epoch: bh_2.epoch(),
fault_type: ConsensusFaultType::TimeOffsetMining,
})
};
// (c) parent-grinding fault
// Here extra is the "witness", a third block that shows the connection between A and B as
// A's sibling and B's parent.
// Specifically, since A is of lower height, it must be that B was mined omitting A from its tipset
if !extra.is_empty() {
let bh_3: BlockHeader = from_slice(extra)?;
if bh_1.parents() != bh_3.parents()
&& bh_1.epoch() != bh_3.epoch()
&& bh_2.parents().cids().contains(bh_3.cid())
&& !bh_2.parents().cids().contains(bh_1.cid())
{
cf = Some(ConsensusFault {
target: *bh_1.miner_address(),
epoch: bh_2.epoch(),
fault_type: ConsensusFaultType::ParentGrinding,
})
}
};
dutterbutter marked this conversation as resolved.
Show resolved Hide resolved
// (4) expensive final checks

// check blocks are properly signed by their respective miner
// note we do not need to check extra's: it is a parent to block b
// which itself is signed, so it was willingly included by the miner
self.verify_block_signature(&bh_1)?;
self.verify_block_signature(&bh_2)?;

Ok(cf)
}
}

impl<'bs, BS> DefaultSyscalls<'bs, BS>
where
BS: BlockStore,
{
fn verify_block_signature(&self, bh: &BlockHeader) -> Result<(), Box<dyn StdError>> {
// TODO look into attaching StateTree to DefaultSyscalls
let state = StateTree::new_from_root(self.store, bh.state_root())?;

let actor = state
.get_actor(bh.miner_address())?
.ok_or_else(|| format!("actor not found {:?}", bh.miner_address()))?;

let ms: miner::State = self
.store
.get(&actor.state)?
.ok_or_else(|| format!("actor state not found {:?}", actor.state.to_string()))?;

let work_address = resolve_to_key_addr(&state, self.store, &ms.info.worker)?;
bh.check_block_signature(&work_address)?;
Ok(())
}
}
Loading