mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 16:08:26 -05:00
beacon: Block Processing Validity Conditions (#310)
This commit is contained in:
committed by
Preston Van Loon
parent
48b047fd61
commit
d46f1f6502
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -17,4 +17,6 @@ const (
|
||||
MaxValidators = 4194304
|
||||
// NotariesPerCrosslink fixed to 100.
|
||||
NotariesPerCrosslink = 100
|
||||
// SlotLength in seconds.
|
||||
SlotLength = 8
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user