mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00

* [NOD-395] Write a test for the diffFromAcceptanceData crash. * [NOD-395] Converted MultiBlockTxsAcceptanceData into a slice. * [NOD-395] Fix failing test. * [NOD-395] Populate multiBlockTxsAcceptanceData bottom-to-top. * [NOD-395] Add comment to FindAcceptanceData. * [NOD-395] Remove no-longer relevant note about probability in TestOrderInDiffFromAcceptanceData.
279 lines
7.9 KiB
Go
279 lines
7.9 KiB
Go
package blockdag
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"github.com/daglabs/btcd/util/subnetworkid"
|
|
"github.com/pkg/errors"
|
|
"io"
|
|
"math"
|
|
|
|
"github.com/daglabs/btcd/database"
|
|
"github.com/daglabs/btcd/util"
|
|
"github.com/daglabs/btcd/util/daghash"
|
|
"github.com/daglabs/btcd/util/txsort"
|
|
"github.com/daglabs/btcd/wire"
|
|
)
|
|
|
|
// compactFeeData is a specialized data type to store a compact list of fees
|
|
// inside a block.
|
|
// Every transaction gets a single uint64 value, stored as a plain binary list.
|
|
// The transactions are ordered the same way they are ordered inside the block, making it easy
|
|
// to traverse every transaction in a block and extract its fee.
|
|
//
|
|
// compactFeeFactory is used to create such a list.
|
|
// compactFeeIterator is used to iterate over such a list.
|
|
|
|
type compactFeeData []byte
|
|
|
|
func (cfd compactFeeData) Len() int {
|
|
return len(cfd) / 8
|
|
}
|
|
|
|
type compactFeeFactory struct {
|
|
buffer *bytes.Buffer
|
|
writer *bufio.Writer
|
|
}
|
|
|
|
func newCompactFeeFactory() *compactFeeFactory {
|
|
buffer := bytes.NewBuffer([]byte{})
|
|
return &compactFeeFactory{
|
|
buffer: buffer,
|
|
writer: bufio.NewWriter(buffer),
|
|
}
|
|
}
|
|
|
|
func (cfw *compactFeeFactory) add(txFee uint64) error {
|
|
return binary.Write(cfw.writer, binary.LittleEndian, txFee)
|
|
}
|
|
|
|
func (cfw *compactFeeFactory) data() (compactFeeData, error) {
|
|
err := cfw.writer.Flush()
|
|
|
|
return compactFeeData(cfw.buffer.Bytes()), err
|
|
}
|
|
|
|
type compactFeeIterator struct {
|
|
reader io.Reader
|
|
}
|
|
|
|
func (cfd compactFeeData) iterator() *compactFeeIterator {
|
|
return &compactFeeIterator{
|
|
reader: bufio.NewReader(bytes.NewBuffer(cfd)),
|
|
}
|
|
}
|
|
|
|
func (cfr *compactFeeIterator) next() (uint64, error) {
|
|
var txFee uint64
|
|
|
|
err := binary.Read(cfr.reader, binary.LittleEndian, &txFee)
|
|
|
|
return txFee, err
|
|
}
|
|
|
|
// The following functions relate to storing and retrieving fee data from the database
|
|
var feeBucket = []byte("fees")
|
|
|
|
// getBluesFeeData returns the compactFeeData for all nodes's blues,
|
|
// used to calculate the fees this blockNode needs to pay
|
|
func (node *blockNode) getBluesFeeData(dag *BlockDAG) (map[daghash.Hash]compactFeeData, error) {
|
|
bluesFeeData := make(map[daghash.Hash]compactFeeData)
|
|
|
|
err := dag.db.View(func(dbTx database.Tx) error {
|
|
for _, blueBlock := range node.blues {
|
|
feeData, err := dbFetchFeeData(dbTx, blueBlock.hash)
|
|
if err != nil {
|
|
return errors.Errorf("Error getting fee data for block %s: %s", blueBlock.hash, err)
|
|
}
|
|
|
|
bluesFeeData[*blueBlock.hash] = feeData
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return bluesFeeData, nil
|
|
}
|
|
|
|
func dbStoreFeeData(dbTx database.Tx, blockHash *daghash.Hash, feeData compactFeeData) error {
|
|
feeBucket, err := dbTx.Metadata().CreateBucketIfNotExists(feeBucket)
|
|
if err != nil {
|
|
return errors.Errorf("Error creating or retrieving fee bucket: %s", err)
|
|
}
|
|
|
|
return feeBucket.Put(blockHash.CloneBytes(), feeData)
|
|
}
|
|
|
|
func dbFetchFeeData(dbTx database.Tx, blockHash *daghash.Hash) (compactFeeData, error) {
|
|
feeBucket := dbTx.Metadata().Bucket(feeBucket)
|
|
if feeBucket == nil {
|
|
return nil, errors.New("Fee bucket does not exist")
|
|
}
|
|
|
|
feeData := feeBucket.Get(blockHash.CloneBytes())
|
|
if feeData == nil {
|
|
return nil, errors.Errorf("No fee data found for block %s", blockHash)
|
|
}
|
|
|
|
return feeData, nil
|
|
}
|
|
|
|
// The following functions deal with building and validating the coinbase transaction
|
|
|
|
func (node *blockNode) validateCoinbaseTransaction(dag *BlockDAG, block *util.Block, txsAcceptanceData MultiBlockTxsAcceptanceData) error {
|
|
if node.isGenesis() {
|
|
return nil
|
|
}
|
|
blockCoinbaseTx := block.CoinbaseTransaction().MsgTx()
|
|
scriptPubKey, extraData, err := DeserializeCoinbasePayload(blockCoinbaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
expectedCoinbaseTransaction, err := node.expectedCoinbaseTransaction(dag, txsAcceptanceData, scriptPubKey, extraData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !expectedCoinbaseTransaction.Hash().IsEqual(block.CoinbaseTransaction().Hash()) {
|
|
return ruleError(ErrBadCoinbaseTransaction, "Coinbase transaction is not built as expected")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// expectedCoinbaseTransaction returns the coinbase transaction for the current block
|
|
func (node *blockNode) expectedCoinbaseTransaction(dag *BlockDAG, txsAcceptanceData MultiBlockTxsAcceptanceData, scriptPubKey []byte, extraData []byte) (*util.Tx, error) {
|
|
bluesFeeData, err := node.getBluesFeeData(dag)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
txIns := []*wire.TxIn{}
|
|
txOuts := []*wire.TxOut{}
|
|
|
|
for _, blue := range node.blues {
|
|
txIn, txOut, err := coinbaseInputAndOutputForBlueBlock(dag, blue, txsAcceptanceData, bluesFeeData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
txIns = append(txIns, txIn)
|
|
if txOut != nil {
|
|
txOuts = append(txOuts, txOut)
|
|
}
|
|
}
|
|
payload, err := SerializeCoinbasePayload(scriptPubKey, extraData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
coinbaseTx := wire.NewSubnetworkMsgTx(wire.TxVersion, txIns, txOuts, subnetworkid.SubnetworkIDCoinbase, 0, payload)
|
|
sortedCoinbaseTx := txsort.Sort(coinbaseTx)
|
|
return util.NewTx(sortedCoinbaseTx), nil
|
|
}
|
|
|
|
// SerializeCoinbasePayload builds the coinbase payload based on the provided scriptPubKey and extra data.
|
|
func SerializeCoinbasePayload(scriptPubKey []byte, extraData []byte) ([]byte, error) {
|
|
w := &bytes.Buffer{}
|
|
err := wire.WriteVarInt(w, uint64(len(scriptPubKey)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = w.Write(scriptPubKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = w.Write(extraData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return w.Bytes(), nil
|
|
}
|
|
|
|
// DeserializeCoinbasePayload deserialize the coinbase payload to its component (scriptPubKey and extra data).
|
|
func DeserializeCoinbasePayload(tx *wire.MsgTx) (scriptPubKey []byte, extraData []byte, err error) {
|
|
r := bytes.NewReader(tx.Payload)
|
|
scriptPubKeyLen, err := wire.ReadVarInt(r)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
scriptPubKey = make([]byte, scriptPubKeyLen)
|
|
_, err = r.Read(scriptPubKey)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
extraData = make([]byte, r.Len())
|
|
if r.Len() != 0 {
|
|
_, err = r.Read(extraData)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
return scriptPubKey, extraData, nil
|
|
}
|
|
|
|
// feeInputAndOutputForBlueBlock calculates the input and output that should go into the coinbase transaction of blueBlock
|
|
// If blueBlock gets no fee - returns only txIn and nil for txOut
|
|
func coinbaseInputAndOutputForBlueBlock(dag *BlockDAG, blueBlock *blockNode,
|
|
txsAcceptanceData MultiBlockTxsAcceptanceData, feeData map[daghash.Hash]compactFeeData) (
|
|
*wire.TxIn, *wire.TxOut, error) {
|
|
|
|
blockTxsAcceptanceData, ok := txsAcceptanceData.FindAcceptanceData(blueBlock.hash)
|
|
if !ok {
|
|
return nil, nil, errors.Errorf("No txsAcceptanceData for block %s", blueBlock.hash)
|
|
}
|
|
blockFeeData, ok := feeData[*blueBlock.hash]
|
|
if !ok {
|
|
return nil, nil, errors.Errorf("No feeData for block %s", blueBlock.hash)
|
|
}
|
|
|
|
if len(blockTxsAcceptanceData.TxAcceptanceData) != blockFeeData.Len() {
|
|
return nil, nil, errors.Errorf(
|
|
"length of accepted transaction data(%d) and fee data(%d) is not equal for block %s",
|
|
len(blockTxsAcceptanceData.TxAcceptanceData), blockFeeData.Len(), blueBlock.hash)
|
|
}
|
|
|
|
txIn := &wire.TxIn{
|
|
SignatureScript: []byte{},
|
|
PreviousOutpoint: wire.Outpoint{
|
|
TxID: daghash.TxID(*blueBlock.hash),
|
|
Index: math.MaxUint32,
|
|
},
|
|
Sequence: wire.MaxTxInSequenceNum,
|
|
}
|
|
|
|
totalFees := uint64(0)
|
|
feeIterator := blockFeeData.iterator()
|
|
|
|
for _, txAcceptanceData := range blockTxsAcceptanceData.TxAcceptanceData {
|
|
fee, err := feeIterator.next()
|
|
if err != nil {
|
|
return nil, nil, errors.Errorf("Error retrieving fee from compactFeeData iterator: %s", err)
|
|
}
|
|
if txAcceptanceData.IsAccepted {
|
|
totalFees += fee
|
|
}
|
|
}
|
|
|
|
totalReward := CalcBlockSubsidy(blueBlock.height, dag.dagParams) + totalFees
|
|
|
|
if totalReward == 0 {
|
|
return txIn, nil, nil
|
|
}
|
|
|
|
// the ScriptPubKey for the coinbase is parsed from the coinbase payload
|
|
scriptPubKey, _, err := DeserializeCoinbasePayload(blockTxsAcceptanceData.TxAcceptanceData[0].Tx.MsgTx())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
txOut := &wire.TxOut{
|
|
Value: totalReward,
|
|
ScriptPubKey: scriptPubKey,
|
|
}
|
|
|
|
return txIn, txOut, nil
|
|
}
|