diff --git a/domain/consensus/database/txcontext.go b/domain/consensus/database/txcontext.go index dc81fcd03..b883045aa 100644 --- a/domain/consensus/database/txcontext.go +++ b/domain/consensus/database/txcontext.go @@ -21,3 +21,8 @@ func (dtc *DomainTxContext) StoreBlockRelation(blockHash *externalapi.DomainHash func NewDomainTxContext(dbTx *dbaccess.TxContext) *DomainTxContext { return &DomainTxContext{dbTx: dbTx} } + +// Commit commits this database transaction +func (dtc *DomainTxContext) Commit() error { + return dtc.dbTx.Commit() +} diff --git a/domain/consensus/datastructures/acceptancedatastore/acceptancedatastore.go b/domain/consensus/datastructures/acceptancedatastore/acceptancedatastore.go index 31f11279f..bfd3d92ae 100644 --- a/domain/consensus/datastructures/acceptancedatastore/acceptancedatastore.go +++ b/domain/consensus/datastructures/acceptancedatastore/acceptancedatastore.go @@ -15,7 +15,7 @@ func New() model.AcceptanceDataStore { } // Stage stages the given acceptanceData for the given blockHash -func (ads *acceptanceDataStore) Stage(blockHash *externalapi.DomainHash, acceptanceData *model.BlockAcceptanceData) { +func (ads *acceptanceDataStore) Stage(blockHash *externalapi.DomainHash, acceptanceData []*model.BlockAcceptanceData) { panic("implement me") } @@ -32,7 +32,7 @@ func (ads *acceptanceDataStore) Commit(dbTx model.DBTxProxy) error { } // Get gets the acceptanceData associated with the given blockHash -func (ads *acceptanceDataStore) Get(dbContext model.DBContextProxy, blockHash *externalapi.DomainHash) (*model.BlockAcceptanceData, error) { +func (ads *acceptanceDataStore) Get(dbContext model.DBContextProxy, blockHash *externalapi.DomainHash) ([]*model.BlockAcceptanceData, error) { return nil, nil } diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index 75da38cf0..221580091 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -16,6 +16,7 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/processes/blockprocessor" "github.com/kaspanet/kaspad/domain/consensus/processes/blockvalidator" + "github.com/kaspanet/kaspad/domain/consensus/processes/coinbasemanager" "github.com/kaspanet/kaspad/domain/consensus/processes/consensusstatemanager" "github.com/kaspanet/kaspad/domain/consensus/processes/dagtopologymanager" "github.com/kaspanet/kaspad/domain/consensus/processes/dagtraversalmanager" @@ -96,8 +97,10 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, databaseContext *dba domainDBContext, pastMedianTimeManager, ghostdagDataStore) - - genesisHash := externalapi.DomainHash([32]byte(*dagParams.GenesisHash)) + coinbaseManager := coinbasemanager.New( + ghostdagDataStore, + acceptanceDataStore) + genesisHash := externalapi.DomainHash(*dagParams.GenesisHash) blockValidator := blockvalidator.New( dagParams.PowMax, false, @@ -130,10 +133,17 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, databaseContext *dba difficultyManager, pastMedianTimeManager, ghostdagManager, + coinbaseManager, acceptanceDataStore, blockStore, blockStatusStore, - blockRelationStore) + blockRelationStore, + multisetStore, + ghostdagDataStore, + consensusStateStore, + pruningStore, + reachabilityDataStore, + utxoDiffStore) return &consensus{ consensusStateManager: consensusStateManager, diff --git a/domain/consensus/model/acceptancedata.go b/domain/consensus/model/acceptancedata.go index 2d994bc10..f7fcf4916 100644 --- a/domain/consensus/model/acceptancedata.go +++ b/domain/consensus/model/acceptancedata.go @@ -11,7 +11,7 @@ type BlockAcceptanceData struct { // TransactionAcceptanceData stores a transaction together with an indication // if it was accepted or not by some block type TransactionAcceptanceData struct { - Tx *externalapi.DomainTransaction - Fee uint64 - IsAccepted bool + Transaction *externalapi.DomainTransaction + Fee uint64 + IsAccepted bool } diff --git a/domain/consensus/model/blockstatus.go b/domain/consensus/model/blockstatus.go index 2a220b923..284e48b74 100644 --- a/domain/consensus/model/blockstatus.go +++ b/domain/consensus/model/blockstatus.go @@ -4,8 +4,8 @@ package model type BlockStatus byte const ( - // StatusDataStored indicates that the block's payload is stored on disk. - StatusDataStored BlockStatus = iota + // StatusInvalid indicates that the block is invalid. + StatusInvalid BlockStatus = iota // StatusValid indicates that the block has been fully validated. StatusValid diff --git a/domain/consensus/model/externalapi/coinbase.go b/domain/consensus/model/externalapi/coinbase.go index 9e9962829..a0ce7fead 100644 --- a/domain/consensus/model/externalapi/coinbase.go +++ b/domain/consensus/model/externalapi/coinbase.go @@ -3,6 +3,6 @@ package externalapi // DomainCoinbaseData contains data by which a coinbase transaction // is built type DomainCoinbaseData struct { - scriptPublicKey []byte - extraData []byte + ScriptPublicKey []byte + ExtraData []byte } diff --git a/domain/consensus/model/interface_datastructures_acceptancedatastore.go b/domain/consensus/model/interface_datastructures_acceptancedatastore.go index 7b796374c..c70bce1d8 100644 --- a/domain/consensus/model/interface_datastructures_acceptancedatastore.go +++ b/domain/consensus/model/interface_datastructures_acceptancedatastore.go @@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // AcceptanceDataStore represents a store of AcceptanceData type AcceptanceDataStore interface { - Stage(blockHash *externalapi.DomainHash, acceptanceData *BlockAcceptanceData) + Store + Stage(blockHash *externalapi.DomainHash, acceptanceData []*BlockAcceptanceData) IsStaged() bool - Discard() - Commit(dbTx DBTxProxy) error - Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*BlockAcceptanceData, error) + Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) ([]*BlockAcceptanceData, error) Delete(dbTx DBTxProxy, blockHash *externalapi.DomainHash) error } diff --git a/domain/consensus/model/interface_datastructures_block.go b/domain/consensus/model/interface_datastructures_block.go index da9e88568..17406be56 100644 --- a/domain/consensus/model/interface_datastructures_block.go +++ b/domain/consensus/model/interface_datastructures_block.go @@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // BlockStore represents a store of blocks type BlockStore interface { + Store Stage(blockHash *externalapi.DomainHash, block *externalapi.DomainBlock) IsStaged() bool - Discard() - Commit(dbTx DBTxProxy) error Block(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) HasBlock(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (bool, error) Blocks(dbContext DBContextProxy, blockHashes []*externalapi.DomainHash) ([]*externalapi.DomainBlock, error) diff --git a/domain/consensus/model/interface_datastructures_blockrelationstore.go b/domain/consensus/model/interface_datastructures_blockrelationstore.go index b4c5693a9..001e97e61 100644 --- a/domain/consensus/model/interface_datastructures_blockrelationstore.go +++ b/domain/consensus/model/interface_datastructures_blockrelationstore.go @@ -4,11 +4,10 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // BlockRelationStore represents a store of BlockRelations type BlockRelationStore interface { + Store StageBlockRelation(blockHash *externalapi.DomainHash, parentHashes []*externalapi.DomainHash) StageTips(tipHashess []*externalapi.DomainHash) IsAnythingStaged() bool - Discard() - Commit(dbTx DBTxProxy) error BlockRelation(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*BlockRelations, error) Tips(dbContext DBContextProxy) ([]*externalapi.DomainHash, error) } diff --git a/domain/consensus/model/interface_datastructures_blockstatusstore.go b/domain/consensus/model/interface_datastructures_blockstatusstore.go index 14198fe5b..f826faee0 100644 --- a/domain/consensus/model/interface_datastructures_blockstatusstore.go +++ b/domain/consensus/model/interface_datastructures_blockstatusstore.go @@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // BlockStatusStore represents a store of BlockStatuses type BlockStatusStore interface { + Store Stage(blockHash *externalapi.DomainHash, blockStatus BlockStatus) IsStaged() bool - Discard() - Commit(dbTx DBTxProxy) error Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (BlockStatus, error) Exists(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (bool, error) } diff --git a/domain/consensus/model/interface_datastructures_consensusstatestore.go b/domain/consensus/model/interface_datastructures_consensusstatestore.go index 8152565e8..fbcbe41aa 100644 --- a/domain/consensus/model/interface_datastructures_consensusstatestore.go +++ b/domain/consensus/model/interface_datastructures_consensusstatestore.go @@ -4,9 +4,8 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // ConsensusStateStore represents a store for the current consensus state type ConsensusStateStore interface { + Store Stage(consensusStateChanges *ConsensusStateChanges) IsStaged() bool - Discard() - Commit(dbTx DBTxProxy) error UTXOByOutpoint(dbContext DBContextProxy, outpoint *externalapi.DomainOutpoint) (*externalapi.UTXOEntry, error) } diff --git a/domain/consensus/model/interface_datastructures_ghostdagdatastore.go b/domain/consensus/model/interface_datastructures_ghostdagdatastore.go index 83dbd060f..98edfa9eb 100644 --- a/domain/consensus/model/interface_datastructures_ghostdagdatastore.go +++ b/domain/consensus/model/interface_datastructures_ghostdagdatastore.go @@ -4,9 +4,8 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // GHOSTDAGDataStore represents a store of BlockGHOSTDAGData type GHOSTDAGDataStore interface { + Store Stage(blockHash *externalapi.DomainHash, blockGHOSTDAGData *BlockGHOSTDAGData) IsStaged() bool - Discard() - Commit(dbTx DBTxProxy) error Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*BlockGHOSTDAGData, error) } diff --git a/domain/consensus/model/interface_datastructures_multisetstore.go b/domain/consensus/model/interface_datastructures_multisetstore.go index e64560112..5e00c2e4b 100644 --- a/domain/consensus/model/interface_datastructures_multisetstore.go +++ b/domain/consensus/model/interface_datastructures_multisetstore.go @@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // MultisetStore represents a store of Multisets type MultisetStore interface { + Store Stage(blockHash *externalapi.DomainHash, multiset Multiset) IsStaged() bool - Discard() - Commit(dbTx DBTxProxy) error Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (Multiset, error) Delete(dbTx DBTxProxy, blockHash *externalapi.DomainHash) error } diff --git a/domain/consensus/model/interface_datastructures_pruningstore.go b/domain/consensus/model/interface_datastructures_pruningstore.go index 133be8f5f..80ed2b5e2 100644 --- a/domain/consensus/model/interface_datastructures_pruningstore.go +++ b/domain/consensus/model/interface_datastructures_pruningstore.go @@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // PruningStore represents a store for the current pruning state type PruningStore interface { + Store Stage(pruningPointBlockHash *externalapi.DomainHash, pruningPointUTXOSet ReadOnlyUTXOSet) IsStaged() bool - Discard() - Commit(dbTx DBTxProxy) error PruningPoint(dbContext DBContextProxy) (*externalapi.DomainHash, error) PruningPointSerializedUTXOSet(dbContext DBContextProxy) ([]byte, error) } diff --git a/domain/consensus/model/interface_datastructures_reachabilitydatastore.go b/domain/consensus/model/interface_datastructures_reachabilitydatastore.go index 30d127af7..579ff85ea 100644 --- a/domain/consensus/model/interface_datastructures_reachabilitydatastore.go +++ b/domain/consensus/model/interface_datastructures_reachabilitydatastore.go @@ -4,11 +4,10 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // ReachabilityDataStore represents a store of ReachabilityData type ReachabilityDataStore interface { + Store StageReachabilityData(blockHash *externalapi.DomainHash, reachabilityData *ReachabilityData) StageReachabilityReindexRoot(reachabilityReindexRoot *externalapi.DomainHash) IsAnythingStaged() bool - Discard() - Commit(dbTx DBTxProxy) error ReachabilityData(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*ReachabilityData, error) ReachabilityReindexRoot(dbContext DBContextProxy) (*externalapi.DomainHash, error) } diff --git a/domain/consensus/model/interface_datastructures_utxodiffstore.go b/domain/consensus/model/interface_datastructures_utxodiffstore.go index 2c782b432..d60b7be9d 100644 --- a/domain/consensus/model/interface_datastructures_utxodiffstore.go +++ b/domain/consensus/model/interface_datastructures_utxodiffstore.go @@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // UTXODiffStore represents a store of UTXODiffs type UTXODiffStore interface { + Store Stage(blockHash *externalapi.DomainHash, utxoDiff *UTXODiff, utxoDiffChild *externalapi.DomainHash) IsStaged() bool - Discard() - Commit(dbTx DBTxProxy) error UTXODiff(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*UTXODiff, error) UTXODiffChild(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) Delete(dbTx DBTxProxy, blockHash *externalapi.DomainHash) error diff --git a/domain/consensus/model/interface_processes_coinbasemanager.go b/domain/consensus/model/interface_processes_coinbasemanager.go new file mode 100644 index 000000000..1f023d702 --- /dev/null +++ b/domain/consensus/model/interface_processes_coinbasemanager.go @@ -0,0 +1,10 @@ +package model + +import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + +// CoinbaseManager exposes methods for handling blocks' +// coinbase transactions +type CoinbaseManager interface { + ExpectedCoinbaseTransaction(blockHash *externalapi.DomainHash, + coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransaction, error) +} diff --git a/domain/consensus/model/interface_processes_consensusstatemanager.go b/domain/consensus/model/interface_processes_consensusstatemanager.go index f6fbc7ebc..eff5f4506 100644 --- a/domain/consensus/model/interface_processes_consensusstatemanager.go +++ b/domain/consensus/model/interface_processes_consensusstatemanager.go @@ -6,5 +6,5 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" type ConsensusStateManager interface { AddBlockToVirtual(blockHash *externalapi.DomainHash) error PopulateTransactionWithUTXOEntries(transaction *externalapi.DomainTransaction) error - VirtualData() (medianTime int64, blueScore uint64, err error) + VirtualData() (virtualData *VirtualData, err error) } diff --git a/domain/consensus/model/interface_processes_dagtraversalmanager.go b/domain/consensus/model/interface_processes_dagtraversalmanager.go index b483555cb..85a6df8cb 100644 --- a/domain/consensus/model/interface_processes_dagtraversalmanager.go +++ b/domain/consensus/model/interface_processes_dagtraversalmanager.go @@ -2,7 +2,7 @@ package model import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" -// DAGTraversalManager exposes methods for travering blocks +// DAGTraversalManager exposes methods for traversing blocks // in the DAG type DAGTraversalManager interface { HighestChainBlockBelowBlueScore(highHash *externalapi.DomainHash, blueScore uint64) (*externalapi.DomainHash, error) diff --git a/domain/consensus/model/store.go b/domain/consensus/model/store.go new file mode 100644 index 000000000..a6901cd99 --- /dev/null +++ b/domain/consensus/model/store.go @@ -0,0 +1,7 @@ +package model + +// Store is a common interface for data stores +type Store interface { + Discard() + Commit(dbTx DBTxProxy) error +} diff --git a/domain/consensus/model/virtual.go b/domain/consensus/model/virtual.go new file mode 100644 index 000000000..9adc8aad5 --- /dev/null +++ b/domain/consensus/model/virtual.go @@ -0,0 +1,19 @@ +package model + +import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + +// VirtualBlockHash is a marker hash for the virtual block +var VirtualBlockHash = &externalapi.DomainHash{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +} + +// VirtualData is data about the current virtual block +type VirtualData struct { + PastMedianTime int64 + BlueScore uint64 + ParentHashes []*externalapi.DomainHash + SelectedParent *externalapi.DomainHash +} diff --git a/domain/consensus/processes/blockprocessor/blockprocessor.go b/domain/consensus/processes/blockprocessor/blockprocessor.go index 3b39fce30..528f206d1 100644 --- a/domain/consensus/processes/blockprocessor/blockprocessor.go +++ b/domain/consensus/processes/blockprocessor/blockprocessor.go @@ -5,6 +5,7 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/dagconfig" + "github.com/kaspanet/kaspad/infrastructure/logger" ) // blockProcessor is responsible for processing incoming blocks @@ -21,10 +22,20 @@ type blockProcessor struct { difficultyManager model.DifficultyManager ghostdagManager model.GHOSTDAGManager pastMedianTimeManager model.PastMedianTimeManager + coinbaseManager model.CoinbaseManager + acceptanceDataStore model.AcceptanceDataStore blockStore model.BlockStore blockStatusStore model.BlockStatusStore blockRelationStore model.BlockRelationStore + multisetStore model.MultisetStore + ghostdagDataStore model.GHOSTDAGDataStore + consensusStateStore model.ConsensusStateStore + pruningStore model.PruningStore + reachabilityDataStore model.ReachabilityDataStore + utxoDiffStore model.UTXODiffStore + + stores []model.Store } // New instantiates a new BlockProcessor @@ -39,10 +50,17 @@ func New( difficultyManager model.DifficultyManager, pastMedianTimeManager model.PastMedianTimeManager, ghostdagManager model.GHOSTDAGManager, + coinbaseManager model.CoinbaseManager, acceptanceDataStore model.AcceptanceDataStore, blockStore model.BlockStore, blockStatusStore model.BlockStatusStore, - blockRelationStore model.BlockRelationStore) model.BlockProcessor { + blockRelationStore model.BlockRelationStore, + multisetStore model.MultisetStore, + ghostdagDataStore model.GHOSTDAGDataStore, + consensusStateStore model.ConsensusStateStore, + pruningStore model.PruningStore, + reachabilityDataStore model.ReachabilityDataStore, + utxoDiffStore model.UTXODiffStore) model.BlockProcessor { return &blockProcessor{ dagParams: dagParams, @@ -54,25 +72,52 @@ func New( difficultyManager: difficultyManager, pastMedianTimeManager: pastMedianTimeManager, ghostdagManager: ghostdagManager, + coinbaseManager: coinbaseManager, consensusStateManager: consensusStateManager, acceptanceDataStore: acceptanceDataStore, blockStore: blockStore, blockStatusStore: blockStatusStore, blockRelationStore: blockRelationStore, + multisetStore: multisetStore, + ghostdagDataStore: ghostdagDataStore, + consensusStateStore: consensusStateStore, + pruningStore: pruningStore, + reachabilityDataStore: reachabilityDataStore, + utxoDiffStore: utxoDiffStore, + + stores: []model.Store{ + consensusStateStore, + acceptanceDataStore, + blockStore, + blockStatusStore, + blockRelationStore, + multisetStore, + ghostdagDataStore, + consensusStateStore, + pruningStore, + reachabilityDataStore, + utxoDiffStore, + }, } } -// BuildBlock builds a block over the current state, with the transactions -// selected by the given transactionSelector +// BuildBlock builds a block over the current state, with the given +// coinbaseData and the given transactions func (bp *blockProcessor) BuildBlock(coinbaseData *externalapi.DomainCoinbaseData, transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlock, error) { - return nil, nil + onEnd := logger.LogAndMeasureExecutionTime(log, "BuildBlock") + defer onEnd() + + return bp.buildBlock(coinbaseData, transactions) } // ValidateAndInsertBlock validates the given block and, if valid, applies it // to the current state func (bp *blockProcessor) ValidateAndInsertBlock(block *externalapi.DomainBlock) error { - return nil + onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateAndInsertBlock") + defer onEnd() + + return bp.validateAndInsertBlock(block) } diff --git a/domain/consensus/processes/blockprocessor/buildblock.go b/domain/consensus/processes/blockprocessor/buildblock.go new file mode 100644 index 000000000..2ed355a70 --- /dev/null +++ b/domain/consensus/processes/blockprocessor/buildblock.go @@ -0,0 +1,142 @@ +package blockprocessor + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/hashserialization" + "github.com/kaspanet/kaspad/domain/consensus/utils/merkle" + "github.com/kaspanet/kaspad/domain/consensus/utils/transactionid" + "github.com/kaspanet/kaspad/util/mstime" + "sort" +) + +const blockVersion = 1 + +func (bp *blockProcessor) buildBlock(coinbaseData *externalapi.DomainCoinbaseData, + transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlock, error) { + + coinbase, err := bp.newBlockCoinbaseTransaction(coinbaseData) + if err != nil { + return nil, err + } + transactionsWithCoinbase := append([]*externalapi.DomainTransaction{coinbase}, transactions...) + + header, err := bp.buildHeader(transactionsWithCoinbase) + if err != nil { + return nil, err + } + headerHash := hashserialization.HeaderHash(header) + + return &externalapi.DomainBlock{ + Header: header, + Transactions: transactionsWithCoinbase, + Hash: headerHash, + }, nil +} + +func (bp *blockProcessor) newBlockCoinbaseTransaction( + coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransaction, error) { + + return bp.coinbaseManager.ExpectedCoinbaseTransaction(model.VirtualBlockHash, coinbaseData) +} + +func (bp *blockProcessor) buildHeader(transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlockHeader, error) { + virtualData, err := bp.consensusStateManager.VirtualData() + if err != nil { + return nil, err + } + parentHashes, err := bp.newBlockParentHashes(virtualData) + if err != nil { + return nil, err + } + timeInMilliseconds, err := bp.newBlockTime(virtualData) + if err != nil { + return nil, err + } + bits, err := bp.newBlockDifficulty(virtualData) + if err != nil { + return nil, err + } + hashMerkleRoot := bp.newBlockHashMerkleRoot(transactions) + acceptedIDMerkleRoot, err := bp.newBlockAcceptedIDMerkleRoot() + if err != nil { + return nil, err + } + utxoCommitment, err := bp.newBlockUTXOCommitment() + if err != nil { + return nil, err + } + + return &externalapi.DomainBlockHeader{ + Version: blockVersion, + ParentHashes: parentHashes, + HashMerkleRoot: *hashMerkleRoot, + AcceptedIDMerkleRoot: *acceptedIDMerkleRoot, + UTXOCommitment: *utxoCommitment, + TimeInMilliseconds: timeInMilliseconds, + Bits: bits, + }, nil +} + +func (bp *blockProcessor) newBlockParentHashes(virtualData *model.VirtualData) ([]*externalapi.DomainHash, error) { + return virtualData.ParentHashes, nil +} + +func (bp *blockProcessor) newBlockTime(virtualData *model.VirtualData) (int64, error) { + // The timestamp for the block must not be before the median timestamp + // of the last several blocks. Thus, choose the maximum between the + // current time and one second after the past median time. The current + // timestamp is truncated to a millisecond boundary before comparison since a + // block timestamp does not supported a precision greater than one + // millisecond. + newTimestamp := mstime.Now().UnixMilliseconds() + 1 + minTimestamp, err := bp.pastMedianTimeManager.PastMedianTime(virtualData.SelectedParent) + if err != nil { + return 0, err + } + if newTimestamp < minTimestamp { + newTimestamp = minTimestamp + } + return newTimestamp, nil +} + +func (bp *blockProcessor) newBlockDifficulty(virtualData *model.VirtualData) (uint32, error) { + return bp.difficultyManager.RequiredDifficulty(virtualData.SelectedParent) +} + +func (bp *blockProcessor) newBlockHashMerkleRoot(transactions []*externalapi.DomainTransaction) *externalapi.DomainHash { + return merkle.CalculateHashMerkleRoot(transactions) +} + +func (bp *blockProcessor) newBlockAcceptedIDMerkleRoot() (*externalapi.DomainHash, error) { + newBlockAcceptanceData, err := bp.acceptanceDataStore.Get(bp.databaseContext, model.VirtualBlockHash) + if err != nil { + return nil, err + } + + var acceptedTransactions []*externalapi.DomainTransaction + for _, blockAcceptanceData := range newBlockAcceptanceData { + for _, transactionAcceptance := range blockAcceptanceData.TransactionAcceptanceData { + if !transactionAcceptance.IsAccepted { + continue + } + acceptedTransactions = append(acceptedTransactions, transactionAcceptance.Transaction) + } + } + sort.Slice(acceptedTransactions, func(i, j int) bool { + acceptedTransactionIID := hashserialization.TransactionID(acceptedTransactions[i]) + acceptedTransactionJID := hashserialization.TransactionID(acceptedTransactions[j]) + return transactionid.Less(acceptedTransactionIID, acceptedTransactionJID) + }) + + return merkle.CalculateIDMerkleRoot(acceptedTransactions), nil +} + +func (bp *blockProcessor) newBlockUTXOCommitment() (*externalapi.DomainHash, error) { + newBlockMultiset, err := bp.multisetStore.Get(bp.databaseContext, model.VirtualBlockHash) + if err != nil { + return nil, err + } + newBlockUTXOCommitment := newBlockMultiset.Hash() + return newBlockUTXOCommitment, nil +} diff --git a/domain/consensus/processes/blockprocessor/log.go b/domain/consensus/processes/blockprocessor/log.go new file mode 100644 index 000000000..ea06ba304 --- /dev/null +++ b/domain/consensus/processes/blockprocessor/log.go @@ -0,0 +1,7 @@ +package blockprocessor + +import ( + "github.com/kaspanet/kaspad/infrastructure/logger" +) + +var log, _ = logger.Get(logger.SubsystemTags.BDAG) diff --git a/domain/consensus/processes/blockprocessor/validateandinsertblock.go b/domain/consensus/processes/blockprocessor/validateandinsertblock.go new file mode 100644 index 000000000..94d2ed80a --- /dev/null +++ b/domain/consensus/processes/blockprocessor/validateandinsertblock.go @@ -0,0 +1,108 @@ +package blockprocessor + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" + "github.com/pkg/errors" +) + +func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) error { + err := bp.validateBlock(block) + if err != nil { + bp.discardAllChanges() + return err + } + + // Block validations passed, save whatever DAG data was + // collected so far + err = bp.commitAllChanges() + if err != nil { + return err + } + + // Attempt to add the block to the virtual + err = bp.consensusStateManager.AddBlockToVirtual(block.Hash) + if err != nil { + return err + } + + return bp.commitAllChanges() +} + +func (bp *blockProcessor) validateBlock(block *externalapi.DomainBlock) error { + // If either in-isolation or proof-of-work validations fail, simply + // return an error without writing anything in the database. + // This is to prevent spamming attacks. + err := bp.validateBlockInIsolationAndProofOfWork(block) + if err != nil { + return err + } + + // If in-context validations fail, discard all changes and store the + // block with StatusInvalid. + err = bp.validateInContext(block) + if err != nil { + if errors.As(err, &ruleerrors.RuleError{}) { + bp.discardAllChanges() + bp.blockStatusStore.Stage(block.Hash, model.StatusInvalid) + commitErr := bp.commitAllChanges() + if commitErr != nil { + return commitErr + } + } + return err + } + return nil +} + +func (bp *blockProcessor) validateBlockInIsolationAndProofOfWork(block *externalapi.DomainBlock) error { + err := bp.blockValidator.ValidateHeaderInIsolation(block.Hash) + if err != nil { + return err + } + err = bp.blockValidator.ValidateBodyInIsolation(block.Hash) + if err != nil { + return err + } + err = bp.blockValidator.ValidateProofOfWorkAndDifficulty(block.Hash) + if err != nil { + return err + } + return nil +} + +func (bp *blockProcessor) validateInContext(block *externalapi.DomainBlock) error { + bp.blockStore.Stage(block.Hash, block) + err := bp.blockValidator.ValidateHeaderInContext(block.Hash) + if err != nil { + return err + } + err = bp.blockValidator.ValidateBodyInContext(block.Hash) + if err != nil { + return err + } + return nil +} + +func (bp *blockProcessor) discardAllChanges() { + for _, store := range bp.stores { + store.Discard() + } +} + +func (bp *blockProcessor) commitAllChanges() error { + dbTx, err := bp.databaseContext.NewTx() + if err != nil { + return err + } + + for _, store := range bp.stores { + err = store.Commit(dbTx) + if err != nil { + return err + } + } + + return dbTx.Commit() +} diff --git a/domain/consensus/processes/blockvalidator/block_body_in_isolation.go b/domain/consensus/processes/blockvalidator/block_body_in_isolation.go index 61c01d504..ecf6c9f5f 100644 --- a/domain/consensus/processes/blockvalidator/block_body_in_isolation.go +++ b/domain/consensus/processes/blockvalidator/block_body_in_isolation.go @@ -134,7 +134,7 @@ func (v *blockValidator) checkTransactionsInIsolation(block *externalapi.DomainB } func (v *blockValidator) checkBlockHashMerkleRoot(block *externalapi.DomainBlock) error { - calculatedHashMerkleRoot := merkle.CalcHashMerkleRoot(block.Transactions) + calculatedHashMerkleRoot := merkle.CalculateHashMerkleRoot(block.Transactions) if block.Header.HashMerkleRoot != *calculatedHashMerkleRoot { return errors.Wrapf(ruleerrors.ErrBadMerkleRoot, "block hash merkle root is invalid - block "+ "header indicates %s, but calculated value is %s", diff --git a/domain/consensus/processes/coinbasemanager/coinbasemanager.go b/domain/consensus/processes/coinbasemanager/coinbasemanager.go new file mode 100644 index 000000000..4794f7efe --- /dev/null +++ b/domain/consensus/processes/coinbasemanager/coinbasemanager.go @@ -0,0 +1,28 @@ +package coinbasemanager + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" +) + +type coinbaseManager struct { + ghostdagDataStore model.GHOSTDAGDataStore + acceptanceDataStore model.AcceptanceDataStore +} + +// New instantiates a new CoinbaseManager +func New( + ghostdagDataStore model.GHOSTDAGDataStore, + acceptanceDataStore model.AcceptanceDataStore) model.CoinbaseManager { + + return &coinbaseManager{ + ghostdagDataStore: ghostdagDataStore, + acceptanceDataStore: acceptanceDataStore, + } +} + +func (c coinbaseManager) ExpectedCoinbaseTransaction(blockHash *externalapi.DomainHash, + coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransaction, error) { + + panic("implement me") +} diff --git a/domain/consensus/processes/consensusstatemanager/consensusstatemanager.go b/domain/consensus/processes/consensusstatemanager/consensusstatemanager.go index 879900ab5..346e059a8 100644 --- a/domain/consensus/processes/consensusstatemanager/consensusstatemanager.go +++ b/domain/consensus/processes/consensusstatemanager/consensusstatemanager.go @@ -68,9 +68,9 @@ func (csm *consensusStateManager) AddBlockToVirtual(blockHash *externalapi.Domai return nil } -// VirtualData returns the medianTime and blueScore of the current virtual block -func (csm *consensusStateManager) VirtualData() (medianTime int64, blueScore uint64, err error) { - return 0, 0, nil +// VirtualData returns data on the current virtual block +func (csm *consensusStateManager) VirtualData() (virtualData *model.VirtualData, err error) { + panic("implement me") } // PopulateTransactionWithUTXOEntries populates the transaction UTXO entries with data from the virtual. diff --git a/domain/consensus/utils/merkle/merkle.go b/domain/consensus/utils/merkle/merkle.go index e2a5e8329..584a3a9bc 100644 --- a/domain/consensus/utils/merkle/merkle.go +++ b/domain/consensus/utils/merkle/merkle.go @@ -42,9 +42,9 @@ func hashMerkleBranches(left, right *externalapi.DomainHash) *externalapi.Domain return &hash } -// CalcHashMerkleRoot calculates the merkle root of a tree consisted of the given transaction hashes. +// CalculateHashMerkleRoot calculates the merkle root of a tree consisted of the given transaction hashes. // See `merkleRoot` for more info. -func CalcHashMerkleRoot(transactions []*externalapi.DomainTransaction) *externalapi.DomainHash { +func CalculateHashMerkleRoot(transactions []*externalapi.DomainTransaction) *externalapi.DomainHash { txHashes := make([]*externalapi.DomainHash, len(transactions)) for i, tx := range transactions { txHashes[i] = hashserialization.TransactionHash(tx) @@ -52,9 +52,9 @@ func CalcHashMerkleRoot(transactions []*externalapi.DomainTransaction) *external return merkleRoot(txHashes) } -// CalcIDMerkleRoot calculates the merkle root of a tree consisted of the given transaction IDs. +// CalculateIDMerkleRoot calculates the merkle root of a tree consisted of the given transaction IDs. // See `merkleRoot` for more info. -func CalcIDMerkleRoot(transactions []*externalapi.DomainTransaction) *externalapi.DomainHash { +func CalculateIDMerkleRoot(transactions []*externalapi.DomainTransaction) *externalapi.DomainHash { txIDs := make([]*externalapi.DomainHash, len(transactions)) for i, tx := range transactions { txIDs[i] = (*externalapi.DomainHash)(hashserialization.TransactionID(tx)) diff --git a/domain/consensus/utils/transactionid/compare.go b/domain/consensus/utils/transactionid/compare.go new file mode 100644 index 000000000..a9d1017db --- /dev/null +++ b/domain/consensus/utils/transactionid/compare.go @@ -0,0 +1,29 @@ +package transactionid + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" +) + +// cmp compares two transaction IDs and returns: +// +// -1 if a < b +// 0 if a == b +// +1 if a > b +// +func cmp(a, b *externalapi.DomainTransactionID) int { + // We compare the transaction IDs backwards because Hash is stored as a little endian byte array. + for i := externalapi.DomainHashSize - 1; i >= 0; i-- { + switch { + case a[i] < b[i]: + return -1 + case a[i] > b[i]: + return 1 + } + } + return 0 +} + +// Less returns true iff transaction ID a is less than hash b +func Less(a, b *externalapi.DomainTransactionID) bool { + return cmp(a, b) < 0 +} diff --git a/infrastructure/logger/utils.go b/infrastructure/logger/utils.go new file mode 100644 index 000000000..c383a00a8 --- /dev/null +++ b/infrastructure/logger/utils.go @@ -0,0 +1,17 @@ +package logger + +import ( + "time" +) + +// LogAndMeasureExecutionTime logs that `functionName` has +// started. The user is expected to defer `onEnd`, which +// will then log that the function has ended, as well as +// the time duration the function had ran. +func LogAndMeasureExecutionTime(log *Logger, functionName string) (onEnd func()) { + start := time.Now() + log.Debugf("%s start", functionName) + return func() { + log.Debugf("%s end. Took: %s", functionName, time.Since(start)) + } +}