Skip to content

Commit

Permalink
Initial Validation Checks - Message, Timestamp and Block Sig (#219)
Browse files Browse the repository at this point in the history
* Added check msg function
* Added message check
* Added required funds method to message trait
* Added sequence and balance methods to actor state
* check msg implemented
* Added check message functionality for message validation
* Added initial validation checks
* timestamp check
* message state transition checks
* Added block signature check
* made requested changes
* Moved timestamps validation to header.rs
  • Loading branch information
dutterbutter authored Feb 11, 2020
1 parent fdf6c50 commit 67ebaae
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 13 deletions.
14 changes: 13 additions & 1 deletion blockchain/blocks/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use std::fmt;
use std::{fmt, time::SystemTimeError as TimeErr};

#[derive(Debug, PartialEq)]
pub enum Error {
/// Tipset contains invalid data, as described by the string parameter.
InvalidTipSet(String),
/// The given tipset has no blocks
NoBlocks,
/// Invalid signature
InvalidSignature(String),
/// Error in validating arbitrary data
Validation(String),
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidTipSet(msg) => write!(f, "Invalid tipset: {}", msg),
Error::NoBlocks => write!(f, "No blocks for tipset"),
Error::InvalidSignature(msg) => write!(f, "Invalid signature: {}", msg),
Error::Validation(msg) => write!(f, "Error validating data: {}", msg),
}
}
}

impl From<TimeErr> for Error {
fn from(e: TimeErr) -> Error {
Error::Validation(e.to_string())
}
}
47 changes: 45 additions & 2 deletions blockchain/blocks/src/header.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use super::{EPostProof, Ticket, TipSetKeys};
use super::{EPostProof, Error, FullTipset, Ticket, TipSetKeys};
use address::Address;
use cid::{Cid, Error as CidError};
use clock::ChainEpoch;
use crypto::Signature;
use crypto::{is_valid_signature, Signature};
use derive_builder::Builder;
use encoding::{
de::{self, Deserializer},
Expand All @@ -16,6 +16,7 @@ use num_bigint::BigUint;
use raw_block::RawBlock;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::{SystemTime, UNIX_EPOCH};

/// Header of a block
///
Expand Down Expand Up @@ -279,6 +280,48 @@ impl BlockHeader {
self.cached_cid = Cid::from_bytes_default(&self.cached_bytes).map_err(|e| e.to_string())?;
Ok(())
}
/// Check to ensure block signature is valid
pub fn check_block_signature(&self, addr: &Address) -> Result<(), Error> {
if self.signature().bytes().is_empty() {
return Err(Error::InvalidSignature(
"Signature is nil in header".to_string(),
));
}

if !is_valid_signature(&self.cid().to_bytes(), addr, self.signature()) {
return Err(Error::InvalidSignature(
"Block signature is invalid".to_string(),
));
}

Ok(())
}
/// Validates timestamps to ensure BlockHeader was generated at the correct time
pub fn validate_timestamps(&self, base_tipset: &FullTipset) -> Result<(), Error> {
// first check that it is not in the future; see https://github.com/filecoin-project/specs/blob/6ab401c0b92efb6420c6e198ec387cf56dc86057/validation.md
// allowing for some small grace period to deal with small asynchrony
// using ALLOWABLE_CLOCK_DRIFT from Lotus; see https://github.com/filecoin-project/lotus/blob/master/build/params_shared.go#L34:7
const ALLOWABLE_CLOCK_DRIFT: u64 = 1;
let time_now = SystemTime::now().duration_since(UNIX_EPOCH)?;
if self.timestamp() > time_now.as_secs() + ALLOWABLE_CLOCK_DRIFT
|| self.timestamp() > time_now.as_secs()
{
return Err(Error::Validation("Header was from the future".to_string()));
}
const FIXED_BLOCK_DELAY: u64 = 45;
// check that it is appropriately delayed from its parents including null blocks
if self.timestamp()
< base_tipset.tipset()?.min_timestamp()?
+ FIXED_BLOCK_DELAY
* (*self.epoch() - *base_tipset.tipset()?.tip_epoch()).chain_epoch()
{
return Err(Error::Validation(
"Header was generated too soon".to_string(),
));
}

Ok(())
}
}

