mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-09-13 21:10:12 +00:00

* Validate transactions on BuildBlock * Rename tx -> transactions * Add transaction validator to block builder constructor and fix TestValidateAndInsertErrors Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
271 lines
8.5 KiB
Go
271 lines
8.5 KiB
Go
package blockbuilder
|
|
|
|
import (
|
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
|
|
"github.com/pkg/errors"
|
|
"sort"
|
|
|
|
"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()
|
|
|
|
return bb.buildBlock(coinbaseData, transactions)
|
|
}
|
|
|
|
func (bb *blockBuilder) buildBlock(coinbaseData *externalapi.DomainCoinbaseData,
|
|
transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlock, error) {
|
|
|
|
err := bb.validateTransactions(transactions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
coinbase, err := bb.newBlockCoinbaseTransaction(coinbaseData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
transactionsWithCoinbase := append([]*externalapi.DomainTransaction{coinbase}, transactions...)
|
|
|
|
header, err := bb.buildHeader(transactionsWithCoinbase)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &externalapi.DomainBlock{
|
|
Header: header,
|
|
Transactions: transactionsWithCoinbase,
|
|
}, nil
|
|
}
|
|
|
|
func (bb *blockBuilder) validateTransactions(transactions []*externalapi.DomainTransaction) error {
|
|
invalidTransactions := make([]ruleerrors.InvalidTransaction, 0)
|
|
for _, transaction := range transactions {
|
|
err := bb.validateTransaction(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(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(transaction)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
virtualSelectedParentMedianTime, err := bb.pastMedianTimeManager.PastMedianTime(model.VirtualBlockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return bb.transactionValidator.ValidateTransactionInContextAndPopulateMassAndFee(transaction,
|
|
model.VirtualBlockHash, virtualSelectedParentMedianTime)
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockCoinbaseTransaction(
|
|
coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransaction, error) {
|
|
|
|
return bb.coinbaseManager.ExpectedCoinbaseTransaction(model.VirtualBlockHash, coinbaseData)
|
|
}
|
|
|
|
func (bb *blockBuilder) buildHeader(transactions []*externalapi.DomainTransaction) (externalapi.BlockHeader, error) {
|
|
parentHashes, err := bb.newBlockParentHashes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
timeInMilliseconds, err := bb.newBlockTime()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bits, err := bb.newBlockDifficulty()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hashMerkleRoot := bb.newBlockHashMerkleRoot(transactions)
|
|
acceptedIDMerkleRoot, err := bb.newBlockAcceptedIDMerkleRoot()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
utxoCommitment, err := bb.newBlockUTXOCommitment()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return blockheader.NewImmutableBlockHeader(
|
|
constants.MaxBlockVersion,
|
|
parentHashes,
|
|
hashMerkleRoot,
|
|
acceptedIDMerkleRoot,
|
|
utxoCommitment,
|
|
timeInMilliseconds,
|
|
bits,
|
|
0,
|
|
), nil
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockParentHashes() ([]*externalapi.DomainHash, error) {
|
|
virtualBlockRelations, err := bb.blockRelationStore.BlockRelation(bb.databaseContext, model.VirtualBlockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return virtualBlockRelations.Parents, nil
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockTime() (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(model.VirtualBlockHash)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if newTimestamp < minTimestamp {
|
|
newTimestamp = minTimestamp
|
|
}
|
|
return newTimestamp, nil
|
|
}
|
|
|
|
func (bb *blockBuilder) minBlockTime(hash *externalapi.DomainHash) (int64, error) {
|
|
pastMedianTime, err := bb.pastMedianTimeManager.PastMedianTime(hash)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return pastMedianTime + 1, nil
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockDifficulty() (uint32, error) {
|
|
return bb.difficultyManager.RequiredDifficulty(model.VirtualBlockHash)
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockHashMerkleRoot(transactions []*externalapi.DomainTransaction) *externalapi.DomainHash {
|
|
return merkle.CalculateHashMerkleRoot(transactions)
|
|
}
|
|
|
|
func (bb *blockBuilder) newBlockAcceptedIDMerkleRoot() (*externalapi.DomainHash, error) {
|
|
newBlockAcceptanceData, err := bb.acceptanceDataStore.Get(bb.databaseContext, 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() (*externalapi.DomainHash, error) {
|
|
newBlockMultiset, err := bb.multisetStore.Get(bb.databaseContext, model.VirtualBlockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newBlockUTXOCommitment := newBlockMultiset.Hash()
|
|
return newBlockUTXOCommitment, nil
|
|
}
|