mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 07:58:22 -05:00
Hot/cold state management: Replay blocks and gen state (#4877)
* Starting stategen * Replay implementations * Replay tests * Gazelle * Fixed tests * Dont have to verify sig
This commit is contained in:
@@ -122,7 +122,7 @@ func ExecuteStateTransitionNoVerifyAttSigs(
|
||||
}
|
||||
|
||||
// Execute per block transition.
|
||||
state, err = processBlockNoVerifyAttSigs(ctx, state, signed)
|
||||
state, err = ProcessBlockNoVerifyAttSigs(ctx, state, signed)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process block")
|
||||
}
|
||||
@@ -368,7 +368,7 @@ func ProcessBlock(
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// processBlockNoVerifyAttSigs creates a new, modified beacon state by applying block operation
|
||||
// ProcessBlockNoVerifyAttSigs creates a new, modified beacon state by applying block operation
|
||||
// transformations as defined in the Ethereum Serenity specification. It does not validate
|
||||
// block attestation signatures.
|
||||
//
|
||||
@@ -379,7 +379,7 @@ func ProcessBlock(
|
||||
// process_randao(state, block.body)
|
||||
// process_eth1_data(state, block.body)
|
||||
// process_operations(state, block.body)
|
||||
func processBlockNoVerifyAttSigs(
|
||||
func ProcessBlockNoVerifyAttSigs(
|
||||
ctx context.Context,
|
||||
state *stateTrie.BeaconState,
|
||||
signed *ethpb.SignedBeaconBlock,
|
||||
|
||||
34
beacon-chain/stategen/BUILD.bazel
Normal file
34
beacon-chain/stategen/BUILD.bazel
Normal file
@@ -0,0 +1,34 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"replay.go",
|
||||
"service.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/stategen",
|
||||
visibility = ["//beacon-chain:__subpackages__"],
|
||||
deps = [
|
||||
"//beacon-chain/core/state:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/filters:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["replay_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//proto/beacon/p2p/v1:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_ssz//:go_default_library",
|
||||
],
|
||||
)
|
||||
77
beacon-chain/stategen/replay.go
Normal file
77
beacon-chain/stategen/replay.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package stategen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
transition "github.com/prysmaticlabs/prysm/beacon-chain/core/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db/filters"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
)
|
||||
|
||||
// This replays the input blocks on the input state until the target slot is reached.
|
||||
func (s *State) replayBlocks(ctx context.Context, state *state.BeaconState, signed []*ethpb.SignedBeaconBlock, targetSlot uint64) (*state.BeaconState, error) {
|
||||
var err error
|
||||
// The input block list is sorted in decreasing slots order.
|
||||
for i := len(signed) - 1; i >= 0; i-- {
|
||||
// If there is skip slot.
|
||||
for state.Slot() < signed[i].Block.Slot {
|
||||
state, err = transition.ProcessSlot(ctx, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
state, err = transition.ProcessBlockNoVerifyAttSigs(ctx, state, signed[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// If there is skip slots at the end.
|
||||
for state.Slot() < targetSlot {
|
||||
state, err = transition.ProcessSlot(ctx, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// This loads the blocks between start slot and end slot by recursively fetching from end block root.
|
||||
// The Blocks are returned in slot-descending order.
|
||||
func (s *State) loadBlocks(ctx context.Context, startSlot uint64, endSlot uint64, endBlockRoot [32]byte) ([]*ethpb.SignedBeaconBlock, error) {
|
||||
filter := filters.NewFilter().SetStartSlot(startSlot).SetEndSlot(endSlot)
|
||||
blocks, err := s.beaconDB.Blocks(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockRoots, err := s.beaconDB.BlockRoots(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The retrieved blocks and block roots have to be in the same length given same filter.
|
||||
if len(blocks) != len(blockRoots) {
|
||||
return nil, errors.New("length of blocks and roots don't match")
|
||||
}
|
||||
|
||||
// The last retrieved block root has to match input end block root.
|
||||
length := len(blocks)
|
||||
if blockRoots[length-1] != endBlockRoot {
|
||||
return nil, errors.New("end block roots don't match")
|
||||
}
|
||||
|
||||
filteredBlocks := []*ethpb.SignedBeaconBlock{blocks[length-1]}
|
||||
// Starting from second to last index because the last block is already in the filtered block list.
|
||||
for i := length - 2; i >= 0; i-- {
|
||||
b := filteredBlocks[len(filteredBlocks)-1]
|
||||
if bytesutil.ToBytes32(b.Block.ParentRoot) != blockRoots[i] {
|
||||
continue
|
||||
}
|
||||
filteredBlocks = append(filteredBlocks, blocks[i])
|
||||
}
|
||||
|
||||
return filteredBlocks, nil
|
||||
}
|
||||
228
beacon-chain/stategen/replay_test.go
Normal file
228
beacon-chain/stategen/replay_test.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package stategen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/prysmaticlabs/go-ssz"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
|
||||
stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
)
|
||||
|
||||
func TestLoadBlocks_FirstBranch(t *testing.T) {
|
||||
db := testDB.SetupDB(t)
|
||||
defer testDB.TeardownDB(t, db)
|
||||
ctx := context.Background()
|
||||
s := &State{
|
||||
beaconDB: db,
|
||||
}
|
||||
|
||||
roots, savedBlocks, err := tree1(db, []byte{'A'})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
filteredBlocks, err := s.loadBlocks(ctx, 0, 8, roots[len(roots)-1])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wanted := []*ethpb.SignedBeaconBlock{
|
||||
{Block: savedBlocks[8]},
|
||||
{Block: savedBlocks[6]},
|
||||
{Block: savedBlocks[4]},
|
||||
{Block: savedBlocks[2]},
|
||||
{Block: savedBlocks[1]},
|
||||
{Block: savedBlocks[0]},
|
||||
}
|
||||
if !reflect.DeepEqual(filteredBlocks, wanted) {
|
||||
t.Error("Did not get wanted blocks")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadBlocks_SecondBranch(t *testing.T) {
|
||||
db := testDB.SetupDB(t)
|
||||
defer testDB.TeardownDB(t, db)
|
||||
ctx := context.Background()
|
||||
s := &State{
|
||||
beaconDB: db,
|
||||
}
|
||||
|
||||
roots, savedBlocks, err := tree1(db, []byte{'A'})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
filteredBlocks, err := s.loadBlocks(ctx, 0, 5, roots[5])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wanted := []*ethpb.SignedBeaconBlock{
|
||||
{Block: savedBlocks[5]},
|
||||
{Block: savedBlocks[3]},
|
||||
{Block: savedBlocks[1]},
|
||||
{Block: savedBlocks[0]},
|
||||
}
|
||||
if !reflect.DeepEqual(filteredBlocks, wanted) {
|
||||
t.Error("Did not get wanted blocks")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadBlocks_ThirdBranch(t *testing.T) {
|
||||
db := testDB.SetupDB(t)
|
||||
defer testDB.TeardownDB(t, db)
|
||||
ctx := context.Background()
|
||||
s := &State{
|
||||
beaconDB: db,
|
||||
}
|
||||
|
||||
roots, savedBlocks, err := tree1(db, []byte{'A'})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
filteredBlocks, err := s.loadBlocks(ctx, 0, 7, roots[7])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wanted := []*ethpb.SignedBeaconBlock{
|
||||
{Block: savedBlocks[7]},
|
||||
{Block: savedBlocks[6]},
|
||||
{Block: savedBlocks[4]},
|
||||
{Block: savedBlocks[2]},
|
||||
{Block: savedBlocks[1]},
|
||||
{Block: savedBlocks[0]},
|
||||
}
|
||||
if !reflect.DeepEqual(filteredBlocks, wanted) {
|
||||
t.Error("Did not get wanted blocks")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadBlocks_SameSlots(t *testing.T) {
|
||||
db := testDB.SetupDB(t)
|
||||
defer testDB.TeardownDB(t, db)
|
||||
ctx := context.Background()
|
||||
s := &State{
|
||||
beaconDB: db,
|
||||
}
|
||||
|
||||
roots, savedBlocks, err := tree2(db, []byte{'A'})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
filteredBlocks, err := s.loadBlocks(ctx, 0, 3, roots[6])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wanted := []*ethpb.SignedBeaconBlock{
|
||||
{Block: savedBlocks[6]},
|
||||
{Block: savedBlocks[5]},
|
||||
{Block: savedBlocks[1]},
|
||||
{Block: savedBlocks[0]},
|
||||
}
|
||||
if !reflect.DeepEqual(filteredBlocks, wanted) {
|
||||
t.Error("Did not get wanted blocks")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadBlocks_BadStart(t *testing.T) {
|
||||
db := testDB.SetupDB(t)
|
||||
defer testDB.TeardownDB(t, db)
|
||||
ctx := context.Background()
|
||||
s := &State{
|
||||
beaconDB: db,
|
||||
}
|
||||
|
||||
roots, _, err := tree1(db, []byte{'A'})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = s.loadBlocks(ctx, 0, 5, roots[8])
|
||||
if err.Error() != "end block roots don't match" {
|
||||
t.Error("Did not get wanted error")
|
||||
}
|
||||
}
|
||||
|
||||
// tree1 constructs the following tree:
|
||||
// B0 - B1 - - B3 -- B5
|
||||
// \- B2 -- B4 -- B6 ----- B8
|
||||
// \- B7
|
||||
func tree1(db db.Database, genesisRoot []byte) ([][32]byte, []*ethpb.BeaconBlock, error) {
|
||||
b0 := ðpb.BeaconBlock{Slot: 0, ParentRoot: genesisRoot}
|
||||
r0, _ := ssz.HashTreeRoot(b0)
|
||||
b1 := ðpb.BeaconBlock{Slot: 1, ParentRoot: r0[:]}
|
||||
r1, _ := ssz.HashTreeRoot(b1)
|
||||
b2 := ðpb.BeaconBlock{Slot: 2, ParentRoot: r1[:]}
|
||||
r2, _ := ssz.HashTreeRoot(b2)
|
||||
b3 := ðpb.BeaconBlock{Slot: 3, ParentRoot: r1[:]}
|
||||
r3, _ := ssz.HashTreeRoot(b3)
|
||||
b4 := ðpb.BeaconBlock{Slot: 4, ParentRoot: r2[:]}
|
||||
r4, _ := ssz.HashTreeRoot(b4)
|
||||
b5 := ðpb.BeaconBlock{Slot: 5, ParentRoot: r3[:]}
|
||||
r5, _ := ssz.HashTreeRoot(b5)
|
||||
b6 := ðpb.BeaconBlock{Slot: 6, ParentRoot: r4[:]}
|
||||
r6, _ := ssz.HashTreeRoot(b6)
|
||||
b7 := ðpb.BeaconBlock{Slot: 7, ParentRoot: r6[:]}
|
||||
r7, _ := ssz.HashTreeRoot(b7)
|
||||
b8 := ðpb.BeaconBlock{Slot: 8, ParentRoot: r6[:]}
|
||||
r8, _ := ssz.HashTreeRoot(b8)
|
||||
st, err := stateTrie.InitializeFromProtoUnsafe(&pb.BeaconState{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, b := range []*ethpb.BeaconBlock{b0, b1, b2, b3, b4, b5, b6, b7, b8} {
|
||||
if err := db.SaveBlock(context.Background(), ðpb.SignedBeaconBlock{Block: b}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := db.SaveState(context.Background(), st.Copy(), bytesutil.ToBytes32(b.ParentRoot)); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return [][32]byte{r0, r1, r2, r3, r4, r5, r6, r7, r8}, []*ethpb.BeaconBlock{b0, b1, b2, b3, b4, b5, b6, b7, b8}, nil
|
||||
}
|
||||
|
||||
// tree2 constructs the following tree:
|
||||
// B0 - B1
|
||||
// \- B2
|
||||
// \- B2
|
||||
// \- B2
|
||||
// \- B2 -- B3
|
||||
func tree2(db db.Database, genesisRoot []byte) ([][32]byte, []*ethpb.BeaconBlock, error) {
|
||||
b0 := ðpb.BeaconBlock{Slot: 0, ParentRoot: genesisRoot}
|
||||
r0, _ := ssz.HashTreeRoot(b0)
|
||||
b1 := ðpb.BeaconBlock{Slot: 1, ParentRoot: r0[:]}
|
||||
r1, _ := ssz.HashTreeRoot(b1)
|
||||
b21 := ðpb.BeaconBlock{Slot: 2, ParentRoot: r1[:], StateRoot: []byte{'A'}}
|
||||
r21, _ := ssz.HashTreeRoot(b21)
|
||||
b22 := ðpb.BeaconBlock{Slot: 2, ParentRoot: r1[:], StateRoot: []byte{'B'}}
|
||||
r22, _ := ssz.HashTreeRoot(b22)
|
||||
b23 := ðpb.BeaconBlock{Slot: 2, ParentRoot: r1[:], StateRoot: []byte{'C'}}
|
||||
r23, _ := ssz.HashTreeRoot(b23)
|
||||
b24 := ðpb.BeaconBlock{Slot: 2, ParentRoot: r1[:], StateRoot: []byte{'D'}}
|
||||
r24, _ := ssz.HashTreeRoot(b24)
|
||||
b3 := ðpb.BeaconBlock{Slot: 3, ParentRoot: r24[:]}
|
||||
r3, _ := ssz.HashTreeRoot(b3)
|
||||
st, err := stateTrie.InitializeFromProtoUnsafe(&pb.BeaconState{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, b := range []*ethpb.BeaconBlock{b0, b1, b21, b22, b23, b24, b3} {
|
||||
if err := db.SaveBlock(context.Background(), ðpb.SignedBeaconBlock{Block: b}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := db.SaveState(context.Background(), st.Copy(), bytesutil.ToBytes32(b.ParentRoot)); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return [][32]byte{r0, r1, r21, r22, r23, r24, r3}, []*ethpb.BeaconBlock{b0, b1, b21, b22, b23, b24, b3}, nil
|
||||
}
|
||||
18
beacon-chain/stategen/service.go
Normal file
18
beacon-chain/stategen/service.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package stategen
|
||||
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
)
|
||||
|
||||
// State represents a management object that handles the internal
|
||||
// logic of maintaining both hot and cold states in DB.
|
||||
type State struct {
|
||||
beaconDB db.NoHeadAccessDatabase
|
||||
}
|
||||
|
||||
// New returns a new state management object.
|
||||
func New(db db.NoHeadAccessDatabase) *State {
|
||||
return &State{
|
||||
beaconDB: db,
|
||||
}
|
||||
}
|
||||
@@ -197,8 +197,8 @@ var (
|
||||
Hidden: true,
|
||||
}
|
||||
deprecatedEnableAttestationCacheFlag = cli.BoolFlag{
|
||||
Name: "enable-attestation-cache",
|
||||
Usage: deprecatedUsage,
|
||||
Name: "enable-attestation-cache",
|
||||
Usage: deprecatedUsage,
|
||||
Hidden: true,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user