beacon: Check State Hashes while Processing Incoming Blocks (#319)

This commit is contained in:
terence tsao
2018-07-24 14:09:04 -07:00
committed by Raul Jordan
parent d33836b48e
commit c0b4503d5f
5 changed files with 132 additions and 35 deletions

View File

@@ -18,6 +18,7 @@ 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",
"@org_golang_x_crypto//blake2b:go_default_library",
],
)

View File

@@ -1,8 +1,10 @@
package blockchain
import (
"bytes"
"context"
"fmt"
"hash"
"math"
"sync"
"time"
@@ -15,6 +17,7 @@ import (
"github.com/prysmaticlabs/prysm/beacon-chain/types"
"github.com/prysmaticlabs/prysm/beacon-chain/utils"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/blake2b"
)
var stateLookupKey = "beaconchainstate"
@@ -107,6 +110,24 @@ func (b *BeaconChain) CanProcessBlock(fetcher powchain.POWBlockFetcher, block *t
// 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))
// Verify state hashes from the block are correct
hash, err := hashActiveState(*b.ActiveState())
if err != nil {
return false, err
}
if !bytes.Equal(block.Data().ActiveStateHash.Sum(nil), hash.Sum(nil)) {
return false, fmt.Errorf("Active state hash mismatched, wanted: %v, got: %v", hash.Sum(nil), block.Data().ActiveStateHash.Sum(nil))
}
hash, err = hashCrystallizedState(*b.CrystallizedState())
if err != nil {
return false, err
}
if !bytes.Equal(block.Data().CrystallizedStateHash.Sum(nil), hash.Sum(nil)) {
return false, fmt.Errorf("Crystallized state hash mismatched, wanted: %v, got: %v", hash.Sum(nil), block.Data().CrystallizedStateHash.Sum(nil))
}
return validTime, nil
}
@@ -144,6 +165,26 @@ func (b *BeaconChain) computeNewActiveState(seed common.Hash) (*types.ActiveStat
}, nil
}
// hashActiveState serializes the active state object then uses
// blake2b to hash the serialized object.
func hashActiveState(state types.ActiveState) (hash.Hash, error) {
serializedState, err := rlp.EncodeToBytes(state)
if err != nil {
return nil, err
}
return blake2b.New256(serializedState)
}
// hashCrystallizedState serializes the crystallized state object
// then uses blake2b to hash the serialized object.
func hashCrystallizedState(state types.CrystallizedState) (hash.Hash, error) {
serializedState, err := rlp.EncodeToBytes(state)
if err != nil {
return nil, err
}
return blake2b.New256(serializedState)
}
// getAttestersProposer returns lists of random sampled attesters and proposer indices.
func (b *BeaconChain) getAttestersProposer(seed common.Hash) ([]int, int, error) {
attesterCount := math.Min(params.AttesterCount, float64(len(b.CrystallizedState().ActiveValidators)))

View File

@@ -195,7 +195,21 @@ func TestCanProcessBlock(t *testing.T) {
if _, err := beaconChain.CanProcessBlock(&faultyFetcher{}, block); err == nil {
t.Errorf("Using a faulty fetcher should throw an error, received nil")
}
activeState := &types.ActiveState{TotalAttesterDeposits: 10000}
beaconChain.state.ActiveState = activeState
activeHash, err := hashActiveState(*activeState)
if err != nil {
t.Fatalf("Cannot hash active state: %v", err)
}
block.InsertActiveHash(activeHash)
crystallizedHash, err := hashCrystallizedState(types.CrystallizedState{})
if err != nil {
t.Fatalf("Compute crystallized state hash failed: %v", err)
}
block.InsertCrystallizedHash(crystallizedHash)
canProcess, err := beaconChain.CanProcessBlock(&mockFetcher{}, block)
if err != nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
@@ -207,6 +221,8 @@ func TestCanProcessBlock(t *testing.T) {
// Attempting to try a block with that fails the timestamp validity
// condition.
block = types.NewBlock(1000000)
block.InsertActiveHash(activeHash)
block.InsertCrystallizedHash(crystallizedHash)
canProcess, err = beaconChain.CanProcessBlock(&mockFetcher{}, block)
if err != nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
@@ -215,3 +231,53 @@ func TestCanProcessBlock(t *testing.T) {
t.Errorf("Should not be able to process block with invalid timestamp condition")
}
}
func TestProcessBlockWithBadHashes(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()
b, err := NewBeaconChain(db.DB())
if err != nil {
t.Fatalf("Unable to setup beacon chain: %v", err)
}
// Test negative scenario where active state hash is different than node's compute
block := types.NewBlock(1)
activeState := &types.ActiveState{TotalAttesterDeposits: 10000}
stateHash, err := hashActiveState(*activeState)
if err != nil {
t.Fatalf("Cannot hash active state: %v", err)
}
block.InsertActiveHash(stateHash)
b.state.ActiveState = &types.ActiveState{TotalAttesterDeposits: 9999}
canProcess, err := b.CanProcessBlock(&mockFetcher{}, block)
if err == nil {
t.Fatalf("CanProcessBlocks should have failed with diff state hashes")
}
if canProcess {
t.Errorf("CanProcessBlocks should have returned false")
}
// Test negative scenario where crystallized state hash is different than node's compute
crystallizedState := &types.CrystallizedState{CurrentEpoch: 10000}
stateHash, err = hashCrystallizedState(*crystallizedState)
if err != nil {
t.Fatalf("Cannot hash crystallized state: %v", err)
}
block.InsertCrystallizedHash(stateHash)
b.state.CrystallizedState = &types.CrystallizedState{CurrentEpoch: 9999}
canProcess, err = b.CanProcessBlock(&mockFetcher{}, block)
if err == nil {
t.Fatalf("CanProcessBlocks should have failed with diff state hashes")
}
if canProcess {
t.Errorf("CanProcessBlocks should have returned false")
}
}

View File

@@ -1,6 +1,7 @@
package types
import (
"hash"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -31,15 +32,15 @@ func NewGenesisBlock() *Block {
// Data contains the fields in a beacon chain block.
type Data struct {
ParentHash common.Hash // ParentHash is the hash of the parent beacon block.
ParentHash hash.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.
RandaoReveal hash.Hash // RandaoReveal is used for Randao commitment reveal.
AttestationBitmask []byte // AttestationBitmask is the bit field of who from the attestation committee participated.
AttestationAggregateSig []uint // AttestationAggregateSig is validator's aggregate sig.
ShardAggregateVotes []AggregateVote // ShardAggregateVotes is shard aggregate votes.
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.
ActiveStateHash hash.Hash // ActiveStateHash is the state that changes every block.
CrystallizedStateHash hash.Hash // CrystallizedStateHash is the state that changes every epoch.
Timestamp time.Time
}
@@ -50,3 +51,11 @@ type AggregateVote struct {
SignerBitmask []byte // SignerBitmask is the bit mask of every validator that signed.
AggregateSig []uint // AggregateSig is the aggregated signatures of individual shard.
}
func (b *Block) InsertActiveHash(hash hash.Hash) {
b.data.ActiveStateHash = hash
}
func (b *Block) InsertCrystallizedHash(hash hash.Hash) {
b.data.CrystallizedStateHash = hash
}

View File

@@ -12,32 +12,20 @@ type ActiveState struct {
AttesterBitfields []byte // AttesterBitfields represents which validator has attested.
}
// PartialCrosslinkRecord contains information about cross links
// that are being put together during this epoch.
type PartialCrosslinkRecord struct {
ShardID uint16 // ShardID is the shard crosslink being made for.
ShardBlockHash common.Hash // ShardBlockHash is the hash of the block.
NotaryBitfield []byte // NotaryBitfield determines which notary has voted.
AggregateSig []uint // AggregateSig is the aggregated signature of all the notaries who voted.
}
// CrystallizedState contains fields of every epoch state,
// it changes every epoch.
type CrystallizedState struct {
ActiveValidators []ValidatorRecord // ActiveValidators is the list of active validators.
QueuedValidators []ValidatorRecord // QueuedValidators is the list of joined but not yet inducted validators.
ExitedValidators []ValidatorRecord // ExitedValidators is the list of removed validators pending withdrawal.
CurrentShuffling []uint16 // CurrentShuffling is hhe permutation of validators used to determine who cross-links what shard in this epoch.
CurrentEpoch uint64 // CurrentEpoch is the current epoch.
LastJustifiedEpoch uint64 // LastJustifiedEpoch is the last justified epoch.
LastFinalizedEpoch uint64 // LastFinalizedEpoch is the last finalized epoch.
Dynasty uint64 // Dynasty is the current dynasty.
NextShard uint16 // NextShard is the next shard that cross-linking assignment will start from.
CurrentCheckpoint common.Hash // CurrentCheckpoint is the current FFG checkpoint.
CrosslinkRecords []CrosslinkRecord // CrosslinkRecords records about the most recent crosslink for each shard.
TotalDeposits uint // TotalDeposits is the Total balance of deposits.
CrosslinkSeed common.Hash // CrosslinkSeed is used to select the committees for each shard.
CrosslinkSeedLastReset uint64 // CrosslinkSeedLastReset is the last epoch the crosslink seed was reset.
ActiveValidators []ValidatorRecord // ActiveValidators is the list of active validators.
QueuedValidators []ValidatorRecord // QueuedValidators is the list of joined but not yet inducted validators.
ExitedValidators []ValidatorRecord // ExitedValidators is the list of removed validators pending withdrawal.
CurrentShuffling []uint16 // CurrentShuffling is hhe permutation of validators used to determine who cross-links what shard in this epoch.
CurrentEpoch uint64 // CurrentEpoch is the current epoch.
LastJustifiedEpoch uint64 // LastJustifiedEpoch is the last justified epoch.
LastFinalizedEpoch uint64 // LastFinalizedEpoch is the last finalized epoch.
Dynasty uint64 // Dynasty is the current dynasty.
NextShard uint16 // NextShard is the next shard that cross-linking assignment will start from.
CurrentCheckpoint common.Hash // CurrentCheckpoint is the current FFG checkpoint.
TotalDeposits uint // TotalDeposits is the Total balance of deposits.
}
// ValidatorRecord contains information about a validator
@@ -50,13 +38,6 @@ type ValidatorRecord struct {
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
// crosslink to be submitted into the chain.
type CrosslinkRecord struct {
Epoch uint64 // Epoch records the epoch the crosslink was submitted in.
Hash common.Hash // Hash is the block hash.
}
// NewGenesisStates initializes a beacon chain with starting parameters.
func NewGenesisStates() (*ActiveState, *CrystallizedState) {
active := &ActiveState{
@@ -73,7 +54,6 @@ func NewGenesisStates() (*ActiveState, *CrystallizedState) {
LastFinalizedEpoch: 0,
Dynasty: 0,
TotalDeposits: 0,
CrosslinkSeed: common.BytesToHash([]byte{}),
}
return active, crystallized
}