Skip to content

Commit

Permalink
add clock package and block validation
Browse files Browse the repository at this point in the history
  • Loading branch information
frrist committed Jun 12, 2019
1 parent 5d626fc commit f55889f
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 29 deletions.
53 changes: 53 additions & 0 deletions clock/clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package clock

import (
"sync"
"time"
)

// TODO this is the bare min, not wasting time to get killed in review.

// Clock is a thing
type Clock interface {
Now() time.Time
}

// BlockClock is a thing
type BlockClock struct{}

// NewBlockClock is a thing
func NewBlockClock() *BlockClock {
return &BlockClock{}
}

// Now is a thing
func (bc *BlockClock) Now() time.Time {
return time.Now()
}

// MockClock is a thing
type MockClock struct {
nowMu sync.Mutex
now time.Time
}

// NewMockClock is a thing
func NewMockClock(n time.Time) *MockClock {
return &MockClock{
now: n,
}
}

// Now is a thing
func (mc *MockClock) Now() time.Time {
mc.nowMu.Lock()
defer mc.nowMu.Unlock()
return mc.now
}

// Set is a thing
func (mc *MockClock) Set(t time.Time) {
mc.nowMu.Lock()
defer mc.nowMu.Unlock()
mc.now = t
}
73 changes: 45 additions & 28 deletions consensus/block_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,19 @@ import (
"fmt"
"time"

"github.com/pkg/errors"

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

var (
// ErrTooSoon is returned when a block is generated too soon after its parent.
ErrTooSoon = errors.New("block was generated too soon")
// ErrInvalidHeight is returned when a block has an invliad height.
ErrInvalidHeight = errors.New("block has invalid height")
)

// BlockValidator defines an interface used to validate a blocks syntax and
// semantics.
type BlockValidator interface {
Expand All @@ -27,56 +37,63 @@ type BlockSyntaxValidator interface {
ValidateSyntax(ctx context.Context, blk *types.Block) error
}

// BlockValidationClock defines an interface for fetching unix epoch time.
type BlockValidationClock interface {
EpochSeconds() uint64
}

// DefaultBlockValidationClock implements BlockValidationClock using the
// Go time package.
type DefaultBlockValidationClock struct{}

// NewDefaultBlockValidationClock returns a DefaultBlockValidationClock.
func NewDefaultBlockValidationClock() *DefaultBlockValidationClock {
return &DefaultBlockValidationClock{}
}

// EpochSeconds returns Unix time, the number of seconds elapsed since January 1, 1970 UTC.
// The result does not depend on location.
func (ebc *DefaultBlockValidationClock) EpochSeconds() uint64 {
return uint64(time.Now().Unix())
}

// DefaultBlockValidator implements the BlockValidator interface.
type DefaultBlockValidator struct {
clock BlockValidationClock
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) *DefaultBlockValidator {
func NewDefaultBlockValidator(blkTime time.Duration, c clock.Clock) *DefaultBlockValidator {
return &DefaultBlockValidator{
clock: NewDefaultBlockValidationClock(),
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 {
// TODO validate timestamp
// #2886
pmin, err := parents.MinTimestamp()
if err != nil {
return err
}

ph, err := parents.Height()
if err != nil {
return err
}

if uint64(child.Height) <= ph {
return ErrInvalidHeight
}

// 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 errors.Wrapf(ErrTooSoon, "limit: %d, childTs: %d", limit, child.Timestamp)
return ErrTooSoon
}
return nil
}

// ValidateSyntax validates a single block is correctly formed.
func (dv *DefaultBlockValidator) ValidateSyntax(ctx context.Context, blk *types.Block) error {
if uint64(blk.Timestamp) > uint64(dv.Now().Unix()) {
return fmt.Errorf("block generate in future")
}
if !blk.StateRoot.Defined() {
return fmt.Errorf("block has nil StateRoot")
}
// TODO validate timestamp
// TODO validate block signature
// #2886
if blk.Miner.Empty() {
return fmt.Errorf("block has nil miner address")
}
if len(blk.Ticket) == 0 {
return fmt.Errorf("block has nil ticket")
}
// TODO vlidate block signature: 1054
return nil
}

Expand Down
143 changes: 143 additions & 0 deletions consensus/block_validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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/clock"
"github.com/filecoin-project/go-filecoin/consensus"
tf "github.com/filecoin-project/go-filecoin/testhelpers/testflags"
"github.com/filecoin-project/go-filecoin/types"
)

// to block time converts a time to block timestamp
func tbt(t time.Time) types.Uint64 {
return types.Uint64(t.Unix())
}

func TestBlockValidSemantic(t *testing.T) {
tf.UnitTest(t)

blockTime := consensus.DefaultBlockTime
ts := time.Now()
mclock := clock.NewMockClock(ts)
ctx := context.Background()

validator := consensus.NewDefaultBlockValidator(blockTime, mclock)
//panic(fmt.Sprintf("ts: %d, addedTo: %d", tbt(ts), tbt(ts.Add(blockTime))))

t.Run("reject block with same height as parents", func(t *testing.T) {
c := &types.Block{Height: 1, Timestamp: tbt(ts)}
p := &types.Block{Height: 1, Timestamp: tbt(ts)}
parents, err := types.NewTipSet(p)
require.NoError(t, err)

err = validator.ValidateSemantic(ctx, c, &parents)
assert.Equal(t, consensus.ErrInvalidHeight, err)
})

t.Run("reject block mined too soon after parent", func(t *testing.T) {
c := &types.Block{Height: 2, Timestamp: tbt(ts)}
p := &types.Block{Height: 1, Timestamp: tbt(ts)}
parents, err := types.NewTipSet(p)
require.NoError(t, err)

err = validator.ValidateSemantic(ctx, c, &parents)
assert.Equal(t, consensus.ErrTooSoon, err)
})

t.Run("reject block mined too soon after parent with one null block", func(t *testing.T) {
c := &types.Block{Height: 3, Timestamp: tbt(ts)}
p := &types.Block{Height: 1, Timestamp: tbt(ts)}
parents, err := types.NewTipSet(p)
require.NoError(t, err)

err = validator.ValidateSemantic(ctx, c, &parents)
assert.Equal(t, consensus.ErrTooSoon, err)
})

t.Run("accept block mined one block time after parent", func(t *testing.T) {
c := &types.Block{Height: 2, Timestamp: tbt(ts.Add(blockTime))}
p := &types.Block{Height: 1, Timestamp: tbt(ts)}
parents, err := types.NewTipSet(p)
require.NoError(t, err)

err = validator.ValidateSemantic(ctx, c, &parents)
assert.NoError(t, err)
})

t.Run("accept block mined one block time after parent with one null block", func(t *testing.T) {
c := &types.Block{Height: 3, Timestamp: tbt(ts.Add(2 * blockTime))}
p := &types.Block{Height: 1, Timestamp: tbt(ts)}
parents, err := types.NewTipSet(p)
require.NoError(t, err)

err = validator.ValidateSemantic(ctx, c, &parents)
assert.NoError(t, err)
})

}

func TestBlockValidSyntax(t *testing.T) {
tf.UnitTest(t)

blockTime := consensus.DefaultBlockTime

loc, _ := time.LoadLocation("America/New_York")
ts := time.Date(2019, time.April, 1, 0, 0, 0, 0, loc)
mclock := clock.NewMockClock(ts)

ctx := context.Background()

validator := consensus.NewDefaultBlockValidator(blockTime, mclock)

t.Run("reject block generated in future", func(t *testing.T) {
blk := &types.Block{
Timestamp: types.Uint64(ts.Add(time.Second * 1).Unix()),
}
assert.Error(t, validator.ValidateSyntax(ctx, blk))
})

t.Run("reject block with undef StateRoot", func(t *testing.T) {
blk := &types.Block{
StateRoot: cid.Undef,
}
assert.Error(t, validator.ValidateSyntax(ctx, blk))
})

t.Run("reject block with undef miner address", func(t *testing.T) {
blk := &types.Block{
Miner: address.Undef,
}
assert.Error(t, validator.ValidateSyntax(ctx, blk))
})

t.Run("reject block with empty ticket", func(t *testing.T) {
blk := &types.Block{
Ticket: []byte{},
}
assert.Error(t, validator.ValidateSyntax(ctx, blk))
})

// Impossible due to implementation
/*
t.Run("reject block with negative parent weight", func(t *testing.T) {
blk := &types.Block{
ParentWeight: -1,
}
assert.Error(t, validator.ValidateSyntax(ctx, blk))
})
t.Run("reject block with negative height", func(t *testing.T) {
blk := &types.Block{
Height: -1,
}
assert.Error(t, validator.ValidateSyntax(ctx, blk))
})
*/
}
1 change: 1 addition & 0 deletions mining/block_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func (w *DefaultWorker) Generate(ctx context.Context,
Proof: proof,
StateRoot: newStateTreeCid,
Ticket: ticket,
Timestamp: types.Uint64(time.Now().Unix()),
}

for i, msg := range res.PermanentFailures {
Expand Down
3 changes: 2 additions & 1 deletion node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/filecoin-project/go-filecoin/actor/builtin"
"github.com/filecoin-project/go-filecoin/address"
"github.com/filecoin-project/go-filecoin/chain"
"github.com/filecoin-project/go-filecoin/clock"
"github.com/filecoin-project/go-filecoin/config"
"github.com/filecoin-project/go-filecoin/consensus"
"github.com/filecoin-project/go-filecoin/core"
Expand Down Expand Up @@ -378,7 +379,7 @@ func (nc *Config) Build(ctx context.Context) (*Node, error) {
pingService := ping.NewPingService(peerHost)

// setup block validation
blkValid := consensus.NewDefaultBlockValidator(nc.BlockTime)
blkValid := consensus.NewDefaultBlockValidator(nc.BlockTime, clock.NewBlockClock())

// set up bitswap
nwork := bsnet.NewFromIpfsHost(peerHost, router)
Expand Down
1 change: 1 addition & 0 deletions types/tipset.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func (ts TipSet) MinTicket() (Signature, error) {
return ts.blocks[0].Ticket, nil
}

// MinTimestamp returns the smallest timestamp of all blocks in the tipset.
func (ts TipSet) MinTimestamp() (Uint64, error) {
if len(ts.blocks) == 0 {
return 0, errUndefTipSet
Expand Down

0 comments on commit f55889f

Please sign in to comment.