Skip to content

Commit

Permalink
add block message validation to semantic validation (filecoin-project…
Browse files Browse the repository at this point in the history
…#4158)

* add block message validation to semantic validation

* augment error messaging

* add tests for messages of different types coming from same actor
  • Loading branch information
acruikshank authored May 26, 2020
1 parent 8f6abba commit 7312cbc
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ type nodeChainSelector interface {
func NewSyncerSubmodule(ctx context.Context, config syncerConfig, blockstore *BlockstoreSubmodule, network *NetworkSubmodule,
discovery *DiscoverySubmodule, chn *ChainSubmodule, postVerifier consensus.EPoStVerifier) (SyncerSubmodule, error) {
// setup validation
blkValid := consensus.NewDefaultBlockValidator(config.ChainClock())
blkValid := consensus.NewDefaultBlockValidator(config.ChainClock(), chn.MessageStore, chn.State)
msgValid := consensus.NewMessageSyntaxValidator()
syntax := consensus.WrappedSyntaxValidator{
BlockSyntaxValidator: blkValid,
Expand Down
12 changes: 9 additions & 3 deletions internal/pkg/chain/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"context"
"encoding/binary"
"fmt"
"github.com/filecoin-project/go-filecoin/internal/pkg/drand"
"testing"

"github.com/filecoin-project/go-filecoin/internal/pkg/drand"

"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
fbig "github.com/filecoin-project/specs-actors/actors/abi/big"
Expand Down Expand Up @@ -456,8 +457,13 @@ func (e *FakeStateEvaluator) RunStateTransition(ctx context.Context, tip block.T
return e.ComputeState(stateID, blsMessages, secpMessages)
}

// ValidateSemantic is a stub that always returns no error
func (e *FakeStateEvaluator) ValidateSemantic(_ context.Context, _ *block.Block, _ block.TipSet) error {
// ValidateHeaderSemantic is a stub that always returns no error
func (e *FakeStateEvaluator) ValidateHeaderSemantic(_ context.Context, _ *block.Block, _ block.TipSet) error {
return nil
}

// ValidateHeaderSemantic is a stub that always returns no error
func (e *FakeStateEvaluator) ValidateMessagesSemantic(_ context.Context, _ *block.Block, _ block.TipSetKey) error {
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/chainsync/chainsync.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Manager struct {
}

// NewManager creates a new chain sync manager.
func NewManager(fv syncer.FullBlockValidator, hv syncer.HeaderValidator, cs syncer.ChainSelector, s syncer.ChainReaderWriter, m *chain.MessageStore, f syncer.Fetcher, c clock.Clock, detector *slashing.ConsensusFaultDetector) (Manager, error) {
func NewManager(fv syncer.FullBlockValidator, hv syncer.BlockValidator, cs syncer.ChainSelector, s syncer.ChainReaderWriter, m *chain.MessageStore, f syncer.Fetcher, c clock.Clock, detector *slashing.ConsensusFaultDetector) (Manager, error) {
syncer, err := syncer.NewSyncer(fv, hv, cs, s, m, f, status.NewReporter(), c, detector)
if err != nil {
return Manager{}, err
Expand Down
6 changes: 3 additions & 3 deletions internal/pkg/chainsync/fetcher/graphsync_fetcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestGraphsyncFetcher(t *testing.T) {
ctx := context.Background()
bs := bstore.NewBlockstore(dss.MutexWrap(datastore.NewMapDatastore()))
fc, chainClock := clock.NewFakeChain(1234567890, 5*time.Second, time.Second, time.Now().Unix())
bv := consensus.NewDefaultBlockValidator(chainClock)
bv := consensus.NewDefaultBlockValidator(chainClock, nil, nil)
msgV := &consensus.FakeMessageValidator{}
syntax := consensus.WrappedSyntaxValidator{
BlockSyntaxValidator: bv,
Expand Down Expand Up @@ -659,7 +659,7 @@ func TestHeadersOnlyGraphsyncFetch(t *testing.T) {
fc := clock.NewFake(time.Now())
genTime := uint64(1234567890)
chainClock := clock.NewChainClockFromClock(genTime, 5*time.Second, time.Second, fc)
bv := consensus.NewDefaultBlockValidator(chainClock)
bv := consensus.NewDefaultBlockValidator(chainClock, nil, nil)
msgV := &consensus.FakeMessageValidator{}
syntax := consensus.WrappedSyntaxValidator{
BlockSyntaxValidator: bv,
Expand Down Expand Up @@ -816,7 +816,7 @@ func TestRealWorldGraphsyncFetchOnlyHeaders(t *testing.T) {

bs := bstore.NewBlockstore(dss.MutexWrap(datastore.NewMapDatastore()))

bv := consensus.NewDefaultBlockValidator(chainClock)
bv := consensus.NewDefaultBlockValidator(chainClock, nil, nil)
msgV := &consensus.FakeMessageValidator{}
syntax := consensus.WrappedSyntaxValidator{BlockSyntaxValidator: bv,
MessageSyntaxValidator: msgV,
Expand Down
32 changes: 21 additions & 11 deletions internal/pkg/chainsync/internal/syncer/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ type Syncer struct {

// Evaluates tipset messages and stores the resulting states.
fullValidator FullBlockValidator
// Validates headers
headerValidator HeaderValidator
// Validates headers and message structure
blockValidator BlockValidator
// Selects the heaviest of two chains
chainSelector ChainSelector
// Provides and stores validated tipsets and their state roots.
Expand Down Expand Up @@ -105,11 +105,13 @@ type ChainSelector interface {
Weight(ctx context.Context, ts block.TipSet, stRoot cid.Cid) (fbig.Int, error)
}

// HeaderValidator does semanitc validation on headers
type HeaderValidator interface {
// ValidateSemantic validates conditions on a block header that can be
// BlockValidator does semanitc validation on headers
type BlockValidator interface {
// ValidateHeaderSemantic validates conditions on a block header that can be
// checked with the parent header but not parent state.
ValidateSemantic(ctx context.Context, header *block.Block, parents block.TipSet) error
ValidateHeaderSemantic(ctx context.Context, header *block.Block, parents block.TipSet) error
// ValidateMessagesSemantic validates a block's messages against parent state without applying the messages
ValidateMessagesSemantic(ctx context.Context, child *block.Block, parents block.TipSetKey) error
}

// FullBlockValidator does semantic validation on fullblocks.
Expand Down Expand Up @@ -150,14 +152,14 @@ var logSyncer = logging.Logger("chainsync.syncer")

// NewSyncer constructs a Syncer ready for use. The chain reader must have a
// head tipset to initialize the staging field.
func NewSyncer(fv FullBlockValidator, hv HeaderValidator, cs ChainSelector, s ChainReaderWriter, m messageStore, f Fetcher, sr status.Reporter, c clock.Clock, fd faultDetector) (*Syncer, error) {
func NewSyncer(fv FullBlockValidator, hv BlockValidator, cs ChainSelector, s ChainReaderWriter, m messageStore, f Fetcher, sr status.Reporter, c clock.Clock, fd faultDetector) (*Syncer, error) {
return &Syncer{
fetcher: f,
badTipSets: &BadTipSetCache{
bad: make(map[string]struct{}),
},
fullValidator: fv,
headerValidator: hv,
blockValidator: hv,
chainSelector: cs,
chainStore: s,
messageProvider: m,
Expand Down Expand Up @@ -221,7 +223,7 @@ func (syncer *Syncer) fetchAndValidateHeaders(ctx context.Context, ci *block.Cha
}
for i, ts := range headers {
for i := 0; i < ts.Len(); i++ {
err = syncer.headerValidator.ValidateSemantic(ctx, ts.At(i), parent)
err = syncer.blockValidator.ValidateHeaderSemantic(ctx, ts.At(i), parent)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -486,7 +488,7 @@ func (syncer *Syncer) handleNewTipSet(ctx context.Context, ci *block.ChainInfo)

// Once headers check out, fetch messages
_, err = syncer.fetcher.FetchTipSets(ctx, ci.Head, ci.Sender, func(t block.TipSet) (bool, error) {
parents, err := t.Parents()
parentsKey, err := t.Parents()
if err != nil {
return true, err
}
Expand All @@ -495,9 +497,17 @@ func (syncer *Syncer) handleNewTipSet(ctx context.Context, ci *block.ChainInfo)
return false, err
}

// validate block message structure
for i := 0; i < t.Len(); i++ {
err := syncer.blockValidator.ValidateMessagesSemantic(ctx, t.At(i), parentsKey)
if err != nil {
return false, err
}
}

// update status with latest fetched head and height
syncer.reporter.UpdateStatus(status.FetchHead(t.Key()), status.FetchHeight(height))
return syncer.chainStore.HasTipSetAndState(ctx, parents), nil
return syncer.chainStore.HasTipSetAndState(ctx, parentsKey), nil
})
if err != nil {
return errors.Wrapf(err, "failure fetching full blocks")
Expand Down
9 changes: 7 additions & 2 deletions internal/pkg/chainsync/internal/syncer/syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,13 +426,18 @@ func (pv *poisonValidator) RunStateTransition(_ context.Context, ts block.TipSet
return cid.Undef, nil, nil
}

func (pv *poisonValidator) ValidateSemantic(_ context.Context, header *block.Block, _ block.TipSet) error {
func (pv *poisonValidator) ValidateHeaderSemantic(_ context.Context, header *block.Block, _ block.TipSet) error {
if pv.headerFailureTS == header.Timestamp {
return errors.New("val semantic fails on poison timestamp")
}
return nil
}

// ValidateHeaderSemantic is a stub that always returns no error
func (pv *poisonValidator) ValidateMessagesSemantic(_ context.Context, _ *block.Block, _ block.TipSetKey) error {
return nil
}

func TestSemanticallyBadTipSetFails(t *testing.T) {
tf.UnitTest(t)
ctx := context.Background()
Expand Down Expand Up @@ -546,7 +551,7 @@ func setup(ctx context.Context, t *testing.T) (*chain.Builder, *chain.Store, *sy
return setupWithValidator(ctx, t, eval, eval)
}

func setupWithValidator(ctx context.Context, t *testing.T, fullVal syncer.FullBlockValidator, headerVal syncer.HeaderValidator) (*chain.Builder, *chain.Store, *syncer.Syncer) {
func setupWithValidator(ctx context.Context, t *testing.T, fullVal syncer.FullBlockValidator, headerVal syncer.BlockValidator) (*chain.Builder, *chain.Store, *syncer.Syncer) {
builder := chain.NewBuilder(t, address.Undef)
genesis := builder.NewGenesis()
genStateRoot, err := builder.GetTipSetStateRoot(genesis.Key())
Expand Down
105 changes: 101 additions & 4 deletions internal/pkg/consensus/block_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,28 @@ import (
"context"
"fmt"

"github.com/filecoin-project/go-filecoin/internal/pkg/vm/actor"
"github.com/filecoin-project/specs-actors/actors/builtin"

"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-filecoin/internal/pkg/vm"
"github.com/ipfs/go-cid"
"github.com/pkg/errors"

"github.com/filecoin-project/go-filecoin/internal/pkg/block"
"github.com/filecoin-project/go-filecoin/internal/pkg/clock"
"github.com/filecoin-project/go-filecoin/internal/pkg/types"
)

type messageStore interface {
LoadMessages(context.Context, cid.Cid) ([]*types.SignedMessage, []*types.UnsignedMessage, error)
LoadReceipts(context.Context, cid.Cid) ([]vm.MessageReceipt, error)
}

type chainState interface {
GetActorAt(ctx context.Context, tipKey block.TipSetKey, addr address.Address) (*actor.Actor, error)
}

// BlockValidator defines an interface used to validate a blocks syntax and
// semantics.
type BlockValidator interface {
Expand All @@ -26,7 +43,8 @@ type SyntaxValidator interface {
// BlockSemanticValidator defines an interface used to validate a blocks
// semantics.
type BlockSemanticValidator interface {
ValidateSemantic(ctx context.Context, child *block.Block, parents block.TipSet) error
ValidateHeaderSemantic(ctx context.Context, child *block.Block, parents block.TipSet) error
ValidateMessagesSemantic(ctx context.Context, child *block.Block, parents block.TipSetKey) error
}

// BlockSyntaxValidator defines an interface used to validate a blocks
Expand All @@ -45,6 +63,8 @@ type MessageSyntaxValidator interface {
// DefaultBlockValidator implements the BlockValidator interface.
type DefaultBlockValidator struct {
clock.ChainEpochClock
ms messageStore
cs chainState
}

// WrappedSyntaxValidator implements syntax validator interface
Expand All @@ -55,9 +75,11 @@ type WrappedSyntaxValidator struct {

// NewDefaultBlockValidator returns a new DefaultBlockValidator. It uses `blkTime`
// to validate blocks and uses the DefaultBlockValidationClock.
func NewDefaultBlockValidator(c clock.ChainEpochClock) *DefaultBlockValidator {
func NewDefaultBlockValidator(c clock.ChainEpochClock, m messageStore, cs chainState) *DefaultBlockValidator {
return &DefaultBlockValidator{
ChainEpochClock: c,
ms: m,
cs: cs,
}
}

Expand Down Expand Up @@ -89,9 +111,9 @@ func (dv *DefaultBlockValidator) TimeMatchesEpoch(b *block.Block) error {
return nil
}

// ValidateSemantic checks validation conditions on a header that can be
// ValidateHeaderSemantic checks validation conditions on a header that can be
// checked given only the parent header.
func (dv *DefaultBlockValidator) ValidateSemantic(ctx context.Context, child *block.Block, parents block.TipSet) error {
func (dv *DefaultBlockValidator) ValidateHeaderSemantic(ctx context.Context, child *block.Block, parents block.TipSet) error {
ph, err := parents.Height()
if err != nil {
return err
Expand All @@ -104,6 +126,81 @@ func (dv *DefaultBlockValidator) ValidateSemantic(ctx context.Context, child *bl
return nil
}

// ValidateFullSemantic checks validation conditions on a block's messages that don't require message execution.
func (dv *DefaultBlockValidator) ValidateMessagesSemantic(ctx context.Context, child *block.Block, parents block.TipSetKey) error {
// validate call sequence numbers
secpMsgs, blsMsgs, err := dv.ms.LoadMessages(ctx, child.Messages.Cid)
if err != nil {
return errors.Wrapf(err, "block validation failed loading message list %s for block %s", child.Messages, child.Cid())
}

expectedCallSeqNum := map[address.Address]uint64{}
for _, msg := range blsMsgs {
msgCid, err := msg.Cid()
if err != nil {
return err
}

from, err := dv.getAndValidateFromActor(ctx, msg, parents)
if err != nil {
return errors.Wrapf(err, "from actor %s for message %s of block %s invalid", msg.From, msgCid, child.Cid())
}

err = dv.validateMessage(msg, expectedCallSeqNum, from)
if err != nil {
return errors.Wrapf(err, "message %s of block %s invalid", msgCid, child.Cid())
}
}

for _, msg := range secpMsgs {
msgCid, err := msg.Cid()
if err != nil {
return err
}

from, err := dv.getAndValidateFromActor(ctx, &msg.Message, parents)
if err != nil {
return errors.Wrapf(err, "from actor %s for message %s of block %s invalid", msg.Message.From, msgCid, child.Cid())
}

err = dv.validateMessage(&msg.Message, expectedCallSeqNum, from)
if err != nil {
return errors.Wrapf(err, "message %s of block %s invalid", msgCid, child.Cid())
}
}

return nil
}

func (dv *DefaultBlockValidator) getAndValidateFromActor(ctx context.Context, msg *types.UnsignedMessage, parents block.TipSetKey) (*actor.Actor, error) {
actor, err := dv.cs.GetActorAt(ctx, parents, msg.From)
if err != nil {
return nil, err
}

// ensure actor is an account actor
if !actor.Code.Equals(builtin.AccountActorCodeID) {
return nil, errors.New("sent from non-account actor")
}

return actor, nil
}

func (dv *DefaultBlockValidator) validateMessage(msg *types.UnsignedMessage, expectedCallSeqNum map[address.Address]uint64, fromActor *actor.Actor) error {
callSeq, ok := expectedCallSeqNum[msg.From]
if !ok {
callSeq = fromActor.CallSeqNum
}

// ensure message is in the correct order
if callSeq != msg.CallSeqNum {
return fmt.Errorf("callseqnum (%d) out of order (expected %d) from %s", msg.CallSeqNum, callSeq, msg.From)
}

expectedCallSeqNum[msg.From] = callSeq + 1
return nil
}

// ValidateSyntax validates a single block is correctly formed.
// TODO this is an incomplete implementation #3277
func (dv *DefaultBlockValidator) ValidateSyntax(ctx context.Context, blk *block.Block) error {
Expand Down
Loading

0 comments on commit 7312cbc

Please sign in to comment.