-
Notifications
You must be signed in to change notification settings - Fork 467
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Patch/block validation 0.2.3 (#2958)
* add block validation interfaces * plumb block validation interfaces to test and sync * move block syntax validation to fetcher * add timestamp to block * move blocktime onto consensus and plumb * move the blocktime default to the consensus package and add plumbing and porcelain cals to access it. * add MinTimestamp to tipset and test * add clock package and block validation
- Loading branch information
Showing
34 changed files
with
517 additions
and
124 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package clock | ||
|
||
import ( | ||
"time" | ||
) | ||
|
||
// Clock defines an interface for fetching time that may be used instead of the | ||
// time module. | ||
type Clock interface { | ||
Now() time.Time | ||
} | ||
|
||
// SystemClock delegates calls to the time package. | ||
type SystemClock struct{} | ||
|
||
// NewSystemClock returns a SystemClock that delegates calls to the time package. | ||
func NewSystemClock() *SystemClock { | ||
return &SystemClock{} | ||
} | ||
|
||
// Now returns the current local time. | ||
func (bc *SystemClock) Now() time.Time { | ||
return time.Now() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package consensus | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/filecoin-project/go-filecoin/clock" | ||
"github.com/filecoin-project/go-filecoin/types" | ||
) | ||
|
||
// BlockValidator defines an interface used to validate a blocks syntax and | ||
// semantics. | ||
type BlockValidator interface { | ||
BlockSemanticValidator | ||
BlockSyntaxValidator | ||
} | ||
|
||
// BlockSemanticValidator defines an interface used to validate a blocks | ||
// semantics. | ||
type BlockSemanticValidator interface { | ||
ValidateSemantic(ctx context.Context, child *types.Block, parents *types.TipSet) error | ||
} | ||
|
||
// BlockSyntaxValidator defines an interface used to validate a blocks | ||
// syntax. | ||
type BlockSyntaxValidator interface { | ||
ValidateSyntax(ctx context.Context, blk *types.Block) error | ||
} | ||
|
||
// DefaultBlockValidator implements the BlockValidator interface. | ||
type DefaultBlockValidator struct { | ||
clock.Clock | ||
blockTime time.Duration | ||
} | ||
|
||
// NewDefaultBlockValidator returns a new DefaultBlockValidator. It uses `blkTime` | ||
// to validate blocks and uses the DefaultBlockValidationClock. | ||
func NewDefaultBlockValidator(blkTime time.Duration, c clock.Clock) *DefaultBlockValidator { | ||
return &DefaultBlockValidator{ | ||
Clock: c, | ||
blockTime: blkTime, | ||
} | ||
} | ||
|
||
// ValidateSemantic validates a block is correctly derived from its parent. | ||
func (dv *DefaultBlockValidator) ValidateSemantic(ctx context.Context, child *types.Block, parents *types.TipSet) error { | ||
pmin, err := parents.MinTimestamp() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
ph, err := parents.Height() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if uint64(child.Height) <= ph { | ||
return fmt.Errorf("block %s has invalid height %d", child.Cid().String(), child.Height) | ||
} | ||
|
||
// check that child is appropriately delayed from its parents including | ||
// null blocks. | ||
// TODO replace check on height when #2222 lands | ||
limit := uint64(pmin) + uint64(dv.BlockTime().Seconds())*(uint64(child.Height)-ph) | ||
if uint64(child.Timestamp) < limit { | ||
return fmt.Errorf("block %s with timestamp %d generated too far past parent, expected timestamp < %d", child.Cid().String(), child.Timestamp, limit) | ||
} | ||
return nil | ||
} | ||
|
||
// ValidateSyntax validates a single block is correctly formed. | ||
func (dv *DefaultBlockValidator) ValidateSyntax(ctx context.Context, blk *types.Block) error { | ||
now := uint64(dv.Now().Unix()) | ||
if uint64(blk.Timestamp) > now { | ||
return fmt.Errorf("block %s with timestamp %d generate in future at time %d", blk.Cid().String(), blk.Timestamp, now) | ||
} | ||
if !blk.StateRoot.Defined() { | ||
return fmt.Errorf("block %s has nil StateRoot", blk.Cid().String()) | ||
} | ||
if blk.Miner.Empty() { | ||
return fmt.Errorf("block %s has nil miner address", blk.Miner.String()) | ||
} | ||
if len(blk.Ticket) == 0 { | ||
return fmt.Errorf("block %s has nil ticket", blk.Cid().String()) | ||
} | ||
// TODO validate block signature: 1054 | ||
return nil | ||
} | ||
|
||
// BlockTime returns the block time the DefaultBlockValidator uses to validate | ||
/// blocks against. | ||
func (dv *DefaultBlockValidator) BlockTime() time.Duration { | ||
return dv.blockTime | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package consensus_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
"time" | ||
|
||
"github.com/ipfs/go-cid" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/filecoin-project/go-filecoin/address" | ||
"github.com/filecoin-project/go-filecoin/consensus" | ||
th "github.com/filecoin-project/go-filecoin/testhelpers" | ||
tf "github.com/filecoin-project/go-filecoin/testhelpers/testflags" | ||
"github.com/filecoin-project/go-filecoin/types" | ||
) | ||
|
||
func TestBlockValidSemantic(t *testing.T) { | ||
tf.UnitTest(t) | ||
|
||
blockTime := consensus.DefaultBlockTime | ||
ts := time.Unix(1234567890, 0) | ||
mclock := th.NewFakeSystemClock(ts) | ||
ctx := context.Background() | ||
|
||
validator := consensus.NewDefaultBlockValidator(blockTime, mclock) | ||
|
||
t.Run("reject block with same height as parents", func(t *testing.T) { | ||
// passes with valid height | ||
c := &types.Block{Height: 2, Timestamp: types.Uint64(ts.Add(blockTime).Unix())} | ||
p := &types.Block{Height: 1, Timestamp: types.Uint64(ts.Unix())} | ||
parents := consensus.RequireNewTipSet(require.New(t), p) | ||
require.NoError(t, validator.ValidateSemantic(ctx, c, &parents)) | ||
|
||
// invalidate parent by matching child height | ||
p = &types.Block{Height: 2, Timestamp: types.Uint64(ts.Unix())} | ||
parents = consensus.RequireNewTipSet(require.New(t), p) | ||
|
||
err := validator.ValidateSemantic(ctx, c, &parents) | ||
assert.Error(t, err) | ||
assert.Contains(t, err.Error(), "invalid height") | ||
|
||
}) | ||
|
||
t.Run("reject block mined too soon after parent", func(t *testing.T) { | ||
// Passes with correct timestamp | ||
c := &types.Block{Height: 2, Timestamp: types.Uint64(ts.Add(blockTime).Unix())} | ||
p := &types.Block{Height: 1, Timestamp: types.Uint64(ts.Unix())} | ||
parents := consensus.RequireNewTipSet(require.New(t), p) | ||
require.NoError(t, validator.ValidateSemantic(ctx, c, &parents)) | ||
|
||
// fails with invalid timestamp | ||
c = &types.Block{Height: 2, Timestamp: types.Uint64(ts.Unix())} | ||
err := validator.ValidateSemantic(ctx, c, &parents) | ||
assert.Error(t, err) | ||
assert.Contains(t, err.Error(), "too far") | ||
|
||
}) | ||
|
||
t.Run("reject block mined too soon after parent with one null block", func(t *testing.T) { | ||
// Passes with correct timestamp | ||
c := &types.Block{Height: 3, Timestamp: types.Uint64(ts.Add(2 * blockTime).Unix())} | ||
p := &types.Block{Height: 1, Timestamp: types.Uint64(ts.Unix())} | ||
parents := consensus.RequireNewTipSet(require.New(t), p) | ||
err := validator.ValidateSemantic(ctx, c, &parents) | ||
require.NoError(t, err) | ||
|
||
// fail when nul block calc is off by one blocktime | ||
c = &types.Block{Height: 3, Timestamp: types.Uint64(ts.Add(blockTime).Unix())} | ||
err = validator.ValidateSemantic(ctx, c, &parents) | ||
assert.Error(t, err) | ||
assert.Contains(t, err.Error(), "too far") | ||
|
||
// fail with same timestamp as parent | ||
c = &types.Block{Height: 3, Timestamp: types.Uint64(ts.Unix())} | ||
err = validator.ValidateSemantic(ctx, c, &parents) | ||
assert.Error(t, err) | ||
assert.Contains(t, err.Error(), "too far") | ||
|
||
}) | ||
} | ||
|
||
func TestBlockValidSyntax(t *testing.T) { | ||
tf.UnitTest(t) | ||
|
||
blockTime := consensus.DefaultBlockTime | ||
ts := time.Unix(1234567890, 0) | ||
mclock := th.NewFakeSystemClock(ts) | ||
|
||
ctx := context.Background() | ||
|
||
validator := consensus.NewDefaultBlockValidator(blockTime, mclock) | ||
|
||
validTs := types.Uint64(ts.Unix()) | ||
validSt := types.NewCidForTestGetter()() | ||
validAd := address.NewForTestGetter()() | ||
validTi := []byte{1} | ||
// create a valid block | ||
blk := &types.Block{ | ||
Timestamp: validTs, | ||
StateRoot: validSt, | ||
Miner: validAd, | ||
Ticket: validTi, | ||
} | ||
require.NoError(t, validator.ValidateSyntax(ctx, blk)) | ||
|
||
// below we will invalidate each part of the block, assert that it fails | ||
// validation, then revalidate the block | ||
|
||
// invalidate timestamp | ||
blk.Timestamp = types.Uint64(ts.Add(time.Second).Unix()) | ||
require.Error(t, validator.ValidateSyntax(ctx, blk)) | ||
blk.Timestamp = validTs | ||
require.NoError(t, validator.ValidateSyntax(ctx, blk)) | ||
|
||
// invalidate statetooy | ||
blk.StateRoot = cid.Undef | ||
require.Error(t, validator.ValidateSyntax(ctx, blk)) | ||
blk.StateRoot = validSt | ||
require.NoError(t, validator.ValidateSyntax(ctx, blk)) | ||
|
||
// invalidate miner address | ||
blk.Miner = address.Undef | ||
require.Error(t, validator.ValidateSyntax(ctx, blk)) | ||
blk.Miner = validAd | ||
require.NoError(t, validator.ValidateSyntax(ctx, blk)) | ||
|
||
// invalidate ticket | ||
blk.Ticket = []byte{} | ||
require.Error(t, validator.ValidateSyntax(ctx, blk)) | ||
blk.Ticket = validTi | ||
require.NoError(t, validator.ValidateSyntax(ctx, blk)) | ||
|
||
} |
Oops, something went wrong.