[NOD-1416] Implement BlockProcessor. (#969)

* [NOD-1416] Add entry/exit logs to all the functions.

* [NOD-1416] Build some scaffolding inside BlockProcessor.

* [NOD-1416] Implement selectParentsForNewBlock.

* [NOD-1416] Implement validateBlock.

* [NOD-1476] Fix merge errors.

* [NOD-1416] Move buildBlock and validateAndInsertBlock to separate files.

* [NOD-1416] Begin implementing buildBlock.

* [NOD-1416] Implement newBlockDifficulty.

* [NOD-1416] Add skeletons for the rest of the buildBlock functions.

* [NOD-1416] Implement newBlockUTXOCommitment.

* [NOD-1416] Implement newBlockAcceptedIDMerkleRoot.

* [NOD-1416] Implement newBlockHashMerkleRoot.

* [NOD-1416] Fix bad function call.

* [NOD-1416] Implement validateHeaderAndProofOfWork and validateBody.

* [NOD-1416] Use ValidateProofOfWorkAndDifficulty.

* [NOD-1416] Finish validateAndInsertBlock.

* [NOD-1416] Implement newBlockHashMerkleRoot.

* [NOD-1416] Implement newBlockAcceptedIDMerkleRoot.

* [NOD-1416] Fix a comment.

* [NOD-1416] Implement newBlockCoinbaseTransaction.

* [NOD-1416] Add VirtualBlockHash.

* [NOD-1416] Add ParentHashes and SelectedParent to VirtualData().

* [NOD-1416] Make go vet happy.

* [NOD-1416] Implement discardAllChanges.

* [NOD-1416] Implement commitAllChanges.

* [NOD-1416] Fix factory.

* [NOD-1416] Make go vet happy.

* [NOD-1416] Format factory.

* [NOD-1416] Pass transactionsWithCoinbase to buildHeader.

* [NOD-1416] Call VirtualData() from buildHeader.

* [NOD-1416] Fix a typo.

* [NOD-1416] Fix in-out-of-context/header-body confusion.

* [NOD-1416] Extract LogAndMeasureExecutionTime.

* [NOD-1416] Add a comment about LogAndMeasureExecutionTime.

* [NOD-1416] Simplify discardAllChanges and commitAllChanges.

* [NOD-1416] If in-context validations fail, discard all changes and store the block with StatusInvalid.

* [NOD-1416] Add a comment above Store.

* [NOD-1416] Use errors.As instead of errors.Is.
This commit is contained in:
stasatdaglabs 2020-10-27 17:24:15 +02:00 committed by GitHub
parent f62183473c
commit 97b5b0b875
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 466 additions and 49 deletions

View File

@ -21,3 +21,8 @@ func (dtc *DomainTxContext) StoreBlockRelation(blockHash *externalapi.DomainHash
func NewDomainTxContext(dbTx *dbaccess.TxContext) *DomainTxContext { func NewDomainTxContext(dbTx *dbaccess.TxContext) *DomainTxContext {
return &DomainTxContext{dbTx: dbTx} return &DomainTxContext{dbTx: dbTx}
} }
// Commit commits this database transaction
func (dtc *DomainTxContext) Commit() error {
return dtc.dbTx.Commit()
}

View File

@ -15,7 +15,7 @@ func New() model.AcceptanceDataStore {
} }
// Stage stages the given acceptanceData for the given blockHash // 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") panic("implement me")
} }
@ -32,7 +32,7 @@ func (ads *acceptanceDataStore) Commit(dbTx model.DBTxProxy) error {
} }
// Get gets the acceptanceData associated with the given blockHash // 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 return nil, nil
} }

View File

