mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-05 13:46:42 +00:00

* minor text fix * Implement a block template cache with template modification/reuse mechanism * Fix compilation error * Address review comments * Added a through TestModifyBlockTemplate test * Update header timestamp if possible * Avoid copying the transactions when only the header changed * go fmt
346 lines
12 KiB
Go
346 lines
12 KiB
Go
package blockbuilder
|
|
|
|
import (
|
|
"math/big"
|
|
"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
|
|
genesisHash *externalapi.DomainHash
|
|
|
|
difficultyManager model.DifficultyManager
|
|
pastMedianTimeManager model.PastMedianTimeManager
|
|
coinbaseManager model.CoinbaseManager
|
|
consensusStateManager model.ConsensusStateManager
|
|
ghostdagManager model.GHOSTDAGManager
|
|
transactionValidator model.TransactionValidator
|
|
finalityManager model.FinalityManager
|
|
pruningManager model.PruningManager
|
|
blockParentBuilder model.BlockParentBuilder
|
|
|
|
acceptanceDataStore model.AcceptanceDataStore
|
|
blockRelationStore model.BlockRelationStore
|
|
multisetStore model.MultisetStore
|
|
ghostdagDataStore model.GHOSTDAGDataStore
|
|
daaBlocksStore model.DAABlocksStore
|
|
}
|
|
|
|
// New creates a new instance of a BlockBuilder
|
|
func New(
|
|
databaseContext model.DBManager,
|
|
genesisHash *externalapi.DomainHash,
|
|
|
|
difficultyManager model.DifficultyManager,
|
|
pastMedianTimeManager model.PastMedianTimeManager,
|
|
coinbaseManager model.CoinbaseManager,
|
|
consensusStateManager model.ConsensusStateManager,
|
|
ghostdagManager model.GHOSTDAGManager,
|
|
transactionValidator model.TransactionValidator,
|
|
finalityManager model.FinalityManager,
|
|
blockParentBuilder model.BlockParentBuilder,
|
|
pruningManager model.PruningManager,
|
|
|
|
acceptanceDataStore model.AcceptanceDataStore,
|
|
blockRelationStore model.BlockRelationStore,
|
|
multisetStore model.MultisetStore,
|
|
ghostdagDataStore model.GHOSTDAGDataStore,
|
|
daaBlocksStore model.DAABlocksStore,
|
|
) model.BlockBuilder {
|
|
|
|
return &blockBuilder{
|
|
databaseContext: databaseContext,
|
|
genesisHash: genesisHash,
|
|
|
|
difficultyManager: difficultyManager,
|
|
pastMedianTimeManager: pastMedianTimeManager,
|
|
coinbaseManager: coinbaseManager,
|
|
consensusStateManager: consensusStateManager,
|
|
ghostdagManager: ghostdagManager,
|
|
transactionValidator: transactionValidator,
|
|
finalityManager: finalityManager,
|
|
blockParentBuilder: blockParentBuilder,
|
|
pruningManager: pruningManager,
|
|
|
|
acceptanceDataStore: acceptanceDataStore,
|
|
blockRelationStore: blockRelationStore,
|
|
multisetStore: multisetStore,
|
|
ghostdagDataStore: ghostdagDataStore,
|
|
daaBlocksStore: daaBlocksStore,
|
|
}
|
|
}
|
|
|
|
// 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) (block *externalapi.DomainBlock, coinbaseHasRedReward bool, err 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) (block *externalapi.DomainBlock, coinbaseHasRedReward bool, err error) {
|
|
|
|
err = bb.validateTransactions(stagingArea, transactions)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
newBlockPruningPoint, err := bb.newBlockPruningPoint(stagingArea, model.VirtualBlockHash)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
coinbase, coinbaseHasRedReward, err := bb.newBlockCoinbaseTransaction(stagingArea, coinbaseData)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
transactionsWithCoinbase := append([]*externalapi.DomainTransaction{coinbase}, transactions...)
|
|
|
|
header, err := bb.buildHeader(stagingArea, transactionsWithCoinbase, newBlockPruningPoint)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
return &externalapi.DomainBlock{
|
|
Header: header,
|
|
Transactions: transactionsWithCoinbase,
|
|
}, coinbaseHasRedReward, 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
|
|
}
|
|
|
|
virtualPastMedianTime, err := bb.pastMedianTimeManager.PastMedianTime(stagingArea, model.VirtualBlockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = bb.transactionValidator.ValidateTransactionInContextIgnoringUTXO(stagingArea, transaction, model.VirtualBlockHash, virtualPastMedianTime)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return bb.transactionValidator.ValidateTransactionInContextAndPopulateFee(stagingArea, transaction, model.VirtualBlockHash)
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockCoinbaseTransaction(stagingArea *model.StagingArea,
|
|
coinbaseData *externalapi.DomainCoinbaseData) (expectedTransaction *externalapi.DomainTransaction, hasRedReward bool, err error) {
|
|
|
|
return bb.coinbaseManager.ExpectedCoinbaseTransaction(stagingArea, model.VirtualBlockHash, coinbaseData)
|
|
}
|
|
|
|
func (bb *blockBuilder) buildHeader(stagingArea *model.StagingArea, transactions []*externalapi.DomainTransaction,
|
|
newBlockPruningPoint *externalapi.DomainHash) (externalapi.BlockHeader, error) {
|
|
|
|
daaScore, err := bb.newBlockDAAScore(stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
parents, err := bb.newBlockParents(stagingArea, daaScore)
|
|
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
|
|
}
|
|
blueWork, err := bb.newBlockBlueWork(stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blueScore, err := bb.newBlockBlueScore(stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return blockheader.NewImmutableBlockHeader(
|
|
constants.MaxBlockVersion,
|
|
parents,
|
|
hashMerkleRoot,
|
|
acceptedIDMerkleRoot,
|
|
utxoCommitment,
|
|
timeInMilliseconds,
|
|
bits,
|
|
0,
|
|
daaScore,
|
|
blueScore,
|
|
blueWork,
|
|
newBlockPruningPoint,
|
|
), nil
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockParents(stagingArea *model.StagingArea, daaScore uint64) ([]externalapi.BlockLevelParents, error) {
|
|
virtualBlockRelations, err := bb.blockRelationStore.BlockRelation(bb.databaseContext, stagingArea, model.VirtualBlockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bb.blockParentBuilder.BuildParents(stagingArea, daaScore, virtualBlockRelations.Parents)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockDAAScore(stagingArea *model.StagingArea) (uint64, error) {
|
|
return bb.daaBlocksStore.DAAScore(bb.databaseContext, stagingArea, model.VirtualBlockHash)
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockBlueWork(stagingArea *model.StagingArea) (*big.Int, error) {
|
|
virtualGHOSTDAGData, err := bb.ghostdagDataStore.Get(bb.databaseContext, stagingArea, model.VirtualBlockHash, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return virtualGHOSTDAGData.BlueWork(), nil
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockBlueScore(stagingArea *model.StagingArea) (uint64, error) {
|
|
virtualGHOSTDAGData, err := bb.ghostdagDataStore.Get(bb.databaseContext, stagingArea, model.VirtualBlockHash, false)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return virtualGHOSTDAGData.BlueScore(), nil
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockPruningPoint(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
|
|
return bb.pruningManager.ExpectedHeaderPruningPoint(stagingArea, blockHash)
|
|
}
|