mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-23 11:57:18 +00:00
5fdd4e9148
* Add prepare-all-payloads flag * add unit tests * do not use the nsc --------- Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
1107 lines
40 KiB
Go
1107 lines
40 KiB
Go
package blockchain
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
gethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
|
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/blocks"
|
|
testDB "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
|
|
"github.com/prysmaticlabs/prysm/v4/beacon-chain/execution"
|
|
mockExecution "github.com/prysmaticlabs/prysm/v4/beacon-chain/execution/testing"
|
|
doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree"
|
|
forkchoicetypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/types"
|
|
bstate "github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
|
state_native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
|
|
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
|
|
"github.com/prysmaticlabs/prysm/v4/config/features"
|
|
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
|
"github.com/prysmaticlabs/prysm/v4/config/params"
|
|
consensusblocks "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
|
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
|
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
|
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
|
v1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
|
|
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
|
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
|
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
|
logTest "github.com/sirupsen/logrus/hooks/test"
|
|
)
|
|
|
|
func Test_NotifyForkchoiceUpdate_GetPayloadAttrErrorCanContinue(t *testing.T) {
|
|
ctx := context.Background()
|
|
beaconDB := testDB.SetupDB(t)
|
|
altairBlk := util.SaveBlock(t, ctx, beaconDB, util.NewBeaconBlockAltair())
|
|
altairBlkRoot, err := altairBlk.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
bellatrixBlk := util.SaveBlock(t, ctx, beaconDB, util.NewBeaconBlockBellatrix())
|
|
bellatrixBlkRoot, err := bellatrixBlk.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
fcs := doublylinkedtree.New()
|
|
opts := []Option{
|
|
WithDatabase(beaconDB),
|
|
WithStateGen(stategen.New(beaconDB, fcs)),
|
|
WithForkChoiceStore(fcs),
|
|
WithProposerIdsCache(cache.NewProposerPayloadIDsCache()),
|
|
}
|
|
service, err := NewService(ctx, opts...)
|
|
require.NoError(t, err)
|
|
st, _ := util.DeterministicGenesisState(t, 10)
|
|
service.head = &head{
|
|
state: st,
|
|
}
|
|
|
|
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 1, altairBlkRoot, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 2, bellatrixBlkRoot, altairBlkRoot, params.BeaconConfig().ZeroHash, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
|
|
b, err := consensusblocks.NewBeaconBlock(ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
pid := &v1.PayloadIDBytes{1}
|
|
service.cfg.ExecutionEngineCaller = &mockExecution.EngineClient{PayloadIDBytes: pid}
|
|
st, _ = util.DeterministicGenesisState(t, 1)
|
|
require.NoError(t, beaconDB.SaveState(ctx, st, bellatrixBlkRoot))
|
|
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bellatrixBlkRoot))
|
|
|
|
// Intentionally generate a bad state such that `hash_tree_root` fails during `process_slot`
|
|
s, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{})
|
|
require.NoError(t, err)
|
|
arg := ¬ifyForkchoiceUpdateArg{
|
|
headState: s,
|
|
headRoot: [32]byte{},
|
|
headBlock: b,
|
|
}
|
|
|
|
service.cfg.ProposerSlotIndexCache.SetProposerAndPayloadIDs(1, 0, [8]byte{}, [32]byte{})
|
|
got, err := service.notifyForkchoiceUpdate(ctx, arg)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, got, pid) // We still get a payload ID even though the state is bad. This means it returns until the end.
|
|
}
|
|
|
|
func Test_NotifyForkchoiceUpdate(t *testing.T) {
|
|
ctx := context.Background()
|
|
beaconDB := testDB.SetupDB(t)
|
|
altairBlk := util.SaveBlock(t, ctx, beaconDB, util.NewBeaconBlockAltair())
|
|
altairBlkRoot, err := altairBlk.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
bellatrixBlk := util.SaveBlock(t, ctx, beaconDB, util.NewBeaconBlockBellatrix())
|
|
bellatrixBlkRoot, err := bellatrixBlk.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
fcs := doublylinkedtree.New()
|
|
opts := []Option{
|
|
WithDatabase(beaconDB),
|
|
WithStateGen(stategen.New(beaconDB, fcs)),
|
|
WithForkChoiceStore(fcs),
|
|
WithProposerIdsCache(cache.NewProposerPayloadIDsCache()),
|
|
}
|
|
service, err := NewService(ctx, opts...)
|
|
require.NoError(t, err)
|
|
st, _ := util.DeterministicGenesisState(t, 10)
|
|
service.head = &head{
|
|
state: st,
|
|
}
|
|
|
|
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 1, altairBlkRoot, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 2, bellatrixBlkRoot, altairBlkRoot, params.BeaconConfig().ZeroHash, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
|
|
tests := []struct {
|
|
name string
|
|
blk interfaces.ReadOnlyBeaconBlock
|
|
headRoot [32]byte
|
|
finalizedRoot [32]byte
|
|
justifiedRoot [32]byte
|
|
newForkchoiceErr error
|
|
errString string
|
|
}{
|
|
{
|
|
name: "phase0 block",
|
|
blk: func() interfaces.ReadOnlyBeaconBlock {
|
|
b, err := consensusblocks.NewBeaconBlock(ðpb.BeaconBlock{Body: ðpb.BeaconBlockBody{}})
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
},
|
|
{
|
|
name: "altair block",
|
|
blk: func() interfaces.ReadOnlyBeaconBlock {
|
|
b, err := consensusblocks.NewBeaconBlock(ðpb.BeaconBlockAltair{Body: ðpb.BeaconBlockBodyAltair{}})
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
},
|
|
{
|
|
name: "not execution block",
|
|
blk: func() interfaces.ReadOnlyBeaconBlock {
|
|
b, err := consensusblocks.NewBeaconBlock(ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{
|
|
ParentHash: make([]byte, fieldparams.RootLength),
|
|
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
|
StateRoot: make([]byte, fieldparams.RootLength),
|
|
ReceiptsRoot: make([]byte, fieldparams.RootLength),
|
|
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
|
|
PrevRandao: make([]byte, fieldparams.RootLength),
|
|
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
|
BlockHash: make([]byte, fieldparams.RootLength),
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
},
|
|
{
|
|
name: "happy case: finalized root is altair block",
|
|
blk: func() interfaces.ReadOnlyBeaconBlock {
|
|
b, err := consensusblocks.NewBeaconBlock(ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
finalizedRoot: altairBlkRoot,
|
|
justifiedRoot: altairBlkRoot,
|
|
},
|
|
{
|
|
name: "happy case: finalized root is bellatrix block",
|
|
blk: func() interfaces.ReadOnlyBeaconBlock {
|
|
b, err := consensusblocks.NewBeaconBlock(ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
finalizedRoot: bellatrixBlkRoot,
|
|
justifiedRoot: bellatrixBlkRoot,
|
|
},
|
|
{
|
|
name: "forkchoice updated with optimistic block",
|
|
blk: func() interfaces.ReadOnlyBeaconBlock {
|
|
b, err := consensusblocks.NewBeaconBlock(ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
newForkchoiceErr: execution.ErrAcceptedSyncingPayloadStatus,
|
|
finalizedRoot: bellatrixBlkRoot,
|
|
justifiedRoot: bellatrixBlkRoot,
|
|
},
|
|
{
|
|
name: "forkchoice updated with invalid block",
|
|
blk: func() interfaces.ReadOnlyBeaconBlock {
|
|
b, err := consensusblocks.NewBeaconBlock(ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
newForkchoiceErr: execution.ErrInvalidPayloadStatus,
|
|
finalizedRoot: bellatrixBlkRoot,
|
|
justifiedRoot: bellatrixBlkRoot,
|
|
headRoot: [32]byte{'a'},
|
|
errString: ErrInvalidPayload.Error(),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
service.cfg.ExecutionEngineCaller = &mockExecution.EngineClient{ErrForkchoiceUpdated: tt.newForkchoiceErr}
|
|
st, _ := util.DeterministicGenesisState(t, 1)
|
|
require.NoError(t, beaconDB.SaveState(ctx, st, tt.finalizedRoot))
|
|
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, tt.finalizedRoot))
|
|
arg := ¬ifyForkchoiceUpdateArg{
|
|
headState: st,
|
|
headRoot: tt.headRoot,
|
|
headBlock: tt.blk,
|
|
}
|
|
_, err = service.notifyForkchoiceUpdate(ctx, arg)
|
|
if tt.errString != "" {
|
|
require.ErrorContains(t, tt.errString, err)
|
|
if tt.errString == ErrInvalidPayload.Error() {
|
|
require.Equal(t, true, IsInvalidBlock(err))
|
|
require.Equal(t, tt.headRoot, InvalidBlockRoot(err)) // Head root should be invalid. Not block root!
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_NotifyForkchoiceUpdate_NIlLVH(t *testing.T) {
|
|
ctx := context.Background()
|
|
beaconDB := testDB.SetupDB(t)
|
|
|
|
// Prepare blocks
|
|
ba := util.NewBeaconBlockBellatrix()
|
|
ba.Block.Body.ExecutionPayload.BlockNumber = 1
|
|
wba := util.SaveBlock(t, ctx, beaconDB, ba)
|
|
bra, err := wba.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
bb := util.NewBeaconBlockBellatrix()
|
|
bb.Block.Body.ExecutionPayload.BlockNumber = 2
|
|
wbb := util.SaveBlock(t, ctx, beaconDB, bb)
|
|
brb, err := wbb.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
bc := util.NewBeaconBlockBellatrix()
|
|
pc := [32]byte{'C'}
|
|
bc.Block.Body.ExecutionPayload.BlockHash = pc[:]
|
|
bc.Block.Body.ExecutionPayload.BlockNumber = 3
|
|
wbc := util.SaveBlock(t, ctx, beaconDB, bc)
|
|
brc, err := wbc.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
bd := util.NewBeaconBlockBellatrix()
|
|
pd := [32]byte{'D'}
|
|
bd.Block.Body.ExecutionPayload.BlockHash = pd[:]
|
|
bd.Block.Body.ExecutionPayload.BlockNumber = 4
|
|
bd.Block.ParentRoot = brc[:]
|
|
wbd := util.SaveBlock(t, ctx, beaconDB, bd)
|
|
brd, err := wbd.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
// Insert blocks into forkchoice
|
|
service := setupBeaconChain(t, beaconDB)
|
|
fcs := doublylinkedtree.New()
|
|
service.cfg.ForkChoiceStore = fcs
|
|
service.cfg.ProposerSlotIndexCache = cache.NewProposerPayloadIDsCache()
|
|
|
|
fcs.SetBalancesByRooter(func(context.Context, [32]byte) ([]uint64, error) { return []uint64{50, 100, 200}, nil })
|
|
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, &forkchoicetypes.Checkpoint{}))
|
|
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, 1, bra, [32]byte{}, [32]byte{'A'}, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 2, brb, bra, [32]byte{'B'}, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 3, brc, brb, [32]byte{'C'}, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 4, brd, brc, [32]byte{'D'}, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
|
|
// Prepare Engine Mock to return invalid LVH = nil
|
|
service.cfg.ExecutionEngineCaller = &mockExecution.EngineClient{ErrForkchoiceUpdated: execution.ErrInvalidPayloadStatus, OverrideValidHash: [32]byte{'C'}}
|
|
st, _ := util.DeterministicGenesisState(t, 1)
|
|
service.head = &head{
|
|
state: st,
|
|
block: wba,
|
|
}
|
|
|
|
require.NoError(t, beaconDB.SaveState(ctx, st, bra))
|
|
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bra))
|
|
a := ¬ifyForkchoiceUpdateArg{
|
|
headState: st,
|
|
headBlock: wbd.Block(),
|
|
headRoot: brd,
|
|
}
|
|
_, err = service.notifyForkchoiceUpdate(ctx, a)
|
|
require.Equal(t, true, IsInvalidBlock(err))
|
|
require.Equal(t, brd, InvalidBlockRoot(err))
|
|
require.Equal(t, brd, InvalidAncestorRoots(err)[0])
|
|
require.Equal(t, 1, len(InvalidAncestorRoots(err)))
|
|
}
|
|
|
|
//
|
|
//
|
|
// A <- B <- C <- D
|
|
// \
|
|
// ---------- E <- F
|
|
// \
|
|
// ------ G
|
|
// D is the current head, attestations for F and G come late, both are invalid.
|
|
// We switch recursively to F then G and finally to D.
|
|
//
|
|
// We test:
|
|
// 1. forkchoice removes blocks F and G from the forkchoice implementation
|
|
// 2. forkchoice removes the weights of these blocks
|
|
// 3. the blockchain package calls fcu to obtain heads G -> F -> D.
|
|
|
|
func Test_NotifyForkchoiceUpdateRecursive_DoublyLinkedTree(t *testing.T) {
|
|
ctx := context.Background()
|
|
beaconDB := testDB.SetupDB(t)
|
|
|
|
// Prepare blocks
|
|
ba := util.NewBeaconBlockBellatrix()
|
|
ba.Block.Body.ExecutionPayload.BlockNumber = 1
|
|
wba := util.SaveBlock(t, ctx, beaconDB, ba)
|
|
bra, err := wba.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
bb := util.NewBeaconBlockBellatrix()
|
|
bb.Block.Body.ExecutionPayload.BlockNumber = 2
|
|
wbb := util.SaveBlock(t, ctx, beaconDB, bb)
|
|
brb, err := wbb.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
bc := util.NewBeaconBlockBellatrix()
|
|
bc.Block.Body.ExecutionPayload.BlockNumber = 3
|
|
wbc := util.SaveBlock(t, ctx, beaconDB, bc)
|
|
brc, err := wbc.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
bd := util.NewBeaconBlockBellatrix()
|
|
pd := [32]byte{'D'}
|
|
bd.Block.Body.ExecutionPayload.BlockHash = pd[:]
|
|
bd.Block.Body.ExecutionPayload.BlockNumber = 4
|
|
wbd := util.SaveBlock(t, ctx, beaconDB, bd)
|
|
brd, err := wbd.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
be := util.NewBeaconBlockBellatrix()
|
|
pe := [32]byte{'E'}
|
|
be.Block.Body.ExecutionPayload.BlockHash = pe[:]
|
|
be.Block.Body.ExecutionPayload.BlockNumber = 5
|
|
wbe := util.SaveBlock(t, ctx, beaconDB, be)
|
|
bre, err := wbe.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
bf := util.NewBeaconBlockBellatrix()
|
|
pf := [32]byte{'F'}
|
|
bf.Block.Body.ExecutionPayload.BlockHash = pf[:]
|
|
bf.Block.Body.ExecutionPayload.BlockNumber = 6
|
|
bf.Block.ParentRoot = bre[:]
|
|
wbf := util.SaveBlock(t, ctx, beaconDB, bf)
|
|
brf, err := wbf.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
bg := util.NewBeaconBlockBellatrix()
|
|
bg.Block.Body.ExecutionPayload.BlockNumber = 7
|
|
pg := [32]byte{'G'}
|
|
bg.Block.Body.ExecutionPayload.BlockHash = pg[:]
|
|
bg.Block.ParentRoot = bre[:]
|
|
wbg := util.SaveBlock(t, ctx, beaconDB, bg)
|
|
brg, err := wbg.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
// Insert blocks into forkchoice
|
|
service := setupBeaconChain(t, beaconDB)
|
|
fcs := doublylinkedtree.New()
|
|
service.cfg.ForkChoiceStore = fcs
|
|
service.cfg.ProposerSlotIndexCache = cache.NewProposerPayloadIDsCache()
|
|
|
|
fcs.SetBalancesByRooter(func(context.Context, [32]byte) ([]uint64, error) { return []uint64{50, 100, 200}, nil })
|
|
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, &forkchoicetypes.Checkpoint{}))
|
|
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, 1, bra, [32]byte{}, [32]byte{'A'}, ojc, ofc)
|
|
require.NoError(t, err)
|
|
|
|
bState, _ := util.DeterministicGenesisState(t, 10)
|
|
require.NoError(t, beaconDB.SaveState(ctx, bState, bra))
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 2, brb, bra, [32]byte{'B'}, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 3, brc, brb, [32]byte{'C'}, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 4, brd, brc, [32]byte{'D'}, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 5, bre, brb, [32]byte{'E'}, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 6, brf, bre, [32]byte{'F'}, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 7, brg, bre, [32]byte{'G'}, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
|
|
// Insert Attestations to D, F and G so that they have higher weight than D
|
|
// Ensure G is head
|
|
fcs.ProcessAttestation(ctx, []uint64{0}, brd, 1)
|
|
fcs.ProcessAttestation(ctx, []uint64{1}, brf, 1)
|
|
fcs.ProcessAttestation(ctx, []uint64{2}, brg, 1)
|
|
fcs.SetBalancesByRooter(service.cfg.StateGen.ActiveNonSlashedBalancesByRoot)
|
|
jc := &forkchoicetypes.Checkpoint{Epoch: 0, Root: bra}
|
|
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, jc))
|
|
fcs.SetBalancesByRooter(func(context.Context, [32]byte) ([]uint64, error) { return []uint64{50, 100, 200}, nil })
|
|
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, &forkchoicetypes.Checkpoint{}))
|
|
headRoot, err := fcs.Head(ctx)
|
|
require.NoError(t, err)
|
|
require.Equal(t, brg, headRoot)
|
|
|
|
// Prepare Engine Mock to return invalid unless head is D, LVH = E
|
|
service.cfg.ExecutionEngineCaller = &mockExecution.EngineClient{ErrForkchoiceUpdated: execution.ErrInvalidPayloadStatus, ForkChoiceUpdatedResp: pe[:], OverrideValidHash: [32]byte{'D'}}
|
|
st, _ := util.DeterministicGenesisState(t, 1)
|
|
service.head = &head{
|
|
state: st,
|
|
block: wba,
|
|
}
|
|
|
|
require.NoError(t, beaconDB.SaveState(ctx, st, bra))
|
|
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bra))
|
|
a := ¬ifyForkchoiceUpdateArg{
|
|
headState: st,
|
|
headBlock: wbg.Block(),
|
|
headRoot: brg,
|
|
}
|
|
_, err = service.notifyForkchoiceUpdate(ctx, a)
|
|
require.Equal(t, true, IsInvalidBlock(err))
|
|
require.Equal(t, brf, InvalidBlockRoot(err))
|
|
|
|
// Ensure Head is D
|
|
headRoot, err = fcs.Head(ctx)
|
|
require.NoError(t, err)
|
|
require.Equal(t, brd, headRoot)
|
|
|
|
// Ensure F and G where removed but their parent E wasn't
|
|
require.Equal(t, false, fcs.HasNode(brf))
|
|
require.Equal(t, false, fcs.HasNode(brg))
|
|
require.Equal(t, true, fcs.HasNode(bre))
|
|
}
|
|
|
|
func Test_NotifyNewPayload(t *testing.T) {
|
|
cfg := params.BeaconConfig()
|
|
cfg.TerminalTotalDifficulty = "2"
|
|
params.OverrideBeaconConfig(cfg)
|
|
|
|
ctx := context.Background()
|
|
beaconDB := testDB.SetupDB(t)
|
|
fcs := doublylinkedtree.New()
|
|
opts := []Option{
|
|
WithDatabase(beaconDB),
|
|
WithStateGen(stategen.New(beaconDB, fcs)),
|
|
WithForkChoiceStore(fcs),
|
|
}
|
|
phase0State, _ := util.DeterministicGenesisState(t, 1)
|
|
altairState, _ := util.DeterministicGenesisStateAltair(t, 1)
|
|
bellatrixState, _ := util.DeterministicGenesisStateBellatrix(t, 2)
|
|
a := ðpb.SignedBeaconBlockAltair{
|
|
Block: ðpb.BeaconBlockAltair{
|
|
Body: ðpb.BeaconBlockBodyAltair{},
|
|
},
|
|
}
|
|
altairBlk, err := consensusblocks.NewSignedBeaconBlock(a)
|
|
require.NoError(t, err)
|
|
blk := ðpb.SignedBeaconBlockBellatrix{
|
|
Block: ðpb.BeaconBlockBellatrix{
|
|
Slot: 1,
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{
|
|
BlockNumber: 1,
|
|
ParentHash: make([]byte, fieldparams.RootLength),
|
|
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
|
StateRoot: make([]byte, fieldparams.RootLength),
|
|
ReceiptsRoot: make([]byte, fieldparams.RootLength),
|
|
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
|
|
PrevRandao: make([]byte, fieldparams.RootLength),
|
|
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
|
BlockHash: make([]byte, fieldparams.RootLength),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
bellatrixBlk, err := consensusblocks.NewSignedBeaconBlock(util.HydrateSignedBeaconBlockBellatrix(blk))
|
|
require.NoError(t, err)
|
|
service, err := NewService(ctx, opts...)
|
|
require.NoError(t, err)
|
|
st := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(epochsSinceFinalitySaveHotStateDB))
|
|
service.genesisTime = time.Now().Add(time.Duration(-1*int64(st)*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second)
|
|
r, err := bellatrixBlk.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 1, r, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
|
|
tests := []struct {
|
|
postState bstate.BeaconState
|
|
invalidBlock bool
|
|
isValidPayload bool
|
|
blk interfaces.ReadOnlySignedBeaconBlock
|
|
newPayloadErr error
|
|
errString string
|
|
name string
|
|
}{
|
|
{
|
|
name: "phase 0 post state",
|
|
postState: phase0State,
|
|
isValidPayload: true,
|
|
},
|
|
{
|
|
name: "altair post state",
|
|
postState: altairState,
|
|
isValidPayload: true,
|
|
},
|
|
{
|
|
name: "nil beacon block",
|
|
postState: bellatrixState,
|
|
errString: "signed beacon block can't be nil",
|
|
isValidPayload: false,
|
|
},
|
|
{
|
|
name: "new payload with optimistic block",
|
|
postState: bellatrixState,
|
|
blk: bellatrixBlk,
|
|
newPayloadErr: execution.ErrAcceptedSyncingPayloadStatus,
|
|
isValidPayload: false,
|
|
},
|
|
{
|
|
name: "new payload with invalid block",
|
|
postState: bellatrixState,
|
|
blk: bellatrixBlk,
|
|
newPayloadErr: execution.ErrInvalidPayloadStatus,
|
|
errString: ErrInvalidPayload.Error(),
|
|
isValidPayload: false,
|
|
invalidBlock: true,
|
|
},
|
|
{
|
|
name: "altair pre state, altair block",
|
|
postState: bellatrixState,
|
|
blk: altairBlk,
|
|
isValidPayload: true,
|
|
},
|
|
{
|
|
name: "altair pre state, happy case",
|
|
postState: bellatrixState,
|
|
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
|
blk := ðpb.SignedBeaconBlockBellatrix{
|
|
Block: ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{
|
|
ParentHash: bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
b, err := consensusblocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
isValidPayload: true,
|
|
},
|
|
{
|
|
name: "not at merge transition",
|
|
postState: bellatrixState,
|
|
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
|
blk := ðpb.SignedBeaconBlockBellatrix{
|
|
Block: ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{
|
|
ParentHash: make([]byte, fieldparams.RootLength),
|
|
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
|
StateRoot: make([]byte, fieldparams.RootLength),
|
|
ReceiptsRoot: make([]byte, fieldparams.RootLength),
|
|
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
|
|
PrevRandao: make([]byte, fieldparams.RootLength),
|
|
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
|
BlockHash: make([]byte, fieldparams.RootLength),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
b, err := consensusblocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
isValidPayload: true,
|
|
},
|
|
{
|
|
name: "happy case",
|
|
postState: bellatrixState,
|
|
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
|
blk := ðpb.SignedBeaconBlockBellatrix{
|
|
Block: ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{
|
|
ParentHash: bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
b, err := consensusblocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
isValidPayload: true,
|
|
},
|
|
{
|
|
name: "undefined error from ee",
|
|
postState: bellatrixState,
|
|
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
|
blk := ðpb.SignedBeaconBlockBellatrix{
|
|
Block: ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{
|
|
ParentHash: bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
b, err := consensusblocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
newPayloadErr: ErrUndefinedExecutionEngineError,
|
|
errString: ErrUndefinedExecutionEngineError.Error(),
|
|
},
|
|
{
|
|
name: "invalid block hash error from ee",
|
|
postState: bellatrixState,
|
|
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
|
blk := ðpb.SignedBeaconBlockBellatrix{
|
|
Block: ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{
|
|
ParentHash: bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
b, err := consensusblocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
return b
|
|
}(),
|
|
newPayloadErr: ErrInvalidBlockHashPayloadStatus,
|
|
errString: ErrInvalidBlockHashPayloadStatus.Error(),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
e := &mockExecution.EngineClient{ErrNewPayload: tt.newPayloadErr, BlockByHashMap: map[[32]byte]*v1.ExecutionBlock{}}
|
|
e.BlockByHashMap[[32]byte{'a'}] = &v1.ExecutionBlock{
|
|
Header: gethtypes.Header{
|
|
ParentHash: common.BytesToHash([]byte("b")),
|
|
},
|
|
TotalDifficulty: "0x2",
|
|
}
|
|
e.BlockByHashMap[[32]byte{'b'}] = &v1.ExecutionBlock{
|
|
Header: gethtypes.Header{
|
|
ParentHash: common.BytesToHash([]byte("3")),
|
|
},
|
|
TotalDifficulty: "0x1",
|
|
}
|
|
service.cfg.ExecutionEngineCaller = e
|
|
root := [32]byte{'a'}
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, 0, root, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
|
postVersion, postHeader, err := getStateVersionAndPayload(tt.postState)
|
|
require.NoError(t, err)
|
|
isValidPayload, err := service.notifyNewPayload(ctx, postVersion, postHeader, tt.blk)
|
|
if tt.errString != "" {
|
|
require.ErrorContains(t, tt.errString, err)
|
|
if tt.invalidBlock {
|
|
require.Equal(t, true, IsInvalidBlock(err))
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.isValidPayload, isValidPayload)
|
|
require.Equal(t, false, IsInvalidBlock(err))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_NotifyNewPayload_SetOptimisticToValid(t *testing.T) {
|
|
cfg := params.BeaconConfig()
|
|
cfg.TerminalTotalDifficulty = "2"
|
|
params.OverrideBeaconConfig(cfg)
|
|
ctx := context.Background()
|
|
beaconDB := testDB.SetupDB(t)
|
|
fcs := doublylinkedtree.New()
|
|
opts := []Option{
|
|
WithDatabase(beaconDB),
|
|
WithStateGen(stategen.New(beaconDB, fcs)),
|
|
WithForkChoiceStore(fcs),
|
|
}
|
|
bellatrixState, _ := util.DeterministicGenesisStateBellatrix(t, 2)
|
|
blk := ðpb.SignedBeaconBlockBellatrix{
|
|
Block: ðpb.BeaconBlockBellatrix{
|
|
Body: ðpb.BeaconBlockBodyBellatrix{
|
|
ExecutionPayload: &v1.ExecutionPayload{
|
|
ParentHash: bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
bellatrixBlk, err := consensusblocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
service, err := NewService(ctx, opts...)
|
|
require.NoError(t, err)
|
|
e := &mockExecution.EngineClient{BlockByHashMap: map[[32]byte]*v1.ExecutionBlock{}}
|
|
e.BlockByHashMap[[32]byte{'a'}] = &v1.ExecutionBlock{
|
|
Header: gethtypes.Header{
|
|
ParentHash: common.BytesToHash([]byte("b")),
|
|
},
|
|
TotalDifficulty: "0x2",
|
|
}
|
|
e.BlockByHashMap[[32]byte{'b'}] = &v1.ExecutionBlock{
|
|
Header: gethtypes.Header{
|
|
ParentHash: common.BytesToHash([]byte("3")),
|
|
},
|
|
TotalDifficulty: "0x1",
|
|
}
|
|
service.cfg.ExecutionEngineCaller = e
|
|
postVersion, postHeader, err := getStateVersionAndPayload(bellatrixState)
|
|
require.NoError(t, err)
|
|
validated, err := service.notifyNewPayload(ctx, postVersion, postHeader, bellatrixBlk)
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, validated)
|
|
}
|
|
|
|
func Test_GetPayloadAttribute(t *testing.T) {
|
|
ctx := context.Background()
|
|
beaconDB := testDB.SetupDB(t)
|
|
opts := []Option{
|
|
WithDatabase(beaconDB),
|
|
WithStateGen(stategen.New(beaconDB, doublylinkedtree.New())),
|
|
WithProposerIdsCache(cache.NewProposerPayloadIDsCache()),
|
|
}
|
|
|
|
// Cache miss
|
|
service, err := NewService(ctx, opts...)
|
|
require.NoError(t, err)
|
|
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
|
hasPayload, _, vId := service.getPayloadAttribute(ctx, st, 0)
|
|
require.Equal(t, false, hasPayload)
|
|
require.Equal(t, primitives.ValidatorIndex(0), vId)
|
|
|
|
// Cache hit, advance state, no fee recipient
|
|
suggestedVid := primitives.ValidatorIndex(1)
|
|
slot := primitives.Slot(1)
|
|
service.cfg.ProposerSlotIndexCache.SetProposerAndPayloadIDs(slot, suggestedVid, [8]byte{}, [32]byte{})
|
|
hook := logTest.NewGlobal()
|
|
hasPayload, attr, vId := service.getPayloadAttribute(ctx, st, slot)
|
|
require.Equal(t, true, hasPayload)
|
|
require.Equal(t, suggestedVid, vId)
|
|
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
|
|
require.LogsContain(t, hook, "Fee recipient is currently using the burn address")
|
|
|
|
// Cache hit, advance state, has fee recipient
|
|
suggestedAddr := common.HexToAddress("123")
|
|
require.NoError(t, service.cfg.BeaconDB.SaveFeeRecipientsByValidatorIDs(ctx, []primitives.ValidatorIndex{suggestedVid}, []common.Address{suggestedAddr}))
|
|
service.cfg.ProposerSlotIndexCache.SetProposerAndPayloadIDs(slot, suggestedVid, [8]byte{}, [32]byte{})
|
|
hasPayload, attr, vId = service.getPayloadAttribute(ctx, st, slot)
|
|
require.Equal(t, true, hasPayload)
|
|
require.Equal(t, suggestedVid, vId)
|
|
require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient()))
|
|
}
|
|
|
|
func Test_GetPayloadAttribute_PrepareAllPayloads(t *testing.T) {
|
|
ctx := context.Background()
|
|
resetCfg := features.InitWithReset(&features.Flags{
|
|
PrepareAllPayloads: true,
|
|
})
|
|
defer resetCfg()
|
|
|
|
beaconDB := testDB.SetupDB(t)
|
|
opts := []Option{
|
|
WithDatabase(beaconDB),
|
|
WithStateGen(stategen.New(beaconDB, doublylinkedtree.New())),
|
|
WithProposerIdsCache(cache.NewProposerPayloadIDsCache()),
|
|
}
|
|
|
|
hook := logTest.NewGlobal()
|
|
service, err := NewService(ctx, opts...)
|
|
require.NoError(t, err)
|
|
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
|
hasPayload, attr, vId := service.getPayloadAttribute(ctx, st, 0)
|
|
require.Equal(t, true, hasPayload)
|
|
require.Equal(t, primitives.ValidatorIndex(0), vId)
|
|
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
|
|
require.LogsContain(t, hook, "Fee recipient is currently using the burn address")
|
|
}
|
|
|
|
func Test_GetPayloadAttributeV2(t *testing.T) {
|
|
ctx := context.Background()
|
|
beaconDB := testDB.SetupDB(t)
|
|
opts := []Option{
|
|
WithDatabase(beaconDB),
|
|
WithStateGen(stategen.New(beaconDB, doublylinkedtree.New())),
|
|
WithProposerIdsCache(cache.NewProposerPayloadIDsCache()),
|
|
}
|
|
|
|
// Cache miss
|
|
service, err := NewService(ctx, opts...)
|
|
require.NoError(t, err)
|
|
st, _ := util.DeterministicGenesisStateCapella(t, 1)
|
|
hasPayload, _, vId := service.getPayloadAttribute(ctx, st, 0)
|
|
require.Equal(t, false, hasPayload)
|
|
require.Equal(t, primitives.ValidatorIndex(0), vId)
|
|
|
|
// Cache hit, advance state, no fee recipient
|
|
suggestedVid := primitives.ValidatorIndex(1)
|
|
slot := primitives.Slot(1)
|
|
service.cfg.ProposerSlotIndexCache.SetProposerAndPayloadIDs(slot, suggestedVid, [8]byte{}, [32]byte{})
|
|
hook := logTest.NewGlobal()
|
|
hasPayload, attr, vId := service.getPayloadAttribute(ctx, st, slot)
|
|
require.Equal(t, true, hasPayload)
|
|
require.Equal(t, suggestedVid, vId)
|
|
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
|
|
require.LogsContain(t, hook, "Fee recipient is currently using the burn address")
|
|
a, err := attr.Withdrawals()
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(a))
|
|
|
|
// Cache hit, advance state, has fee recipient
|
|
suggestedAddr := common.HexToAddress("123")
|
|
require.NoError(t, service.cfg.BeaconDB.SaveFeeRecipientsByValidatorIDs(ctx, []primitives.ValidatorIndex{suggestedVid}, []common.Address{suggestedAddr}))
|
|
service.cfg.ProposerSlotIndexCache.SetProposerAndPayloadIDs(slot, suggestedVid, [8]byte{}, [32]byte{})
|
|
hasPayload, attr, vId = service.getPayloadAttribute(ctx, st, slot)
|
|
require.Equal(t, true, hasPayload)
|
|
require.Equal(t, suggestedVid, vId)
|
|
require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient()))
|
|
a, err = attr.Withdrawals()
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(a))
|
|
}
|
|
|
|
func Test_UpdateLastValidatedCheckpoint(t *testing.T) {
|
|
params.SetupTestConfigCleanup(t)
|
|
params.OverrideBeaconConfig(params.MainnetConfig())
|
|
|
|
ctx := context.Background()
|
|
beaconDB := testDB.SetupDB(t)
|
|
fcs := doublylinkedtree.New()
|
|
stateGen := stategen.New(beaconDB, fcs)
|
|
opts := []Option{
|
|
WithDatabase(beaconDB),
|
|
WithStateGen(stateGen),
|
|
WithForkChoiceStore(fcs),
|
|
}
|
|
service, err := NewService(ctx, opts...)
|
|
require.NoError(t, err)
|
|
var genesisStateRoot [32]byte
|
|
genesisBlk := blocks.NewGenesisBlock(genesisStateRoot[:])
|
|
util.SaveBlock(t, ctx, beaconDB, genesisBlk)
|
|
genesisRoot, err := genesisBlk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
assert.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, genesisRoot))
|
|
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
fjc := &forkchoicetypes.Checkpoint{Epoch: 0, Root: params.BeaconConfig().ZeroHash}
|
|
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, fjc))
|
|
require.NoError(t, fcs.UpdateFinalizedCheckpoint(fjc))
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, 0, genesisRoot, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
fcs.SetOriginRoot(genesisRoot)
|
|
genesisSummary := ðpb.StateSummary{
|
|
Root: genesisStateRoot[:],
|
|
Slot: 0,
|
|
}
|
|
require.NoError(t, beaconDB.SaveStateSummary(ctx, genesisSummary))
|
|
|
|
// Get last validated checkpoint
|
|
origCheckpoint, err := service.cfg.BeaconDB.LastValidatedCheckpoint(ctx)
|
|
require.NoError(t, err)
|
|
require.NoError(t, beaconDB.SaveLastValidatedCheckpoint(ctx, origCheckpoint))
|
|
|
|
// Optimistic finalized checkpoint
|
|
blk := util.NewBeaconBlock()
|
|
blk.Block.Slot = 320
|
|
blk.Block.ParentRoot = genesisRoot[:]
|
|
util.SaveBlock(t, ctx, beaconDB, blk)
|
|
opRoot, err := blk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
opCheckpoint := ðpb.Checkpoint{
|
|
Root: opRoot[:],
|
|
Epoch: 10,
|
|
}
|
|
opStateSummary := ðpb.StateSummary{
|
|
Root: opRoot[:],
|
|
Slot: 320,
|
|
}
|
|
require.NoError(t, beaconDB.SaveStateSummary(ctx, opStateSummary))
|
|
tenjc := ðpb.Checkpoint{Epoch: 10, Root: genesisRoot[:]}
|
|
tenfc := ðpb.Checkpoint{Epoch: 10, Root: genesisRoot[:]}
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 320, opRoot, genesisRoot, params.BeaconConfig().ZeroHash, tenjc, tenfc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
assert.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, opRoot))
|
|
require.NoError(t, service.updateFinalized(ctx, opCheckpoint))
|
|
cp, err := service.cfg.BeaconDB.LastValidatedCheckpoint(ctx)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, origCheckpoint.Root, cp.Root)
|
|
require.Equal(t, origCheckpoint.Epoch, cp.Epoch)
|
|
|
|
// Validated finalized checkpoint
|
|
blk = util.NewBeaconBlock()
|
|
blk.Block.Slot = 640
|
|
blk.Block.ParentRoot = opRoot[:]
|
|
util.SaveBlock(t, ctx, beaconDB, blk)
|
|
validRoot, err := blk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
validCheckpoint := ðpb.Checkpoint{
|
|
Root: validRoot[:],
|
|
Epoch: 20,
|
|
}
|
|
validSummary := ðpb.StateSummary{
|
|
Root: validRoot[:],
|
|
Slot: 640,
|
|
}
|
|
require.NoError(t, beaconDB.SaveStateSummary(ctx, validSummary))
|
|
twentyjc := ðpb.Checkpoint{Epoch: 20, Root: validRoot[:]}
|
|
twentyfc := ðpb.Checkpoint{Epoch: 20, Root: validRoot[:]}
|
|
state, blkRoot, err = prepareForkchoiceState(ctx, 640, validRoot, genesisRoot, params.BeaconConfig().ZeroHash, twentyjc, twentyfc)
|
|
require.NoError(t, err)
|
|
fcs.SetBalancesByRooter(func(_ context.Context, _ [32]byte) ([]uint64, error) { return []uint64{}, nil })
|
|
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
|
require.NoError(t, fcs.SetOptimisticToValid(ctx, validRoot))
|
|
assert.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, validRoot))
|
|
require.NoError(t, service.updateFinalized(ctx, validCheckpoint))
|
|
cp, err = service.cfg.BeaconDB.LastValidatedCheckpoint(ctx)
|
|
require.NoError(t, err)
|
|
|
|
optimistic, err := service.IsOptimisticForRoot(ctx, validRoot)
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, optimistic)
|
|
require.DeepEqual(t, validCheckpoint.Root, cp.Root)
|
|
require.Equal(t, validCheckpoint.Epoch, cp.Epoch)
|
|
|
|
// Checkpoint with a lower epoch
|
|
oldCp, err := service.cfg.BeaconDB.FinalizedCheckpoint(ctx)
|
|
require.NoError(t, err)
|
|
invalidCp := ðpb.Checkpoint{
|
|
Epoch: oldCp.Epoch - 1,
|
|
}
|
|
// Nothing should happen as we no-op on an invalid checkpoint.
|
|
require.NoError(t, service.updateFinalized(ctx, invalidCp))
|
|
got, err := service.cfg.BeaconDB.FinalizedCheckpoint(ctx)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, oldCp, got)
|
|
}
|
|
|
|
func TestService_removeInvalidBlockAndState(t *testing.T) {
|
|
ctx := context.Background()
|
|
beaconDB := testDB.SetupDB(t)
|
|
fc := doublylinkedtree.New()
|
|
opts := []Option{
|
|
WithDatabase(beaconDB),
|
|
WithStateGen(stategen.New(beaconDB, fc)),
|
|
WithForkChoiceStore(fc),
|
|
}
|
|
service, err := NewService(ctx, opts...)
|
|
require.NoError(t, err)
|
|
|
|
// Deleting unknown block should not error.
|
|
require.NoError(t, service.removeInvalidBlockAndState(ctx, [][32]byte{{'a'}, {'b'}, {'c'}}))
|
|
|
|
// Happy case
|
|
b1 := util.NewBeaconBlock()
|
|
b1.Block.Slot = 1
|
|
blk1 := util.SaveBlock(t, ctx, service.cfg.BeaconDB, b1)
|
|
r1, err := blk1.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
|
require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{
|
|
Slot: 1,
|
|
Root: r1[:],
|
|
}))
|
|
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, st, r1))
|
|
|
|
b2 := util.NewBeaconBlock()
|
|
b2.Block.Slot = 2
|
|
blk2 := util.SaveBlock(t, ctx, service.cfg.BeaconDB, b2)
|
|
r2, err := blk2.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{
|
|
Slot: 2,
|
|
Root: r2[:],
|
|
}))
|
|
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, st, r2))
|
|
|
|
require.NoError(t, service.removeInvalidBlockAndState(ctx, [][32]byte{r1, r2}))
|
|
|
|
require.Equal(t, false, service.hasBlock(ctx, r1))
|
|
require.Equal(t, false, service.hasBlock(ctx, r2))
|
|
require.Equal(t, false, service.cfg.BeaconDB.HasStateSummary(ctx, r1))
|
|
require.Equal(t, false, service.cfg.BeaconDB.HasStateSummary(ctx, r2))
|
|
has, err := service.cfg.StateGen.HasState(ctx, r1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, has)
|
|
has, err = service.cfg.StateGen.HasState(ctx, r2)
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, has)
|
|
}
|
|
|
|
func TestService_getPayloadHash(t *testing.T) {
|
|
ctx := context.Background()
|
|
beaconDB := testDB.SetupDB(t)
|
|
fc := doublylinkedtree.New()
|
|
opts := []Option{
|
|
WithDatabase(beaconDB),
|
|
WithStateGen(stategen.New(beaconDB, fc)),
|
|
WithForkChoiceStore(fc),
|
|
}
|
|
service, err := NewService(ctx, opts...)
|
|
require.NoError(t, err)
|
|
|
|
_, err = service.getPayloadHash(ctx, []byte{})
|
|
require.ErrorIs(t, errBlockNotFoundInCacheOrDB, err)
|
|
|
|
b := util.NewBeaconBlock()
|
|
r, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := consensusblocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
require.NoError(t, service.saveInitSyncBlock(ctx, r, wsb))
|
|
|
|
h, err := service.getPayloadHash(ctx, r[:])
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, params.BeaconConfig().ZeroHash, h)
|
|
|
|
bb := util.NewBeaconBlockBellatrix()
|
|
h = [32]byte{'a'}
|
|
bb.Block.Body.ExecutionPayload.BlockHash = h[:]
|
|
r, err = b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err = consensusblocks.NewSignedBeaconBlock(bb)
|
|
require.NoError(t, err)
|
|
require.NoError(t, service.saveInitSyncBlock(ctx, r, wsb))
|
|
|
|
h, err = service.getPayloadHash(ctx, r[:])
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, [32]byte{'a'}, h)
|
|
}
|