Skip to content

Commit 28b1d09

Browse files
authored
Merge pull request #7919 from multiversx/meta-arbitration
add meta arbitration and reconcile contended headers
2 parents 7b31f0b + c01a931 commit 28b1d09

39 files changed

Lines changed: 1685 additions & 200 deletions

common/constants.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ const MetricNumTimesInForkChoice = "erd_fork_choice_count"
263263
// proofs pool (different-hash proofs received for the same header nonce)
264264
const MetricNumEquivocationProofs = "erd_num_equivocation_proofs"
265265

266+
// MetricNumReconcileSwitches is the metric that counts the reconcile backstop activations, each a
267+
// switch away from a finalized block on equivocation evidence
268+
const MetricNumReconcileSwitches = "erd_num_reconcile_switches"
269+
266270
// MetricHighestFinalBlock is the metric for the nonce of the highest final block
267271
const MetricHighestFinalBlock = "erd_highest_final_nonce"
268272

consensus/spos/bls/v2/subroundSignature.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,10 @@ func (sr *subroundSignature) sendSignatureForManagedKey(ctx context.Context, idx
339339
default:
340340
}
341341

342+
if !sr.reserveSignatureSlot(pkBytes, nonce, currentHash) {
343+
return false
344+
}
345+
342346
signatureShare, err := sr.SigningHandler().SignatureShare(uint16(idx))
343347
if err != nil {
344348
log.Debug("sendSignatureForManagedKey.SignatureShare", "error", err.Error())
@@ -370,6 +374,10 @@ func (sr *subroundSignature) doSignatureJobForSingleKey(ctx context.Context) boo
370374
return false
371375
}
372376

377+
if !sr.reserveSignatureSlot(pkBytes, nonce, currentHash) {
378+
return false
379+
}
380+
373381
signatureShare, err := sr.SigningHandler().CreateSignatureShareForPublicKey(
374382
ctx,
375383
currentHash,
@@ -400,6 +408,22 @@ func (sr *subroundSignature) doSignatureJobForSingleKey(ctx context.Context) boo
400408
return sr.completeSignatureSubRound(sr.SelfPubKey())
401409
}
402410

