mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-09-14 05:20:11 +00:00

* Add StagingArea struct * Implemented staging areas in blockStore * Move blockStagingShard to separate folder * Apply staging shard to acceptanceDataStore * Update blockHeaderStore with StagingArea * Add StagingArea to BlockRelationStore * Add StagingArea to blockStatusStore * Add StagingArea to consensusStateStore * Add StagingArea to daaBlocksStore * Add StagingArea to finalityStore * Add StagingArea to ghostdagDataStore * Add StagingArea to headersSelectedChainStore and headersSelectedTipStore * Add StagingArea to multisetStore * Add StagingArea to pruningStore * Add StagingArea to reachabilityDataStore * Add StagingArea to utxoDiffStore * Fix forgotten compilation error * Update reachability manager and some more things with StagingArea * Add StagingArea to dagTopologyManager, and some more * Add StagingArea to GHOSTDAGManager, and some more * Add StagingArea to difficultyManager, and some more * Add StagingArea to dagTraversalManager, and some more * Add StagingArea to headerTipsManager, and some more * Add StagingArea to constnsusStateManager, pastMedianTimeManager * Add StagingArea to transactionValidator * Add StagingArea to finalityManager * Add StagingArea to mergeDepthManager * Add StagingArea to pruningManager * Add StagingArea to rest of ValidateAndInsertBlock * Add StagingArea to blockValidator * Add StagingArea to coinbaseManager * Add StagingArea to syncManager * Add StagingArea to blockBuilder * Update consensus with StagingArea * Add StagingArea to ghostdag2 * Fix remaining compilation errors * Update names of stagingShards * Fix forgotten stagingArea passing * Mark stagingShard.isCommited = true once commited * Move isStaged to stagingShard, so that it's available without going through store * Make blockHeaderStore count be avilable from stagingShard * Fix remaining forgotten stagingArea passing * commitAllChanges should call dbTx.Commit in the end * Fix all tests tests in blockValidator * Fix all tests in consensusStateManager and some more * Fix all tests in pruningManager * Add many missing stagingAreas in tests * Fix many tests * Fix most of all other tests * Fix ghostdag_test.go * Add comment to StagingArea * Make list of StagingShards an array * Add comment to StagingShardID * Make sure all staging shards are pointer-receiver * Undo bucket rename in block_store * Typo: isCommited -> isCommitted * Add comment explaining why stagingArea.shards is an array
280 lines
9.1 KiB
Go
280 lines
9.1 KiB
Go
package blockbuilder
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/merkle"
|
|
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
"github.com/kaspanet/kaspad/util/mstime"
|
|
)
|
|
|
|
type blockBuilder struct {
|
|
databaseContext model.DBManager
|
|
|
|
difficultyManager model.DifficultyManager
|
|
pastMedianTimeManager model.PastMedianTimeManager
|
|
coinbaseManager model.CoinbaseManager
|
|
consensusStateManager model.ConsensusStateManager
|
|
ghostdagManager model.GHOSTDAGManager
|
|
transactionValidator model.TransactionValidator
|
|
|
|
acceptanceDataStore model.AcceptanceDataStore
|
|
blockRelationStore model.BlockRelationStore
|
|
multisetStore model.MultisetStore
|
|
ghostdagDataStore model.GHOSTDAGDataStore
|
|
}
|
|
|
|
// New creates a new instance of a BlockBuilder
|
|
func New(
|
|
databaseContext model.DBManager,
|
|
|
|
difficultyManager model.DifficultyManager,
|
|
pastMedianTimeManager model.PastMedianTimeManager,
|
|
coinbaseManager model.CoinbaseManager,
|
|
consensusStateManager model.ConsensusStateManager,
|
|
ghostdagManager model.GHOSTDAGManager,
|
|
transactionValidator model.TransactionValidator,
|
|
|
|
acceptanceDataStore model.AcceptanceDataStore,
|
|
blockRelationStore model.BlockRelationStore,
|
|
multisetStore model.MultisetStore,
|
|
ghostdagDataStore model.GHOSTDAGDataStore,
|
|
) model.BlockBuilder {
|
|
|
|
return &blockBuilder{
|
|
databaseContext: databaseContext,
|
|
difficultyManager: difficultyManager,
|
|
pastMedianTimeManager: pastMedianTimeManager,
|
|
coinbaseManager: coinbaseManager,
|
|
consensusStateManager: consensusStateManager,
|
|
ghostdagManager: ghostdagManager,
|
|
transactionValidator: transactionValidator,
|
|
|
|
acceptanceDataStore: acceptanceDataStore,
|
|
blockRelationStore: blockRelationStore,
|
|
multisetStore: multisetStore,
|
|
ghostdagDataStore: ghostdagDataStore,
|
|
}
|
|
}
|
|
|
|
// BuildBlock builds a block over the current state, with the given
|
|
// coinbaseData and the given transactions
|
|
func (bb *blockBuilder) BuildBlock(coinbaseData *externalapi.DomainCoinbaseData,
|
|
transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlock, error) {
|
|
|
|
onEnd := logger.LogAndMeasureExecutionTime(log, "BuildBlock")
|
|
defer onEnd()
|
|
|
|
stagingArea := model.NewStagingArea()
|
|
|
|
return bb.buildBlock(stagingArea, coinbaseData, transactions)
|
|
}
|
|
|
|
func (bb *blockBuilder) buildBlock(stagingArea *model.StagingArea, coinbaseData *externalapi.DomainCoinbaseData,
|
|
transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlock, error) {
|
|
|
|
err := bb.validateTransactions(stagingArea, transactions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
coinbase, err := bb.newBlockCoinbaseTransaction(stagingArea, coinbaseData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
transactionsWithCoinbase := append([]*externalapi.DomainTransaction{coinbase}, transactions...)
|
|
|
|
header, err := bb.buildHeader(stagingArea, transactionsWithCoinbase)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &externalapi.DomainBlock{
|
|
Header: header,
|
|
Transactions: transactionsWithCoinbase,
|
|
}, nil
|
|
}
|
|
|
|
func (bb *blockBuilder) validateTransactions(stagingArea *model.StagingArea,
|
|
transactions []*externalapi.DomainTransaction) error {
|
|
|
|
invalidTransactions := make([]ruleerrors.InvalidTransaction, 0)
|
|
for _, transaction := range transactions {
|
|
err := bb.validateTransaction(stagingArea, transaction)
|
|
if err != nil {
|
|
if !errors.As(err, &ruleerrors.RuleError{}) {
|
|
return err
|
|
}
|
|
invalidTransactions = append(invalidTransactions,
|
|
ruleerrors.InvalidTransaction{Transaction: transaction, Error: err})
|
|
}
|
|
}
|
|
|
|
if len(invalidTransactions) > 0 {
|
|
return ruleerrors.NewErrInvalidTransactionsInNewBlock(invalidTransactions)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (bb *blockBuilder) validateTransaction(
|
|
stagingArea *model.StagingArea, transaction *externalapi.DomainTransaction) error {
|
|
|
|
originalEntries := make([]externalapi.UTXOEntry, len(transaction.Inputs))
|
|
for i, input := range transaction.Inputs {
|
|
originalEntries[i] = input.UTXOEntry
|
|
input.UTXOEntry = nil
|
|
}
|
|
|
|
defer func() {
|
|
for i, input := range transaction.Inputs {
|
|
input.UTXOEntry = originalEntries[i]
|
|
}
|
|
}()
|
|
|
|
err := bb.consensusStateManager.PopulateTransactionWithUTXOEntries(stagingArea, transaction)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
virtualSelectedParentMedianTime, err := bb.pastMedianTimeManager.PastMedianTime(stagingArea, model.VirtualBlockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return bb.transactionValidator.ValidateTransactionInContextAndPopulateMassAndFee(
|
|
stagingArea, transaction, model.VirtualBlockHash, virtualSelectedParentMedianTime)
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockCoinbaseTransaction(stagingArea *model.StagingArea,
|
|
coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransaction, error) {
|
|
|
|
return bb.coinbaseManager.ExpectedCoinbaseTransaction(stagingArea, model.VirtualBlockHash, coinbaseData)
|
|
}
|
|
|
|
func (bb *blockBuilder) buildHeader(stagingArea *model.StagingArea, transactions []*externalapi.DomainTransaction) (
|
|
externalapi.BlockHeader, error) {
|
|
|
|
parentHashes, err := bb.newBlockParentHashes(stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
timeInMilliseconds, err := bb.newBlockTime(stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bits, err := bb.newBlockDifficulty(stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hashMerkleRoot := bb.newBlockHashMerkleRoot(transactions)
|
|
acceptedIDMerkleRoot, err := bb.newBlockAcceptedIDMerkleRoot(stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
utxoCommitment, err := bb.newBlockUTXOCommitment(stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return blockheader.NewImmutableBlockHeader(
|
|
constants.MaxBlockVersion,
|
|
parentHashes,
|
|
hashMerkleRoot,
|
|
acceptedIDMerkleRoot,
|
|
utxoCommitment,
|
|
timeInMilliseconds,
|
|
bits,
|
|
0,
|
|
), nil
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockParentHashes(stagingArea *model.StagingArea) ([]*externalapi.DomainHash, error) {
|
|
virtualBlockRelations, err := bb.blockRelationStore.BlockRelation(bb.databaseContext, stagingArea, model.VirtualBlockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return virtualBlockRelations.Parents, nil
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockTime(stagingArea *model.StagingArea) (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()
|
|
minTimestamp, err := bb.minBlockTime(stagingArea, model.VirtualBlockHash)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if newTimestamp < minTimestamp {
|
|
newTimestamp = minTimestamp
|
|
}
|
|
return newTimestamp, nil
|
|
}
|
|
|
|
func (bb *blockBuilder) minBlockTime(stagingArea *model.StagingArea, hash *externalapi.DomainHash) (int64, error) {
|
|
pastMedianTime, err := bb.pastMedianTimeManager.PastMedianTime(stagingArea, hash)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return pastMedianTime + 1, nil
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockDifficulty(stagingArea *model.StagingArea) (uint32, error) {
|
|
return bb.difficultyManager.RequiredDifficulty(stagingArea, model.VirtualBlockHash)
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockHashMerkleRoot(transactions []*externalapi.DomainTransaction) *externalapi.DomainHash {
|
|
return merkle.CalculateHashMerkleRoot(transactions)
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockAcceptedIDMerkleRoot(stagingArea *model.StagingArea) (*externalapi.DomainHash, error) {
|
|
newBlockAcceptanceData, err := bb.acceptanceDataStore.Get(bb.databaseContext, stagingArea, model.VirtualBlockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return bb.calculateAcceptedIDMerkleRoot(newBlockAcceptanceData)
|
|
}
|
|
|
|
func (bb *blockBuilder) calculateAcceptedIDMerkleRoot(acceptanceData externalapi.AcceptanceData) (*externalapi.DomainHash, error) {
|
|
var acceptedTransactions []*externalapi.DomainTransaction
|
|
for _, blockAcceptanceData := range acceptanceData {
|
|
for _, transactionAcceptance := range blockAcceptanceData.TransactionAcceptanceData {
|
|
if !transactionAcceptance.IsAccepted {
|
|
continue
|
|
}
|
|
acceptedTransactions = append(acceptedTransactions, transactionAcceptance.Transaction)
|
|
}
|
|
}
|
|
sort.Slice(acceptedTransactions, func(i, j int) bool {
|
|
acceptedTransactionIID := consensushashing.TransactionID(acceptedTransactions[i])
|
|
acceptedTransactionJID := consensushashing.TransactionID(acceptedTransactions[j])
|
|
return acceptedTransactionIID.Less(acceptedTransactionJID)
|
|
})
|
|
|
|
return merkle.CalculateIDMerkleRoot(acceptedTransactions), nil
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockUTXOCommitment(stagingArea *model.StagingArea) (*externalapi.DomainHash, error) {
|
|
newBlockMultiset, err := bb.multisetStore.Get(bb.databaseContext, stagingArea, model.VirtualBlockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newBlockUTXOCommitment := newBlockMultiset.Hash()
|
|
return newBlockUTXOCommitment, nil
|
|
}
|