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:
terence tsao
2020-02-16 16:28:20 -07:00
committed by GitHub
parent 8885d715f2
commit b9c140c17d
6 changed files with 362 additions and 5 deletions

View File

@@ -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,

View 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",
],
)

View 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
}

View 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 := &ethpb.BeaconBlock{Slot: 0, ParentRoot: genesisRoot}
r0, _ := ssz.HashTreeRoot(b0)
b1 := &ethpb.BeaconBlock{Slot: 1, ParentRoot: r0[:]}
r1, _ := ssz.HashTreeRoot(b1)
b2 := &ethpb.BeaconBlock{Slot: 2, ParentRoot: r1[:]}
r2, _ := ssz.HashTreeRoot(b2)
b3 := &ethpb.BeaconBlock{Slot: 3, ParentRoot: r1[:]}
r3, _ := ssz.HashTreeRoot(b3)
b4 := &ethpb.BeaconBlock{Slot: 4, ParentRoot: r2[:]}
r4, _ := ssz.HashTreeRoot(b4)
b5 := &ethpb.BeaconBlock{Slot: 5, ParentRoot: r3[:]}
r5, _ := ssz.HashTreeRoot(b5)
b6 := &ethpb.BeaconBlock{Slot: 6, ParentRoot: r4[:]}
r6, _ := ssz.HashTreeRoot(b6)
b7 := &ethpb.BeaconBlock{Slot: 7, ParentRoot: r6[:]}
r7, _ := ssz.HashTreeRoot(b7)
b8 := &ethpb.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(), &ethpb.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 := &ethpb.BeaconBlock{Slot: 0, ParentRoot: genesisRoot}
r0, _ := ssz.HashTreeRoot(b0)
b1 := &ethpb.BeaconBlock{Slot: 1, ParentRoot: r0[:]}
r1, _ := ssz.HashTreeRoot(b1)
b21 := &ethpb.BeaconBlock{Slot: 2, ParentRoot: r1[:], StateRoot: []byte{'A'}}
r21, _ := ssz.HashTreeRoot(b21)
b22 := &ethpb.BeaconBlock{Slot: 2, ParentRoot: r1[:], StateRoot: []byte{'B'}}
r22, _ := ssz.HashTreeRoot(b22)
b23 := &ethpb.BeaconBlock{Slot: 2, ParentRoot: r1[:], StateRoot: []byte{'C'}}
r23, _ := ssz.HashTreeRoot(b23)
b24 := &ethpb.BeaconBlock{Slot: 2, ParentRoot: r1[:], StateRoot: []byte{'D'}}
r24, _ := ssz.HashTreeRoot(b24)
b3 := &ethpb.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(), &ethpb.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
}

View 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,
}
}

View File

@@ -197,8 +197,8 @@ var (
Hidden: true,
}
deprecatedEnableAttestationCacheFlag = cli.BoolFlag{
Name: "enable-attestation-cache",
Usage: deprecatedUsage,
Name: "enable-attestation-cache",
Usage: deprecatedUsage,
Hidden: true,
}
)