411+
// reserveSignatureSlot hard-refuses a second signature for a different hash in the same round (R0)
412+
func (sr *subroundSignature) reserveSignatureSlot(pkBytes []byte, nonce uint64, currentHash []byte) bool {
413+
roundIndex := sr.RoundHandler().Index()
414+
if sr.sentSignatureTracker.ReserveSignatureInRound(pkBytes, roundIndex, currentHash) {
415+
return true
416+
}
417+
418+
log.Warn("refusing second signature for a different hash in the same round",
419+
"pk", pkBytes,
420+
"nonce", nonce,
421+
"round", roundIndex,
422+
"hash", currentHash)
423+
424+
return false
425+
}
426+
403427
func (sr *subroundSignature) getPkForCompetingBlock(nonce uint64, currentHash []byte) []byte {
404428
currentRound := sr.RoundHandler().Index()
405429

consensus/spos/bls/v2/subroundSignature_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,61 @@ func TestSubroundSignature_NewSubroundSignatureShouldWork(t *testing.T) {
444444
assert.Nil(t, err)
445445
}
446446

447+
func TestSubroundSignature_DoSignatureJobRefusesSecondHashInSameRound(t *testing.T) {
448+
t.Parallel()
449+
450+
container := consensusMocks.InitConsensusCore()
451+
consensusState := initializers.InitConsensusState()
452+
ch := make(chan bool, 1)
453+
454+
sr, _ := spos.NewSubround(
455+
bls.SrBlock,
456+
bls.SrSignature,
457+
bls.SrEndRound,
458+
roundTimeDuration,
459+
0.7,
460+
0.85,
461+
"(SIGNATURE)",
462+
consensusState,
463+
ch,
464+
executeStoredMessages,
465+
container,
466+
chainID,
467+
currentPid,
468+
&statusHandler.AppStatusHandlerStub{},
469+
)
470+
471+
broadcastCalled := false
472+
container.SetBroadcastMessenger(&consensusMocks.BroadcastMessengerMock{
473+
BroadcastConsensusMessageCalled: func(message *consensus.Message) error {
474+
broadcastCalled = true
475+
return nil
476+
},
477+
})
478+
479+
srSignature, _ := v2.NewSubroundSignature(
480+
sr,
481+
&statusHandler.AppStatusHandlerStub{},
482+
&testscommon.SentSignatureTrackerStub{
483+
ReserveSignatureInRoundCalled: func(pkBytes []byte, roundIndex int64, headerHash []byte) bool {
484+
return false
485+
},
486+
},
487+
&consensusMocks.SposWorkerMock{},
488+
&dataRetrieverMock.ThrottlerStub{},
489+
v2.NewSignatureEvidenceStore(nil),
490+
)
491+
492+
srSignature.SetHeader(&block.Header{})
493+
leader, err := srSignature.GetLeader()
494+
assert.Nil(t, err)
495+
srSignature.SetSelfPubKey(leader)
496+
497+
r := srSignature.DoSignatureJob()
498+
assert.False(t, r)
499+
assert.False(t, broadcastCalled)
500+
}
501+
447502
func TestSubroundSignature_DoSignatureJob(t *testing.T) {
448503
t.Parallel()
449504

consensus/spos/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ type SentSignaturesTracker interface {
182182
SignatureSent(pkBytes []byte)
183183
RecordSignedNonce(pkBytes []byte, nonce uint64, headerHash []byte, roundIndex int64)
184184
GetSignedNonceInfo(pkBytes []byte, nonce uint64) ([]byte, int64, bool)
185+
ReserveSignatureInRound(pkBytes []byte, roundIndex int64, headerHash []byte) bool
185186
IsInterfaceNil() bool
186187
}
187188

dataRetriever/blockchain/baseBlockchain.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/multiversx/mx-chain-core-go/core"
77
"github.com/multiversx/mx-chain-core-go/core/check"
88
"github.com/multiversx/mx-chain-core-go/data"
9+
910
"github.com/multiversx/mx-chain-go/common"
1011
)
1112

@@ -185,7 +186,8 @@ func (bbc *baseBlockChain) GetLastExecutionResult() data.BaseExecutionResultHand
185186
return bbc.lastExecutionResult
186187
}
187188

