Skip to content

Commit 83a8327

Browse files
potuzterencechain
andauthored
Remove synced tips and use last valid hash (#10439)
* Remove synced tips use last valid hash in removing invalid nodes. * add test * Remove unused code * More unused parameters * Fix proposer boost * terence's review #1 * Fix conflicts * terence's review 2 * rename argument * terence's review #3 * rename optimistic -> status * Minor clean up * revert loop variable change * do not mark lvh as valid Co-authored-by: terence tsao <terence@prysmaticlabs.com>
1 parent bdab34f commit 83a8327

24 files changed

+462
-913
lines changed

beacon-chain/blockchain/chain_info.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ func (s *Service) IsOptimistic(ctx context.Context) (bool, error) {
331331
// IsOptimisticForRoot takes the root and slot as arguments instead of the current head
332332
// and returns true if it is optimistic.
333333
func (s *Service) IsOptimisticForRoot(ctx context.Context, root [32]byte) (bool, error) {
334-
optimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(ctx, root)
334+
optimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(root)
335335
if err == nil {
336336
return optimistic, nil
337337
}

beacon-chain/blockchain/process_block.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ func (s *Service) onBlock(ctx context.Context, signed block.SignedBeaconBlock, b
262262
if err := s.cfg.ForkChoiceStore.Prune(ctx, fRoot); err != nil {
263263
return errors.Wrap(err, "could not prune proto array fork choice nodes")
264264
}
265-
isOptimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(ctx, fRoot)
265+
isOptimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(fRoot)
266266
if err != nil {
267267
return errors.Wrap(err, "could not check if node is optimistically synced")
268268
}

beacon-chain/blockchain/process_block_helpers.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ func (s *Service) updateFinalized(ctx context.Context, cp *ethpb.Checkpoint) err
247247
}
248248

249249
fRoot := bytesutil.ToBytes32(cp.Root)
250-
optimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(ctx, fRoot)
250+
optimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(fRoot)
251251
if err != nil {
252252
return err
253253
}

beacon-chain/forkchoice/doubly-linked-tree/errors.go

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ var errInvalidProposerBoostRoot = errors.New("invalid proposer boost root")
88
var errUnknownFinalizedRoot = errors.New("unknown finalized root")
99
var errUnknownJustifiedRoot = errors.New("unknown justified root")
1010
var errInvalidOptimisticStatus = errors.New("invalid optimistic status")
11+
var errUnknownPayloadHash = errors.New("unknown payload hash")

beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func New(justifiedEpoch, finalizedEpoch types.Epoch) *ForkChoice {
1818
finalizedEpoch: finalizedEpoch,
1919
proposerBoostRoot: [32]byte{},
2020
nodeByRoot: make(map[[fieldparams.RootLength]byte]*Node),
21+
nodeByPayload: make(map[[fieldparams.RootLength]byte]*Node),
2122
pruneThreshold: defaultPruneThreshold,
2223
}
2324

@@ -168,7 +169,7 @@ func (f *ForkChoice) IsCanonical(root [32]byte) bool {
168169
}
169170

170171
// IsOptimistic returns true if the given root has been optimistically synced.
171-
func (f *ForkChoice) IsOptimistic(_ context.Context, root [32]byte) (bool, error) {
172+
func (f *ForkChoice) IsOptimistic(root [32]byte) (bool, error) {
172173
f.store.nodesLock.RLock()
173174
defer f.store.nodesLock.RUnlock()
174175

@@ -302,6 +303,6 @@ func (f *ForkChoice) ForkChoiceNodes() []*pbrpc.ForkChoiceNode {
302303
}
303304

304305
// SetOptimisticToInvalid removes a block with an invalid execution payload from fork choice store
305-
func (f *ForkChoice) SetOptimisticToInvalid(ctx context.Context, root [fieldparams.RootLength]byte) ([][32]byte, error) {
306-
return f.store.removeNode(ctx, root)
306+
func (f *ForkChoice) SetOptimisticToInvalid(ctx context.Context, root, payloadHash [fieldparams.RootLength]byte) ([][32]byte, error) {
307+
return f.store.setOptimisticToInvalid(ctx, root, payloadHash)
307308
}

beacon-chain/forkchoice/doubly-linked-tree/node.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func (n *Node) leadsToViableHead(justifiedEpoch, finalizedEpoch types.Epoch) boo
109109
return n.bestDescendant.viableForHead(justifiedEpoch, finalizedEpoch)
110110
}
111111

112-
// setNodeAndParentValidated sets the current node and the parent as validated (i.e. non-optimistic).
112+
// setNodeAndParentValidated sets the current node and all the ancestors as validated (i.e. non-optimistic).
113113
func (n *Node) setNodeAndParentValidated(ctx context.Context) error {
114114
if ctx.Err() != nil {
115115
return ctx.Err()

beacon-chain/forkchoice/doubly-linked-tree/node_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -180,27 +180,27 @@ func TestNode_SetFullyValidated(t *testing.T) {
180180
require.NoError(t, f.InsertOptimisticBlock(ctx, 4, indexToHash(4), indexToHash(3), params.BeaconConfig().ZeroHash, 1, 1))
181181
require.NoError(t, f.InsertOptimisticBlock(ctx, 5, indexToHash(5), indexToHash(1), params.BeaconConfig().ZeroHash, 1, 1))
182182

183-
opt, err := f.IsOptimistic(ctx, indexToHash(5))
183+
opt, err := f.IsOptimistic(indexToHash(5))
184184
require.NoError(t, err)
185185
require.Equal(t, true, opt)
186186

187-
opt, err = f.IsOptimistic(ctx, indexToHash(4))
187+
opt, err = f.IsOptimistic(indexToHash(4))
188188
require.NoError(t, err)
189189
require.Equal(t, true, opt)
190190

191191
require.NoError(t, f.store.nodeByRoot[indexToHash(4)].setNodeAndParentValidated(ctx))
192192

193193
// block 5 should still be optimistic
194-
opt, err = f.IsOptimistic(ctx, indexToHash(5))
194+
opt, err = f.IsOptimistic(indexToHash(5))
195195
require.NoError(t, err)
196196
require.Equal(t, true, opt)
197197

198198
// block 4 and 3 should now be valid
199-
opt, err = f.IsOptimistic(ctx, indexToHash(4))
199+
opt, err = f.IsOptimistic(indexToHash(4))
200200
require.NoError(t, err)
201201
require.Equal(t, false, opt)
202202

203-
opt, err = f.IsOptimistic(ctx, indexToHash(3))
203+
opt, err = f.IsOptimistic(indexToHash(3))
204204
require.NoError(t, err)
205205
require.Equal(t, false, opt)
206206
}

beacon-chain/forkchoice/doubly-linked-tree/optimistic_sync.go

+45-3
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,54 @@ package doublylinkedtree
22

33
import (
44
"context"
5+
6+
"github.com/prysmaticlabs/prysm/config/params"
57
)
68

9+
func (s *Store) setOptimisticToInvalid(ctx context.Context, root, payloadHash [32]byte) ([][32]byte, error) {
10+
s.nodesLock.Lock()
11+
invalidRoots := make([][32]byte, 0)
12+
node, ok := s.nodeByRoot[root]
13+
if !ok || node == nil {
14+
s.nodesLock.Unlock()
15+
return invalidRoots, ErrNilNode
16+
}
17+
// Check if last valid hash is an ancestor of the passed node.
18+
lastValid, ok := s.nodeByPayload[payloadHash]
19+
if !ok || lastValid == nil {
20+
s.nodesLock.Unlock()
21+
return invalidRoots, errUnknownPayloadHash
22+
}
23+
firstInvalid := node
24+
for ; firstInvalid.parent != nil && firstInvalid.parent.payloadHash != payloadHash; firstInvalid = firstInvalid.parent {
25+
if ctx.Err() != nil {
26+
s.nodesLock.Unlock()
27+
return invalidRoots, ctx.Err()
28+
}
29+
}
30+
// If the last valid payload is in a different fork, we remove only the
31+
// passed node.
32+
if firstInvalid.parent == nil {
33+
firstInvalid = node
34+
}
35+
s.nodesLock.Unlock()
36+
return s.removeNode(ctx, firstInvalid)
37+
}
38+
739
// removeNode removes the node with the given root and all of its children
840
// from the Fork Choice Store.
9-
func (s *Store) removeNode(ctx context.Context, root [32]byte) ([][32]byte, error) {
41+
func (s *Store) removeNode(ctx context.Context, node *Node) ([][32]byte, error) {
1042
s.nodesLock.Lock()
1143
defer s.nodesLock.Unlock()
1244
invalidRoots := make([][32]byte, 0)
1345

14-
node, ok := s.nodeByRoot[root]
15-
if !ok || node == nil {
46+
if node == nil {
1647
return invalidRoots, ErrNilNode
1748
}
1849
if !node.optimistic || node.parent == nil {
1950
return invalidRoots, errInvalidOptimisticStatus
2051
}
52+
2153
children := node.parent.children
2254
if len(children) == 1 {
2355
node.parent.children = []*Node{}
@@ -47,6 +79,16 @@ func (s *Store) removeNodeAndChildren(ctx context.Context, node *Node, invalidRo
4779
}
4880
}
4981
invalidRoots = append(invalidRoots, node.root)
82+
s.proposerBoostLock.Lock()
83+
if node.root == s.proposerBoostRoot {
84+
s.proposerBoostRoot = [32]byte{}
85+
}
86+
if node.root == s.previousProposerBoostRoot {
87+
s.previousProposerBoostRoot = params.BeaconConfig().ZeroHash
88+
s.previousProposerBoostScore = 0
89+
}
90+
s.proposerBoostLock.Unlock()
5091
delete(s.nodeByRoot, node.root)
92+
delete(s.nodeByPayload, node.payloadHash)
5193
return invalidRoots, nil
5294
}

beacon-chain/forkchoice/doubly-linked-tree/optimistic_sync_test.go

+75-13
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,71 @@ import (
2424
func TestPruneInvalid(t *testing.T) {
2525
tests := []struct {
2626
root [32]byte // the root of the new INVALID block
27+
payload [32]byte // the last valid hash
2728
wantedNodeNumber int
2829
wantedRoots [][32]byte
2930
}{
3031
{
3132
[32]byte{'j'},
33+
[32]byte{'B'},
3234
12,
3335
[][32]byte{[32]byte{'j'}},
3436
},
3537
{
3638
[32]byte{'c'},
39+
[32]byte{'B'},
3740
4,
3841
[][32]byte{[32]byte{'f'}, [32]byte{'e'}, [32]byte{'i'}, [32]byte{'h'}, [32]byte{'l'},
3942
[32]byte{'k'}, [32]byte{'g'}, [32]byte{'d'}, [32]byte{'c'}},
4043
},
4144
{
4245
[32]byte{'i'},
46+
[32]byte{'H'},
4347
12,
4448
[][32]byte{[32]byte{'i'}},
4549
},
4650
{
4751
[32]byte{'h'},
52+
[32]byte{'G'},
4853
11,
4954
[][32]byte{[32]byte{'i'}, [32]byte{'h'}},
5055
},
5156
{
5257
[32]byte{'g'},
58+
[32]byte{'D'},
59+
8,
60+
[][32]byte{[32]byte{'i'}, [32]byte{'h'}, [32]byte{'l'}, [32]byte{'k'}, [32]byte{'g'}},
61+
},
62+
{
63+
[32]byte{'i'},
64+
[32]byte{'D'},
65+
8,
66+
[][32]byte{[32]byte{'i'}, [32]byte{'h'}, [32]byte{'l'}, [32]byte{'k'}, [32]byte{'g'}},
67+
},
68+
{
69+
[32]byte{'f'},
70+
[32]byte{'D'},
71+
11,
72+
[][32]byte{[32]byte{'f'}, [32]byte{'e'}},
73+
},
74+
{
75+
[32]byte{'h'},
76+
[32]byte{'C'},
77+
5,
78+
[][32]byte{
79+
[32]byte{'f'},
80+
[32]byte{'e'},
81+
[32]byte{'i'},
82+
[32]byte{'h'},
83+
[32]byte{'l'},
84+
[32]byte{'k'},
85+
[32]byte{'g'},
86+
[32]byte{'d'},
87+
},
88+
},
89+
{
90+
[32]byte{'g'},
91+
[32]byte{'E'},
5392
8,
5493
[][32]byte{[32]byte{'i'}, [32]byte{'h'}, [32]byte{'l'}, [32]byte{'k'}, [32]byte{'g'}},
5594
},
@@ -58,22 +97,45 @@ func TestPruneInvalid(t *testing.T) {
5897
ctx := context.Background()
5998
f := setup(1, 1)
6099

61-
require.NoError(t, f.InsertOptimisticBlock(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 1, 1))
62-
require.NoError(t, f.InsertOptimisticBlock(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, params.BeaconConfig().ZeroHash, 1, 1))
63-
require.NoError(t, f.InsertOptimisticBlock(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, params.BeaconConfig().ZeroHash, 1, 1))
64-
require.NoError(t, f.InsertOptimisticBlock(ctx, 102, [32]byte{'j'}, [32]byte{'b'}, params.BeaconConfig().ZeroHash, 1, 1))
65-
require.NoError(t, f.InsertOptimisticBlock(ctx, 103, [32]byte{'d'}, [32]byte{'c'}, params.BeaconConfig().ZeroHash, 1, 1))
66-
require.NoError(t, f.InsertOptimisticBlock(ctx, 104, [32]byte{'e'}, [32]byte{'d'}, params.BeaconConfig().ZeroHash, 1, 1))
67-
require.NoError(t, f.InsertOptimisticBlock(ctx, 104, [32]byte{'g'}, [32]byte{'d'}, params.BeaconConfig().ZeroHash, 1, 1))
68-
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'f'}, [32]byte{'e'}, params.BeaconConfig().ZeroHash, 1, 1))
69-
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'h'}, [32]byte{'g'}, params.BeaconConfig().ZeroHash, 1, 1))
70-
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'k'}, [32]byte{'g'}, params.BeaconConfig().ZeroHash, 1, 1))
71-
require.NoError(t, f.InsertOptimisticBlock(ctx, 106, [32]byte{'i'}, [32]byte{'h'}, params.BeaconConfig().ZeroHash, 1, 1))
72-
require.NoError(t, f.InsertOptimisticBlock(ctx, 106, [32]byte{'l'}, [32]byte{'k'}, params.BeaconConfig().ZeroHash, 1, 1))
100+
require.NoError(t, f.InsertOptimisticBlock(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1))
101+
require.NoError(t, f.InsertOptimisticBlock(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1))
102+
require.NoError(t, f.InsertOptimisticBlock(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1))
103+
require.NoError(t, f.InsertOptimisticBlock(ctx, 102, [32]byte{'j'}, [32]byte{'b'}, [32]byte{'J'}, 1, 1))
104+
require.NoError(t, f.InsertOptimisticBlock(ctx, 103, [32]byte{'d'}, [32]byte{'c'}, [32]byte{'D'}, 1, 1))
105+
require.NoError(t, f.InsertOptimisticBlock(ctx, 104, [32]byte{'e'}, [32]byte{'d'}, [32]byte{'E'}, 1, 1))
106+
require.NoError(t, f.InsertOptimisticBlock(ctx, 104, [32]byte{'g'}, [32]byte{'d'}, [32]byte{'G'}, 1, 1))
107+
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'f'}, [32]byte{'e'}, [32]byte{'F'}, 1, 1))
108+
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'h'}, [32]byte{'g'}, [32]byte{'H'}, 1, 1))
109+
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'k'}, [32]byte{'g'}, [32]byte{'K'}, 1, 1))
110+
require.NoError(t, f.InsertOptimisticBlock(ctx, 106, [32]byte{'i'}, [32]byte{'h'}, [32]byte{'I'}, 1, 1))
111+
require.NoError(t, f.InsertOptimisticBlock(ctx, 106, [32]byte{'l'}, [32]byte{'k'}, [32]byte{'L'}, 1, 1))
73112

