Ori Newman 7e9b5b9010
Security Patch + HF (#2142)
* HF

* Fix lint
2022-09-21 18:58:32 +03:00

349 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
hfDAAScore uint64
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,
hfDAAScore uint64,
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,
hfDAAScore: hfDAAScore,
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, daaScore >= bb.hfDAAScore)
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.BlockVersion,
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, postHF bool) *externalapi.DomainHash {
return merkle.CalculateHashMerkleRoot(transactions, postHF)
}
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)
}