mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-05-22 14:56:44 +00:00
259 lines
7.3 KiB
Go
259 lines
7.3 KiB
Go
package reachabilitymanager_test
|
|
|
|
import (
|
|
"compress/gzip"
|
|
"fmt"
|
|
"github.com/kaspanet/kaspad/domain/consensus"
|
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
|
"github.com/kaspanet/kaspad/domain/dagconfig"
|
|
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
"github.com/pkg/errors"
|
|
"math"
|
|
"math/rand"
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
// Test configuration
|
|
const (
|
|
numBlocksExponent = 12
|
|
logLevel = "warn"
|
|
)
|
|
|
|
func initializeTest(t *testing.T, testName string) (tc testapi.TestConsensus, teardown func(keepDataDir bool)) {
|
|
t.Parallel()
|
|
logger.SetLogLevels(logLevel)
|
|
params := dagconfig.SimnetParams
|
|
params.SkipProofOfWork = true
|
|
tc, teardown, err := consensus.NewFactory().NewTestConsensus(¶ms, false, testName)
|
|
if err != nil {
|
|
t.Fatalf("Error setting up consensus: %+v", err)
|
|
}
|
|
return tc, teardown
|
|
}
|
|
|
|
func buildJsonDAG(t *testing.T, tc testapi.TestConsensus, attackJson bool) (tips []*externalapi.DomainHash) {
|
|
filePrefix := "noattack"
|
|
if attackJson {
|
|
filePrefix = "attack"
|
|
}
|
|
fileName := fmt.Sprintf(
|
|
"../../testdata/reachability/%s-dag-blocks--2^%d-delay-factor--1-k--18.json.gz",
|
|
filePrefix, numBlocksExponent)
|
|
|
|
f, err := os.Open(fileName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
gzipReader, err := gzip.NewReader(f)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer gzipReader.Close()
|
|
|
|
tips, err = tc.MineJSON(gzipReader, testapi.MineJSONBlockTypeUTXOInvalidHeader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = tc.ReachabilityManager().ValidateIntervals(tc.DAGParams().GenesisHash)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return tips
|
|
}
|
|
|
|
func addArbitraryBlocks(t *testing.T, tc testapi.TestConsensus) {
|
|
// After loading json, add arbitrary blocks all over the DAG to stretch
|
|
// reindex logic, and validate intervals post each addition
|
|
|
|
blocks, err := tc.ReachabilityManager().GetAllNodes(tc.DAGParams().GenesisHash)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
numChainsToAdd := len(blocks) / 2 // Multiply the size of the DAG with arbitrary blocks
|
|
maxBlocksInChain := 20
|
|
validationFreq := int(math.Max(1, float64(numChainsToAdd/100)))
|
|
|
|
randSource := rand.New(rand.NewSource(33233))
|
|
|
|
for i := 0; i < numChainsToAdd; i++ {
|
|
randomIndex := randSource.Intn(len(blocks))
|
|
randomParent := blocks[randomIndex]
|
|
newBlock, _, err := tc.AddUTXOInvalidHeader([]*externalapi.DomainHash{randomParent})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
blocks = append(blocks, newBlock)
|
|
// Add a random-length chain every few blocks
|
|
if randSource.Intn(8) == 0 {
|
|
numBlocksInChain := randSource.Intn(maxBlocksInChain)
|
|
chainBlock := newBlock
|
|
for j := 0; j < numBlocksInChain; j++ {
|
|
chainBlock, _, err = tc.AddUTXOInvalidHeader([]*externalapi.DomainHash{chainBlock})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
blocks = append(blocks, chainBlock)
|
|
}
|
|
}
|
|
// Normally, validate intervals for new chain only
|
|
validationRoot := newBlock
|
|
// However every 'validation frequency' blocks validate intervals for entire DAG
|
|
if i%validationFreq == 0 || i == numChainsToAdd-1 {
|
|
validationRoot = tc.DAGParams().GenesisHash
|
|
}
|
|
err = tc.ReachabilityManager().ValidateIntervals(validationRoot)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func addAlternatingReorgBlocks(t *testing.T, tc testapi.TestConsensus, tips []*externalapi.DomainHash) {
|
|
// Create alternating reorgs to test the cases where
|
|
// reindex root is out of current header selected tip chain
|
|
|
|
reindexRoot, err := tc.ReachabilityDataStore().ReachabilityReindexRoot(tc.DatabaseContext())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Try finding two tips; one which has reindex root on it's chain (chainTip), and one which
|
|
// does not (reorgTip). The latter is expected to exist in json attack files.
|
|
var chainTip, reorgTip *externalapi.DomainHash
|
|
for _, block := range tips {
|
|
isRootAncestorOfTip, err := tc.ReachabilityManager().IsReachabilityTreeAncestorOf(reindexRoot, block)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if isRootAncestorOfTip {
|
|
chainTip = block
|
|
} else {
|
|
reorgTip = block
|
|
}
|
|
}
|
|
|
|
if reorgTip == nil {
|
|
t.Fatal(errors.Errorf("DAG from jsom file is expected to contain a tip " +
|
|
"disagreeing with reindex root chain"))
|
|
}
|
|
|
|
if chainTip == nil {
|
|
t.Fatal(errors.Errorf("reindex root is not on any header tip chain, this is unexpected behavior"))
|
|
}
|
|
|
|
chainTipGHOSTDAGData, err := tc.GHOSTDAGDataStore().Get(tc.DatabaseContext(), chainTip)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
reorgTipGHOSTDAGData, err := tc.GHOSTDAGDataStore().Get(tc.DatabaseContext(), reorgTip)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Get both chains close to each other (we care about blue score and not
|
|
// blue work because we have SkipProofOfWork=true)
|
|
if chainTipGHOSTDAGData.BlueScore() > reorgTipGHOSTDAGData.BlueScore() {
|
|
blueScoreDiff := int(chainTipGHOSTDAGData.BlueScore() - reorgTipGHOSTDAGData.BlueScore())
|
|
for i := 0; i < blueScoreDiff+5; i++ {
|
|
reorgTip, _, err = tc.AddUTXOInvalidHeader([]*externalapi.DomainHash{reorgTip})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
} else {
|
|
blueScoreDiff := int(reorgTipGHOSTDAGData.BlueScore() - chainTipGHOSTDAGData.BlueScore())
|
|
for i := 0; i < blueScoreDiff+5; i++ {
|
|
chainTip, _, err = tc.AddUTXOInvalidHeader([]*externalapi.DomainHash{chainTip})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
err = tc.ReachabilityManager().ValidateIntervals(tc.DAGParams().GenesisHash)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Alternate between the chains 200 times
|
|
for i := 0; i < 200; i++ {
|
|
if i%2 == 0 {
|
|
for j := 0; j < 10; j++ {
|
|
chainTip, _, err = tc.AddUTXOInvalidHeader([]*externalapi.DomainHash{chainTip})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
} else {
|
|
for j := 0; j < 10; j++ {
|
|
reorgTip, _, err = tc.AddUTXOInvalidHeader([]*externalapi.DomainHash{reorgTip})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
err = tc.ReachabilityManager().ValidateIntervals(tc.DAGParams().GenesisHash)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Since current logic switches reindex root chain with reindex slack threshold - at last make the switch happen
|
|
for i := 0; i < int(tc.ReachabilityManager().ReachabilityReindexSlack())+10; i++ {
|
|
reorgTip, _, err = tc.AddUTXOInvalidHeader([]*externalapi.DomainHash{reorgTip})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
err = tc.ReachabilityManager().ValidateIntervals(tc.DAGParams().GenesisHash)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestNoAttack(t *testing.T) {
|
|
tc, teardown := initializeTest(t, "TestNoAttack")
|
|
defer teardown(false)
|
|
buildJsonDAG(t, tc, false)
|
|
}
|
|
|
|
func TestAttack(t *testing.T) {
|
|
tc, teardown := initializeTest(t, "TestAttack")
|
|
defer teardown(false)
|
|
buildJsonDAG(t, tc, true)
|
|
}
|
|
|
|
func TestNoAttackFuzzy(t *testing.T) {
|
|
tc, teardown := initializeTest(t, "TestNoAttackFuzzy")
|
|
defer teardown(false)
|
|
tc.ReachabilityManager().SetReachabilityReindexSlack(10)
|
|
buildJsonDAG(t, tc, false)
|
|
addArbitraryBlocks(t, tc)
|
|
}
|
|
|
|
func TestAttackFuzzy(t *testing.T) {
|
|
tc, teardown := initializeTest(t, "TestAttackFuzzy")
|
|
defer teardown(false)
|
|
tc.ReachabilityManager().SetReachabilityReindexSlack(10)
|
|
buildJsonDAG(t, tc, true)
|
|
addArbitraryBlocks(t, tc)
|
|
}
|
|
|
|
func TestAttackAlternateReorg(t *testing.T) {
|
|
tc, teardown := initializeTest(t, "TestAttackAlternateReorg")
|
|
defer teardown(false)
|
|
tc.ReachabilityManager().SetReachabilityReindexSlack(256)
|
|
tips := buildJsonDAG(t, tc, true)
|
|
addAlternatingReorgBlocks(t, tc, tips)
|
|
}
|