From aa5cec39ca8961a02cf1f3f8e30eecfdec18bb8c Mon Sep 17 00:00:00 2001 From: Henri S Date: Fri, 10 Apr 2020 11:14:43 -0400 Subject: [PATCH] first stab --- chain/types/blockheader.go | 9 +++ chain/vm/syscalls.go | 124 ++++++++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 3 deletions(-) diff --git a/chain/types/blockheader.go b/chain/types/blockheader.go index de96e535e..8716c9170 100644 --- a/chain/types/blockheader.go +++ b/chain/types/blockheader.go @@ -161,6 +161,15 @@ func CidArrsEqual(a, b []cid.Cid) bool { return true } +func CidArrsContains(a []cid.Cid, b cid.Cid) bool { + for _, elem := range a { + if elem.Equals(b) { + return true + } + } + return false +} + var blocksPerEpoch = NewInt(build.BlocksPerEpoch) const sha256bits = 256 diff --git a/chain/vm/syscalls.go b/chain/vm/syscalls.go index 53a162553..63d25095b 100644 --- a/chain/vm/syscalls.go +++ b/chain/vm/syscalls.go @@ -1,14 +1,21 @@ package vm import ( + "bytes" "context" "fmt" "github.com/filecoin-project/go-address" "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "github.com/minio/blake2b-simd" mh "github.com/multiformats/go-multihash" + "github.com/pkg/errors" "golang.org/x/xerrors" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/sigs" "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/crypto" "github.com/filecoin-project/specs-actors/actors/runtime" @@ -27,6 +34,10 @@ func Syscalls(verifier ffiwrapper.Verifier) runtime.Syscalls { } type syscallShim struct { + ctx context.Context + + cstate *state.StateTree + cst *cbor.BasicIpldStore verifier ffiwrapper.Verifier } @@ -46,15 +57,122 @@ func (ss *syscallShim) ComputeUnsealedSectorCID(st abi.RegisteredProof, pieces [ } func (ss *syscallShim) HashBlake2b(data []byte) [32]byte { - panic("NYI") + return blake2b.Sum256(data) } +// Checks validity of the submitted consensus fault with the two block headers needed to prove the fault +// and an optional extra one to check common ancestry (as needed). +// Note that the blocks are ordered: the method requires a.Epoch() <= b.Epoch(). func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte, epoch abi.ChainEpoch) (*runtime.ConsensusFault, error) { - panic("NYI") + // 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 + + // are blocks the same? + if bytes.Equal(a, b) { + return nil, fmt.Errorf("no consensus fault: submitted blocks are the same") + } + + // can blocks be decoded properly? + var blockA, blockB, blockC types.BlockHeader + if decodeErr := blockA.UnmarshalCBOR(bytes.NewReader(a)); decodeErr != nil { + return nil, errors.Wrapf(decodeErr, "cannot decode first block header") + } + + if decodeErr := blockB.UnmarshalCBOR(bytes.NewReader(b)); decodeErr != nil { + return nil, errors.Wrapf(decodeErr, "cannot decode second block header") + } + + if len(extra) > 0 { + if decodeErr := blockC.UnmarshalCBOR(bytes.NewReader(extra)); decodeErr != nil { + return nil, errors.Wrapf(decodeErr, "cannot decode extra") + } + } + + // (1) check conditions necessary to any consensus fault + + // were blocks mined by same miner? + if blockA.Miner != blockB.Miner { + return nil, fmt.Errorf("no consensus fault: blocks not mined by same miner") + } + + // block a must be earlier or equal to block b, epoch wise (ie at least as early in the chain). + if blockB.Height < blockA.Height { + return nil, fmt.Errorf("first block must not be of higher height than second") + } + + // (2) check the consensus faults themselves + var consensusFault *runtime.ConsensusFault + + // (a) double-fork mining fault + if blockA.Height == blockB.Height { + consensusFault = &runtime.ConsensusFault{ + Target: blockA.Miner, + Epoch: blockB.Height, + Type: runtime.ConsensusFaultDoubleForkMining, + } + } + + // (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 !types.CidArrsEqual(blockA.Parents, blockB.Parents) && blockA.Height != blockB.Height { + consensusFault = &runtime.ConsensusFault{ + Target: blockA.Miner, + Epoch: blockB.Height, + Type: runtime.ConsensusFaultTimeOffsetMining, + } + } + + // (c) parent-grinding fault + // TODO HS: check is the blockB.height == blockA.height + 1 cond nec? + // Here extra is the "witness", a third block that shows the connection between A and B + // Specifically, since A is of lower height, it must be that B was mined omitting A from its tipset + // so there must be a block C such that A and C are siblings and C is B's parent + if types.CidArrsEqual(blockA.Parents, blockC.Parents) && types.CidArrsContains(blockB.Parents, blockC.Cid()) && + !types.CidArrsContains(blockB.Parents, blockA.Cid()) && blockB.Height == blockA.Height+1 { + consensusFault = &runtime.ConsensusFault{ + Target: blockA.Miner, + Epoch: blockB.Height, + Type: runtime.ConsensusFaultParentGrinding, + } + } + + // (3) 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 + if sigErr := ss.VerifyBlockSig(&blockA); sigErr != nil { + return nil, errors.Wrapf(sigErr, "cannot verify first block sig") + } + + if sigErr := ss.VerifyBlockSig(&blockB); sigErr != nil { + return nil, errors.Wrapf(sigErr, "cannot verify first block sig") + } + + return consensusFault, nil +} + +func (ss *syscallShim) VerifyBlockSig(blk *types.BlockHeader) error { + + // // load actorState + + // // get publicKeyAddr + // waddr := ss.ResolveToKeyAddr(ss.cstate, ss.cst, mas.Info.Worker) + + if err := sigs.CheckBlockSignature(blk, ss.ctx, waddr); err != nil { + return err + } + + return nil } func (ss *syscallShim) VerifyPoSt(proof abi.PoStVerifyInfo) error { - ok, err := ss.verifier.VerifyFallbackPost(context.TODO(), proof) + ok, err := ss.verifier.VerifyFallbackPost(ss.ctx, proof) if err != nil { return err }