/// human-readable string representation of a block CID
Expand Down
9 changes: 7 additions & 2 deletions blockchain/blocks/src/tipset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub struct TipSetKeys {
// https://github.com/ChainSafe/forest/issues/143

impl TipSetKeys {
/// constructor
pub fn new(cids: Vec<Cid>) -> Self {
Self { cids }
}
/// checks whether the set contains exactly the same CIDs as another.
fn equals(&self, key: &TipSetKeys) -> bool {
if self.cids.len() != key.cids.len() {
Expand Down Expand Up @@ -157,7 +161,7 @@ impl Tipset {
Ok(self.blocks[0].ticket().clone())
}
/// Returns the smallest timestamp of all blocks in the tipset
fn min_timestamp(&self) -> Result<u64, Error> {
pub fn min_timestamp(&self) -> Result<u64, Error> {
if self.blocks.is_empty() {
return Err(Error::NoBlocks);
}
Expand Down Expand Up @@ -216,7 +220,8 @@ impl FullTipset {
for block in self.blocks() {
headers.push(block.to_header().clone())
}
Ok(Tipset::new(headers))?
let tip: Tipset = Tipset::new(headers)?;
Ok(tip)
}
}

Expand Down
3 changes: 3 additions & 0 deletions blockchain/sync_manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ chain = { path ="../chain" }
message = { package = "forest_message", path = "../../vm/message" }
raw_block = { package = "raw_block", path = "../raw_block" }
multihash = "0.9.4"
state = { package = "state_tree", path = "../../vm/state_tree/" }
num-bigint = { git = "https://github.com/austinabell/num-bigint", rev = "f7084a9ed5a2b08d9bfb67790cb4ce9212193f31" }
crypto = { path= "../../crypto" }

[dev-dependencies]
cid = { package = "forest_cid", path = "../../ipld/cid" }
Expand Down
14 changes: 13 additions & 1 deletion blockchain/sync_manager/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use chain::Error as StoreErr;
use cid::Error as CidErr;
use db::Error as DbErr;
use encoding::{error::Error as SerdeErr, Error as EncErr};
use std::fmt;
use std::{fmt, time::SystemTimeError as TimeErr};

#[derive(Debug, PartialEq)]
pub enum Error {
Expand All @@ -26,6 +26,10 @@ pub enum Error {
KeyValueStore(String),
/// Error originating from the AMT
AMT(String),
/// Error originating from state
State(String),
/// Error in validating arbitrary data
Validation(String),
}

impl fmt::Display for Error {
Expand All @@ -45,6 +49,8 @@ impl fmt::Display for Error {
Error::InvalidCid(msg) => write!(f, "Error originating from CID construction: {}", msg),
Error::Store(msg) => write!(f, "Error originating from ChainStore: {}", msg),
Error::AMT(msg) => write!(f, "Error originating from the AMT: {}", msg),
Error::State(msg) => write!(f, "Error originating from the State: {}", msg),
Error::Validation(msg) => write!(f, "Error validating data: {}", msg),
}
}
}
Expand Down Expand Up @@ -90,3 +96,9 @@ impl From<AmtErr> for Error {
Error::AMT(e.to_string())
}
}

impl From<TimeErr> for Error {
fn from(e: TimeErr) -> Error {
Error::Validation(e.to_string())
}
}
124 changes: 121 additions & 3 deletions blockchain/sync_manager/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@

use super::errors::Error;
use super::manager::SyncManager;
use address::Address;
use amt::{BlockStore, AMT};
use blocks::{Block, FullTipset, TipSetKeys, Tipset};
use chain::ChainStore;
use cid::{Cid, Error as CidError};
use crypto::is_valid_signature;
use libp2p::core::PeerId;
use message::MsgMeta;
use message::{Message, MsgMeta};
use num_bigint::BigUint;
use raw_block::RawBlock;
use state::{HamtStateTree, StateTree};
use std::collections::HashMap;

pub struct Syncer<'a> {
// TODO add ability to send msg to all subscribers indicating incoming blocks
Expand All @@ -28,6 +33,12 @@ pub struct Syncer<'a> {
_own: PeerId,
}

/// Message data used to ensure valid state transition
struct MsgMetaData {
balance: BigUint,
sequence: u64,
}

impl<'a> Syncer<'a> {
/// TODO add constructor

Expand Down Expand Up @@ -63,8 +74,7 @@ impl<'a> Syncer<'a> {
/// bls and secp messages contained in the passed in block and stores them in a key-value store
fn validate_msg_data(&self, block: &Block) -> Result<(), Error> {
let sm_root = self.compute_msg_data(block)?;
// TODO change message_receipts to messages() once #192 is in
if block.to_header().message_receipts() != &sm_root {
if block.to_header().messages() != &sm_root {
return Err(Error::InvalidRoots);
}

Expand Down Expand Up @@ -122,6 +132,114 @@ impl<'a> Syncer<'a> {
let fts = FullTipset::new(blocks);
Ok(fts)
}
// Block message validation checks
pub fn check_blk_msgs(&self, block: Block, _tip: Tipset) -> Result<(), Error> {
// TODO retrieve bls public keys for verify_bls_aggregate
// for _m in block.bls_msgs() {
// }
// TODO verify_bls_aggregate

// check msgs for validity
fn check_msg<T: Message>(
msg: &T,
msg_meta_data: &mut HashMap<Address, MsgMetaData>,
tree: &HamtStateTree,
) -> Result<(), Error>
where
T: Message,
{
let updated_state: MsgMetaData = match msg_meta_data.get(msg.from()) {
// address is present begin validity checks
Some(MsgMetaData { sequence, balance }) => {
// sequence equality check
if *sequence != msg.sequence() {
return Err(Error::Validation("Sequences are not equal".to_string()));
}

// sufficient funds check
if *balance < msg.required_funds() {
return Err(Error::Validation(
"Insufficient funds for message execution".to_string(),
));
}
// update balance and increment sequence by 1
MsgMetaData {
balance: balance - msg.required_funds(),
sequence: sequence + 1,
}
}
// MsgMetaData not found with provided address key, insert sequence and balance with address as key
None => {
let actor = tree.get_actor(msg.from()).ok_or_else(|| {
Error::State("Could not retrieve actor from state tree".to_owned())
})?;

MsgMetaData {
sequence: actor.sequence,
balance: actor.balance,
}
}
};
// update hash map with updated state
msg_meta_data.insert(msg.from().clone(), updated_state);
Ok(())
}
let mut msg_meta_data: HashMap<Address, MsgMetaData> = HashMap::new();
// TODO retrieve tipset state and load state tree
// temporary
let tree = HamtStateTree::default();
// loop through bls messages and check msg validity
for m in block.bls_msgs() {
check_msg(m, &mut msg_meta_data, &tree)?;
}
// loop through secp messages and check msg validity and signature
for m in block.secp_msgs() {
check_msg(m, &mut msg_meta_data, &tree)?;
// signature validation
if !is_valid_signature(&m.cid()?.to_bytes(), m.from(), m.signature()) {
return Err(Error::Validation(
"Message signature is not valid".to_string(),
));
}
}
// validate message root from header matches message root
let sm_root = self.compute_msg_data(&block)?;
if block.to_header().messages() != &sm_root {
return Err(Error::InvalidRoots);
}

Ok(())
}

/// Validates block semantically according to https://github.com/filecoin-project/specs/blob/6ab401c0b92efb6420c6e198ec387cf56dc86057/validation.md
pub fn validate(&self, block: Block) -> Result<(), Error> {
// get header from full block
let header = block.to_header();

// check if block has been signed
if header.signature().bytes().is_empty() {
return Err(Error::Validation("Signature is nil in header".to_string()));
}

let base_tipset = self.load_fts(TipSetKeys::new(header.parents().cids.clone()))?;
// time stamp checks
header.validate_timestamps(&base_tipset)?;

// check messages to ensure valid state transitions
self.check_blk_msgs(block.clone(), base_tipset.tipset()?)?;

// 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())?;

// TODO winner_check
// TODO miner_check
// TODO verify_ticket_vrf
// TODO verify_election_proof_check

Ok(())
}
}

pub fn cids_from_messages<T: RawBlock>(messages: &[T]) -> Result<Vec<Cid>, CidError> {
Expand Down
15 changes: 15 additions & 0 deletions node/clock/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use encoding::{
};
use std::convert::TryInto;
use std::num::TryFromIntError;
use std::ops::Sub;

const _ISO_FORMAT: &str = "%FT%X.%.9F";
const EPOCH_DURATION: i32 = 15;
Expand Down Expand Up @@ -75,9 +76,23 @@ impl ChainEpochClock {
}
}

impl Sub for ChainEpoch {
type Output = ChainEpoch;

fn sub(self, other: ChainEpoch) -> ChainEpoch {
ChainEpoch {
0: self.0 - other.0,
}
}
}

impl ChainEpoch {
/// Returns ChainEpoch based on the given unix timestamp
pub fn new(timestamp: i64) -> Result<ChainEpoch, TryFromIntError> {
Ok(ChainEpoch(timestamp.try_into()?))
}
// Returns chain epoch
pub fn chain_epoch(&self) -> &u64 {
&self.0
}
}
8 changes: 4 additions & 4 deletions vm/actor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ impl Cbor for ActorID {}
/// State of all actor implementations
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ActorState {
code_id: CodeID,
state: Cid,
balance: BigUint,
sequence: u64,
pub code_id: CodeID,
pub state: Cid,
pub balance: BigUint,
pub sequence: u64,
}

impl ActorState {
Expand Down
2 changes: 2 additions & 0 deletions vm/message/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub trait Message {
fn gas_price(&self) -> &BigUint;
/// Returns the gas limit for the message
fn gas_limit(&self) -> &BigUint;
/// Returns the required funds for the message
fn required_funds(&self) -> BigUint;
}

pub struct MsgMeta {
Expand Down
Loading

0 comments on commit 67ebaae

Please sign in to comment.