188-
// SetLastExecutionInfo sets header, execution result and final block info atomically
189+
// SetLastExecutionInfo sets header and execution result atomically; the final block info is
190+
// settlement-anchored and set separately by the block processors
189191
func (bbc *baseBlockChain) SetLastExecutionInfo(
190192
header data.HeaderHandler,
191193
result data.BaseExecutionResultHandler,
@@ -199,9 +201,7 @@ func (bbc *baseBlockChain) SetLastExecutionInfo(
199201

200202
headerHash := result.GetHeaderHash()
201203
rootHash := result.GetRootHash()
202-
headerNonce := result.GetHeaderNonce()
203204

204-
bbc.setFinalBlockInfoUnprotected(headerNonce, headerHash, rootHash)
205205
bbc.setLastExecutedBlockHeaderAndRootHashUnprotected(header, headerHash, rootHash)
206206
bbc.lastExecutionResult = result
207207
}

factory/mock/forkDetectorMock.go

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,23 @@ import (
88

99
// ForkDetectorMock is a mock implementation for the ForkDetector interface
1010
type ForkDetectorMock struct {
11-
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
12-
RemoveHeaderCalled func(nonce uint64, hash []byte)
13-
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
14-
CheckForkCalled func() *process.ForkInfo
15-
GetHighestFinalBlockNonceCalled func() uint64
16-
GetHighestFinalBlockHashCalled func() []byte
17-
ProbableHighestNonceCalled func() uint64
18-
ResetForkCalled func()
19-
GetNotarizedHeaderHashCalled func(nonce uint64) []byte
20-
SetRollBackNonceCalled func(nonce uint64)
21-
RestoreToGenesisCalled func()
22-
ResetProbableHighestNonceCalled func()
23-
SetFinalToLastCheckpointCalled func()
24-
ReceivedProofCalled func(proof data.HeaderProofHandler)
25-
AddCheckpointCalled func(nonce uint64, round uint64, hash []byte)
11+
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
12+
RemoveHeaderCalled func(nonce uint64, hash []byte)
13+
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
14+
ReconcileFinalCheckpointCalled func(nonce uint64)
15+
CheckForkCalled func() *process.ForkInfo
16+
GetHighestFinalBlockNonceCalled func() uint64
17+
GetHighestFinalBlockHashCalled func() []byte
18+
GetHighestSettledBlockInfoCalled func() (uint64, []byte)
19+
ProbableHighestNonceCalled func() uint64
20+
ResetForkCalled func()
21+
GetNotarizedHeaderHashCalled func(nonce uint64) []byte
22+
SetRollBackNonceCalled func(nonce uint64)
23+
RestoreToGenesisCalled func()
24+
ResetProbableHighestNonceCalled func()
25+
SetFinalToLastCheckpointCalled func()
26+
ReceivedProofCalled func(proof data.HeaderProofHandler)
27+
AddCheckpointCalled func(nonce uint64, round uint64, hash []byte)
2628
}
2729

2830
// RestoreToGenesis -
@@ -54,6 +56,13 @@ func (fdm *ForkDetectorMock) RemoveCommittedHeader(nonce uint64, hash []byte) {
5456
}
5557
}
5658

59+
// ReconcileFinalCheckpoint -
60+
func (fdm *ForkDetectorMock) ReconcileFinalCheckpoint(nonce uint64) {
61+
if fdm.ReconcileFinalCheckpointCalled != nil {
62+
fdm.ReconcileFinalCheckpointCalled(nonce)
63+
}
64+
}
65+
5766
// CheckFork is a mock implementation for CheckFork
5867
func (fdm *ForkDetectorMock) CheckFork() *process.ForkInfo {
5968
if fdm.CheckForkCalled != nil {
@@ -78,6 +87,23 @@ func (fdm *ForkDetectorMock) GetHighestFinalBlockHash() []byte {
7887
return nil
7988
}
8089

90+
// GetHighestSettledBlockInfo -
91+
func (fdm *ForkDetectorMock) GetHighestSettledBlockInfo() (uint64, []byte) {
92+
if fdm.GetHighestSettledBlockInfoCalled != nil {
93+
return fdm.GetHighestSettledBlockInfoCalled()
94+
}
95+
96+
nonce := uint64(0)
97+
if fdm.GetHighestFinalBlockNonceCalled != nil {
98+
nonce = fdm.GetHighestFinalBlockNonceCalled()
99+
}
100+
var hash []byte
101+
if fdm.GetHighestFinalBlockHashCalled != nil {
102+
hash = fdm.GetHighestFinalBlockHashCalled()
103+
}
104+
return nonce, hash
105+
}
106+
81107
// ProbableHighestNonce is a mock implementation for GetProbableHighestNonce
82108
func (fdm *ForkDetectorMock) ProbableHighestNonce() uint64 {
83109
if fdm.ProbableHighestNonceCalled != nil {

integrationTests/mock/forkDetectorStub.go

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,23 @@ import (
88

99
// ForkDetectorStub is a mock implementation for the ForkDetector interface
1010
type ForkDetectorStub struct {
11-
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
12-
RemoveHeaderCalled func(nonce uint64, hash []byte)
13-
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
14-
CheckForkCalled func() *process.ForkInfo
15-
GetHighestFinalBlockNonceCalled func() uint64
16-
GetHighestFinalBlockHashCalled func() []byte
17-
ProbableHighestNonceCalled func() uint64
18-
ResetForkCalled func()
19-
GetNotarizedHeaderHashCalled func(nonce uint64) []byte
20-
RestoreToGenesisCalled func()
21-
SetRollBackNonceCalled func(nonce uint64)
22-
ResetProbableHighestNonceCalled func()
23-
SetFinalToLastCheckpointCalled func()
24-
ReceivedProofCalled func(proof data.HeaderProofHandler)
25-
AddCheckpointCalled func(nonce uint64, round uint64, hash []byte)
11+
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
12+
RemoveHeaderCalled func(nonce uint64, hash []byte)
13+
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
14+
ReconcileFinalCheckpointCalled func(nonce uint64)
15+
CheckForkCalled func() *process.ForkInfo
16+
GetHighestFinalBlockNonceCalled func() uint64
17+
GetHighestFinalBlockHashCalled func() []byte
18+
GetHighestSettledBlockInfoCalled func() (uint64, []byte)
19+
ProbableHighestNonceCalled func() uint64
20+
ResetForkCalled func()
21+
GetNotarizedHeaderHashCalled func(nonce uint64) []byte
22+
RestoreToGenesisCalled func()
23+
SetRollBackNonceCalled func(nonce uint64)
24+
ResetProbableHighestNonceCalled func()
25+
SetFinalToLastCheckpointCalled func()
26+
ReceivedProofCalled func(proof data.HeaderProofHandler)
27+
AddCheckpointCalled func(nonce uint64, round uint64, hash []byte)
2628
}
2729

2830
// RestoreToGenesis -
@@ -60,6 +62,13 @@ func (fdm *ForkDetectorStub) RemoveCommittedHeader(nonce uint64, hash []byte) {
6062
}
6163
}
6264

65+
// ReconcileFinalCheckpoint -
66+
func (fdm *ForkDetectorStub) ReconcileFinalCheckpoint(nonce uint64) {
67+
if fdm.ReconcileFinalCheckpointCalled != nil {
68+
fdm.ReconcileFinalCheckpointCalled(nonce)
69+
}
70+
}
71+
6372
// CheckFork is a mock implementation for CheckFork
6473
func (fdm *ForkDetectorStub) CheckFork() *process.ForkInfo {
6574
if fdm.CheckForkCalled != nil {
@@ -81,6 +90,23 @@ func (fdm *ForkDetectorStub) GetHighestFinalBlockHash() []byte {
8190
return fdm.GetHighestFinalBlockHashCalled()
8291
}
8392

93+
// GetHighestSettledBlockInfo -
94+
func (fdm *ForkDetectorStub) GetHighestSettledBlockInfo() (uint64, []byte) {
95+
if fdm.GetHighestSettledBlockInfoCalled != nil {
96+
return fdm.GetHighestSettledBlockInfoCalled()
97+
}
98+
99+
nonce := uint64(0)
100+
if fdm.GetHighestFinalBlockNonceCalled != nil {
101+
nonce = fdm.GetHighestFinalBlockNonceCalled()
102+
}
103+
var hash []byte
104+
if fdm.GetHighestFinalBlockHashCalled != nil {
105+
hash = fdm.GetHighestFinalBlockHashCalled()
106+
}
107+
return nonce, hash
108+
}
109+
84110
// ProbableHighestNonce is a mock implementation for ProbableHighestNonce
85111
func (fdm *ForkDetectorStub) ProbableHighestNonce() uint64 {
86112
if fdm.ProbableHighestNonceCalled != nil {

node/metrics/metrics.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func InitBaseMetrics(appStatusHandler core.AppStatusHandler) error {
4646
appStatusHandler.SetUInt64Value(common.MetricNumShardHeadersProcessed, initUint)
4747
appStatusHandler.SetUInt64Value(common.MetricNumTimesInForkChoice, initUint)
4848
appStatusHandler.SetUInt64Value(common.MetricNumEquivocationProofs, initUint)
49+
appStatusHandler.SetUInt64Value(common.MetricNumReconcileSwitches, initUint)
4950
appStatusHandler.SetUInt64Value(common.MetricHighestFinalBlock, initUint)
5051
appStatusHandler.SetUInt64Value(common.MetricCountConsensusAcceptedBlocks, initUint)
5152
appStatusHandler.SetUInt64Value(common.MetricRoundsPassedInCurrentEpoch, initUint)

node/metrics/metrics_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func TestInitBaseMetrics(t *testing.T) {
4040
common.MetricNumShardHeadersProcessed,
4141
common.MetricNumTimesInForkChoice,
4242
common.MetricNumEquivocationProofs,
43+
common.MetricNumReconcileSwitches,
4344
common.MetricHighestFinalBlock,
4445
common.MetricCountConsensusAcceptedBlocks,
4546
common.MetricRoundsPassedInCurrentEpoch,

0 commit comments

Comments
 (0)