74-
roots, err := f.store.removeNode(context.Background(), tc.root)
113+
roots, err := f.store.setOptimisticToInvalid(context.Background(), tc.root, tc.payload)
75114
require.NoError(t, err)
76115
require.DeepEqual(t, tc.wantedRoots, roots)
77116
require.Equal(t, tc.wantedNodeNumber, f.NodeCount())
78117
}
79118
}
119+
120+
// This is a regression test (10445)
121+
func TestSetOptimisticToInvalid_ProposerBoost(t *testing.T) {
122+
ctx := context.Background()
123+
f := setup(1, 1)
124+
125+
require.NoError(t, f.InsertOptimisticBlock(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1))
126+
require.NoError(t, f.InsertOptimisticBlock(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1))
127+
require.NoError(t, f.InsertOptimisticBlock(ctx, 101, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1))
128+
f.store.proposerBoostLock.Lock()
129+
f.store.proposerBoostRoot = [32]byte{'c'}
130+
f.store.previousProposerBoostScore = 10
131+
f.store.previousProposerBoostRoot = [32]byte{'b'}
132+
f.store.proposerBoostLock.Unlock()
133+
134+
_, err := f.SetOptimisticToInvalid(ctx, [32]byte{'c'}, [32]byte{'A'})
135+
require.NoError(t, err)
136+
f.store.proposerBoostLock.RLock()
137+
require.Equal(t, uint64(0), f.store.previousProposerBoostScore)
138+
require.DeepEqual(t, [32]byte{}, f.store.proposerBoostRoot)
139+
require.DeepEqual(t, params.BeaconConfig().ZeroHash, f.store.previousProposerBoostRoot)
140+
f.store.proposerBoostLock.RUnlock()
141+
}

