prysm-pulse/beacon-chain/chaintest/backend/simulated_backend.go

178 lines
6.0 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"
"strconv"
"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"
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
db *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,
db: db,
}, nil
}
// RunChainTest 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) RunChainTest(testCase *ChainTestCase) error {
defer teardownDB(sb.db)
// 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,
Balance: c.MaxDeposit * c.Gwei,
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.db)
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 {
// 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)
genesisTime := params.BeaconConfig().GenesisTime.Unix()
deposits := make([]*pb.Deposit, params.BeaconConfig().DepositsForChainStart)
for i := 0; i < len(deposits); i++ {
depositInput := &pb.DepositInput{
Pubkey: []byte(strconv.Itoa(i)),
RandaoCommitmentHash32: []byte("simulated"),
}
depositData, err := b.EncodeDepositData(
depositInput,
params.BeaconConfig().MaxDepositInGwei,
genesisTime,
)
if err != nil {
return fmt.Errorf("could not encode initial block deposits: %v", err)
}
deposits[i] = &pb.Deposit{DepositData: depositData}
}
beaconState, err := state.InitialBeaconState(deposits, uint64(genesisTime), nil)
if err != nil {
return err
}
// 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)
prevBlockRoot := hashutil.Hash(encodedGenesisBlock)
startTime := time.Now()
for i := uint64(0); i < testCase.Config.NumSlots; i++ {
newState, err := state.ExecuteStateTransition(beaconState, nil, prevBlockRoot)
if err != nil {
return fmt.Errorf("could not execute state transition: %v", err)
}
beaconState = newState
}
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
}