@ -16,6 +16,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/processes/blockprocessor" "github.com/kaspanet/kaspad/domain/consensus/processes/blockprocessor"
"github.com/kaspanet/kaspad/domain/consensus/processes/blockvalidator" "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/consensusstatemanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/dagtopologymanager" "github.com/kaspanet/kaspad/domain/consensus/processes/dagtopologymanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/dagtraversalmanager" "github.com/kaspanet/kaspad/domain/consensus/processes/dagtraversalmanager"
@ -96,8 +97,10 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, databaseContext *dba
domainDBContext, domainDBContext,
pastMedianTimeManager, pastMedianTimeManager,
ghostdagDataStore) ghostdagDataStore)
coinbaseManager := coinbasemanager.New(
genesisHash := externalapi.DomainHash([32]byte(*dagParams.GenesisHash)) ghostdagDataStore,
acceptanceDataStore)
genesisHash := externalapi.DomainHash(*dagParams.GenesisHash)
blockValidator := blockvalidator.New( blockValidator := blockvalidator.New(
dagParams.PowMax, dagParams.PowMax,
false, false,
@ -130,10 +133,17 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, databaseContext *dba
difficultyManager, difficultyManager,
pastMedianTimeManager, pastMedianTimeManager,
ghostdagManager, ghostdagManager,
coinbaseManager,
acceptanceDataStore, acceptanceDataStore,
blockStore, blockStore,
blockStatusStore, blockStatusStore,
blockRelationStore) blockRelationStore,
multisetStore,
ghostdagDataStore,
consensusStateStore,
pruningStore,
reachabilityDataStore,
utxoDiffStore)
return &consensus{ return &consensus{
consensusStateManager: consensusStateManager, consensusStateManager: consensusStateManager,

View File

@ -11,7 +11,7 @@ type BlockAcceptanceData struct {
// TransactionAcceptanceData stores a transaction together with an indication // TransactionAcceptanceData stores a transaction together with an indication
// if it was accepted or not by some block // if it was accepted or not by some block
type TransactionAcceptanceData struct { type TransactionAcceptanceData struct {
Tx *externalapi.DomainTransaction Transaction *externalapi.DomainTransaction
Fee uint64 Fee uint64
IsAccepted bool IsAccepted bool
} }

View File

@ -4,8 +4,8 @@ package model
type BlockStatus byte type BlockStatus byte
const ( const (
// StatusDataStored indicates that the block's payload is stored on disk. // StatusInvalid indicates that the block is invalid.
StatusDataStored BlockStatus = iota StatusInvalid BlockStatus = iota
// StatusValid indicates that the block has been fully validated. // StatusValid indicates that the block has been fully validated.
StatusValid StatusValid

View File

@ -3,6 +3,6 @@ package externalapi
// DomainCoinbaseData contains data by which a coinbase transaction // DomainCoinbaseData contains data by which a coinbase transaction
// is built // is built
type DomainCoinbaseData struct { type DomainCoinbaseData struct {
scriptPublicKey []byte ScriptPublicKey []byte
extraData []byte ExtraData []byte
} }

View File

@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// AcceptanceDataStore represents a store of AcceptanceData // AcceptanceDataStore represents a store of AcceptanceData
type AcceptanceDataStore interface { type AcceptanceDataStore interface {
Stage(blockHash *externalapi.DomainHash, acceptanceData *BlockAcceptanceData) Store
Stage(blockHash *externalapi.DomainHash, acceptanceData []*BlockAcceptanceData)
IsStaged() bool IsStaged() bool
Discard() Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) ([]*BlockAcceptanceData, error)
Commit(dbTx DBTxProxy) error
Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*BlockAcceptanceData, error)
Delete(dbTx DBTxProxy, blockHash *externalapi.DomainHash) error Delete(dbTx DBTxProxy, blockHash *externalapi.DomainHash) error
} }

View File

@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// BlockStore represents a store of blocks // BlockStore represents a store of blocks
type BlockStore interface { type BlockStore interface {
Store
Stage(blockHash *externalapi.DomainHash, block *externalapi.DomainBlock) Stage(blockHash *externalapi.DomainHash, block *externalapi.DomainBlock)
IsStaged() bool IsStaged() bool
Discard()
Commit(dbTx DBTxProxy) error
Block(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) Block(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error)
HasBlock(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (bool, error) HasBlock(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (bool, error)
Blocks(dbContext DBContextProxy, blockHashes []*externalapi.DomainHash) ([]*externalapi.DomainBlock, error) Blocks(dbContext DBContextProxy, blockHashes []*externalapi.DomainHash) ([]*externalapi.DomainBlock, error)

View File

@ -4,11 +4,10 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// BlockRelationStore represents a store of BlockRelations // BlockRelationStore represents a store of BlockRelations
type BlockRelationStore interface { type BlockRelationStore interface {
Store
StageBlockRelation(blockHash *externalapi.DomainHash, parentHashes []*externalapi.DomainHash) StageBlockRelation(blockHash *externalapi.DomainHash, parentHashes []*externalapi.DomainHash)
StageTips(tipHashess []*externalapi.DomainHash) StageTips(tipHashess []*externalapi.DomainHash)
IsAnythingStaged() bool IsAnythingStaged() bool
Discard()
Commit(dbTx DBTxProxy) error
BlockRelation(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*BlockRelations, error) BlockRelation(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*BlockRelations, error)
Tips(dbContext DBContextProxy) ([]*externalapi.DomainHash, error) Tips(dbContext DBContextProxy) ([]*externalapi.DomainHash, error)
} }

View File

@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// BlockStatusStore represents a store of BlockStatuses // BlockStatusStore represents a store of BlockStatuses
type BlockStatusStore interface { type BlockStatusStore interface {
Store
Stage(blockHash *externalapi.DomainHash, blockStatus BlockStatus) Stage(blockHash *externalapi.DomainHash, blockStatus BlockStatus)
IsStaged() bool IsStaged() bool
Discard()
Commit(dbTx DBTxProxy) error
Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (BlockStatus, error) Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (BlockStatus, error)
Exists(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (bool, error) Exists(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (bool, error)
} }

View File

@ -4,9 +4,8 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// ConsensusStateStore represents a store for the current consensus state // ConsensusStateStore represents a store for the current consensus state
type ConsensusStateStore interface { type ConsensusStateStore interface {
Store
Stage(consensusStateChanges *ConsensusStateChanges) Stage(consensusStateChanges *ConsensusStateChanges)
IsStaged() bool IsStaged() bool
Discard()
Commit(dbTx DBTxProxy) error
UTXOByOutpoint(dbContext DBContextProxy, outpoint *externalapi.DomainOutpoint) (*externalapi.UTXOEntry, error) UTXOByOutpoint(dbContext DBContextProxy, outpoint *externalapi.DomainOutpoint) (*externalapi.UTXOEntry, error)
} }

View File

@ -4,9 +4,8 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// GHOSTDAGDataStore represents a store of BlockGHOSTDAGData // GHOSTDAGDataStore represents a store of BlockGHOSTDAGData
type GHOSTDAGDataStore interface { type GHOSTDAGDataStore interface {
Store
Stage(blockHash *externalapi.DomainHash, blockGHOSTDAGData *BlockGHOSTDAGData) Stage(blockHash *externalapi.DomainHash, blockGHOSTDAGData *BlockGHOSTDAGData)
IsStaged() bool IsStaged() bool
Discard()
Commit(dbTx DBTxProxy) error
Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*BlockGHOSTDAGData, error) Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*BlockGHOSTDAGData, error)
} }

View File

@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// MultisetStore represents a store of Multisets // MultisetStore represents a store of Multisets
type MultisetStore interface { type MultisetStore interface {
Store
Stage(blockHash *externalapi.DomainHash, multiset Multiset) Stage(blockHash *externalapi.DomainHash, multiset Multiset)
IsStaged() bool IsStaged() bool
Discard()
Commit(dbTx DBTxProxy) error
Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (Multiset, error) Get(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (Multiset, error)
Delete(dbTx DBTxProxy, blockHash *externalapi.DomainHash) error Delete(dbTx DBTxProxy, blockHash *externalapi.DomainHash) error
} }

View File

@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// PruningStore represents a store for the current pruning state // PruningStore represents a store for the current pruning state
type PruningStore interface { type PruningStore interface {
Store
Stage(pruningPointBlockHash *externalapi.DomainHash, pruningPointUTXOSet ReadOnlyUTXOSet) Stage(pruningPointBlockHash *externalapi.DomainHash, pruningPointUTXOSet ReadOnlyUTXOSet)
IsStaged() bool IsStaged() bool
Discard()
Commit(dbTx DBTxProxy) error
PruningPoint(dbContext DBContextProxy) (*externalapi.DomainHash, error) PruningPoint(dbContext DBContextProxy) (*externalapi.DomainHash, error)
PruningPointSerializedUTXOSet(dbContext DBContextProxy) ([]byte, error) PruningPointSerializedUTXOSet(dbContext DBContextProxy) ([]byte, error)
} }

View File

@ -4,11 +4,10 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// ReachabilityDataStore represents a store of ReachabilityData // ReachabilityDataStore represents a store of ReachabilityData
type ReachabilityDataStore interface { type ReachabilityDataStore interface {
Store
StageReachabilityData(blockHash *externalapi.DomainHash, reachabilityData *ReachabilityData) StageReachabilityData(blockHash *externalapi.DomainHash, reachabilityData *ReachabilityData)
StageReachabilityReindexRoot(reachabilityReindexRoot *externalapi.DomainHash) StageReachabilityReindexRoot(reachabilityReindexRoot *externalapi.DomainHash)
IsAnythingStaged() bool IsAnythingStaged() bool
Discard()
Commit(dbTx DBTxProxy) error
ReachabilityData(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*ReachabilityData, error) ReachabilityData(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*ReachabilityData, error)
ReachabilityReindexRoot(dbContext DBContextProxy) (*externalapi.DomainHash, error) ReachabilityReindexRoot(dbContext DBContextProxy) (*externalapi.DomainHash, error)
} }

View File

@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// UTXODiffStore represents a store of UTXODiffs // UTXODiffStore represents a store of UTXODiffs
type UTXODiffStore interface { type UTXODiffStore interface {
Store
Stage(blockHash *externalapi.DomainHash, utxoDiff *UTXODiff, utxoDiffChild *externalapi.DomainHash) Stage(blockHash *externalapi.DomainHash, utxoDiff *UTXODiff, utxoDiffChild *externalapi.DomainHash)
IsStaged() bool IsStaged() bool
Discard()
Commit(dbTx DBTxProxy) error
UTXODiff(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*UTXODiff, error) UTXODiff(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*UTXODiff, error)
UTXODiffChild(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) UTXODiffChild(dbContext DBContextProxy, blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error)
Delete(dbTx DBTxProxy, blockHash *externalapi.DomainHash) error Delete(dbTx DBTxProxy, blockHash *externalapi.DomainHash) error

View File

@ -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)
}

View File

@ -6,5 +6,5 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
type ConsensusStateManager interface { type ConsensusStateManager interface {
AddBlockToVirtual(blockHash *externalapi.DomainHash) error AddBlockToVirtual(blockHash *externalapi.DomainHash) error
PopulateTransactionWithUTXOEntries(transaction *externalapi.DomainTransaction) error PopulateTransactionWithUTXOEntries(transaction *externalapi.DomainTransaction) error
VirtualData() (medianTime int64, blueScore uint64, err error) VirtualData() (virtualData *VirtualData, err error)
} }

View File

@ -2,7 +2,7 @@ package model
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// DAGTraversalManager exposes methods for travering blocks // DAGTraversalManager exposes methods for traversing blocks
// in the DAG // in the DAG
type DAGTraversalManager interface { type DAGTraversalManager interface {
HighestChainBlockBelowBlueScore(highHash *externalapi.DomainHash, blueScore uint64) (*externalapi.DomainHash, error) HighestChainBlockBelowBlueScore(highHash *externalapi.DomainHash, blueScore uint64) (*externalapi.DomainHash, error)

View File

@ -0,0 +1,7 @@
package model
// Store is a common interface for data stores
type Store interface {
Discard()
Commit(dbTx DBTxProxy) error
}

View File

@ -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
}

View File

@ -5,6 +5,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model" "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/dagconfig" "github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/logger"
) )
// blockProcessor is responsible for processing incoming blocks // blockProcessor is responsible for processing incoming blocks
@ -21,10 +22,20 @@ type blockProcessor struct {
difficultyManager model.DifficultyManager difficultyManager model.DifficultyManager
ghostdagManager model.GHOSTDAGManager ghostdagManager model.GHOSTDAGManager
pastMedianTimeManager model.PastMedianTimeManager pastMedianTimeManager model.PastMedianTimeManager
coinbaseManager model.CoinbaseManager
acceptanceDataStore model.AcceptanceDataStore acceptanceDataStore model.AcceptanceDataStore
blockStore model.BlockStore blockStore model.BlockStore
blockStatusStore model.BlockStatusStore blockStatusStore model.BlockStatusStore
blockRelationStore model.BlockRelationStore 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 // New instantiates a new BlockProcessor
@ -39,10 +50,17 @@ func New(
difficultyManager model.DifficultyManager, difficultyManager model.DifficultyManager,
pastMedianTimeManager model.PastMedianTimeManager, pastMedianTimeManager model.PastMedianTimeManager,
ghostdagManager model.GHOSTDAGManager, ghostdagManager model.GHOSTDAGManager,
coinbaseManager model.CoinbaseManager,
acceptanceDataStore model.AcceptanceDataStore, acceptanceDataStore model.AcceptanceDataStore,
blockStore model.BlockStore, blockStore model.BlockStore,
blockStatusStore model.BlockStatusStore, 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{ return &blockProcessor{
dagParams: dagParams, dagParams: dagParams,
@ -54,25 +72,52 @@ func New(
difficultyManager: difficultyManager, difficultyManager: difficultyManager,
pastMedianTimeManager: pastMedianTimeManager, pastMedianTimeManager: pastMedianTimeManager,
ghostdagManager: ghostdagManager, ghostdagManager: ghostdagManager,
coinbaseManager: coinbaseManager,
consensusStateManager: consensusStateManager, consensusStateManager: consensusStateManager,
acceptanceDataStore: acceptanceDataStore, acceptanceDataStore: acceptanceDataStore,
blockStore: blockStore, blockStore: blockStore,
blockStatusStore: blockStatusStore, blockStatusStore: blockStatusStore,
blockRelationStore: blockRelationStore, 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 // BuildBlock builds a block over the current state, with the given
// selected by the given transactionSelector // coinbaseData and the given transactions
func (bp *blockProcessor) BuildBlock(coinbaseData *externalapi.DomainCoinbaseData, func (bp *blockProcessor) BuildBlock(coinbaseData *externalapi.DomainCoinbaseData,
transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlock, error) { 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 // ValidateAndInsertBlock validates the given block and, if valid, applies it
// to the current state // to the current state
func (bp *blockProcessor) ValidateAndInsertBlock(block *externalapi.DomainBlock) error { func (bp *blockProcessor) ValidateAndInsertBlock(block *externalapi.DomainBlock) error {
return nil onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateAndInsertBlock")
defer onEnd()
return bp.validateAndInsertBlock(block)
} }

View File

@ -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
}

View File

@ -0,0 +1,7 @@
package blockprocessor
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
)
var log, _ = logger.Get(logger.SubsystemTags.BDAG)

View File

@ -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()
}

View File

@ -134,7 +134,7 @@ func (v *blockValidator) checkTransactionsInIsolation(block *externalapi.DomainB
} }
func (v *blockValidator) checkBlockHashMerkleRoot(block *externalapi.DomainBlock) error { func (v *blockValidator) checkBlockHashMerkleRoot(block *externalapi.DomainBlock) error {
calculatedHashMerkleRoot := merkle.CalcHashMerkleRoot(block.Transactions) calculatedHashMerkleRoot := merkle.CalculateHashMerkleRoot(block.Transactions)
if block.Header.HashMerkleRoot != *calculatedHashMerkleRoot { if block.Header.HashMerkleRoot != *calculatedHashMerkleRoot {
return errors.Wrapf(ruleerrors.ErrBadMerkleRoot, "block hash merkle root is invalid - block "+ return errors.Wrapf(ruleerrors.ErrBadMerkleRoot, "block hash merkle root is invalid - block "+
"header indicates %s, but calculated value is %s", "header indicates %s, but calculated value is %s",

View File

@ -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")
}

View File

@ -68,9 +68,9 @@ func (csm *consensusStateManager) AddBlockToVirtual(blockHash *externalapi.Domai
return nil return nil
} }
// VirtualData returns the medianTime and blueScore of the current virtual block // VirtualData returns data on the current virtual block
func (csm *consensusStateManager) VirtualData() (medianTime int64, blueScore uint64, err error) { func (csm *consensusStateManager) VirtualData() (virtualData *model.VirtualData, err error) {
return 0, 0, nil panic("implement me")
} }
// PopulateTransactionWithUTXOEntries populates the transaction UTXO entries with data from the virtual. // PopulateTransactionWithUTXOEntries populates the transaction UTXO entries with data from the virtual.

View File

@ -42,9 +42,9 @@ func hashMerkleBranches(left, right *externalapi.DomainHash) *externalapi.Domain
return &hash 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. // 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)) txHashes := make([]*externalapi.DomainHash, len(transactions))
for i, tx := range transactions { for i, tx := range transactions {
txHashes[i] = hashserialization.TransactionHash(tx) txHashes[i] = hashserialization.TransactionHash(tx)
@ -52,9 +52,9 @@ func CalcHashMerkleRoot(transactions []*externalapi.DomainTransaction) *external
return merkleRoot(txHashes) 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. // 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)) txIDs := make([]*externalapi.DomainHash, len(transactions))
for i, tx := range transactions { for i, tx := range transactions {
txIDs[i] = (*externalapi.DomainHash)(hashserialization.TransactionID(tx)) txIDs[i] = (*externalapi.DomainHash)(hashserialization.TransactionID(tx))

View File

@ -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
}

View File

@ -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))
}
}