From d578e092cd536a32c8372a28dcc6276cbba9ce14 Mon Sep 17 00:00:00 2001 From: wgr523 Date: Thu, 8 Jun 2023 01:16:13 +0800 Subject: [PATCH] stop reorg at committed blocks (#276) This PR makes committed blocks non-reorg-able inside `Blockchain` struct. This ensures V2 consensus safety property in the aspect of blockchain head: committed blocks' state will not be reorg and users will always see committed blocks (or their child blocks) as current head of the blockchain. * stop reorg at committed blocks * fix tests * fix tests --- .../tests/engine_v2_tests/commit_test.go | 54 +++++++++++++++++++ core/blockchain.go | 25 +++++++++ 2 files changed, 79 insertions(+) create mode 100644 consensus/tests/engine_v2_tests/commit_test.go diff --git a/consensus/tests/engine_v2_tests/commit_test.go b/consensus/tests/engine_v2_tests/commit_test.go new file mode 100644 index 0000000000..e9f22a1cee --- /dev/null +++ b/consensus/tests/engine_v2_tests/commit_test.go @@ -0,0 +1,54 @@ +package engine_v2_tests + +import ( + "strings" + "testing" + + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/params" + "github.com/stretchr/testify/assert" +) + +func TestNormalReorgWhenNotInvolveCommittedBlock(t *testing.T) { + // create 3 forking blockss, so the committed block is not in the forking numbers + var numOfForks = new(int) + *numOfForks = 3 + blockchain, _, currentBlock, signer, signFn, forkedBlock := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks}) + engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 + + var extraField types.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField) + if err != nil { + t.Fatal("Fail to decode extra data", err) + } + engineV2.ProcessQCFaker(blockchain, extraField.QuorumCert) + assert.Equal(t, uint64(903), engineV2.GetLatestCommittedBlockInfo().Number.Uint64()) + blockCoinBase := "0x111000000000000000000000000000000123" + newBlock := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, forkedBlock, int(forkedBlock.NumberU64())+1, int64(extraField.Round)+10, blockCoinBase, signer, signFn, nil, nil, forkedBlock.Header().Root.Hex()) + err = blockchain.InsertBlock(newBlock) + assert.Nil(t, err) +} + +func TestShouldNotReorgCommittedBlock(t *testing.T) { + // create 4 forking blocks, so the committed block is in the forking numbers + var numOfForks = new(int) + *numOfForks = 4 + blockchain, _, currentBlock, signer, signFn, forkedBlock := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks}) + engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 + + var extraField types.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField) + if err != nil { + t.Fatal("Fail to decode extra data", err) + } + engineV2.ProcessQCFaker(blockchain, extraField.QuorumCert) + assert.Equal(t, uint64(903), engineV2.GetLatestCommittedBlockInfo().Number.Uint64()) + blockCoinBase := "0x111000000000000000000000000000000123" + newBlock := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, forkedBlock, int(forkedBlock.NumberU64())+1, int64(extraField.Round)+10, blockCoinBase, signer, signFn, nil, nil, forkedBlock.Header().Root.Hex()) + err = blockchain.InsertBlock(newBlock) + assert.NotNil(t, err) + assert.True(t, strings.Contains(err.Error(), "reorg")) + assert.True(t, strings.Contains(err.Error(), "attack")) +} diff --git a/core/blockchain.go b/core/blockchain.go index 0de36014ae..7998b493cc 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2200,6 +2200,31 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { return fmt.Errorf("Invalid new chain") } } + // Ensure XDPoS engine committed block will be not reverted + if xdpos, ok := bc.Engine().(*XDPoS.XDPoS); ok { + latestCommittedBlock := xdpos.EngineV2.GetLatestCommittedBlockInfo() + if latestCommittedBlock != nil { + currentBlock := bc.CurrentBlock() + currentBlock.Number().Cmp(latestCommittedBlock.Number) + cmp := commonBlock.Number().Cmp(latestCommittedBlock.Number) + if cmp < 0 { + for _, oldBlock := range oldChain { + if oldBlock.Number().Cmp(latestCommittedBlock.Number) == 0 { + if oldBlock.Hash() != latestCommittedBlock.Hash { + log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "committed hash", latestCommittedBlock.Hash) + } else { + log.Warn("Stop reorg, blockchain is under forking attack", "old committed num", oldBlock.Number(), "old committed hash", oldBlock.Hash()) + return fmt.Errorf("stop reorg, blockchain is under forking attack. old committed num %d, hash %x", oldBlock.Number(), oldBlock.Hash()) + } + } + } + } else if cmp == 0 { + if commonBlock.Hash() != latestCommittedBlock.Hash { + log.Error("Impossible reorg, please file an issue", "oldnum", commonBlock.Number(), "oldhash", commonBlock.Hash(), "committed hash", latestCommittedBlock.Hash) + } + } + } + } // Ensure the user sees large reorgs if len(oldChain) > 0 && len(newChain) > 0 { logFn := log.Warn