Add finality check to ResolveVirtual (#2041)

* Add finality check to ResolveVirtual

* Warn on finality conflict only if needed
This commit is contained in:
Ori Newman 2022-05-06 00:29:48 +03:00 committed by GitHub
parent 7390651072
commit c839337425
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 146 additions and 0 deletions

View File

@ -485,3 +485,136 @@ func TestBoundedMergeDepth(t *testing.T) {
test(consensusConfig.MergeDepth, consensusConfig.GenesisHash, true, true) 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)
}
}
for i := 0; ; i++ {
_, isCompletelyResolved, err := tc.ResolveVirtual()
if err != nil {
panic(err)
}
if isCompletelyResolved {
t.Log("Resolved virtual")
break
}
}
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")
}
})
}

View File

@ -37,6 +37,19 @@ func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*ex
isCompletelyResolved := true isCompletelyResolved := true
for _, tip := range tips { for _, tip := range tips {
log.Debugf("Resolving tip %s", tip) log.Debugf("Resolving tip %s", tip)
isViolatingFinality, shouldNotify, err := csm.isViolatingFinality(readStagingArea, tip)
if err != nil {
return nil, false, err
}
if isViolatingFinality {
if shouldNotify {
//TODO: Send finality conflict notification
log.Warnf("Skipping %s tip resolution because it violates finality", tip)
}
continue
}
resolveStagingArea := model.NewStagingArea() resolveStagingArea := model.NewStagingArea()
unverifiedBlocks, err := csm.getUnverifiedChainBlocks(resolveStagingArea, tip) unverifiedBlocks, err := csm.getUnverifiedChainBlocks(resolveStagingArea, tip)
if err != nil { if err != nil {