beacon: Block Processing Validity Conditions (#310)

This commit is contained in:
Raul Jordan
2018-07-23 10:43:41 -05:00
committed by Preston Van Loon
parent 48b047fd61
commit d46f1f6502
10 changed files with 153 additions and 30 deletions

View File

@@ -18,7 +18,6 @@ go_library(
"@com_github_ethereum_go_ethereum//ethdb:go_default_library",
"@com_github_ethereum_go_ethereum//rlp:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_syndtr_goleveldb//leveldb/errors:go_default_library",
],
)
@@ -35,6 +34,9 @@ go_test(
"//beacon-chain/types:go_default_library",
"//beacon-chain/utils:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//core/types:go_default_library",
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
"@com_github_ethereum_go_ethereum//p2p/enr:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
],
)

View File

@@ -1,18 +1,20 @@
package blockchain
import (
"context"
"fmt"
"math"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/prysmaticlabs/prysm/beacon-chain/params"
"github.com/prysmaticlabs/prysm/beacon-chain/powchain"
"github.com/prysmaticlabs/prysm/beacon-chain/types"
"github.com/prysmaticlabs/prysm/beacon-chain/utils"
"github.com/sirupsen/logrus"
leveldberrors "github.com/syndtr/goleveldb/leveldb/errors"
)
var stateLookupKey = "beaconchainstate"
@@ -37,14 +39,18 @@ func NewBeaconChain(db ethdb.Database) (*BeaconChain, error) {
db: db,
state: &beaconState{},
}
enc, err := db.Get([]byte(stateLookupKey))
if err != nil && err.Error() == leveldberrors.ErrNotFound.Error() {
has, err := db.Has([]byte(stateLookupKey))
if err != nil {
return nil, err
}
if !has {
log.Info("No chainstate found on disk, initializing beacon from genesis")
active, crystallized := types.NewGenesisStates()
beaconChain.state.ActiveState = active
beaconChain.state.CrystallizedState = crystallized
return beaconChain, nil
}
enc, err := db.Get([]byte(stateLookupKey))
if err != nil {
return nil, err
}
@@ -65,6 +71,11 @@ func (b *BeaconChain) CrystallizedState() *types.CrystallizedState {
return b.state.CrystallizedState
}
// GenesisBlock returns the canonical, genesis block.
func (b *BeaconChain) GenesisBlock() *types.Block {
return types.NewGenesisBlock()
}
// MutateActiveState allows external services to modify the active state.
func (b *BeaconChain) MutateActiveState(activeState *types.ActiveState) error {
defer b.lock.Unlock()
@@ -81,6 +92,24 @@ func (b *BeaconChain) MutateCrystallizedState(crystallizedState *types.Crystalli
return b.persist()
}
// CanProcessBlock decides if an incoming p2p block can be processed into the chain's block trie.
func (b *BeaconChain) CanProcessBlock(fetcher powchain.POWBlockFetcher, block *types.Block) (bool, error) {
mainchainBlock, err := fetcher.BlockByHash(context.Background(), block.Data().MainChainRef)
if err != nil {
return false, err
}
// There needs to be a valid mainchain block for the reference hash in a beacon block.
if mainchainBlock == nil {
return false, nil
}
// TODO: check if the parentHash pointed by the beacon block is in the beaconDB.
// Calculate the timestamp validity condition.
slotDuration := time.Duration(block.Data().SlotNumber*params.SlotLength) * time.Second
validTime := time.Now().After(b.GenesisBlock().Data().Timestamp.Add(slotDuration))
return validTime, nil
}
// persist stores the RLP encoding of the latest beacon chain state into the db.
func (b *BeaconChain) persist() error {
encodedState, err := rlp.EncodeToBytes(b.state)

View File

@@ -2,22 +2,37 @@ package blockchain
import (
"bytes"
"fmt"
"os"
"context"
"errors"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/prysmaticlabs/prysm/beacon-chain/database"
"github.com/prysmaticlabs/prysm/beacon-chain/types"
"github.com/prysmaticlabs/prysm/beacon-chain/utils"
logTest "github.com/sirupsen/logrus/hooks/test"
)
type faultyFetcher struct{}
func (f *faultyFetcher) BlockByHash(ctx context.Context, hash common.Hash) (*gethTypes.Block, error) {
return nil, errors.New("cannot fetch block")
}
type mockFetcher struct{}
func (m *mockFetcher) BlockByHash(ctx context.Context, hash common.Hash) (*gethTypes.Block, error) {
block := gethTypes.NewBlock(&gethTypes.Header{}, nil, nil, nil)
return block, nil
}
func TestNewBeaconChain(t *testing.T) {
hook := logTest.NewGlobal()
tmp := fmt.Sprintf("%s/beacontest", os.TempDir())
config := &database.BeaconDBConfig{DataDir: tmp, Name: "beacontest", InMemory: false}
config := &database.BeaconDBConfig{DataDir: "", Name: "", InMemory: true}
db, err := database.NewBeaconDB(config)
if err != nil {
t.Fatalf("unable to setup db: %v", err)
@@ -45,8 +60,7 @@ func TestNewBeaconChain(t *testing.T) {
}
func TestMutateActiveState(t *testing.T) {
tmp := fmt.Sprintf("%s/beacontest", os.TempDir())
config := &database.BeaconDBConfig{DataDir: tmp, Name: "beacontest2", InMemory: false}
config := &database.BeaconDBConfig{DataDir: "", Name: "", InMemory: true}
db, err := database.NewBeaconDB(config)
if err != nil {
t.Fatalf("unable to setup db: %v", err)
@@ -83,8 +97,7 @@ func TestMutateActiveState(t *testing.T) {
}
func TestMutateCrystallizedState(t *testing.T) {
tmp := fmt.Sprintf("%s/beacontest", os.TempDir())
config := &database.BeaconDBConfig{DataDir: tmp, Name: "beacontest3", InMemory: false}
config := &database.BeaconDBConfig{DataDir: "", Name: "", InMemory: true}
db, err := database.NewBeaconDB(config)
if err != nil {
t.Fatalf("unable to setup db: %v", err)
@@ -122,22 +135,26 @@ func TestMutateCrystallizedState(t *testing.T) {
}
func TestGetAttestersProposer(t *testing.T) {
tmp := fmt.Sprintf("%s/beacontest", os.TempDir())
config := &database.BeaconDBConfig{DataDir: tmp, Name: "beacontest4", InMemory: false}
config := &database.BeaconDBConfig{DataDir: "", Name: "", InMemory: true}
db, err := database.NewBeaconDB(config)
if err != nil {
t.Fatalf("unable to setup db: %v", err)
t.Fatalf("Unable to setup db: %v", err)
}
db.Start()
beaconChain, err := NewBeaconChain(db.DB())
if err != nil {
t.Fatalf("unable to setup beacon chain: %v", err)
t.Fatalf("Unable to setup beacon chain: %v", err)
}
priv, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("Could not generate key: %v", err)
}
var validators []types.ValidatorRecord
// Create 1000 validators in ActiveValidators.
for i := 0; i < 1000; i++ {
validator := types.ValidatorRecord{WithdrawalAddress: common.Address{'A'}}
validator := types.ValidatorRecord{WithdrawalAddress: common.Address{'A'}, PubKey: enr.Secp256k1(priv.PublicKey)}
validators = append(validators, validator)
}
@@ -160,3 +177,41 @@ func TestGetAttestersProposer(t *testing.T) {
t.Errorf("Get attesters failed, expected: %v got: %v", validatorList[:len(attesters)], attesters)
}
}
func TestCanProcessBlock(t *testing.T) {
config := &database.BeaconDBConfig{DataDir: "", Name: "", InMemory: true}
db, err := database.NewBeaconDB(config)
if err != nil {
t.Fatalf("unable to setup db: %v", err)
}
db.Start()
beaconChain, err := NewBeaconChain(db.DB())
if err != nil {
t.Fatalf("Unable to setup beacon chain: %v", err)
}
block := types.NewBlock(1)
// Using a faulty fetcher should throw an error.
if _, err := beaconChain.CanProcessBlock(&faultyFetcher{}, block); err == nil {
t.Errorf("Using a faulty fetcher should throw an error, received nil")
}
canProcess, err := beaconChain.CanProcessBlock(&mockFetcher{}, block)
if err != nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
}
if !canProcess {
t.Errorf("Should be able to process block, could not")
}
// Attempting to try a block with that fails the timestamp validity
// condition.
block = types.NewBlock(1000000)
canProcess, err = beaconChain.CanProcessBlock(&mockFetcher{}, block)
if err != nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
}
if canProcess {
t.Errorf("Should not be able to process block with invalid timestamp condition")
}
}

View File

@@ -53,7 +53,7 @@ func (c *ChainService) updateActiveState() {
for {
select {
case block := <-c.latestBeaconBlock:
log.WithFields(logrus.Fields{"activeStateHash": block.ActiveStateHash}).Debug("Received beacon block")
log.WithFields(logrus.Fields{"activeStateHash": block.Data().ActiveStateHash}).Debug("Received beacon block")
// TODO: Using latest block hash for seed, this will eventually be replaced by randao
activeState, err := c.chain.computeNewActiveState(c.web3Service.LatestBlockHash())

View File

@@ -17,4 +17,6 @@ const (
MaxValidators = 4194304
// NotariesPerCrosslink fixed to 100.
NotariesPerCrosslink = 100
// SlotLength in seconds.
SlotLength = 8
)

View File

@@ -21,6 +21,11 @@ type Reader interface {
SubscribeNewHead(ctx context.Context, ch chan<- *gethTypes.Header) (ethereum.Subscription, error)
}
// POWBlockFetcher defines a struct that can retrieve mainchain blocks.
type POWBlockFetcher interface {
BlockByHash(ctx context.Context, hash common.Hash) (*gethTypes.Block, error)
}
// Logger subscribe filtered log on the PoW chain
type Logger interface {
SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- gethTypes.Log) (ethereum.Subscription, error)

View File

@@ -8,5 +8,8 @@ go_library(
],
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/types",
visibility = ["//beacon-chain:__subpackages__"],
deps = ["@com_github_ethereum_go_ethereum//common:go_default_library"],
deps = [
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//p2p/enr:go_default_library",
],
)

View File

@@ -1,9 +1,36 @@
package types
import "github.com/ethereum/go-ethereum/common"
import (
"time"
// Header contains the block header fields in beacon chain.
"github.com/ethereum/go-ethereum/common"
)
// Block defines a beacon chain core primitive.
type Block struct {
data *Data
}
// Data getter makes the block's properties read-only.
func (b *Block) Data() *Data {
return b.data
}
// NewBlock creates a new beacon block given certain arguments.
func NewBlock(slotNumber uint64) *Block {
data := &Data{Timestamp: time.Now(), SlotNumber: slotNumber}
return &Block{data}
}
// NewGenesisBlock returns the canonical, genesis block for the beacon chain protocol.
func NewGenesisBlock() *Block {
timestamp := time.Date(2018, time.July, 21, 12, 0, 0, 0, time.UTC)
// TODO: Add more default fields.
return &Block{data: &Data{Timestamp: timestamp}}
}
// Data contains the fields in a beacon chain block.
type Data struct {
ParentHash common.Hash // ParentHash is the hash of the parent beacon block.
SlotNumber uint64 // Slot number is the number a client should check to know when it creates block.
RandaoReveal common.Hash // RandaoReveal is used for Randao commitment reveal.
@@ -13,6 +40,7 @@ type Block struct {
MainChainRef common.Hash // MainChainRef is the reference to main chain block.
ActiveStateHash []byte // ActiveStateHash is the state that changes every block.
CrystallizedStateHash []byte // CrystallizedStateHash is the state that changes every epoch.
Timestamp time.Time
}
// AggregateVote contains the fields of aggregate vote in individual shard.

View File

@@ -1,9 +1,8 @@
package types
import (
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/p2p/enr"
)
// ActiveState contains fields of current state of beacon chain,
@@ -43,12 +42,12 @@ type CrystallizedState struct {
// ValidatorRecord contains information about a validator
type ValidatorRecord struct {
PubKey ecdsa.PublicKey // PubKey is the validator's public key.
WithdrawalShard uint16 // WithdrawalShard is the shard balance will be sent to after withdrawal.
WithdrawalAddress common.Address // WithdrawalAddress is the address balance will be sent to after withdrawal.
RandaoCommitment common.Hash // RandaoCommitment is validator's current RANDAO beacon commitment.
Balance uint64 // Balance is validator's current balance.
SwitchDynasty uint64 // SwitchDynasty is the dynasty where the validator can (be inducted | be removed | withdraw their balance).
PubKey enr.Secp256k1 // PubKey is the validator's public key.
WithdrawalShard uint16 // WithdrawalShard is the shard balance will be sent to after withdrawal.
WithdrawalAddress common.Address // WithdrawalAddress is the address balance will be sent to after withdrawal.
RandaoCommitment common.Hash // RandaoCommitment is validator's current RANDAO beacon commitment.
Balance uint64 // Balance is validator's current balance.
SwitchDynasty uint64 // SwitchDynasty is the dynasty where the validator can (be inducted | be removed | withdraw their balance).
}
// CrosslinkRecord contains the fields of last fully formed

View File

@@ -8,7 +8,7 @@ import (
"golang.org/x/crypto/blake2s"
)
// Shuffle returns a list of pseudorandomly sampled
// ShuffleIndices returns a list of pseudorandomly sampled
// indices. This is used to use to select attesters and proposers.
func ShuffleIndices(seed common.Hash, validatorCount int) ([]int, error) {
if validatorCount > params.MaxValidators {