mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-26 13:18:57 +00:00
220 lines
7.8 KiB
Go
220 lines
7.8 KiB
Go
// Package backend contains utilities for simulating an entire
|
|
// ETH 2.0 beacon chain for e2e tests and benchmarking
|
|
// purposes.
|
|
package backend
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/blockchain"
|
|
b "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/state"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/utils"
|
|
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
|
|
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
|
"github.com/prysmaticlabs/prysm/shared/slices"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// SimulatedBackend allowing for a programmatic advancement
|
|
// of an in-memory beacon chain for client test runs
|
|
// and other e2e use cases.
|
|
type SimulatedBackend struct {
|
|
chainService *blockchain.ChainService
|
|
beaconDB *db.BeaconDB
|
|
}
|
|
|
|
// NewSimulatedBackend creates an instance by initializing a chain service
|
|
// utilizing a mockDB which will act according to test run parameters specified
|
|
// in the common ETH 2.0 client test YAML format.
|
|
func NewSimulatedBackend() (*SimulatedBackend, error) {
|
|
db, err := setupDB()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not setup simulated backend db: %v", err)
|
|
}
|
|
cs, err := blockchain.NewChainService(context.Background(), &blockchain.Config{
|
|
BeaconDB: db,
|
|
IncomingBlockBuf: 0,
|
|
EnablePOWChain: false,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &SimulatedBackend{
|
|
chainService: cs,
|
|
beaconDB: db,
|
|
}, nil
|
|
}
|
|
|
|
// RunForkChoiceTest uses a parsed set of chaintests from a YAML file
|
|
// according to the ETH 2.0 client chain test specification and runs them
|
|
// against the simulated backend.
|
|
func (sb *SimulatedBackend) RunForkChoiceTest(testCase *ForkChoiceTestCase) error {
|
|
defer teardownDB(sb.beaconDB)
|
|
// Utilize the config parameters in the test case to setup
|
|
// the DB and set global config parameters accordingly.
|
|
// Config parameters include: ValidatorCount, ShardCount,
|
|
// CycleLength, MinCommitteeSize, and more based on the YAML
|
|
// test language specification.
|
|
c := params.BeaconConfig()
|
|
c.ShardCount = testCase.Config.ShardCount
|
|
c.EpochLength = testCase.Config.CycleLength
|
|
c.TargetCommitteeSize = testCase.Config.MinCommitteeSize
|
|
params.OverrideBeaconConfig(c)
|
|
|
|
// Then, we create the validators based on the custom test config.
|
|
randaoPreCommit := [32]byte{}
|
|
randaoReveal := hashutil.Hash(randaoPreCommit[:])
|
|
validators := make([]*pb.ValidatorRecord, testCase.Config.ValidatorCount)
|
|
for i := uint64(0); i < testCase.Config.ValidatorCount; i++ {
|
|
validators[i] = &pb.ValidatorRecord{
|
|
ExitSlot: params.BeaconConfig().EntryExitDelay,
|
|
Pubkey: []byte{},
|
|
RandaoCommitmentHash32: randaoReveal[:],
|
|
}
|
|
}
|
|
// TODO(#718): Next step is to update and save the blocks specified
|
|
// in the case case into the DB.
|
|
//
|
|
// Then, we call the updateHead routine and confirm the
|
|
// chain's head is the expected result from the test case.
|
|
return nil
|
|
}
|
|
|
|
// RunShuffleTest uses validator set specified from a YAML file, runs the validator shuffle
|
|
// algorithm, then compare the output with the expected output from the YAML file.
|
|
func (sb *SimulatedBackend) RunShuffleTest(testCase *ShuffleTestCase) error {
|
|
defer teardownDB(sb.beaconDB)
|
|
seed := common.BytesToHash([]byte(testCase.Seed))
|
|
output, err := utils.ShuffleIndices(seed, testCase.Input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !reflect.DeepEqual(output, testCase.Output) {
|
|
return fmt.Errorf("shuffle result error: expected %v, actual %v", testCase.Output, output)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RunStateTransitionTest advances a beacon chain state transition an N amount of
|
|
// slots from a genesis state, with a block being processed at every iteration
|
|
// of the state transition function.
|
|
func (sb *SimulatedBackend) RunStateTransitionTest(testCase *StateTestCase) error {
|
|
defer teardownDB(sb.beaconDB)
|
|
// We setup the initial configuration for running state
|
|
// transition tests below.
|
|
c := params.BeaconConfig()
|
|
c.EpochLength = testCase.Config.EpochLength
|
|
c.DepositsForChainStart = testCase.Config.DepositsForChainStart
|
|
params.OverrideBeaconConfig(c)
|
|
|
|
// We create a list of randao hash onions for the given number of slots
|
|
// the simulation will attempt.
|
|
hashOnions := generateSimulatedRandaoHashOnions(testCase.Config.NumSlots)
|
|
|
|
// We then generate initial validator deposits for initializing the
|
|
// beacon state based where every validator will use the last layer in the randao
|
|
// onions list as the commitment in the deposit instance.
|
|
lastRandaoLayer := hashOnions[len(hashOnions)-1]
|
|
initialDeposits, err := generateInitialSimulatedDeposits(lastRandaoLayer)
|
|
if err != nil {
|
|
return fmt.Errorf("could not simulate initial validator deposits: %v", err)
|
|
}
|
|
|
|
genesisTime := params.BeaconConfig().GenesisTime.Unix()
|
|
beaconState, err := state.InitialBeaconState(initialDeposits, uint64(genesisTime), nil)
|
|
if err != nil {
|
|
return fmt.Errorf("could not initialize simulated beacon state")
|
|
}
|
|
|
|
// We do not expect hashing initial beacon state and genesis block to
|
|
// fail, so we can safely ignore the error below.
|
|
// #nosec G104
|
|
encodedState, _ := proto.Marshal(beaconState)
|
|
stateRoot := hashutil.Hash(encodedState)
|
|
genesisBlock := b.NewGenesisBlock(stateRoot[:])
|
|
// #nosec G104
|
|
encodedGenesisBlock, _ := proto.Marshal(genesisBlock)
|
|
genesisBlockRoot := hashutil.Hash(encodedGenesisBlock)
|
|
|
|
// We now keep track of generated blocks for each state transition in
|
|
// a slice.
|
|
prevBlockRoots := [][32]byte{genesisBlockRoot}
|
|
|
|
// We keep track of the randao layers peeled for each proposer index in a map.
|
|
layersPeeledForProposer := make(map[uint32]int, len(beaconState.ValidatorRegistry))
|
|
for idx := range beaconState.ValidatorRegistry {
|
|
layersPeeledForProposer[uint32(idx)] = 0
|
|
}
|
|
|
|
startTime := time.Now()
|
|
for i := uint64(0); i < testCase.Config.NumSlots; i++ {
|
|
prevBlockRoot := prevBlockRoots[len(prevBlockRoots)-1]
|
|
|
|
proposerIndex, err := findNextSlotProposerIndex(beaconState)
|
|
if err != nil {
|
|
return fmt.Errorf("could not fetch beacon proposer index: %v", err)
|
|
}
|
|
|
|
// If the slot is marked as skipped in the configuration options,
|
|
// we simply run the state transition with a nil block argument.
|
|
if slices.IsInUint64(i, testCase.Config.SkipSlots) {
|
|
newState, err := state.ExecuteStateTransition(beaconState, nil, prevBlockRoot)
|
|
if err != nil {
|
|
return fmt.Errorf("could not execute state transition: %v", err)
|
|
}
|
|
beaconState = newState
|
|
layersPeeledForProposer[proposerIndex]++
|
|
continue
|
|
}
|
|
|
|
layersPeeled := layersPeeledForProposer[proposerIndex]
|
|
blockRandaoReveal := determineSimulatedBlockRandaoReveal(layersPeeled, hashOnions)
|
|
|
|
// We generate a new block to pass into the state transition.
|
|
newBlock, newBlockRoot, err := generateSimulatedBlock(
|
|
beaconState,
|
|
prevBlockRoot,
|
|
blockRandaoReveal,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("could not generate simulated beacon block %v", err)
|
|
}
|
|
newState, err := state.ExecuteStateTransition(beaconState, newBlock, prevBlockRoot)
|
|
if err != nil {
|
|
return fmt.Errorf("could not execute state transition: %v", err)
|
|
}
|
|
|
|
// We then keep track of information about the state after the
|
|
// state transition was applied.
|
|
beaconState = newState
|
|
prevBlockRoots = append(prevBlockRoots, newBlockRoot)
|
|
layersPeeledForProposer[proposerIndex]++
|
|
}
|
|
|
|
endTime := time.Now()
|
|
log.Infof(
|
|
"%d state transitions with %d deposits finished in %v",
|
|
testCase.Config.NumSlots,
|
|
testCase.Config.DepositsForChainStart,
|
|
endTime.Sub(startTime),
|
|
)
|
|
|
|
if beaconState.GetSlot() != testCase.Results.Slot {
|
|
return fmt.Errorf(
|
|
"incorrect state slot after %d state transitions without blocks, wanted %d, received %d",
|
|
testCase.Config.NumSlots,
|
|
testCase.Config.NumSlots,
|
|
testCase.Results.Slot,
|
|
)
|
|
}
|
|
return nil
|
|
}
|