mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
616 lines
24 KiB
Go
616 lines
24 KiB
Go
package consensus_test
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/kaspanet/kaspad/domain/consensus"
|
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
|
"github.com/pkg/errors"
|
|
"math"
|
|
"math/rand"
|
|
"testing"
|
|
)
|
|
|
|
func TestFinality(t *testing.T) {
|
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
|
// Set finalityInterval to 20 blocks, so that test runs quickly
|
|
consensusConfig.FinalityDuration = 20 * consensusConfig.TargetTimePerBlock
|
|
|
|
factory := consensus.NewFactory()
|
|
consensus, teardown, err := factory.NewTestConsensus(consensusConfig, "TestFinality")
|
|
if err != nil {
|
|
t.Fatalf("Error setting up consensus: %+v", err)
|
|
}
|
|
defer teardown(false)
|
|
|
|
buildAndInsertBlock := func(parentHashes []*externalapi.DomainHash) (*externalapi.DomainBlock, error) {
|
|
block, _, err := consensus.BuildBlockWithParents(parentHashes, nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = consensus.ValidateAndInsertBlock(block, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return block, nil
|
|
}
|
|
|
|
// Build a chain of `finalityInterval - 1` blocks
|
|
finalityInterval := consensusConfig.FinalityDepth()
|
|
var mainChainTip *externalapi.DomainBlock
|
|
mainChainTipHash := consensusConfig.GenesisHash
|
|
|
|
for i := uint64(0); i < finalityInterval-1; i++ {
|
|
mainChainTip, err = buildAndInsertBlock([]*externalapi.DomainHash{mainChainTipHash})
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed to process Block #%d: %+v", i, err)
|
|
}
|
|
mainChainTipHash = consensushashing.BlockHash(mainChainTip)
|
|
|
|
blockInfo, err := consensus.GetBlockInfo(mainChainTipHash)
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Block #%d failed to get info: %+v", i, err)
|
|
}
|
|
if blockInfo.BlockStatus != externalapi.StatusUTXOValid {
|
|
t.Fatalf("Block #%d in main chain expected to have status '%s', but got '%s'",
|
|
i, externalapi.StatusUTXOValid, blockInfo.BlockStatus)
|
|
}
|
|
}
|
|
|
|
// Mine another chain of `finality-Interval - 2` blocks
|
|
var sideChainTip *externalapi.DomainBlock
|
|
sideChainTipHash := consensusConfig.GenesisHash
|
|
for i := uint64(0); i < finalityInterval-2; i++ {
|
|
sideChainTip, err = buildAndInsertBlock([]*externalapi.DomainHash{sideChainTipHash})
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed to process sidechain Block #%d: %+v", i, err)
|
|
}
|
|
sideChainTipHash = consensushashing.BlockHash(sideChainTip)
|
|
|
|
blockInfo, err := consensus.GetBlockInfo(sideChainTipHash)
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Block #%d failed to get info: %v", i, err)
|
|
} else if !blockInfo.Exists {
|
|
t.Fatalf("TestFinality: Failed getting block info, doesn't exists")
|
|
}
|
|
if blockInfo.BlockStatus != externalapi.StatusUTXOPendingVerification {
|
|
t.Fatalf("Block #%d in side chain expected to have status '%s', but got '%s'",
|
|
i, externalapi.StatusUTXOPendingVerification, blockInfo.BlockStatus)
|
|
}
|
|
}
|
|
|
|
stagingArea := model.NewStagingArea()
|
|
|
|
// Add two more blocks in the side-chain until it becomes the selected chain
|
|
for i := uint64(0); i < 2; i++ {
|
|
sideChainTip, err = buildAndInsertBlock([]*externalapi.DomainHash{sideChainTipHash})
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed to process sidechain Block #%d: %v", i, err)
|
|
}
|
|
sideChainTipHash = consensushashing.BlockHash(sideChainTip)
|
|
}
|
|
|
|
// Make sure that now the sideChainTip is valid and selectedTip
|
|
blockInfo, err := consensus.GetBlockInfo(sideChainTipHash)
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed to get block info: %v", err)
|
|
} else if !blockInfo.Exists {
|
|
t.Fatalf("TestFinality: Failed getting block info, doesn't exists")
|
|
}
|
|
if blockInfo.BlockStatus != externalapi.StatusUTXOValid {
|
|
t.Fatalf("TestFinality: Overtaking block in side-chain expected to have status '%s', but got '%s'",
|
|
externalapi.StatusUTXOValid, blockInfo.BlockStatus)
|
|
}
|
|
selectedTip, err := consensus.GetVirtualSelectedParent()
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed getting virtual selectedParent: %v", err)
|
|
}
|
|
if !selectedTip.Equal(sideChainTipHash) {
|
|
t.Fatalf("Overtaking block in side-chain is not selectedTip")
|
|
}
|
|
|
|
// Add two more blocks to main chain, to move finality point to first non-genesis block in mainChain
|
|
for i := uint64(0); i < 2; i++ {
|
|
mainChainTip, err = buildAndInsertBlock([]*externalapi.DomainHash{mainChainTipHash})
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed to process sidechain Block #%d: %v", i, err)
|
|
}
|
|
mainChainTipHash = consensushashing.BlockHash(mainChainTip)
|
|
}
|
|
|
|
virtualFinality, err := consensus.FinalityManager().VirtualFinalityPoint(stagingArea)
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed getting the virtual's finality point: %v", err)
|
|
}
|
|
|
|
if virtualFinality.Equal(consensusConfig.GenesisHash) {
|
|
t.Fatalf("virtual's finalityPoint is still genesis after adding finalityInterval + 1 blocks to the main chain")
|
|
}
|
|
|
|
// TODO: Make sure that a finality conflict notification is sent
|
|
// Add two more blocks to the side chain, so that it violates finality and gets status UTXOPendingVerification even
|
|
// though it is the block with the highest blue score.
|
|
for i := uint64(0); i < 2; i++ {
|
|
sideChainTip, err = buildAndInsertBlock([]*externalapi.DomainHash{sideChainTipHash})
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed to process sidechain Block #%d: %v", i, err)
|
|
}
|
|
sideChainTipHash = consensushashing.BlockHash(sideChainTip)
|
|
}
|
|
|
|
// Check that sideChainTip hash higher blue score than the selected parent
|
|
selectedTip, err = consensus.GetVirtualSelectedParent()
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed getting virtual selectedParent: %v", err)
|
|
}
|
|
selectedTipGhostDagData, err :=
|
|
consensus.GHOSTDAGDataStore().Get(consensus.DatabaseContext(), stagingArea, selectedTip, false)
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed getting the ghost dag data of the selected tip: %v", err)
|
|
}
|
|
|
|
sideChainTipGhostDagData, err :=
|
|
consensus.GHOSTDAGDataStore().Get(consensus.DatabaseContext(), stagingArea, sideChainTipHash, false)
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed getting the ghost dag data of the sidechain tip: %v", err)
|
|
}
|
|
|
|
if selectedTipGhostDagData.BlueWork().Cmp(sideChainTipGhostDagData.BlueWork()) == 1 {
|
|
t.Fatalf("sideChainTip is not the bluest tip when it is expected to be")
|
|
}
|
|
|
|
// Blocks violating finality should have a UTXOPendingVerification status
|
|
blockInfo, err = consensus.GetBlockInfo(sideChainTipHash)
|
|
if err != nil {
|
|
t.Fatalf("TestFinality: Failed to get block info: %v", err)
|
|
} else if !blockInfo.Exists {
|
|
t.Fatalf("TestFinality: Failed getting block info, doesn't exists")
|
|
}
|
|
if blockInfo.BlockStatus != externalapi.StatusUTXOPendingVerification {
|
|
t.Fatalf("TestFinality: Finality violating block expected to have status '%s', but got '%s'",
|
|
externalapi.StatusUTXOPendingVerification, blockInfo.BlockStatus)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestBoundedMergeDepth(t *testing.T) {
|
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
|
rd := rand.New(rand.NewSource(0))
|
|
// Set finalityInterval to 50 blocks, so that test runs quickly
|
|
consensusConfig.K = 5
|
|
consensusConfig.MergeDepth = 7
|
|
consensusConfig.FinalityDuration = 20 * consensusConfig.TargetTimePerBlock
|
|
|
|
if uint64(consensusConfig.K) >= consensusConfig.FinalityDepth() {
|
|
t.Fatal("K must be smaller than finality duration for this test to run")
|
|
}
|
|
|
|
if uint64(consensusConfig.K) >= consensusConfig.MergeDepth {
|
|
t.Fatal("K must be smaller than merge depth for this test to run")
|
|
}
|
|
|
|
checkViolatingMergeDepth := func(consensus testapi.TestConsensus, parents []*externalapi.DomainHash) (*externalapi.DomainBlock, bool) {
|
|
block, _, err := consensus.BuildBlockWithParents(parents, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: BuildBlockWithParents failed: %+v", err)
|
|
return nil, false // fo some reason go doesn't recognize that t.Fatalf never returns
|
|
}
|
|
|
|
err = consensus.ValidateAndInsertBlock(block, true)
|
|
if err == nil {
|
|
return block, false
|
|
} else if errors.Is(err, ruleerrors.ErrViolatingBoundedMergeDepth) {
|
|
return block, true
|
|
} else {
|
|
t.Fatalf("TestBoundedMergeDepth: expected err: %v, found err: %v", ruleerrors.ErrViolatingBoundedMergeDepth, err)
|
|
return nil, false // fo some reason go doesn't recognize that t.Fatalf never returns
|
|
}
|
|
}
|
|
|
|
processBlock := func(consensus testapi.TestConsensus, block *externalapi.DomainBlock, name string) {
|
|
err := consensus.ValidateAndInsertBlock(block, true)
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: %s got unexpected error from ProcessBlock: %+v", name, err)
|
|
|
|
}
|
|
}
|
|
|
|
buildAndInsertBlock := func(consensus testapi.TestConsensus, parentHashes []*externalapi.DomainHash) *externalapi.DomainBlock {
|
|
block, _, err := consensus.BuildBlockWithParents(parentHashes, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: Failed building block: %+v", err)
|
|
}
|
|
err = consensus.ValidateAndInsertBlock(block, true)
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: Failed Inserting block to consensus: %v", err)
|
|
}
|
|
return block
|
|
}
|
|
|
|
getStatus := func(consensus testapi.TestConsensus, block *externalapi.DomainBlock) externalapi.BlockStatus {
|
|
blockInfo, err := consensus.GetBlockInfo(consensushashing.BlockHash(block))
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: Failed to get block info: %v", err)
|
|
} else if !blockInfo.Exists {
|
|
t.Fatalf("TestBoundedMergeDepth: Failed to get block info, block doesn't exists")
|
|
}
|
|
return blockInfo.BlockStatus
|
|
}
|
|
|
|
syncConsensuses := func(tcSyncer, tcSyncee testapi.TestConsensus) {
|
|
syncerVirtualSelectedParent, err := tcSyncer.GetVirtualSelectedParent()
|
|
if err != nil {
|
|
t.Fatalf("GetVirtualSelectedParent: %+v", err)
|
|
}
|
|
|
|
missingHeaderHashes, _, err := tcSyncer.GetHashesBetween(consensusConfig.GenesisHash, syncerVirtualSelectedParent, math.MaxUint64)
|
|
if err != nil {
|
|
t.Fatalf("GetHashesBetween: %+v", err)
|
|
}
|
|
|
|
for i, blocksHash := range missingHeaderHashes {
|
|
blockInfo, err := tcSyncee.GetBlockInfo(blocksHash)
|
|
if err != nil {
|
|
t.Fatalf("GetBlockInfo: %+v", err)
|
|
}
|
|
|
|
if blockInfo.Exists {
|
|
continue
|
|
}
|
|
|
|
block, _, err := tcSyncer.GetBlock(blocksHash)
|
|
if err != nil {
|
|
t.Fatalf("GetBlockHeader: %+v", err)
|
|
}
|
|
|
|
err = tcSyncee.ValidateAndInsertBlock(block, true)
|
|
if err != nil {
|
|
t.Fatalf("ValidateAndInsertBlock %d: %+v", i, err)
|
|
}
|
|
}
|
|
|
|
synceeVirtualSelectedParent, err := tcSyncee.GetVirtualSelectedParent()
|
|
if err != nil {
|
|
t.Fatalf("Tips: %+v", err)
|
|
}
|
|
|
|
if !syncerVirtualSelectedParent.Equal(synceeVirtualSelectedParent) {
|
|
t.Fatalf("Syncee's selected tip is %s while syncer's is %s", synceeVirtualSelectedParent, syncerVirtualSelectedParent)
|
|
}
|
|
}
|
|
|
|
factory := consensus.NewFactory()
|
|
consensusReal, teardownFunc2, err := factory.NewTestConsensus(consensusConfig, "TestBoundedMergeTestReal")
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: Error setting up consensus: %+v", err)
|
|
}
|
|
defer teardownFunc2(false)
|
|
|
|
test := func(depth uint64, root *externalapi.DomainHash, checkVirtual, isRealDepth bool) {
|
|
consensusBuild, teardownFunc1, err := factory.NewTestConsensus(consensusConfig, "TestBoundedMergeTestBuild")
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: Error setting up consensus: %+v", err)
|
|
}
|
|
defer teardownFunc1(false)
|
|
consensusBuild.BlockBuilder().SetNonceCounter(rd.Uint64())
|
|
|
|
syncConsensuses(consensusReal, consensusBuild)
|
|
// Create a block on top on genesis
|
|
block1 := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{root})
|
|
|
|
// Create a chain
|
|
selectedChain := make([]*externalapi.DomainBlock, 0, depth+1)
|
|
parent := consensushashing.BlockHash(block1)
|
|
// Make sure this is always bigger than `blocksChain2` so it will stay the selected chain
|
|
for i := uint64(0); i < depth+2; i++ {
|
|
block := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{parent})
|
|
selectedChain = append(selectedChain, block)
|
|
parent = consensushashing.BlockHash(block)
|
|
}
|
|
|
|
// Create another chain
|
|
blocksChain2 := make([]*externalapi.DomainBlock, 0, depth+1)
|
|
parent = consensushashing.BlockHash(block1)
|
|
for i := uint64(0); i < depth+1; i++ {
|
|
block := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{parent})
|
|
blocksChain2 = append(blocksChain2, block)
|
|
parent = consensushashing.BlockHash(block)
|
|
}
|
|
|
|
// Now test against the real DAG
|
|
// submit block1
|
|
processBlock(consensusReal, block1, "block1")
|
|
|
|
// submit chain1
|
|
for i, block := range selectedChain {
|
|
processBlock(consensusReal, block, fmt.Sprintf("selectedChain block No %d", i))
|
|
}
|
|
|
|
// submit chain2
|
|
for i, block := range blocksChain2 {
|
|
processBlock(consensusReal, block, fmt.Sprintf("blocksChain2 block No %d", i))
|
|
}
|
|
|
|
// submit a block pointing at tip(chain1) and on first block in chain2 directly
|
|
mergeDepthViolatingBlockBottom, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(blocksChain2[0]), consensushashing.BlockHash(selectedChain[len(selectedChain)-1])})
|
|
if isViolatingMergeDepth != isRealDepth {
|
|
t.Fatalf("TestBoundedMergeDepth: Expects isViolatingMergeDepth to be %t", isRealDepth)
|
|
}
|
|
|
|
// submit a block pointing at tip(chain1) and tip(chain2) should also obviously violate merge depth (this points at first block in chain2 indirectly)
|
|
mergeDepthViolatingTop, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(blocksChain2[len(blocksChain2)-1]), consensushashing.BlockHash(selectedChain[len(selectedChain)-1])})
|
|
if isViolatingMergeDepth != isRealDepth {
|
|
t.Fatalf("TestBoundedMergeDepth: Expects isViolatingMergeDepth to be %t", isRealDepth)
|
|
}
|
|
|
|
// the location of the parents in the slices need to be both `-X` so the `selectedChain` one will have higher blueScore (it's a chain longer by 1)
|
|
kosherizingBlock, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(blocksChain2[len(blocksChain2)-3]), consensushashing.BlockHash(selectedChain[len(selectedChain)-3])})
|
|
kosherizingBlockHash := consensushashing.BlockHash(kosherizingBlock)
|
|
if isViolatingMergeDepth {
|
|
t.Fatalf("TestBoundedMergeDepth: Expected blueKosherizingBlock to not violate merge depth")
|
|
}
|
|
|
|
if checkVirtual {
|
|
stagingArea := model.NewStagingArea()
|
|
virtualGhotDagData, err := consensusReal.GHOSTDAGDataStore().Get(consensusReal.DatabaseContext(),
|
|
stagingArea, model.VirtualBlockHash, false)
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the ghostdag data of the virtual: %v", err)
|
|
}
|
|
// Make sure it's actually blue
|
|
found := false
|
|
for _, blue := range virtualGhotDagData.MergeSetBlues() {
|
|
if blue.Equal(kosherizingBlockHash) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("TestBoundedMergeDepth: Expected kosherizingBlock to be blue by the virtual")
|
|
}
|
|
}
|
|
|
|
pointAtBlueKosherizing, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{kosherizingBlockHash, consensushashing.BlockHash(selectedChain[len(selectedChain)-1])})
|
|
if isViolatingMergeDepth {
|
|
t.Fatalf("TestBoundedMergeDepth: Expected selectedTip to not violate merge depth")
|
|
}
|
|
|
|
if checkVirtual {
|
|
virtualSelectedParent, err := consensusReal.GetVirtualSelectedParent()
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
|
}
|
|
|
|
if !virtualSelectedParent.Equal(consensushashing.BlockHash(pointAtBlueKosherizing)) {
|
|
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", consensushashing.BlockHash(pointAtBlueKosherizing), virtualSelectedParent)
|
|
}
|
|
}
|
|
|
|
// Now let's make the kosherizing block red and try to merge again
|
|
tip := consensushashing.BlockHash(selectedChain[len(selectedChain)-1])
|
|
// we use k-1 because `kosherizingBlock` points at tip-2, so 2+k-1 = k+1 anticone.
|
|
for i := 0; i < int(consensusConfig.K)-1; i++ {
|
|
block := buildAndInsertBlock(consensusReal, []*externalapi.DomainHash{tip})
|
|
tip = consensushashing.BlockHash(block)
|
|
}
|
|
|
|
if checkVirtual {
|
|
virtualSelectedParent, err := consensusReal.GetVirtualSelectedParent()
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
|
}
|
|
|
|
if !virtualSelectedParent.Equal(tip) {
|
|
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", tip, virtualSelectedParent)
|
|
}
|
|
|
|
virtualGhotDagData, err := consensusReal.GHOSTDAGDataStore().Get(
|
|
consensusReal.DatabaseContext(), model.NewStagingArea(), model.VirtualBlockHash, false)
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the ghostdag data of the virtual: %v", err)
|
|
}
|
|
// Make sure it's actually blue
|
|
found := false
|
|
for _, blue := range virtualGhotDagData.MergeSetBlues() {
|
|
if blue.Equal(kosherizingBlockHash) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
t.Fatalf("expected kosherizingBlock to be red by the virtual")
|
|
}
|
|
}
|
|
|
|
pointAtRedKosherizing, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{kosherizingBlockHash, tip})
|
|
if isViolatingMergeDepth != isRealDepth {
|
|
t.Fatalf("TestBoundedMergeDepth: Expects isViolatingMergeDepth to be %t", isRealDepth)
|
|
}
|
|
|
|
// Now `pointAtBlueKosherizing` itself is actually still blue, so we can still point at that even though we can't point at kosherizing directly anymore
|
|
transitiveBlueKosherizing, isViolatingMergeDepth :=
|
|
checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(pointAtBlueKosherizing), tip})
|
|
if isViolatingMergeDepth {
|
|
t.Fatalf("TestBoundedMergeDepth: Expected transitiveBlueKosherizing to not violate merge depth")
|
|
}
|
|
|
|
if checkVirtual {
|
|
virtualSelectedParent, err := consensusReal.GetVirtualSelectedParent()
|
|
if err != nil {
|
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
|
}
|
|
|
|
if !virtualSelectedParent.Equal(consensushashing.BlockHash(transitiveBlueKosherizing)) {
|
|
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", consensushashing.BlockHash(transitiveBlueKosherizing), virtualSelectedParent)
|
|
}
|
|
|
|
// Lets validate the status of all the interesting blocks
|
|
if getStatus(consensusReal, pointAtBlueKosherizing) != externalapi.StatusUTXOValid {
|
|
t.Fatalf("TestBoundedMergeDepth: pointAtBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusUTXOValid, getStatus(consensusReal, pointAtBlueKosherizing))
|
|
}
|
|
if getStatus(consensusReal, pointAtRedKosherizing) != externalapi.StatusInvalid {
|
|
t.Fatalf("TestBoundedMergeDepth: pointAtRedKosherizing expected status '%s' but got '%s'", externalapi.StatusInvalid, getStatus(consensusReal, pointAtRedKosherizing))
|
|
}
|
|
if getStatus(consensusReal, transitiveBlueKosherizing) != externalapi.StatusUTXOValid {
|
|
t.Fatalf("TestBoundedMergeDepth: transitiveBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusUTXOValid, getStatus(consensusReal, transitiveBlueKosherizing))
|
|
}
|
|
if getStatus(consensusReal, mergeDepthViolatingBlockBottom) != externalapi.StatusInvalid {
|
|
t.Fatalf("TestBoundedMergeDepth: mergeDepthViolatingBlockBottom expected status '%s' but got '%s'", externalapi.StatusInvalid, getStatus(consensusReal, mergeDepthViolatingBlockBottom))
|
|
}
|
|
if getStatus(consensusReal, mergeDepthViolatingTop) != externalapi.StatusInvalid {
|
|
t.Fatalf("TestBoundedMergeDepth: mergeDepthViolatingTop expected status '%s' but got '%s'", externalapi.StatusInvalid, getStatus(consensusReal, mergeDepthViolatingTop))
|
|
}
|
|
if getStatus(consensusReal, kosherizingBlock) != externalapi.StatusUTXOPendingVerification {
|
|
t.Fatalf("kosherizingBlock expected status '%s' but got '%s'", externalapi.StatusUTXOPendingVerification, getStatus(consensusReal, kosherizingBlock))
|
|
}
|
|
|
|
for i, b := range blocksChain2 {
|
|
if getStatus(consensusReal, b) != externalapi.StatusUTXOPendingVerification {
|
|
t.Fatalf("blocksChain2[%d] expected status '%s' but got '%s'", i, externalapi.StatusUTXOPendingVerification, getStatus(consensusReal, b))
|
|
}
|
|
}
|
|
for i, b := range selectedChain {
|
|
if getStatus(consensusReal, b) != externalapi.StatusUTXOValid {
|
|
t.Fatalf("selectedChain[%d] expected status '%s' but got '%s'", i, externalapi.StatusUTXOValid, getStatus(consensusReal, b))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
test(consensusConfig.MergeDepth, consensusConfig.GenesisHash, true, true)
|
|
})
|
|
}
|
|
|
|
func TestFinalityResolveVirtual(t *testing.T) {
|
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
|
// Set finalityInterval to 20 blocks, so that test runs quickly
|
|
consensusConfig.FinalityDuration = 20 * consensusConfig.TargetTimePerBlock
|
|
|
|
factory := consensus.NewFactory()
|
|
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestFinalityResolveVirtual")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer teardown(false)
|
|
|
|
tip := consensusConfig.GenesisHash
|
|
for {
|
|
tip, _, err = tc.AddBlock([]*externalapi.DomainHash{tip}, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
virtualFinalityPoint, err := tc.FinalityManager().VirtualFinalityPoint(model.NewStagingArea())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !virtualFinalityPoint.Equal(consensusConfig.GenesisHash) {
|
|
break
|
|
}
|
|
}
|
|
|
|
tcAttacker, teardownAttacker, err := factory.NewTestConsensus(consensusConfig, "TestFinalityResolveVirtual_attacker")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer teardownAttacker(false)
|
|
|
|
virtualSelectedParent, err := tc.GetVirtualSelectedParent()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
stagingArea := model.NewStagingArea()
|
|
virtualSelectedParentGHOSTDAGData, err := tc.GHOSTDAGDataStore().Get(tc.DatabaseContext(), stagingArea, virtualSelectedParent, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
t.Logf("Selected tip blue score %d", virtualSelectedParentGHOSTDAGData.BlueScore())
|
|
|
|
sideChain := make([]*externalapi.DomainBlock, 0)
|
|
|
|
for i := uint64(0); ; i++ {
|
|
tips, err := tcAttacker.Tips()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
block, _, err := tcAttacker.BuildBlockWithParents(tips, nil, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// We change the nonce of the first block so its hash won't be similar to any of the
|
|
// honest DAG blocks. As a result the rest of the side chain should have unique hashes
|
|
// as well.
|
|
if i == 0 {
|
|
mutableHeader := block.Header.ToMutable()
|
|
mutableHeader.SetNonce(uint64(rand.NewSource(84147).Int63()))
|
|
block.Header = mutableHeader.ToImmutable()
|
|
}
|
|
|
|
err = tcAttacker.ValidateAndInsertBlock(block, true)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
sideChain = append(sideChain, block)
|
|
|
|
blockHash := consensushashing.BlockHash(block)
|
|
ghostdagData, err := tcAttacker.GHOSTDAGDataStore().Get(tcAttacker.DatabaseContext(), stagingArea, blockHash, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if virtualSelectedParentGHOSTDAGData.BlueWork().Cmp(ghostdagData.BlueWork()) == -1 {
|
|
break
|
|
}
|
|
}
|
|
|
|
sideChainTipHash := consensushashing.BlockHash(sideChain[len(sideChain)-1])
|
|
sideChainTipGHOSTDAGData, err := tcAttacker.GHOSTDAGDataStore().Get(tcAttacker.DatabaseContext(), stagingArea, sideChainTipHash, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
t.Logf("Side chain tip (%s) blue score %d", sideChainTipHash, sideChainTipGHOSTDAGData.BlueScore())
|
|
|
|
for _, block := range sideChain {
|
|
err := tc.ValidateAndInsertBlock(block, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
err = tc.ResolveVirtual(nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
t.Log("Resolved virtual")
|
|
|
|
sideChainTipGHOSTDAGData, err = tc.GHOSTDAGDataStore().Get(tc.DatabaseContext(), stagingArea, sideChainTipHash, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
t.Logf("Side chain tip (%s) blue score %d", sideChainTipHash, sideChainTipGHOSTDAGData.BlueScore())
|
|
|
|
newVirtualSelectedParent, err := tc.GetVirtualSelectedParent()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if !newVirtualSelectedParent.Equal(virtualSelectedParent) {
|
|
t.Fatalf("A finality reorg has happened")
|
|
}
|
|
})
|
|
}
|