mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-07 14:46:44 +00:00
[RES-65] Add a test for BoundedMergeDepth - new (#1131)
* Test bounded merge depth * Fix a bug in GetBlockInfo, where trying to use reachability on an invalid block * Add a test to reproduce and test the GetBlockInfo bug
This commit is contained in:
parent
0fa13357c3
commit
f9c2137344
@ -124,6 +124,11 @@ func (s *consensus) GetBlockInfo(blockHash *externalapi.DomainHash) (*externalap
|
|||||||
}
|
}
|
||||||
blockInfo.BlockStatus = blockStatus
|
blockInfo.BlockStatus = blockStatus
|
||||||
|
|
||||||
|
// If the status is invalid, then we don't have the necessary reachability data to check if it's in PruningPoint.Future.
|
||||||
|
if blockStatus == externalapi.StatusInvalid {
|
||||||
|
return blockInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
isBlockInHeaderPruningPointFuture, err := s.syncManager.IsBlockInHeaderPruningPointFuture(blockHash)
|
isBlockInHeaderPruningPointFuture, err := s.syncManager.IsBlockInHeaderPruningPointFuture(blockHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
75
domain/consensus/consensus_test.go
Normal file
75
domain/consensus/consensus_test.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package consensus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||||
|
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConsensus_GetBlockInfo(t *testing.T) {
|
||||||
|
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||||
|
|
||||||
|
factory := NewFactory()
|
||||||
|
consensus, teardown, err := factory.NewTestConsensus(params, "TestConsensus_GetBlockInfo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error setting up consensus: %+v", err)
|
||||||
|
}
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
invalidBlock, err := consensus.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
invalidBlock.Header.TimeInMilliseconds = 0
|
||||||
|
err = consensus.ValidateAndInsertBlock(invalidBlock)
|
||||||
|
if !errors.Is(err, ruleerrors.ErrTimeTooOld) {
|
||||||
|
t.Fatalf("Expected block to be invalid with err: %v, instead found: %v", ruleerrors.ErrTimeTooOld, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := consensus.GetBlockInfo(consensusserialization.BlockHash(invalidBlock))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get block info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.Exists {
|
||||||
|
t.Fatal("The block is missing")
|
||||||
|
}
|
||||||
|
if info.BlockStatus != externalapi.StatusInvalid {
|
||||||
|
t.Fatalf("Expected block status: %s, instead got: %s", externalapi.StatusInvalid, info.BlockStatus)
|
||||||
|
}
|
||||||
|
if info.IsBlockInHeaderPruningPointFuture != false {
|
||||||
|
t.Fatalf("Expected IsBlockInHeaderPruningPointFuture=false, instead found: %t", info.IsBlockInHeaderPruningPointFuture)
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyCoinbase := externalapi.DomainCoinbaseData{}
|
||||||
|
validBlock, err := consensus.BuildBlock(&emptyCoinbase, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("consensus.BuildBlock with an empty coinbase shouldn't fail: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = consensus.ValidateAndInsertBlock(validBlock)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("consensus.ValidateAndInsertBlock with a block straight from consensus.BuildBlock should not fail: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err = consensus.GetBlockInfo(consensusserialization.BlockHash(validBlock))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get block info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.Exists {
|
||||||
|
t.Fatal("The block is missing")
|
||||||
|
}
|
||||||
|
if info.BlockStatus != externalapi.StatusValid {
|
||||||
|
t.Fatalf("Expected block status: %s, instead got: %s", externalapi.StatusValid, info.BlockStatus)
|
||||||
|
}
|
||||||
|
if info.IsBlockInHeaderPruningPointFuture != true {
|
||||||
|
t.Fatalf("Expected IsBlockInHeaderPruningPointFuture=true, instead found: %t", info.IsBlockInHeaderPruningPointFuture)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
@ -1,11 +1,16 @@
|
|||||||
package consensus
|
package consensus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"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/consensusserialization"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -167,3 +172,249 @@ func TestFinality(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBoundedMergeDepth(t *testing.T) {
|
||||||
|
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||||
|
// Set finalityInterval to 50 blocks, so that test runs quickly
|
||||||
|
params.FinalityDuration = 50 * params.TargetTimePerBlock
|
||||||
|
finalityInterval := int(params.FinalityDepth())
|
||||||
|
|
||||||
|
if int(params.K) >= finalityInterval {
|
||||||
|
t.Fatal("K must be smaller than finality duration 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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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(consensusserialization.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
|
||||||
|
}
|
||||||
|
|
||||||
|
factory := NewFactory()
|
||||||
|
consensusBuild, teardownFunc1, err := factory.NewTestConsensus(params, "BoundedMergeTestBuild")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Error setting up consensus: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
consensusReal, teardownFunc2, err := factory.NewTestConsensus(params, "BoundedMergeTestReal")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Error setting up consensus: %+v", err)
|
||||||
|
}
|
||||||
|
defer teardownFunc2()
|
||||||
|
|
||||||
|
// Create a block on top on genesis
|
||||||
|
block1 := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{params.GenesisHash})
|
||||||
|
|
||||||
|
// Create a chain
|
||||||
|
selectedChain := make([]*externalapi.DomainBlock, 0, finalityInterval+1)
|
||||||
|
parent := consensusserialization.BlockHash(block1)
|
||||||
|
// Make sure this is always bigger than `blocksChain2` so it will stay the selected chain
|
||||||
|
for i := 0; i < finalityInterval+2; i++ {
|
||||||
|
block := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{parent})
|
||||||
|
selectedChain = append(selectedChain, block)
|
||||||
|
parent = consensusserialization.BlockHash(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create another chain
|
||||||
|
blocksChain2 := make([]*externalapi.DomainBlock, 0, finalityInterval+1)
|
||||||
|
parent = consensusserialization.BlockHash(block1)
|
||||||
|
for i := 0; i < finalityInterval+1; i++ {
|
||||||
|
block := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{parent})
|
||||||
|
blocksChain2 = append(blocksChain2, block)
|
||||||
|
parent = consensusserialization.BlockHash(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Teardown and assign nil to make sure we use the right DAG from here on.
|
||||||
|
teardownFunc1()
|
||||||
|
consensusBuild = nil
|
||||||
|
|
||||||
|
// 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{consensusserialization.BlockHash(blocksChain2[0]), consensusserialization.BlockHash(selectedChain[len(selectedChain)-1])})
|
||||||
|
if !isViolatingMergeDepth {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected mergeDepthViolatingBlockBottom to violate merge depth")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{consensusserialization.BlockHash(blocksChain2[len(blocksChain2)-1]), consensusserialization.BlockHash(selectedChain[len(selectedChain)-1])})
|
||||||
|
if !isViolatingMergeDepth {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected mergeDepthViolatingTop to violate merge depth")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{consensusserialization.BlockHash(blocksChain2[len(blocksChain2)-3]), consensusserialization.BlockHash(selectedChain[len(selectedChain)-3])})
|
||||||
|
kosherizingBlockHash := consensusserialization.BlockHash(kosherizingBlock)
|
||||||
|
if isViolatingMergeDepth {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected blueKosherizingBlock to not violate merge depth")
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualGhotDagData, err := consensusReal.GHOSTDAGDataStore().Get(consensusReal.DatabaseContext(), model.VirtualBlockHash)
|
||||||
|
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 == *kosherizingBlockHash {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected kosherizingBlock to be blue by the virtual")
|
||||||
|
}
|
||||||
|
|
||||||
|
pointAtBlueKosherizing, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{kosherizingBlockHash, consensusserialization.BlockHash(selectedChain[len(selectedChain)-1])})
|
||||||
|
if isViolatingMergeDepth {
|
||||||
|
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected selectedTip to not violate merge depth")
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualSelectedParent, err := consensusReal.GetVirtualSelectedParent()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *consensusserialization.BlockHash(virtualSelectedParent) != *consensusserialization.BlockHash(pointAtBlueKosherizing) {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", consensusserialization.BlockHash(pointAtBlueKosherizing), consensusserialization.BlockHash(virtualSelectedParent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now let's make the kosherizing block red and try to merge again
|
||||||
|
tip := consensusserialization.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(params.K)-1; i++ {
|
||||||
|
block := buildAndInsertBlock(consensusReal, []*externalapi.DomainHash{tip})
|
||||||
|
tip = consensusserialization.BlockHash(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualSelectedParent, err = consensusReal.GetVirtualSelectedParent()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *consensusserialization.BlockHash(virtualSelectedParent) != *tip {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", tip, consensusserialization.BlockHash(virtualSelectedParent))
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualGhotDagData, err = consensusReal.GHOSTDAGDataStore().Get(consensusReal.DatabaseContext(), model.VirtualBlockHash)
|
||||||
|
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 == *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 {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected selectedTipRedKosherize to violate merge depth")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{consensusserialization.BlockHash(pointAtBlueKosherizing), tip})
|
||||||
|
if isViolatingMergeDepth {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected transitiveBlueKosherizing to not violate merge depth")
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualSelectedParent, err = consensusReal.GetVirtualSelectedParent()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *consensusserialization.BlockHash(virtualSelectedParent) != *consensusserialization.BlockHash(transitiveBlueKosherizing) {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", consensusserialization.BlockHash(transitiveBlueKosherizing), consensusserialization.BlockHash(virtualSelectedParent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lets validate the status of all the interesting blocks
|
||||||
|
if getStatus(consensusReal, pointAtBlueKosherizing) != externalapi.StatusValid {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: pointAtBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusValid, 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.StatusValid {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: transitiveBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusValid, 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.StatusValid {
|
||||||
|
t.Fatalf("selectedChain[%d] expected status '%s' but got '%s'", i, externalapi.StatusValid, getStatus(consensusReal, b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user