diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index b93ca8e87..9b5137de8 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -19,12 +19,28 @@ type consensus struct { transactionValidator model.TransactionValidator syncManager model.SyncManager pastMedianTimeManager model.PastMedianTimeManager + blockValidator model.BlockValidator + coinbaseManager model.CoinbaseManager + dagTopologyManager model.DAGTopologyManager + dagTraversalManager model.DAGTraversalManager + difficultyManager model.DifficultyManager + ghostdagManager model.GHOSTDAGManager + headerTipsManager model.HeaderTipsManager + mergeDepthManager model.MergeDepthManager + pruningManager model.PruningManager + reachabilityManager model.ReachabilityManager - blockStore model.BlockStore - blockHeaderStore model.BlockHeaderStore - pruningStore model.PruningStore - ghostdagDataStore model.GHOSTDAGDataStore - blockStatusStore model.BlockStatusStore + blockStore model.BlockStore + blockHeaderStore model.BlockHeaderStore + pruningStore model.PruningStore + ghostdagDataStore model.GHOSTDAGDataStore + blockRelationStore model.BlockRelationStore + blockStatusStore model.BlockStatusStore + consensusStateStore model.ConsensusStateStore + headerTipsStore model.HeaderTipsStore + multisetStore model.MultisetStore + reachabilityDataStore model.ReachabilityDataStore + utxoDiffStore model.UTXODiffStore } // BuildBlock builds a block over the current state, with the transactions diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index 830a51fcc..3c7927706 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -1,8 +1,12 @@ package consensus import ( + "io/ioutil" + "os" "sync" + "github.com/kaspanet/kaspad/infrastructure/db/database/ldb" + consensusdatabase "github.com/kaspanet/kaspad/domain/consensus/database" "github.com/kaspanet/kaspad/domain/consensus/datastructures/acceptancedatastore" "github.com/kaspanet/kaspad/domain/consensus/datastructures/blockheaderstore" @@ -42,7 +46,7 @@ import ( // Factory instantiates new Consensuses type Factory interface { NewConsensus(dagParams *dagconfig.Params, db infrastructuredatabase.Database) (externalapi.Consensus, error) - NewTestConsensus(dagParams *dagconfig.Params, db infrastructuredatabase.Database) (testapi.TestConsensus, error) + NewTestConsensus(dagParams *dagconfig.Params, testName string) (tc testapi.TestConsensus, teardown func(), err error) } type factory struct{} @@ -255,12 +259,27 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat transactionValidator: transactionValidator, syncManager: syncManager, pastMedianTimeManager: pastMedianTimeManager, + blockValidator: blockValidator, + coinbaseManager: coinbaseManager, + dagTopologyManager: dagTopologyManager, + dagTraversalManager: dagTraversalManager, + difficultyManager: difficultyManager, + ghostdagManager: ghostdagManager, + headerTipsManager: headerTipsManager, + mergeDepthManager: mergeDepthManager, + pruningManager: pruningManager, + reachabilityManager: reachabilityManager, - blockStore: blockStore, - blockHeaderStore: blockHeaderStore, - pruningStore: pruningStore, - ghostdagDataStore: ghostdagDataStore, - blockStatusStore: blockStatusStore, + blockStore: blockStore, + blockHeaderStore: blockHeaderStore, + pruningStore: pruningStore, + ghostdagDataStore: ghostdagDataStore, + blockStatusStore: blockStatusStore, + blockRelationStore: blockRelationStore, + consensusStateStore: consensusStateStore, + headerTipsStore: headerTipsStore, + multisetStore: multisetStore, + reachabilityDataStore: reachabilityDataStore, } genesisInfo, err := c.GetBlockInfo(genesisHash) @@ -278,18 +297,32 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat return c, nil } -func (f *factory) NewTestConsensus(dagParams *dagconfig.Params, db infrastructuredatabase.Database) ( - testapi.TestConsensus, error) { +func (f *factory) NewTestConsensus(dagParams *dagconfig.Params, testName string) ( + tc testapi.TestConsensus, teardown func(), err error) { + testDatabaseDir, err := ioutil.TempDir("", testName) + if err != nil { + return nil, nil, err + } + db, err := ldb.NewLevelDB(testDatabaseDir) + if err != nil { + return nil, nil, err + } consensusAsInterface, err := f.NewConsensus(dagParams, db) if err != nil { - return nil, err + return nil, nil, err } consensusAsImplementation := consensusAsInterface.(*consensus) - return &testConsensus{ + tc = &testConsensus{ consensus: consensusAsImplementation, testBlockBuilder: blockbuilder.NewTestBlockBuilder(consensusAsImplementation.blockBuilder), - }, nil + } + teardown = func() { + db.Close() + os.RemoveAll(testDatabaseDir) + } + + return tc, teardown, nil } diff --git a/domain/consensus/model/testapi/test_consensus.go b/domain/consensus/model/testapi/test_consensus.go index ce291126d..1a1e994bc 100644 --- a/domain/consensus/model/testapi/test_consensus.go +++ b/domain/consensus/model/testapi/test_consensus.go @@ -1,6 +1,9 @@ package testapi -import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" +import ( + "github.com/kaspanet/kaspad/domain/consensus/model" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" +) // TestConsensus wraps the Consensus interface with some methods that are needed by tests only type TestConsensus interface { @@ -8,4 +11,39 @@ type TestConsensus interface { BuildBlockWithParents(parentHashes []*externalapi.DomainHash, coinbaseData *externalapi.DomainCoinbaseData, transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlock, error) + + // AddBlock builds a block with given information, solves it, and adds to the DAG. + // Returns the hash of the added block + AddBlock(parentHashes []*externalapi.DomainHash, coinbaseData *externalapi.DomainCoinbaseData, + transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, error) + + AcceptanceDataStore() model.AcceptanceDataStore + BlockHeaderStore() model.BlockHeaderStore + BlockRelationStore() model.BlockRelationStore + BlockStatusStore() model.BlockStatusStore + BlockStore() model.BlockStore + ConsensusStateStore() model.ConsensusStateStore + GHOSTDAGDataStore() model.GHOSTDAGDataStore + HeaderTipsStore() model.HeaderTipsStore + MultisetStore() model.MultisetStore + PruningStore() model.PruningStore + ReachabilityDataStore() model.ReachabilityDataStore + UTXODiffStore() model.UTXODiffStore + + BlockBuilder() model.BlockBuilder + BlockProcessor() model.BlockProcessor + BlockValidator() model.BlockValidator + CoinbaseManager() model.CoinbaseManager + ConsensusStateManager() model.ConsensusStateManager + DAGTopologyManager() model.DAGTopologyManager + DAGTraversalManager() model.DAGTraversalManager + DifficultyManager() model.DifficultyManager + GHOSTDAGManager() model.GHOSTDAGManager + HeaderTipsManager() model.HeaderTipsManager + MergeDepthManager() model.MergeDepthManager + PastMedianTimeManager() model.PastMedianTimeManager + PruningManager() model.PruningManager + ReachabilityManager() model.ReachabilityManager + SyncManager() model.SyncManager + TransactionValidator() model.TransactionValidator } diff --git a/domain/consensus/test_consensus.go b/domain/consensus/test_consensus.go index 22c3eb80f..bf4c31077 100644 --- a/domain/consensus/test_consensus.go +++ b/domain/consensus/test_consensus.go @@ -1,8 +1,14 @@ package consensus import ( + "errors" + "math" + "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization" + "github.com/kaspanet/kaspad/domain/consensus/utils/hashes" + "github.com/kaspanet/kaspad/util" ) type testConsensus struct { @@ -19,3 +25,39 @@ func (tc *testConsensus) BuildBlockWithParents(parentHashes []*externalapi.Domai return tc.testBlockBuilder.BuildBlockWithParents(parentHashes, coinbaseData, transactions) } + +func (tc *testConsensus) AddBlock(parentHashes []*externalapi.DomainHash, coinbaseData *externalapi.DomainCoinbaseData, + transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, error) { + + // Require write lock because BuildBlockWithParents stages temporary data + tc.lock.Lock() + defer tc.lock.Unlock() + + block, err := tc.testBlockBuilder.BuildBlockWithParents(parentHashes, coinbaseData, transactions) + if err != nil { + return nil, err + } + + solveBlock(block) + + err = tc.ValidateAndInsertBlock(block) + if err != nil { + return nil, err + } + + return consensusserialization.BlockHash(block), nil +} + +func solveBlock(block *externalapi.DomainBlock) { + targetDifficulty := util.CompactToBig(block.Header.Bits) + + for i := uint64(0); i < math.MaxUint64; i++ { + block.Header.Nonce = i + hash := consensusserialization.BlockHash(block) + if hashes.ToBig(hash).Cmp(targetDifficulty) <= 0 { + return + } + } + + panic(errors.New("went over all the nonce space and couldn't find a single one that gives a valid block")) +} diff --git a/domain/consensus/test_consensus_getters.go b/domain/consensus/test_consensus_getters.go new file mode 100644 index 000000000..6a6574f98 --- /dev/null +++ b/domain/consensus/test_consensus_getters.go @@ -0,0 +1,115 @@ +package consensus + +import "github.com/kaspanet/kaspad/domain/consensus/model" + +func (tc *testConsensus) AcceptanceDataStore() model.AcceptanceDataStore { + return tc.AcceptanceDataStore() +} + +func (tc *testConsensus) BlockHeaderStore() model.BlockHeaderStore { + return tc.blockHeaderStore +} + +func (tc *testConsensus) BlockRelationStore() model.BlockRelationStore { + return tc.blockRelationStore +} + +func (tc *testConsensus) BlockStatusStore() model.BlockStatusStore { + return tc.blockStatusStore +} + +func (tc *testConsensus) BlockStore() model.BlockStore { + return tc.blockStore +} + +func (tc *testConsensus) ConsensusStateStore() model.ConsensusStateStore { + return tc.consensusStateStore +} + +func (tc *testConsensus) GHOSTDAGDataStore() model.GHOSTDAGDataStore { + return tc.ghostdagDataStore +} + +func (tc *testConsensus) HeaderTipsStore() model.HeaderTipsStore { + return tc.headerTipsStore +} + +func (tc *testConsensus) MultisetStore() model.MultisetStore { + return tc.multisetStore +} + +func (tc *testConsensus) PruningStore() model.PruningStore { + return tc.pruningStore +} + +func (tc *testConsensus) ReachabilityDataStore() model.ReachabilityDataStore { + return tc.reachabilityDataStore +} + +func (tc *testConsensus) UTXODiffStore() model.UTXODiffStore { + return tc.utxoDiffStore +} + +func (tc *testConsensus) BlockBuilder() model.BlockBuilder { + return tc.blockBuilder +} + +func (tc *testConsensus) BlockProcessor() model.BlockProcessor { + return tc.blockProcessor +} + +func (tc *testConsensus) BlockValidator() model.BlockValidator { + return tc.blockValidator +} + +func (tc *testConsensus) CoinbaseManager() model.CoinbaseManager { + return tc.coinbaseManager +} + +func (tc *testConsensus) ConsensusStateManager() model.ConsensusStateManager { + return tc.consensusStateManager +} + +func (tc *testConsensus) DAGTopologyManager() model.DAGTopologyManager { + return tc.dagTopologyManager +} + +func (tc *testConsensus) DAGTraversalManager() model.DAGTraversalManager { + return tc.dagTraversalManager +} + +func (tc *testConsensus) DifficultyManager() model.DifficultyManager { + return tc.difficultyManager +} + +func (tc *testConsensus) GHOSTDAGManager() model.GHOSTDAGManager { + return tc.ghostdagManager +} + +func (tc *testConsensus) HeaderTipsManager() model.HeaderTipsManager { + return tc.headerTipsManager +} + +func (tc *testConsensus) MergeDepthManager() model.MergeDepthManager { + return tc.mergeDepthManager +} + +func (tc *testConsensus) PastMedianTimeManager() model.PastMedianTimeManager { + return tc.pastMedianTimeManager +} + +func (tc *testConsensus) PruningManager() model.PruningManager { + return tc.pruningManager +} + +func (tc *testConsensus) ReachabilityManager() model.ReachabilityManager { + return tc.reachabilityManager +} + +func (tc *testConsensus) SyncManager() model.SyncManager { + return tc.syncManager +} + +func (tc *testConsensus) TransactionValidator() model.TransactionValidator { + return tc.transactionValidator +} diff --git a/domain/consensus/utils/testutils/for_all_nets.go b/domain/consensus/utils/testutils/for_all_nets.go new file mode 100644 index 000000000..0fd718a8d --- /dev/null +++ b/domain/consensus/utils/testutils/for_all_nets.go @@ -0,0 +1,27 @@ +package testutils + +import ( + "testing" + + "github.com/kaspanet/kaspad/domain/dagconfig" +) + +// ForAllNets runs the passed testFunc with all available networks +// if setDifficultyToMinumum = true - will modify the net params to have minimal difficulty, like in SimNet +func ForAllNets(t *testing.T, setDifficultyToMinimum bool, testFunc func(*testing.T, *dagconfig.Params)) { + allParams := []dagconfig.Params{ + dagconfig.MainnetParams, + dagconfig.TestnetParams, + dagconfig.SimnetParams, + dagconfig.DevnetParams, + } + + for _, params := range allParams { + if setDifficultyToMinimum { + params.DisableDifficultyAdjustment = dagconfig.SimnetParams.DisableDifficultyAdjustment + params.TargetTimePerBlock = dagconfig.SimnetParams.TargetTimePerBlock + } + + testFunc(t, ¶ms) + } +}