Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions factory/mock/forkDetectorMock.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type ForkDetectorMock struct {
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
RemoveHeaderCalled func(nonce uint64, hash []byte)
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
CheckForkCalled func() *process.ForkInfo
GetHighestFinalBlockNonceCalled func() uint64
GetHighestFinalBlockHashCalled func() []byte
Expand Down Expand Up @@ -46,6 +47,13 @@ func (fdm *ForkDetectorMock) RemoveHeader(nonce uint64, hash []byte) {
}
}

// RemoveCommittedHeader -
func (fdm *ForkDetectorMock) RemoveCommittedHeader(nonce uint64, hash []byte) {
if fdm.RemoveCommittedHeaderCalled != nil {
fdm.RemoveCommittedHeaderCalled(nonce, hash)
}
}

// CheckFork is a mock implementation for CheckFork
func (fdm *ForkDetectorMock) CheckFork() *process.ForkInfo {
if fdm.CheckForkCalled != nil {
Expand Down
8 changes: 8 additions & 0 deletions integrationTests/mock/forkDetectorStub.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type ForkDetectorStub struct {
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
RemoveHeaderCalled func(nonce uint64, hash []byte)
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
CheckForkCalled func() *process.ForkInfo
GetHighestFinalBlockNonceCalled func() uint64
GetHighestFinalBlockHashCalled func() []byte
Expand Down Expand Up @@ -52,6 +53,13 @@ func (fdm *ForkDetectorStub) RemoveHeader(nonce uint64, hash []byte) {
}
}

// RemoveCommittedHeader -
func (fdm *ForkDetectorStub) RemoveCommittedHeader(nonce uint64, hash []byte) {
if fdm.RemoveCommittedHeaderCalled != nil {
fdm.RemoveCommittedHeaderCalled(nonce, hash)
}
}

// CheckFork is a mock implementation for CheckFork
func (fdm *ForkDetectorStub) CheckFork() *process.ForkInfo {
if fdm.CheckForkCalled != nil {
Expand Down
6 changes: 6 additions & 0 deletions node/mock/forkDetectorMock.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type ForkDetectorMock struct {
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
RemoveHeaderCalled func(nonce uint64, hash []byte)
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
CheckForkCalled func() *process.ForkInfo
GetHighestFinalBlockNonceCalled func() uint64
GetHighestFinalBlockHashCalled func() []byte
Expand Down Expand Up @@ -39,6 +40,11 @@ func (fdm *ForkDetectorMock) RemoveHeader(nonce uint64, hash []byte) {
fdm.RemoveHeaderCalled(nonce, hash)
}

// RemoveCommittedHeader -
func (fdm *ForkDetectorMock) RemoveCommittedHeader(nonce uint64, hash []byte) {
fdm.RemoveCommittedHeaderCalled(nonce, hash)
}

// CheckFork is a mock implementation for CheckFork
func (fdm *ForkDetectorMock) CheckFork() *process.ForkInfo {
return fdm.CheckForkCalled()
Expand Down
1 change: 1 addition & 0 deletions process/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ type Bootstrapper interface {
type ForkDetector interface {
AddHeader(header data.HeaderHandler, headerHash []byte, state BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
RemoveHeader(nonce uint64, hash []byte)
RemoveCommittedHeader(nonce uint64, hash []byte)
CheckFork() *ForkInfo
GetHighestFinalBlockNonce() uint64
GetHighestFinalBlockHash() []byte
Expand Down
8 changes: 8 additions & 0 deletions process/mock/forkDetectorMock.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type ForkDetectorMock struct {
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
RemoveHeaderCalled func(nonce uint64, hash []byte)
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
CheckForkCalled func() *process.ForkInfo
GetHighestFinalBlockNonceCalled func() uint64
GetHighestFinalBlockHashCalled func() []byte
Expand Down Expand Up @@ -45,6 +46,13 @@ func (fdm *ForkDetectorMock) RemoveHeader(nonce uint64, hash []byte) {
}
}

// RemoveCommittedHeader -
func (fdm *ForkDetectorMock) RemoveCommittedHeader(nonce uint64, hash []byte) {
if fdm.RemoveCommittedHeaderCalled != nil {
fdm.RemoveCommittedHeaderCalled(nonce, hash)
}
}

// CheckFork -
func (fdm *ForkDetectorMock) CheckFork() *process.ForkInfo {
if fdm.CheckForkCalled != nil {
Expand Down
47 changes: 47 additions & 0 deletions process/sync/baseForkDetector.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,53 @@ func (bfd *baseForkDetector) RemoveHeader(nonce uint64, hash []byte) {
"final checkpoint nonce", bfd.finalCheckpoint().nonce)
}

// RemoveCommittedHeader removes a reverted committed header together with its checkpoint, proof
// included, so a same-nonce sibling can be adopted; it never removes at or below the final checkpoint
func (bfd *baseForkDetector) RemoveCommittedHeader(nonce uint64, hash []byte) {
finalCheckpointNonce := bfd.finalCheckpoint().nonce
if nonce <= finalCheckpointNonce {
log.Warn("baseForkDetector.RemoveCommittedHeader: refusing removal at or below the final checkpoint",
"nonce", nonce,
"hash", hash,
"final checkpoint nonce", finalCheckpointNonce)
return
}

bfd.removeCheckpointWithNonce(nonce)

preservedHdrsInfo := make([]*headerInfo, 0)

bfd.mutHeaders.Lock()

hdrsInfo := bfd.headers[nonce]
for _, hdrInfo := range hdrsInfo {
if hdrInfo.state != process.BHNotarized && bytes.Equal(hash, hdrInfo.hash) {
continue
}

preservedHdrsInfo = append(preservedHdrsInfo, hdrInfo)
}

if len(preservedHdrsInfo) == 0 {
delete(bfd.headers, nonce)
} else {
bfd.headers[nonce] = preservedHdrsInfo
}

bfd.mutHeaders.Unlock()

bfd.forkDetector.computeFinalCheckpoint()

probableHighestNonce := bfd.computeProbableHighestNonce()
bfd.setProbableHighestNonce(probableHighestNonce)

log.Debug("forkDetector.RemoveCommittedHeader",
"nonce", nonce,
"hash", hash,
"probable highest nonce", probableHighestNonce,
"final checkpoint nonce", bfd.finalCheckpoint().nonce)
}

func (bfd *baseForkDetector) removeCheckpointWithNonce(nonce uint64) {
bfd.mutFork.Lock()
preservedCheckpoint := make([]*checkpointInfo, 0)
Expand Down
58 changes: 58 additions & 0 deletions process/sync/baseForkDetector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1768,3 +1768,61 @@ func TestBaseForkDetector_ReceivedProofForBlockHeaderShouldSetProof(t *testing.T
assert.Equal(t, []byte("hash0"), hdrInfos[1].Hash())
assert.Equal(t, true, hdrInfos[1].HasProof())
}

func TestBaseForkDetector_RemoveCommittedHeader(t *testing.T) {
t.Parallel()

sfd, _ := sync.NewShardForkDetector(
&mock.RoundHandlerMock{RoundIndex: 5},
&testscommon.TimeCacheStub{},
&mock.BlockTrackerMock{},
0,
0,
&enableEpochsHandlerMock.EnableEpochsHandlerStub{
IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool {
return flag == common.AndromedaFlag || flag == common.SupernovaFlag
},
},
&testscommon.EnableRoundsHandlerStub{},
&dataRetriever.ProofsPoolMock{
HasProofCalled: func(shardID uint32, headerHash []byte) bool {
return true
},
},
&chainParameters.ChainParametersHandlerStub{},
testscommon.GetDefaultProcessConfigsHandler(),
0,
)

hash1, hash2, competitorHash := []byte("hash1"), []byte("hash2"), []byte("competitorHash")
hdr1 := &block.Header{Nonce: 1, Round: 1, PubKeysBitmap: []byte("X")}
contendedHdr2 := &block.Header{Nonce: 2, Round: 4, PrevHash: hash1, PubKeysBitmap: []byte("X")}

_ = sfd.AddHeader(hdr1, hash1, process.BHProcessed, nil, nil)
_ = sfd.AddHeader(contendedHdr2, hash2, process.BHProcessed, nil, nil)
sfd.ReceivedProof(&block.HeaderProof{
HeaderHash: competitorHash,
HeaderNonce: 2,
HeaderRound: 3,
HeaderShardId: 0,
})

// RemoveHeader refuses proofed headers, the committed one included
sfd.RemoveHeader(2, hash2)
assert.Len(t, sfd.GetHeaders(2), 2)
assert.Equal(t, uint64(2), sfd.LastCheckpointNonce())

// the deliberate switch removal drops the committed header and its checkpoint despite the proof
sfd.RemoveCommittedHeader(2, hash2)
hdrInfos := sfd.GetHeaders(2)
assert.Len(t, hdrInfos, 1)
assert.Equal(t, competitorHash, hdrInfos[0].Hash())
assert.Equal(t, uint64(1), sfd.LastCheckpointNonce())
assert.Equal(t, uint64(1), sfd.FinalCheckpointNonce())

// removal at or below the final checkpoint is refused
sfd.RemoveCommittedHeader(1, hash1)
assert.Len(t, sfd.GetHeaders(1), 1)
assert.Equal(t, uint64(1), sfd.LastCheckpointNonce())
assert.Equal(t, uint64(1), sfd.FinalCheckpointNonce())
}
113 changes: 95 additions & 18 deletions process/sync/baseSync.go
Original file line number Diff line number Diff line change
Expand Up @@ -1685,7 +1685,8 @@ func (boot *baseBootstrap) rollBack(revertUsingForkNonce bool) error {
var currBody data.BodyHandler

defer func() {
if !roleBackOneBlockExecuted {
isHeaderV3 := !check.IfNil(currHeader) && currHeader.IsHeaderV3()
if !roleBackOneBlockExecuted && !isHeaderV3 {
err = boot.scheduledTxsExecutionHandler.RollBackToBlock(currHeaderHash)
if err != nil {
rootHash := boot.chainHandler.GetGenesisHeader().GetRootHash()
Expand All @@ -1712,7 +1713,9 @@ func (boot *baseBootstrap) rollBack(revertUsingForkNonce bool) error {
}

allowRollBack := boot.shouldAllowRollback(currHeader, currHeaderHash)
if !revertUsingForkNonce && !allowRollBack {
// a header v3 switch must never cross the final checkpoint, not even fork-driven
isRollBackDenied := !allowRollBack && (!revertUsingForkNonce || currHeader.IsHeaderV3())
if isRollBackDenied {
return ErrRollBackBehindFinalHeader
}

Expand All @@ -1735,13 +1738,22 @@ func (boot *baseBootstrap) rollBack(revertUsingForkNonce bool) error {
"nonce", boot.forkDetector.GetHighestFinalBlockNonce(),
)

currBody, err = boot.rollBackOneBlock(
currHeaderHash,
currHeader,
prevHeaderHash,
prevHeader,
)
roleBackOneBlockExecuted = true
if currHeader.IsHeaderV3() {
currBody, err = boot.rollBackOneBlockV3(
Comment on lines +1741 to +1742

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we set prepared for sync to false in case of rollback on v3?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is done on the defer realignAfterV3RollBack.
will refactor a bit to have the realign called also in case of error, where it would have been missed.

currHeaderHash,
currHeader,
prevHeaderHash,
prevHeader,
)
} else {
currBody, err = boot.rollBackOneBlock(
currHeaderHash,
currHeader,
prevHeaderHash,
prevHeader,
)
roleBackOneBlockExecuted = true
}
if err != nil {
return err
}
Expand All @@ -1765,15 +1777,17 @@ func (boot *baseBootstrap) rollBack(revertUsingForkNonce bool) error {
return err
}

err = boot.scheduledTxsExecutionHandler.RollBackToBlock(prevHeaderHash)
if err != nil {
scheduledInfo := &process.ScheduledInfo{
RootHash: prevHeader.GetRootHash(),
IntermediateTxs: make(map[block.Type][]data.TransactionHandler),
GasAndFees: process.GetZeroGasAndFees(),
MiniBlocks: make(block.MiniBlockSlice, 0),
if !currHeader.IsHeaderV3() {
err = boot.scheduledTxsExecutionHandler.RollBackToBlock(prevHeaderHash)
if err != nil {
scheduledInfo := &process.ScheduledInfo{
RootHash: prevHeader.GetRootHash(),
IntermediateTxs: make(map[block.Type][]data.TransactionHandler),
GasAndFees: process.GetZeroGasAndFees(),
MiniBlocks: make(block.MiniBlockSlice, 0),
}
boot.scheduledTxsExecutionHandler.SetScheduledInfo(scheduledInfo)
}
boot.scheduledTxsExecutionHandler.SetScheduledInfo(scheduledInfo)
}

err = boot.outportHandler.RevertIndexedBlock(&outportcore.HeaderDataWithBody{
Expand Down Expand Up @@ -1803,9 +1817,12 @@ func (boot *baseBootstrap) rollBack(revertUsingForkNonce bool) error {
}

func (boot *baseBootstrap) shouldAllowRollback(currHeader data.HeaderHandler, currHeaderHash []byte) bool {
if check.IfNil(currHeader) || currHeader.IsHeaderV3() {
if check.IfNil(currHeader) {
return false
}
if currHeader.IsHeaderV3() {
return boot.shouldAllowRollbackV3(currHeader)
}

finalBlockNonce := boot.forkDetector.GetHighestFinalBlockNonce()
finalBlockHash := boot.forkDetector.GetHighestFinalBlockHash()
Expand All @@ -1831,6 +1848,21 @@ func (boot *baseBootstrap) shouldAllowRollback(currHeader data.HeaderHandler, cu
return allowRollBack
}

// shouldAllowRollbackV3 allows replacing a committed block only while it is not final (R-SWITCH);
// the state is never reverted through tries, the adopted sibling re-executes asynchronously
func (boot *baseBootstrap) shouldAllowRollbackV3(currHeader data.HeaderHandler) bool {
finalBlockNonce := boot.forkDetector.GetHighestFinalBlockNonce()
allowRollBack := currHeader.GetNonce() > finalBlockNonce

log.Debug("baseBootstrap.shouldAllowRollbackV3",
"nonce", currHeader.GetNonce(),
"final block nonce", finalBlockNonce,
"allowRollBack", allowRollBack,
)

return allowRollBack
}

func (boot *baseBootstrap) canRollbackBlock(currHeader data.HeaderHandler) bool {
firstCommittedNonce := boot.blockProcessor.NonceOfFirstCommittedBlock()

Expand Down Expand Up @@ -1889,6 +1921,51 @@ func (boot *baseBootstrap) rollBackOneBlock(
return currBlockBody, nil
}

// rollBackOneBlockV3 reverts a committed, not yet final header so a same-nonce sibling can be
// adopted; the trie state is not reverted, the sibling's execution results are produced async
func (boot *baseBootstrap) rollBackOneBlockV3(
currHeaderHash []byte,
currHeader data.HeaderHandler,
prevHeaderHash []byte,
prevHeader data.HeaderHandler,
) (data.BodyHandler, error) {
err := boot.chainHandler.SetCurrentBlockHeaderAndHash(prevHeaderHash, prevHeader)
if err != nil {
return nil, err
}

defer func() {
if err != nil {
errNotCritical := boot.chainHandler.SetCurrentBlockHeaderAndHash(currHeaderHash, currHeader)
if errNotCritical != nil {
log.Warn("rollBackOneBlockV3: cannot restore current block info", "error", errNotCritical)
}
}
}()

err = boot.executionManager.RemoveAtNonceAndHigher(currHeader.GetNonce())
if err != nil {
return nil, err
}

currBlockBody, errNotCritical := boot.blockBootstrapper.getBlockBody(currHeader)
if errNotCritical != nil {
log.Debug("rollBackOneBlockV3 getBlockBody error", "error", errNotCritical)
}

err = boot.blockProcessor.RestoreBlockIntoPools(currHeader, currBlockBody)
if err != nil {
return nil, err
}

hash := boot.removeHeaderFromPools(currHeader)
boot.forkDetector.RemoveCommittedHeader(currHeader.GetNonce(), hash)
nonceToByteSlice := boot.uint64Converter.ToByteSlice(currHeader.GetNonce())
_ = boot.headerNonceHashStore.Remove(nonceToByteSlice)

return currBlockBody, nil
}

func (boot *baseBootstrap) getRootHashFromBlock(hdr data.HeaderHandler, hdrHash []byte) []byte {
hdrRootHash := hdr.GetRootHash()
scheduledHdrRootHash, err := boot.scheduledTxsExecutionHandler.GetScheduledRootHashForHeader(hdrHash)
Expand Down
Loading
Loading