erigon-pulse/cl/spectest/consensus_tests/fork_choice.go
a 436493350e
Sentinel refactor (#8296)
1. changes sentinel to use an http-like interface

2. moves hexutil, crypto/blake2b, metrics packages to erigon-lib
2023-10-22 01:17:18 +02:00

262 lines
7.3 KiB
Go

package consensus_tests
import (
"context"
"fmt"
"github.com/ledgerwatch/erigon/spectest"
"io/fs"
"testing"
"github.com/ledgerwatch/erigon/cl/abstract"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/ledgerwatch/erigon/cl/phase1/forkchoice"
"github.com/ledgerwatch/erigon/cl/phase1/forkchoice/fork_graph"
"github.com/ledgerwatch/erigon/cl/pool"
"github.com/spf13/afero"
"github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/cl/cltypes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func (f *ForkChoiceStep) StepType() string {
if f.PayloadStatus != nil {
return "on_payload_info"
}
if f.AttesterSlashing != nil {
return "on_attester_slashing"
}
if f.PowBlock != nil {
return "on_merge_block"
}
if f.Block != nil {
return "on_block"
}
if f.Attestation != nil {
return "attestation"
}
if f.Tick != nil {
return "on_tick"
}
if f.Checks != nil {
return "checks"
}
return "unknown"
}
type ForkChoiceStep struct {
Tick *int `yaml:"tick,omitempty"`
Valid *bool `yaml:"valid,omitempty"`
Attestation *string `yaml:"attestation,omitempty"`
Block *string `yaml:"block,omitempty"`
PowBlock *string `yaml:"pow_block,omitempty"`
AttesterSlashing *string `yaml:"attester_slashing,omitempty"`
BlockHash *string `yaml:"block_hash,omitempty"`
PayloadStatus *ForkChoicePayloadStatus `yaml:"payload_status,omitempty"`
Checks *ForkChoiceChecks `yaml:"checks,omitempty"`
}
func (f *ForkChoiceStep) GetTick() int {
if f.Tick == nil {
return 0
}
return *f.Tick
}
func (f *ForkChoiceStep) GetValid() bool {
if f.Valid == nil {
return true
}
return *f.Valid
}
func (f *ForkChoiceStep) GetAttestation() string {
if f.Attestation == nil {
return ""
}
return *f.Attestation
}
func (f *ForkChoiceStep) GetBlock() string {
if f.Block == nil {
return ""
}
return *f.Block
}
func (f *ForkChoiceStep) GetPowBlock() string {
if f.PowBlock == nil {
return ""
}
return *f.PowBlock
}
func (f *ForkChoiceStep) GetAttesterSlashing() string {
if f.AttesterSlashing == nil {
return ""
}
return *f.AttesterSlashing
}
func (f *ForkChoiceStep) GetBlockHash() string {
if f.BlockHash == nil {
return ""
}
return *f.BlockHash
}
func (f *ForkChoiceStep) GetPayloadStatus() *ForkChoicePayloadStatus {
if f.PayloadStatus == nil {
return nil
}
return f.PayloadStatus
}
func (f *ForkChoiceStep) GetChecks() *ForkChoiceChecks {
if f.Checks == nil {
return nil
}
return f.Checks
}
type ForkChoiceChecks struct {
Head *struct {
Slot *int `yaml:"slot,omitempty"`
Root *common.Hash `yaml:"root,omitempty"`
} `yaml:"head,omitempty"`
Time *int `yaml:"time,omitempty"`
GenesisTime *int `yaml:"genesis_time,omitempty"`
JustifiedCheckpoint *struct {
Epoch *int `yaml:"epoch,omitempty"`
Root *common.Hash `yaml:"root,omitempty"`
} `yaml:"justified_checkpoint,omitempty"`
FinalizedCheckpoint *struct {
Epoch *int `yaml:"epoch,omitempty"`
Root *common.Hash `yaml:"root,omitempty"`
} `yaml:"finalized_checkpoint,omitempty"`
ProposerBoostRoot *common.Hash `yaml:"proposer_boost_root,omitempty"`
}
type ForkChoicePayloadStatus struct {
Status string `yaml:"status"`
LatestValidHash string `yaml:"latest_valid_hash"`
ValidationError string `yaml:"validation_error"`
}
type ForkChoice struct {
}
func NewForkChoice(fn func(s abstract.BeaconState) error) *ForkChoice {
return &ForkChoice{}
}
func (b *ForkChoice) Run(t *testing.T, root fs.FS, c spectest.TestCase) (err error) {
anchorBlock, err := spectest.ReadAnchorBlock(root, c.Version(), "anchor_block.ssz_snappy")
require.NoError(t, err)
// TODO: what to do with anchor block ?
_ = anchorBlock
anchorState, err := spectest.ReadBeaconState(root, c.Version(), "anchor_state.ssz_snappy")
require.NoError(t, err)
forkStore, err := forkchoice.NewForkChoiceStore(context.Background(), anchorState, nil, nil, pool.NewOperationsPool(&clparams.MainnetBeaconConfig), fork_graph.NewForkGraphDisk(anchorState, afero.NewMemMapFs()))
require.NoError(t, err)
var steps []ForkChoiceStep
err = spectest.ReadYml(root, "steps.yaml", &steps)
require.NoError(t, err)
for stepIdx, step := range steps {
stepstr := fmt.Sprintf("step %d: %s", stepIdx, step.StepType())
switch step.StepType() {
case "on_payload_info":
// we are not doing engine level mocking, so simply return the test when this is received
return nil
case "on_attester_slashing":
data := &cltypes.AttesterSlashing{}
err := spectest.ReadSsz(root, c.Version(), step.GetAttesterSlashing()+".ssz_snappy", data)
require.NoError(t, err, stepstr)
err = forkStore.OnAttesterSlashing(data, false)
if step.GetValid() {
require.NoError(t, err, stepstr)
} else {
require.Error(t, err, stepstr)
}
case "on_merge_block":
return nil
case "on_block":
blk := cltypes.NewSignedBeaconBlock(anchorState.BeaconConfig())
err := spectest.ReadSsz(root, c.Version(), step.GetBlock()+".ssz_snappy", blk)
require.NoError(t, err, stepstr)
err = forkStore.OnBlock(blk, true, true)
if step.GetValid() {
require.NoError(t, err, stepstr)
} else {
require.Error(t, err, stepstr)
}
case "attestation":
att := &solid.Attestation{}
err := spectest.ReadSsz(root, c.Version(), step.GetAttestation()+".ssz_snappy", att)
require.NoError(t, err, stepstr)
err = forkStore.OnAttestation(att, false)
if step.GetValid() {
require.NoError(t, err, stepstr)
} else {
require.Error(t, err, stepstr)
}
case "on_tick":
forkStore.OnTick(uint64(step.GetTick()))
//TODO: onTick needs to be able to return error
// if step.GetValid() {
// require.NoError(t, err)
// } else {
// require.Error(t, err)
// }
case "checks":
chk := step.GetChecks()
doCheck(t, stepstr, forkStore, chk)
default:
}
}
return nil
}
func doCheck(t *testing.T, stepstr string, store *forkchoice.ForkChoiceStore, e *ForkChoiceChecks) {
if e.Head != nil {
root, v, err := store.GetHead()
require.NoError(t, err, stepstr)
if e.Head.Root != nil {
assert.EqualValues(t, *e.Head.Root, root, stepstr)
}
if e.Head.Slot != nil {
assert.EqualValues(t, *e.Head.Slot, int(v), stepstr)
}
}
if e.Time != nil {
assert.EqualValues(t, *e.Time, store.Time(), stepstr)
}
/*if e.GenesisTime != nil {
// TODO: what value to use here??
//assert.EqualValues(t, e.Time, store.GenesisTime())
}*/
if e.ProposerBoostRoot != nil {
assert.EqualValues(t, *e.ProposerBoostRoot, store.ProposerBoostRoot(), stepstr)
}
if e.FinalizedCheckpoint != nil {
cp := store.FinalizedCheckpoint()
if e.FinalizedCheckpoint.Root != nil {
assert.EqualValues(t, *e.FinalizedCheckpoint.Root, cp.BlockRoot(), stepstr)
}
if e.FinalizedCheckpoint.Epoch != nil {
assert.EqualValues(t, *e.FinalizedCheckpoint.Epoch, cp.Epoch(), stepstr)
}
}
if e.JustifiedCheckpoint != nil {
cp := store.JustifiedCheckpoint()
if e.JustifiedCheckpoint.Root != nil {
assert.EqualValues(t, *e.JustifiedCheckpoint.Root, cp.BlockRoot(), stepstr)
}
if e.JustifiedCheckpoint.Epoch != nil {
assert.EqualValues(t, *e.JustifiedCheckpoint.Epoch, cp.Epoch(), stepstr)
}
}
}