Skip to content

Commit 7b31f0b

Browse files
authored
Merge pull request #7916 from multiversx/rollback-v3
rollback v3 above final
2 parents d462be6 + 6bafb1f commit 7b31f0b

11 files changed

Lines changed: 403 additions & 21 deletions

File tree

factory/mock/forkDetectorMock.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
type ForkDetectorMock struct {
1111
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
1212
RemoveHeaderCalled func(nonce uint64, hash []byte)
13+
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
1314
CheckForkCalled func() *process.ForkInfo
1415
GetHighestFinalBlockNonceCalled func() uint64
1516
GetHighestFinalBlockHashCalled func() []byte
@@ -46,6 +47,13 @@ func (fdm *ForkDetectorMock) RemoveHeader(nonce uint64, hash []byte) {
4647
}
4748
}
4849

50+
// RemoveCommittedHeader -
51+
func (fdm *ForkDetectorMock) RemoveCommittedHeader(nonce uint64, hash []byte) {
52+
if fdm.RemoveCommittedHeaderCalled != nil {
53+
fdm.RemoveCommittedHeaderCalled(nonce, hash)
54+
}
55+
}
56+
4957
// CheckFork is a mock implementation for CheckFork
5058
func (fdm *ForkDetectorMock) CheckFork() *process.ForkInfo {
5159
if fdm.CheckForkCalled != nil {

integrationTests/mock/forkDetectorStub.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
type ForkDetectorStub struct {
1111
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
1212
RemoveHeaderCalled func(nonce uint64, hash []byte)
13+
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
1314
CheckForkCalled func() *process.ForkInfo
1415
GetHighestFinalBlockNonceCalled func() uint64
1516
GetHighestFinalBlockHashCalled func() []byte
@@ -52,6 +53,13 @@ func (fdm *ForkDetectorStub) RemoveHeader(nonce uint64, hash []byte) {
5253
}
5354
}
5455

56+
// RemoveCommittedHeader -
57+
func (fdm *ForkDetectorStub) RemoveCommittedHeader(nonce uint64, hash []byte) {
58+
if fdm.RemoveCommittedHeaderCalled != nil {
59+
fdm.RemoveCommittedHeaderCalled(nonce, hash)
60+
}
61+
}
62+
5563
// CheckFork is a mock implementation for CheckFork
5664
func (fdm *ForkDetectorStub) CheckFork() *process.ForkInfo {
5765
if fdm.CheckForkCalled != nil {

node/mock/forkDetectorMock.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
type ForkDetectorMock struct {
1111
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
1212
RemoveHeaderCalled func(nonce uint64, hash []byte)
13+
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
1314
CheckForkCalled func() *process.ForkInfo
1415
GetHighestFinalBlockNonceCalled func() uint64
1516
GetHighestFinalBlockHashCalled func() []byte
@@ -39,6 +40,11 @@ func (fdm *ForkDetectorMock) RemoveHeader(nonce uint64, hash []byte) {
3940
fdm.RemoveHeaderCalled(nonce, hash)
4041
}
4142

43+
// RemoveCommittedHeader -
44+
func (fdm *ForkDetectorMock) RemoveCommittedHeader(nonce uint64, hash []byte) {
45+
fdm.RemoveCommittedHeaderCalled(nonce, hash)
46+
}
47+
4248
// CheckFork is a mock implementation for CheckFork
4349
func (fdm *ForkDetectorMock) CheckFork() *process.ForkInfo {
4450
return fdm.CheckForkCalled()

process/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ type Bootstrapper interface {
468468
type ForkDetector interface {
469469
AddHeader(header data.HeaderHandler, headerHash []byte, state BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
470470
RemoveHeader(nonce uint64, hash []byte)
471+
RemoveCommittedHeader(nonce uint64, hash []byte)
471472
CheckFork() *ForkInfo
472473
GetHighestFinalBlockNonce() uint64
473474
GetHighestFinalBlockHash() []byte

process/mock/forkDetectorMock.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
type ForkDetectorMock struct {
1111
AddHeaderCalled func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error
1212
RemoveHeaderCalled func(nonce uint64, hash []byte)
13+
RemoveCommittedHeaderCalled func(nonce uint64, hash []byte)
1314
CheckForkCalled func() *process.ForkInfo
1415
GetHighestFinalBlockNonceCalled func() uint64
1516
GetHighestFinalBlockHashCalled func() []byte
@@ -45,6 +46,13 @@ func (fdm *ForkDetectorMock) RemoveHeader(nonce uint64, hash []byte) {
4546
}
4647
}
4748

49+
// RemoveCommittedHeader -
50+
func (fdm *ForkDetectorMock) RemoveCommittedHeader(nonce uint64, hash []byte) {
51+
if fdm.RemoveCommittedHeaderCalled != nil {
52+
fdm.RemoveCommittedHeaderCalled(nonce, hash)
53+
}
54+
}
55+
4856
// CheckFork -
4957
func (fdm *ForkDetectorMock) CheckFork() *process.ForkInfo {
5058
if fdm.CheckForkCalled != nil {

process/sync/baseForkDetector.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,53 @@ func (bfd *baseForkDetector) RemoveHeader(nonce uint64, hash []byte) {
279279
"final checkpoint nonce", bfd.finalCheckpoint().nonce)
280280
}
281281

282+
// RemoveCommittedHeader removes a reverted committed header together with its checkpoint, proof
283+
// included, so a same-nonce sibling can be adopted; it never removes at or below the final checkpoint
284+
func (bfd *baseForkDetector) RemoveCommittedHeader(nonce uint64, hash []byte) {
285+
finalCheckpointNonce := bfd.finalCheckpoint().nonce
286+
if nonce <= finalCheckpointNonce {
287+
log.Warn("baseForkDetector.RemoveCommittedHeader: refusing removal at or below the final checkpoint",
288+
"nonce", nonce,
289+
"hash", hash,
290+
"final checkpoint nonce", finalCheckpointNonce)
291+
return
292+
}
293+
294+
bfd.removeCheckpointWithNonce(nonce)
295+
296+
preservedHdrsInfo := make([]*headerInfo, 0)
297+
298+
bfd.mutHeaders.Lock()
299+
300+
hdrsInfo := bfd.headers[nonce]
301+
for _, hdrInfo := range hdrsInfo {
302+
if hdrInfo.state != process.BHNotarized && bytes.Equal(hash, hdrInfo.hash) {
303+
continue
304+
}
305+
306+
preservedHdrsInfo = append(preservedHdrsInfo, hdrInfo)
307+
}
308+
309+
if len(preservedHdrsInfo) == 0 {
310+
delete(bfd.headers, nonce)
311+
} else {
312+
bfd.headers[nonce] = preservedHdrsInfo
313+
}
314+
315+
bfd.mutHeaders.Unlock()
316+
317+
bfd.forkDetector.computeFinalCheckpoint()
318+
319+
probableHighestNonce := bfd.computeProbableHighestNonce()
320+
bfd.setProbableHighestNonce(probableHighestNonce)
321+
322+
log.Debug("forkDetector.RemoveCommittedHeader",
323+
"nonce", nonce,
324+
"hash", hash,
325+
"probable highest nonce", probableHighestNonce,
326+
"final checkpoint nonce", bfd.finalCheckpoint().nonce)
327+
}
328+
282329
func (bfd *baseForkDetector) removeCheckpointWithNonce(nonce uint64) {
283330
bfd.mutFork.Lock()
284331
preservedCheckpoint := make([]*checkpointInfo, 0)

process/sync/baseForkDetector_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,3 +1768,61 @@ func TestBaseForkDetector_ReceivedProofForBlockHeaderShouldSetProof(t *testing.T
17681768
assert.Equal(t, []byte("hash0"), hdrInfos[1].Hash())
17691769
assert.Equal(t, true, hdrInfos[1].HasProof())
17701770
}
1771+
1772+
func TestBaseForkDetector_RemoveCommittedHeader(t *testing.T) {
1773+
t.Parallel()
1774+
1775+
sfd, _ := sync.NewShardForkDetector(
1776+
&mock.RoundHandlerMock{RoundIndex: 5},
1777+
&testscommon.TimeCacheStub{},
1778+
&mock.BlockTrackerMock{},
1779+
0,
1780+
0,
1781+
&enableEpochsHandlerMock.EnableEpochsHandlerStub{
1782+
IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool {
1783+
return flag == common.AndromedaFlag || flag == common.SupernovaFlag
1784+
},
1785+
},
1786+
&testscommon.EnableRoundsHandlerStub{},
1787+
&dataRetriever.ProofsPoolMock{
1788+
HasProofCalled: func(shardID uint32, headerHash []byte) bool {
1789+
return true
1790+
},
1791+
},
1792+
&chainParameters.ChainParametersHandlerStub{},
1793+
testscommon.GetDefaultProcessConfigsHandler(),
1794+
0,
1795+
)
1796+
1797+
hash1, hash2, competitorHash := []byte("hash1"), []byte("hash2"), []byte("competitorHash")
1798+
hdr1 := &block.Header{Nonce: 1, Round: 1, PubKeysBitmap: []byte("X")}
1799+
contendedHdr2 := &block.Header{Nonce: 2, Round: 4, PrevHash: hash1, PubKeysBitmap: []byte("X")}
1800+
1801+
_ = sfd.AddHeader(hdr1, hash1, process.BHProcessed, nil, nil)
1802+
_ = sfd.AddHeader(contendedHdr2, hash2, process.BHProcessed, nil, nil)
1803+
sfd.ReceivedProof(&block.HeaderProof{
1804+
HeaderHash: competitorHash,
1805+
HeaderNonce: 2,
1806+
HeaderRound: 3,
1807+
HeaderShardId: 0,
1808+
})
1809+
1810+
// RemoveHeader refuses proofed headers, the committed one included
1811+
sfd.RemoveHeader(2, hash2)
1812+
assert.Len(t, sfd.GetHeaders(2), 2)
1813+
assert.Equal(t, uint64(2), sfd.LastCheckpointNonce())
1814+
1815+
// the deliberate switch removal drops the committed header and its checkpoint despite the proof
1816+
sfd.RemoveCommittedHeader(2, hash2)
1817+
hdrInfos := sfd.GetHeaders(2)
1818+
assert.Len(t, hdrInfos, 1)
1819+
assert.Equal(t, competitorHash, hdrInfos[0].Hash())
1820+
assert.Equal(t, uint64(1), sfd.LastCheckpointNonce())
1821+
assert.Equal(t, uint64(1), sfd.FinalCheckpointNonce())
1822+
1823+
// removal at or below the final checkpoint is refused
1824+
sfd.RemoveCommittedHeader(1, hash1)
1825+
assert.Len(t, sfd.GetHeaders(1), 1)
1826+
assert.Equal(t, uint64(1), sfd.LastCheckpointNonce())
1827+
assert.Equal(t, uint64(1), sfd.FinalCheckpointNonce())
1828+
}

process/sync/baseSync.go

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,7 +1685,8 @@ func (boot *baseBootstrap) rollBack(revertUsingForkNonce bool) error {
16851685
var currBody data.BodyHandler
16861686

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

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

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

1738-
currBody, err = boot.rollBackOneBlock(
1739-
currHeaderHash,
1740-
currHeader,
1741-
prevHeaderHash,
1742-
prevHeader,
1743-
)
1744-
roleBackOneBlockExecuted = true
1741+
if currHeader.IsHeaderV3() {
1742+
currBody, err = boot.rollBackOneBlockV3(
1743+
currHeaderHash,
1744+
currHeader,
1745+
prevHeaderHash,
1746+
prevHeader,
1747+
)
1748+
} else {
1749+
currBody, err = boot.rollBackOneBlock(
1750+
currHeaderHash,
1751+
currHeader,
1752+
prevHeaderHash,
1753+
prevHeader,
1754+
)
1755+
roleBackOneBlockExecuted = true
1756+
}
17451757
if err != nil {
17461758
return err
17471759
}
@@ -1765,15 +1777,17 @@ func (boot *baseBootstrap) rollBack(revertUsingForkNonce bool) error {
17651777
return err
17661778
}
17671779

1768-
err = boot.scheduledTxsExecutionHandler.RollBackToBlock(prevHeaderHash)
1769-
if err != nil {
1770-
scheduledInfo := &process.ScheduledInfo{
1771-
RootHash: prevHeader.GetRootHash(),
1772-
IntermediateTxs: make(map[block.Type][]data.TransactionHandler),
1773-
GasAndFees: process.GetZeroGasAndFees(),
1774-
MiniBlocks: make(block.MiniBlockSlice, 0),
1780+
if !currHeader.IsHeaderV3() {
1781+
err = boot.scheduledTxsExecutionHandler.RollBackToBlock(prevHeaderHash)
1782+
if err != nil {
1783+
scheduledInfo := &process.ScheduledInfo{
1784+
RootHash: prevHeader.GetRootHash(),
1785+
IntermediateTxs: make(map[block.Type][]data.TransactionHandler),
1786+
GasAndFees: process.GetZeroGasAndFees(),
1787+
MiniBlocks: make(block.MiniBlockSlice, 0),
1788+
}
1789+
boot.scheduledTxsExecutionHandler.SetScheduledInfo(scheduledInfo)
17751790
}
1776-
boot.scheduledTxsExecutionHandler.SetScheduledInfo(scheduledInfo)
17771791
}
17781792

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

18051819
func (boot *baseBootstrap) shouldAllowRollback(currHeader data.HeaderHandler, currHeaderHash []byte) bool {
1806-
if check.IfNil(currHeader) || currHeader.IsHeaderV3() {
1820+
if check.IfNil(currHeader) {
18071821
return false
18081822
}
1823+
if currHeader.IsHeaderV3() {
1824+
return boot.shouldAllowRollbackV3(currHeader)
1825+
}
18091826

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

1851+
// shouldAllowRollbackV3 allows replacing a committed block only while it is not final (R-SWITCH);
1852+
// the state is never reverted through tries, the adopted sibling re-executes asynchronously
1853+
func (boot *baseBootstrap) shouldAllowRollbackV3(currHeader data.HeaderHandler) bool {
1854+
finalBlockNonce := boot.forkDetector.GetHighestFinalBlockNonce()
1855+
allowRollBack := currHeader.GetNonce() > finalBlockNonce
1856+
1857+
log.Debug("baseBootstrap.shouldAllowRollbackV3",
1858+
"nonce", currHeader.GetNonce(),
1859+
"final block nonce", finalBlockNonce,
1860+
"allowRollBack", allowRollBack,
1861+
)
1862+
1863+
return allowRollBack
1864+
}
1865+
18341866
func (boot *baseBootstrap) canRollbackBlock(currHeader data.HeaderHandler) bool {
18351867
firstCommittedNonce := boot.blockProcessor.NonceOfFirstCommittedBlock()
18361868

@@ -1889,6 +1921,51 @@ func (boot *baseBootstrap) rollBackOneBlock(
18891921
return currBlockBody, nil
18901922
}
18911923

1924+
// rollBackOneBlockV3 reverts a committed, not yet final header so a same-nonce sibling can be
1925+
// adopted; the trie state is not reverted, the sibling's execution results are produced async
1926+
func (boot *baseBootstrap) rollBackOneBlockV3(
1927+
currHeaderHash []byte,
1928+
currHeader data.HeaderHandler,
1929+
prevHeaderHash []byte,
1930+
prevHeader data.HeaderHandler,
1931+
) (data.BodyHandler, error) {
1932+
err := boot.chainHandler.SetCurrentBlockHeaderAndHash(prevHeaderHash, prevHeader)
1933+
if err != nil {
1934+
return nil, err
1935+
}
1936+
1937+
defer func() {
1938+
if err != nil {
1939+
errNotCritical := boot.chainHandler.SetCurrentBlockHeaderAndHash(currHeaderHash, currHeader)
1940+
if errNotCritical != nil {
1941+
log.Warn("rollBackOneBlockV3: cannot restore current block info", "error", errNotCritical)
1942+
}
1943+
}
1944+
}()
1945+
1946+
err = boot.executionManager.RemoveAtNonceAndHigher(currHeader.GetNonce())
1947+
if err != nil {
1948+
return nil, err
1949+
}
1950+
1951+
currBlockBody, errNotCritical := boot.blockBootstrapper.getBlockBody(currHeader)
1952+
if errNotCritical != nil {
1953+
log.Debug("rollBackOneBlockV3 getBlockBody error", "error", errNotCritical)
1954+
}
1955+
1956+
err = boot.blockProcessor.RestoreBlockIntoPools(currHeader, currBlockBody)
1957+
if err != nil {
1958+
return nil, err
1959+
}
1960+
1961+
hash := boot.removeHeaderFromPools(currHeader)
1962+
boot.forkDetector.RemoveCommittedHeader(currHeader.GetNonce(), hash)
1963+
nonceToByteSlice := boot.uint64Converter.ToByteSlice(currHeader.GetNonce())
1964+
_ = boot.headerNonceHashStore.Remove(nonceToByteSlice)
1965+
1966+
return currBlockBody, nil
1967+
}
1968+
18921969
func (boot *baseBootstrap) getRootHashFromBlock(hdr data.HeaderHandler, hdrHash []byte) []byte {
18931970
hdrRootHash := hdr.GetRootHash()
18941971
scheduledHdrRootHash, err := boot.scheduledTxsExecutionHandler.GetScheduledRootHashForHeader(hdrHash)

0 commit comments

Comments
 (0)