beacon-chain/forkchoice/doubly-linked-tree/store.go

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ func (s *Store) insert(ctx context.Context,
121121
payloadHash: payloadHash,
122122
}
123123

124+
s.nodeByPayload[payloadHash] = n
124125
s.nodeByRoot[root] = n
125126
if parent != nil {
126127
parent.children = append(parent.children, n)

beacon-chain/forkchoice/doubly-linked-tree/store_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ func TestStore_Insert(t *testing.T) {
107107
// The new node does not have a parent.
108108
treeRootNode := &Node{slot: 0, root: indexToHash(0)}
109109
nodeByRoot := map[[32]byte]*Node{indexToHash(0): treeRootNode}
110-
s := &Store{nodeByRoot: nodeByRoot, treeRootNode: treeRootNode}
110+
nodeByPayload := map[[32]byte]*Node{indexToHash(0): treeRootNode}
111+
s := &Store{nodeByRoot: nodeByRoot, treeRootNode: treeRootNode, nodeByPayload: nodeByPayload}
111112
payloadHash := [32]byte{'a'}
112113
require.NoError(t, s.insert(context.Background(), 100, indexToHash(100), indexToHash(0), payloadHash, 1, 1))
113114
assert.Equal(t, 2, len(s.nodeByRoot), "Did not insert block")

beacon-chain/forkchoice/doubly-linked-tree/types.go

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type Store struct {
2626
treeRootNode *Node // the root node of the store tree.
2727
headNode *Node // last head Node
2828
nodeByRoot map[[fieldparams.RootLength]byte]*Node // nodes indexed by roots.
29+
nodeByPayload map[[fieldparams.RootLength]byte]*Node // nodes indexed by payload Hash
2930
nodesLock sync.RWMutex
3031
proposerBoostLock sync.RWMutex
3132
}

beacon-chain/forkchoice/interfaces.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type ForkChoicer interface {
2424
type HeadRetriever interface {
2525
Head(context.Context, types.Epoch, [32]byte, []uint64, types.Epoch) ([32]byte, error)
2626
Tips() ([][32]byte, []types.Slot)
27-
IsOptimistic(ctx context.Context, root [32]byte) (bool, error)
27+
IsOptimistic(root [32]byte) (bool, error)
2828
}
2929

3030
// BlockProcessor processes the block that's used for accounting fork choice.
@@ -71,5 +71,5 @@ type Getter interface {
7171
// Setter allows to set forkchoice information
7272
type Setter interface {
7373
SetOptimisticToValid(context.Context, [fieldparams.RootLength]byte) error
74-
SetOptimisticToInvalid(context.Context, [fieldparams.RootLength]byte) ([][32]byte, error)
74+
SetOptimisticToInvalid(context.Context, [fieldparams.RootLength]byte, [fieldparams.RootLength]byte) ([][32]byte, error)
7575
}

beacon-chain/forkchoice/protoarray/errors.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import "errors"
55
var errUnknownFinalizedRoot = errors.New("unknown finalized root")
66
var errUnknownJustifiedRoot = errors.New("unknown justified root")
77
var errInvalidNodeIndex = errors.New("node index is invalid")
8+
var errInvalidFinalizedNode = errors.New("invalid finalized block on chain")
89
var ErrUnknownNodeRoot = errors.New("unknown block root")
910
var errInvalidJustifiedIndex = errors.New("justified index is invalid")
10-
var errInvalidBestChildIndex = errors.New("best child index is invalid")
1111
var errInvalidBestDescendantIndex = errors.New("best descendant index is invalid")
1212
var errInvalidParentDelta = errors.New("parent delta is invalid")
1313
var errInvalidNodeDelta = errors.New("node delta is invalid")
1414
var errInvalidDeltaLength = errors.New("delta length is invalid")
15-
var errInvalidSyncedTips = errors.New("invalid synced tips")
15+
var errInvalidOptimisticStatus = errors.New("invalid optimistic status")

beacon-chain/forkchoice/protoarray/helpers.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,16 @@ func copyNode(node *Node) *Node {
8585
return &Node{}
8686
}
8787

88-
copiedRoot := [32]byte{}
89-
copy(copiedRoot[:], node.root[:])
90-
9188
return &Node{
9289
slot: node.slot,
93-
root: copiedRoot,
90+
root: node.root,
9491
parent: node.parent,
9592
payloadHash: node.payloadHash,
9693
justifiedEpoch: node.justifiedEpoch,
9794
finalizedEpoch: node.finalizedEpoch,
9895
weight: node.weight,
9996
bestChild: node.bestChild,
10097
bestDescendant: node.bestDescendant,
98+
status: node.status,
10199
}
102100
}

0 commit comments

Comments
 (0)