This removes the treasury agenda flag from the stake.IsSSGen function since it is now unused due to it being removed from the stake.CheckSSGen function.
1104 lines
35 KiB
Go
1104 lines
35 KiB
Go
// Copyright (c) 2020-2022 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package blockchain
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/decred/dcrd/blockchain/stake/v5"
|
|
"github.com/decred/dcrd/blockchain/standalone/v2"
|
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
"github.com/decred/dcrd/database/v3"
|
|
"github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr"
|
|
"github.com/decred/dcrd/dcrutil/v4"
|
|
"github.com/decred/dcrd/txscript/v4"
|
|
"github.com/decred/dcrd/wire"
|
|
)
|
|
|
|
// errDbTreasury signifies that a problem was encountered when fetching or
|
|
// writing the treasury balance for a given block.
|
|
type errDbTreasury string
|
|
|
|
// Error implements the error interface.
|
|
func (e errDbTreasury) Error() string {
|
|
return string(e)
|
|
}
|
|
|
|
// Is implements the interface to work with the standard library's errors.Is.
|
|
//
|
|
// It returns true in the following cases:
|
|
// - The target is errDbTreasury
|
|
func (e errDbTreasury) Is(target error) bool {
|
|
var err errDbTreasury
|
|
return errors.As(target, &err)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// The treasury state index consists of an entry for every known block. It
|
|
// contains the balance of the treasury as of that block as well as all of the
|
|
// amounts added to and spent from the treasury in the block.
|
|
//
|
|
// The serialized key format is:
|
|
//
|
|
// <block hash>
|
|
//
|
|
// Field Type Size
|
|
// block hash chainhash.Hash chainhash.HashSize
|
|
//
|
|
// The serialized value format is:
|
|
//
|
|
// <balance><num values><values info>
|
|
//
|
|
// Field Type Size
|
|
// balance VLQ variable
|
|
// num values VLQ variable
|
|
// values
|
|
// flag VLQ variable
|
|
// value VLQ variable
|
|
//
|
|
// The flag attribute of each value is a bit field, serialized as follows:
|
|
//
|
|
// 00000ttt
|
|
// | | mask
|
|
// | \---- 0x07: treasuryValueType
|
|
// \-------------- : Unused
|
|
//
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// treasuryValueType specifies the possible types of values that modify the
|
|
// treasury balance.
|
|
type treasuryValueType byte
|
|
|
|
// IsDebit returns true if the type of value is a debit from the treasury
|
|
// account.
|
|
func (typ treasuryValueType) IsDebit() bool {
|
|
return typ == treasuryValueFee || typ == treasuryValueTSpend
|
|
}
|
|
|
|
// The following constants define the known types of values that modify the
|
|
// treasury balance.
|
|
const (
|
|
treasuryValueTBase treasuryValueType = 0x01
|
|
treasuryValueTAdd treasuryValueType = 0x02
|
|
treasuryValueFee treasuryValueType = 0x03
|
|
treasuryValueTSpend treasuryValueType = 0x04
|
|
|
|
// tvFlagTypMask is the mask of the bits used to encode the type in the
|
|
// flags field of a serialized treasuryValue.
|
|
tvFlagTypMask byte = 0x07
|
|
)
|
|
|
|
// treasuryValue specifies the type and amount that of a value that changes the
|
|
// treasury balance.
|
|
//
|
|
// NOTE: for tspends and tspend fees, the amount is *negative*.
|
|
type treasuryValue struct {
|
|
typ treasuryValueType
|
|
amount int64
|
|
}
|
|
|
|
// treasuryState records the treasury balance as of this block as well as the
|
|
// yet to mature balance-changing values (treasurybase, adds, spends and spend
|
|
// fees) included in the block itself.
|
|
//
|
|
// Note that treasurybase and tadds are positive and tspends and tspend fees
|
|
// are negative. Additionally the values are written in the exact same order
|
|
// as they appear in the block. This can be used to verify the correctness of
|
|
// the record if needed.
|
|
type treasuryState struct {
|
|
// balance is the treasury balance as of this block.
|
|
balance int64
|
|
|
|
// values stores all balance-changing values included in this block (for use
|
|
// when block is mature).
|
|
values []treasuryValue
|
|
}
|
|
|
|
// serializeTreasuryState serializes the treasury state into a single byte slice
|
|
// according to the format described in detail above.
|
|
func serializeTreasuryState(ts treasuryState) ([]byte, error) {
|
|
// Just a little sanity testing.
|
|
if ts.balance < 0 {
|
|
str := fmt.Sprintf("invalid treasury balance: %v", ts.balance)
|
|
return nil, errDbTreasury(str)
|
|
}
|
|
|
|
// Calculate total number of bytes it will take to serialize the treasury
|
|
// state according to the format described above.
|
|
serializeSize := serializeSizeVLQ(uint64(ts.balance)) +
|
|
serializeSizeVLQ(uint64(len(ts.values)))
|
|
for _, value := range ts.values {
|
|
// Prevent serialization of wrong negative value. Note that
|
|
// zero is still allowed even in negative types.
|
|
wantNegative := value.typ.IsDebit()
|
|
gotNegative := value.amount < 0
|
|
if value.amount != 0 && wantNegative != gotNegative {
|
|
str := fmt.Sprintf("incorrect negative value for type "+
|
|
"%d: %d", value.typ, value.amount)
|
|
return nil, errDbTreasury(str)
|
|
}
|
|
|
|
serializeSize++ // Flag which is currently a 1 byte long VLQ.
|
|
serializeSize += serializeSizeVLQ(absInt64(value.amount))
|
|
}
|
|
|
|
// Serialize the treasury state according to the format described above.
|
|
serialized := make([]byte, serializeSize)
|
|
offset := putVLQ(serialized, uint64(ts.balance))
|
|
offset += putVLQ(serialized[offset:], uint64(len(ts.values)))
|
|
for _, value := range ts.values {
|
|
// tspends and fees are negative but store them as positive to
|
|
// reduce storage needs.
|
|
amount := absInt64(value.amount)
|
|
flag := uint64(byte(value.typ) & tvFlagTypMask)
|
|
|
|
offset += putVLQ(serialized[offset:], flag)
|
|
offset += putVLQ(serialized[offset:], amount)
|
|
}
|
|
return serialized, nil
|
|
}
|
|
|
|
// deserializeTreasuryState deserializes the passed serialized treasury state
|
|
// according to the format described above.
|
|
func deserializeTreasuryState(data []byte) (*treasuryState, error) {
|
|
// Deserialize the balance.
|
|
balance, offset := deserializeVLQ(data)
|
|
if offset == 0 {
|
|
return nil, errDeserialize("unexpected end of data while reading " +
|
|
"treasury balance")
|
|
}
|
|
|
|
// Deserialize the number of values.
|
|
var values []treasuryValue
|
|
numValues, bytesRead := deserializeVLQ(data[offset:])
|
|
if bytesRead == 0 {
|
|
return nil, errDeserialize("unexpected end of data while reading " +
|
|
"number of value entries")
|
|
}
|
|
offset += bytesRead
|
|
|
|
// Deserialize individual values.
|
|
if numValues > 0 {
|
|
values = make([]treasuryValue, numValues)
|
|
for i := uint64(0); i < numValues; i++ {
|
|
flag, bytesRead := deserializeVLQ(data[offset:])
|
|
offset += bytesRead
|
|
if bytesRead == 0 {
|
|
return nil, errDeserialize(fmt.Sprintf("unexpected end of "+
|
|
"data while reading value flag #%d", i))
|
|
}
|
|
|
|
value, bytesRead := deserializeVLQ(data[offset:])
|
|
offset += bytesRead
|
|
if bytesRead == 0 {
|
|
return nil, errDeserialize(fmt.Sprintf("unexpected end of "+
|
|
"data while reading value amount #%d", i))
|
|
}
|
|
|
|
typ := treasuryValueType(byte(flag) & tvFlagTypMask)
|
|
amount := int64(value)
|
|
|
|
// Debits (tspends and fees) are negative but stored as
|
|
// positive, so negate the amount if needed.
|
|
if typ.IsDebit() {
|
|
amount = -amount
|
|
}
|
|
values[i] = treasuryValue{
|
|
typ: typ,
|
|
amount: amount,
|
|
}
|
|
}
|
|
}
|
|
|
|
var ts treasuryState
|
|
ts.balance = int64(balance)
|
|
ts.values = values
|
|
return &ts, nil
|
|
}
|
|
|
|
// dbPutTreasuryBalance inserts a treasury state record into the database.
|
|
func dbPutTreasuryBalance(dbTx database.Tx, hash chainhash.Hash, ts treasuryState) error {
|
|
// Serialize the current treasury state.
|
|
serializedData, err := serializeTreasuryState(ts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Store the current treasury state into the database.
|
|
meta := dbTx.Metadata()
|
|
bucket := meta.Bucket(treasuryBucketName)
|
|
return bucket.Put(hash[:], serializedData)
|
|
}
|
|
|
|
// dbFetchTreasuryBalance uses an existing database transaction to fetch the
|
|
// treasury state.
|
|
func dbFetchTreasuryBalance(dbTx database.Tx, hash chainhash.Hash) (*treasuryState, error) {
|
|
meta := dbTx.Metadata()
|
|
bucket := meta.Bucket(treasuryBucketName)
|
|
|
|
v := bucket.Get(hash[:])
|
|
if v == nil {
|
|
str := fmt.Sprintf("treasury db missing key: %v", hash)
|
|
return nil, errDbTreasury(str)
|
|
}
|
|
|
|
return deserializeTreasuryState(v)
|
|
}
|
|
|
|
// dbFetchTreasurySingle wraps dbFetchTreasuryBalance in a view.
|
|
func (b *BlockChain) dbFetchTreasurySingle(hash chainhash.Hash) (*treasuryState, error) {
|
|
var ts *treasuryState
|
|
err := b.db.View(func(dbTx database.Tx) error {
|
|
var err error
|
|
ts, err = dbFetchTreasuryBalance(dbTx, hash)
|
|
return err
|
|
})
|
|
return ts, err
|
|
}
|
|
|
|
// serializeTSpend serializes the TSpend data for use in the database.
|
|
// The format is as follows:
|
|
// Block []chainhash.Hash (blocks where TSpend was mined).
|
|
func serializeTSpend(blocks []chainhash.Hash) ([]byte, error) {
|
|
serializedData := new(bytes.Buffer)
|
|
err := binary.Write(serializedData, byteOrder, int64(len(blocks)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, v := range blocks {
|
|
err := binary.Write(serializedData, byteOrder, v[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return serializedData.Bytes(), nil
|
|
}
|
|
|
|
// deserializeTSpend deserializes a binary blob into a []chainhash.Hash.
|
|
func deserializeTSpend(data []byte) ([]chainhash.Hash, error) {
|
|
buf := bytes.NewReader(data)
|
|
var count int64
|
|
err := binary.Read(buf, byteOrder, &count)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read count: %w", err)
|
|
}
|
|
hashes := make([]chainhash.Hash, count)
|
|
for i := int64(0); i < count; i++ {
|
|
err := binary.Read(buf, byteOrder, &hashes[i])
|
|
if err != nil {
|
|
return nil,
|
|
fmt.Errorf("failed to read idx %v: %w", i, err)
|
|
}
|
|
}
|
|
|
|
return hashes, nil
|
|
}
|
|
|
|
// dbPutTSpend inserts a number of treasury tspends into a single database
|
|
// record. Note that this call is the low level write to the database. Use
|
|
// dbUpdateTSpend instead.
|
|
func dbPutTSpend(dbTx database.Tx, tx chainhash.Hash, blocks []chainhash.Hash) error {
|
|
// Serialize the current treasury state.
|
|
serializedData, err := serializeTSpend(blocks)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Store the current treasury state into the database.
|
|
meta := dbTx.Metadata()
|
|
bucket := meta.Bucket(treasuryTSpendBucketName)
|
|
return bucket.Put(tx[:], serializedData)
|
|
}
|
|
|
|
// errDbTSpend signifies that the provided hash was not found during a fetch.
|
|
type errDbTSpend string
|
|
|
|
// Error implements the error interface.
|
|
func (e errDbTSpend) Error() string {
|
|
return string(e)
|
|
}
|
|
|
|
// dbFetchTSpend uses an existing database transaction to fetch the block
|
|
// hashes that contains the provided transaction.
|
|
func dbFetchTSpend(dbTx database.Tx, tx chainhash.Hash) ([]chainhash.Hash, error) {
|
|
meta := dbTx.Metadata()
|
|
bucket := meta.Bucket(treasuryTSpendBucketName)
|
|
|
|
v := bucket.Get(tx[:])
|
|
if v == nil {
|
|
return nil, errDbTSpend(fmt.Sprintf("tspend db missing key: %v",
|
|
tx))
|
|
}
|
|
|
|
return deserializeTSpend(v)
|
|
}
|
|
|
|
// dbUpdateTSpend performs a read/modify/write operation on the provided
|
|
// transaction hash. It reads the record and appends the block hash and then
|
|
// writes it back to the database. Note that the append is dumb and does not
|
|
// deduplicate. This is ok because in practice a TX cannot appear in the same
|
|
// block more than once.
|
|
func dbUpdateTSpend(dbTx database.Tx, tx, block chainhash.Hash) error {
|
|
var derr errDbTSpend
|
|
hashes, err := dbFetchTSpend(dbTx, tx)
|
|
if err != nil && !errors.As(err, &derr) {
|
|
return err
|
|
}
|
|
|
|
hashes = append(hashes, block)
|
|
return dbPutTSpend(dbTx, tx, hashes)
|
|
}
|
|
|
|
// FetchTSpend returns the blocks a treasury spend tx was included in.
|
|
func (b *BlockChain) FetchTSpend(tspend chainhash.Hash) ([]chainhash.Hash, error) {
|
|
var hashes []chainhash.Hash
|
|
err := b.db.View(func(dbTx database.Tx) error {
|
|
var err error
|
|
hashes, err = dbFetchTSpend(dbTx, tspend)
|
|
return err
|
|
})
|
|
return hashes, err
|
|
}
|
|
|
|
// calculateTreasuryBalance calculates the treasury balance as of the provided
|
|
// node.
|
|
//
|
|
// The treasury balance for a given block is calculated as the balance of its
|
|
// parent block plus all maturing TADDs and TreasuryBases minus all maturing
|
|
// TSPENDS.
|
|
//
|
|
// The "maturing" TADDs, TreasuryBases and TSPENDS are those that were in the
|
|
// CoinbaseMaturity ancestor block of the passed node.
|
|
func (b *BlockChain) calculateTreasuryBalance(dbTx database.Tx, node *blockNode) int64 {
|
|
wantNode := node.RelativeAncestor(int64(b.chainParams.CoinbaseMaturity))
|
|
if wantNode == nil {
|
|
// Since the node does not exist we can safely assume the
|
|
// balance is 0. This is true at the beginning of the chain
|
|
// because before CoinbaseMaturity blocks there can be no
|
|
// mature treasurybase or funds from which to create a TADD.
|
|
return 0
|
|
}
|
|
|
|
// Current balance is in the parent node
|
|
ts, err := dbFetchTreasuryBalance(dbTx, node.parent.hash)
|
|
if err != nil {
|
|
// Since the node.parent.hash does not exist in the treasury db
|
|
// we can safely assume the balance is 0
|
|
return 0
|
|
}
|
|
|
|
// Fetch values that need to be added to the treasury balance.
|
|
wts, err := dbFetchTreasuryBalance(dbTx, wantNode.hash)
|
|
if err != nil {
|
|
// Since wantNode does not exist in the treasury db we can
|
|
// safely assume the balance is 0
|
|
return 0
|
|
}
|
|
|
|
// Add all TAdd values to the balance. Note that negative Values are
|
|
// TSpend.
|
|
var netValue int64
|
|
for _, v := range wts.values {
|
|
netValue += v.amount
|
|
}
|
|
|
|
return ts.balance + netValue
|
|
}
|
|
|
|
// dbPutTreasuryBalance inserts the current balance and the future treasury
|
|
// add/spend into the database.
|
|
func (b *BlockChain) dbPutTreasuryBalance(dbTx database.Tx, block *dcrutil.Block, node *blockNode) error {
|
|
// Calculate balance as of this node
|
|
balance := b.calculateTreasuryBalance(dbTx, node)
|
|
msgBlock := block.MsgBlock()
|
|
ts := treasuryState{
|
|
balance: balance,
|
|
values: make([]treasuryValue, 0, len(msgBlock.Transactions)*2),
|
|
}
|
|
trsyLog.Tracef("dbPutTreasuryBalance: %v start balance %v",
|
|
node.hash.String(), balance)
|
|
for _, v := range msgBlock.STransactions {
|
|
if stake.IsTAdd(v) {
|
|
// This is a TAdd, pull amount out of TxOut[0]. Note
|
|
// that TxOut[1], if it exists, contains the change
|
|
// output. We have to ignore change.
|
|
tv := treasuryValue{
|
|
typ: treasuryValueTAdd,
|
|
amount: v.TxOut[0].Value,
|
|
}
|
|
ts.values = append(ts.values, tv)
|
|
trsyLog.Tracef(" dbPutTreasuryBalance: balance TADD "+
|
|
"%v", tv.amount)
|
|
} else if stake.IsTreasuryBase(v) {
|
|
tv := treasuryValue{
|
|
typ: treasuryValueTBase,
|
|
amount: v.TxOut[0].Value,
|
|
}
|
|
ts.values = append(ts.values, tv)
|
|
trsyLog.Tracef(" dbPutTreasuryBalance: balance "+
|
|
"treasury base %v", tv.amount)
|
|
} else if stake.IsTSpend(v) {
|
|
// This is a TSpend, pull values out of block. Skip
|
|
// first TxOut since it is an OP_RETURN.
|
|
var totalOut int64
|
|
for _, vv := range v.TxOut[1:] {
|
|
tv := treasuryValue{
|
|
typ: treasuryValueTSpend,
|
|
amount: -vv.Value,
|
|
}
|
|
trsyLog.Tracef(" dbPutTreasuryBalance: "+
|
|
"balance TSPEND %v", tv.amount)
|
|
ts.values = append(ts.values, tv)
|
|
totalOut += vv.Value
|
|
}
|
|
|
|
// Fees are supposed to be stored as negative amounts
|
|
// in a treasuryValue value, so calculate it backwards
|
|
// from the usual way of `in - out`.
|
|
fee := totalOut - v.TxIn[0].ValueIn
|
|
tv := treasuryValue{
|
|
typ: treasuryValueFee,
|
|
amount: fee,
|
|
}
|
|
trsyLog.Tracef(" dbPutTreasuryBalance: "+
|
|
"balance fee %v", tv.amount)
|
|
ts.values = append(ts.values, tv)
|
|
}
|
|
}
|
|
|
|
hash := block.Hash()
|
|
return dbPutTreasuryBalance(dbTx, *hash, ts)
|
|
}
|
|
|
|
// dbPutTSpend inserts the TSpends that are included in this block to the
|
|
// database.
|
|
func (b *BlockChain) dbPutTSpend(dbTx database.Tx, block *dcrutil.Block) error {
|
|
hash := block.Hash()
|
|
msgBlock := block.MsgBlock()
|
|
trsyLog.Tracef("dbPutTSpend: processing block %v", hash)
|
|
for _, v := range msgBlock.STransactions {
|
|
if !stake.IsTSpend(v) {
|
|
continue
|
|
}
|
|
|
|
// Store TSpend and the block it was included in.
|
|
txHash := v.TxHash()
|
|
trsyLog.Tracef(" dbPutTSpend: tspend %v", txHash)
|
|
err := dbUpdateTSpend(dbTx, txHash, *hash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TreasuryBalanceInfo models information about the treasury balance as of a
|
|
// given block.
|
|
type TreasuryBalanceInfo struct {
|
|
// BlockHeight is the height of the requested block.
|
|
BlockHeight int64
|
|
|
|
// Balance is the balance of the treasury as of the requested block.
|
|
Balance uint64
|
|
|
|
// Updates specifies all additions to and spends from the treasury in
|
|
// the requested block.
|
|
Updates []int64
|
|
}
|
|
|
|
// TreasuryBalance returns treasury balance information as of the given block.
|
|
func (b *BlockChain) TreasuryBalance(hash *chainhash.Hash) (*TreasuryBalanceInfo, error) {
|
|
node := b.index.LookupNode(hash)
|
|
if node == nil || !b.index.CanValidate(node) {
|
|
return nil, unknownBlockError(hash)
|
|
}
|
|
|
|
// Treasury agenda is never active for the genesis block.
|
|
if node.parent == nil {
|
|
str := fmt.Sprintf("treasury balance not available for block %s", hash)
|
|
return nil, contextError(ErrNoTreasuryBalance, str)
|
|
}
|
|
|
|
// Ensure the treasury agenda is active as of the requested block.
|
|
var isActive bool
|
|
b.chainLock.Lock()
|
|
isActive, err := b.isTreasuryAgendaActive(node.parent)
|
|
b.chainLock.Unlock()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !isActive {
|
|
str := fmt.Sprintf("treasury balance not available for block %s", hash)
|
|
return nil, contextError(ErrNoTreasuryBalance, str)
|
|
}
|
|
|
|
// Load treasury balance information.
|
|
var ts *treasuryState
|
|
err = b.db.View(func(dbTx database.Tx) error {
|
|
ts, err = dbFetchTreasuryBalance(dbTx, node.hash)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
updates := make([]int64, len(ts.values))
|
|
for i := range ts.values {
|
|
updates[i] = ts.values[i].amount
|
|
}
|
|
|
|
return &TreasuryBalanceInfo{
|
|
BlockHeight: node.height,
|
|
Balance: uint64(ts.balance),
|
|
Updates: updates,
|
|
}, nil
|
|
}
|
|
|
|
// verifyTSpendSignature verifies that the provided signature and public key
|
|
// were the ones that signed the provided message transaction.
|
|
func verifyTSpendSignature(msgTx *wire.MsgTx, signature, pubKey []byte) error {
|
|
// Calculate signature hash.
|
|
sigHash, err := txscript.CalcSignatureHash(nil,
|
|
txscript.SigHashAll, msgTx, 0, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("CalcSignatureHash: %w", err)
|
|
}
|
|
|
|
// Lift Signature from bytes.
|
|
sig, err := schnorr.ParseSignature(signature)
|
|
if err != nil {
|
|
return fmt.Errorf("ParseSignature: %w", err)
|
|
}
|
|
|
|
// Lift public PI key from bytes.
|
|
pk, err := schnorr.ParsePubKey(pubKey)
|
|
if err != nil {
|
|
return fmt.Errorf("ParsePubKey: %w", err)
|
|
}
|
|
|
|
// Verify transaction was properly signed.
|
|
if !sig.Verify(sigHash, pk) {
|
|
return fmt.Errorf("Verify failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifyTSpendSignature verifies that the provided signature and public key
|
|
// were the ones that signed the provided message transaction.
|
|
//
|
|
// Note: This function should only be called with a valid TSpend.
|
|
func VerifyTSpendSignature(msgTx *wire.MsgTx, signature, pubKey []byte) error {
|
|
return verifyTSpendSignature(msgTx, signature, pubKey)
|
|
}
|
|
|
|
// sumPastTreasuryChanges sums up the amounts spent from and added to the
|
|
// treasury (respectively) found within the range (node-nbBlocks..node). Note
|
|
// that this sum is _inclusive_ of the passed block and is performed in
|
|
// descending order. Generally, the passed node will be a node immediately
|
|
// before a TVI block.
|
|
//
|
|
// It also returns the node immediately before the last checked node (that is,
|
|
// the node before node-nbBlocks).
|
|
func (b *BlockChain) sumPastTreasuryChanges(preTVINode *blockNode, nbBlocks uint64) (int64, int64, *blockNode, error) {
|
|
node := preTVINode
|
|
var spent, added int64
|
|
var derr errDbTreasury
|
|
for i := uint64(0); i < nbBlocks && node != nil; i++ {
|
|
ts, err := b.dbFetchTreasurySingle(node.hash)
|
|
if errors.As(err, &derr) {
|
|
// Record doesn't exist. Means we reached the end of
|
|
// when treasury records are available.
|
|
node = nil
|
|
continue
|
|
} else if err != nil {
|
|
return 0, 0, nil, err
|
|
}
|
|
|
|
// Range over values.
|
|
for _, v := range ts.values {
|
|
if v.typ.IsDebit() {
|
|
// treasuryValues record debits as negative
|
|
// amounts, so invert it here.
|
|
spent += -v.amount
|
|
} else {
|
|
added += v.amount
|
|
}
|
|
}
|
|
|
|
node = node.parent
|
|
}
|
|
|
|
return spent, added, node, nil
|
|
}
|
|
|
|
// maxTreasuryExpenditureDCP0006 returns the maximum amount of funds that can
|
|
// be spent from the treasury at the block after the provided node.
|
|
//
|
|
// This is code that was activated as part of the 'treasury' consensus upgrade
|
|
// as defined in DCP0006.
|
|
//
|
|
// See notes on maxTreasuryExpenditure().
|
|
func (b *BlockChain) maxTreasuryExpenditureDCP0006(preTVINode *blockNode) (int64, error) {
|
|
// The expenditure policy check is roughly speaking:
|
|
//
|
|
// "The sum of tspends inside an expenditure window cannot exceed
|
|
// the average of the tspends in the previous N windows in addition
|
|
// to an X% increase."
|
|
//
|
|
// So in order to calculate the maximum expenditure for the _next_
|
|
// block (the block after `preTVINode`, which must be a TVI block) we
|
|
// need to add all the tspends in the expenditure window that ends in
|
|
// `preTVINode` and subtract that amount from the average of the
|
|
// preceding (N) windows.
|
|
//
|
|
// Currently this function is pretty naive. It simply iterates through
|
|
// prior blocks one at a time. This is very expensive and may need to
|
|
// be rethought into a proper index.
|
|
|
|
policyWindow := b.chainParams.TreasuryVoteInterval *
|
|
b.chainParams.TreasuryVoteIntervalMultiplier *
|
|
b.chainParams.TreasuryExpenditureWindow
|
|
|
|
// Each policy window starts at a TVI block and ends at the block
|
|
// immediately prior to another TVI (inclusive of preTVINode).
|
|
//
|
|
// First: sum up tspends inside the most recent policyWindow.
|
|
spentRecentWindow, _, node, err := b.sumPastTreasuryChanges(preTVINode, policyWindow)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Next, sum up all tspends inside the N prior policy windows. If a
|
|
// given policy window does not have _any_ tspends, it isn't counted
|
|
// towards the average.
|
|
var spentPriorWindows int64
|
|
var nbNonEmptyWindows int64
|
|
for i := uint64(0); i < b.chainParams.TreasuryExpenditurePolicy && node != nil; i++ {
|
|
var spent int64
|
|
spent, _, node, err = b.sumPastTreasuryChanges(node, policyWindow)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if spent > 0 {
|
|
spentPriorWindows += spent
|
|
nbNonEmptyWindows++
|
|
}
|
|
}
|
|
|
|
// Calculate the average spent in each window. If there were _zero_
|
|
// prior windows with tspends, fall back to using the bootstrap
|
|
// average.
|
|
var avgSpentPriorWindows int64
|
|
if nbNonEmptyWindows > 0 {
|
|
avgSpentPriorWindows = spentPriorWindows / nbNonEmptyWindows
|
|
} else {
|
|
avgSpentPriorWindows = int64(b.chainParams.TreasuryExpenditureBootstrap)
|
|
}
|
|
|
|
// Treasury can spend up to 150% the average amount of the prior
|
|
// windows ("expenditure allowance").
|
|
avgPlusAllowance := avgSpentPriorWindows + avgSpentPriorWindows/2
|
|
|
|
// The maximum expenditure allowed for the next block is the difference
|
|
// between the maximum possible and what has already been spent in the most
|
|
// recent policy window. This is capped at zero on the lower end to account
|
|
// for cases where the policy _already_ spent more than the allowed.
|
|
var allowedToSpend int64
|
|
if avgPlusAllowance > spentRecentWindow {
|
|
allowedToSpend = avgPlusAllowance - spentRecentWindow
|
|
}
|
|
|
|
trsyLog.Tracef(" maxTSpendExpenditure: recentWindow %d priorWindows %d "+
|
|
"(%d non-empty) allowedToSpend %d", spentRecentWindow,
|
|
spentPriorWindows, nbNonEmptyWindows, allowedToSpend)
|
|
|
|
return allowedToSpend, nil
|
|
}
|
|
|
|
// maxTreasuryExpenditureDCP0007 returns the maximum amount of funds that can
|
|
// be spent from the treasury at the block after the provided node.
|
|
//
|
|
// This is code that was activated as part of the 'reverttreasurypolicy'
|
|
// consensus upgrade as defined in DCP0007.
|
|
//
|
|
// See notes on maxTreasuryExpenditure().
|
|
func (b *BlockChain) maxTreasuryExpenditureDCP0007(preTVINode *blockNode) (int64, error) {
|
|
// The expenditure policy check is roughly speaking:
|
|
//
|
|
// "The sum of tspends inside an expenditure window cannot exceed
|
|
// the total amount of income received by the treasury in the same
|
|
// window in addition to an X% increase."
|
|
//
|
|
// Currently this function is pretty naive. It simply iterates through
|
|
// prior blocks one at a time. This is very expensive and may need to
|
|
// be rethought into a proper index.
|
|
|
|
policyWindow := b.chainParams.TreasuryVoteInterval *
|
|
b.chainParams.TreasuryVoteIntervalMultiplier *
|
|
b.chainParams.TreasuryExpenditureWindow
|
|
|
|
// Each policy window starts at a TVI block and ends at the block
|
|
// immediately prior to another TVI (inclusive of preTVINode).
|
|
//
|
|
// First: sum up tspends, tadds and tbases inside the most recent
|
|
// policyWindow.
|
|
spentRecent, addedRecent, _, err := b.sumPastTreasuryChanges(preTVINode, policyWindow)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Treasury can spend up to 150% the amount received in the previous
|
|
// window.
|
|
addedPlusAllowance := addedRecent + addedRecent/2
|
|
|
|
// The maximum expenditure allowed for the next block is the difference
|
|
// between the maximum possible and what has already been spent in the most
|
|
// recent policy window. This is capped at zero on the lower end to account
|
|
// for cases where the policy _already_ spent more than the allowed.
|
|
var allowedToSpend int64
|
|
if addedPlusAllowance > spentRecent {
|
|
allowedToSpend = addedPlusAllowance - spentRecent
|
|
}
|
|
|
|
trsyLog.Tracef(" maxTSpendExpenditureDCP0007: spent %d, "+
|
|
"added %d, allowedToSpend %d", spentRecent,
|
|
addedRecent, allowedToSpend)
|
|
|
|
return allowedToSpend, nil
|
|
}
|
|
|
|
// maxTreasuryExpenditure returns the maximum amount of funds that can be spent
|
|
// from the treasury at the block after the provided node. A set of TSPENDs
|
|
// added to a TVI block that is a child to the passed node may spend up to the
|
|
// returned amount.
|
|
//
|
|
// The passed node MUST correspond to a node immediately prior to a TVI block.
|
|
func (b *BlockChain) maxTreasuryExpenditure(preTVINode *blockNode) (int64, error) {
|
|
isRevertPolicyActive, err := b.isRevertTreasuryPolicyActive(preTVINode)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if isRevertPolicyActive {
|
|
return b.maxTreasuryExpenditureDCP0007(preTVINode)
|
|
}
|
|
return b.maxTreasuryExpenditureDCP0006(preTVINode)
|
|
}
|
|
|
|
// MaxTreasuryExpenditure is the maximum amount of funds that can be spent from
|
|
// the treasury by a set of TSpends for a block that extends the given block
|
|
// hash. Function will return 0 if it is called on an invalid TVI.
|
|
func (b *BlockChain) MaxTreasuryExpenditure(preTVIBlock *chainhash.Hash) (int64, error) {
|
|
preTVINode := b.index.LookupNode(preTVIBlock)
|
|
if preTVINode == nil {
|
|
return 0, fmt.Errorf("unknown block %s", preTVIBlock)
|
|
}
|
|
|
|
if !standalone.IsTreasuryVoteInterval(uint64(preTVINode.height+1),
|
|
b.chainParams.TreasuryVoteInterval) {
|
|
return 0, nil
|
|
}
|
|
|
|
return b.maxTreasuryExpenditure(preTVINode)
|
|
}
|
|
|
|
// checkTSpendsExpenditure verifies that the sum of TSpend expenditures is
|
|
// within the allowable range for the chain ending in the given node. There is
|
|
// a hard requirement that the passed node is a block immediately prior to a
|
|
// TVI block (with the tspends assumed to be txs in a block that extends
|
|
// preTVINode).
|
|
//
|
|
// The expenditure check is performed against the balance at preTVINode.
|
|
//
|
|
// This function must be called with the block index read lock held.
|
|
func (b *BlockChain) checkTSpendsExpenditure(preTVINode *blockNode, totalTSpendAmount int64) error {
|
|
trsyLog.Tracef("checkTSpendExpenditure: processing %d tspent at height %d",
|
|
totalTSpendAmount, preTVINode.height+1)
|
|
if totalTSpendAmount == 0 {
|
|
// Nothing to do.
|
|
return nil
|
|
}
|
|
if totalTSpendAmount < 0 {
|
|
return fmt.Errorf("invalid precondition: totalTSpendAmount must "+
|
|
"not be negative (got %d)", totalTSpendAmount)
|
|
}
|
|
|
|
// Ensure that we are not depleting treasury.
|
|
var (
|
|
treasuryBalance int64
|
|
err error
|
|
)
|
|
err = b.db.View(func(dbTx database.Tx) error {
|
|
treasuryBalance = b.calculateTreasuryBalance(dbTx, preTVINode)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if treasuryBalance-totalTSpendAmount < 0 {
|
|
return fmt.Errorf("treasury balance may not become negative: "+
|
|
"balance %v spend %v", treasuryBalance, totalTSpendAmount)
|
|
}
|
|
trsyLog.Tracef(" checkTSpendExpenditure: balance %v spend %v",
|
|
treasuryBalance, totalTSpendAmount)
|
|
|
|
allowedToSpend, err := b.maxTreasuryExpenditure(preTVINode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if totalTSpendAmount > allowedToSpend {
|
|
return fmt.Errorf("treasury spend greater than allowed %v > %v",
|
|
totalTSpendAmount, allowedToSpend)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// checkTSpendExists verifies that the provided TSpend has not been mined in a
|
|
// block on the chain of prevNode.
|
|
func (b *BlockChain) checkTSpendExists(prevNode *blockNode, tspend chainhash.Hash) error {
|
|
trsyLog.Tracef(" checkTSpendExists: tspend %v", tspend)
|
|
|
|
var derr errDbTSpend
|
|
blocks, err := b.FetchTSpend(tspend)
|
|
if errors.As(err, &derr) {
|
|
// Record does not exist.
|
|
return nil
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Do fork detection on all blocks.
|
|
for _, v := range blocks {
|
|
// Lookup blockNode.
|
|
node := b.index.LookupNode(&v)
|
|
if node == nil {
|
|
// This should not happen.
|
|
trsyLog.Errorf(" checkTSpendExists: block not found "+
|
|
"%v tspend %v", v, tspend)
|
|
continue
|
|
}
|
|
|
|
if !node.IsAncestorOf(prevNode) {
|
|
trsyLog.Errorf(" checkTSpendExists: not ancestor "+
|
|
"block %v tspend %v", v, tspend)
|
|
continue
|
|
}
|
|
trsyLog.Errorf(" checkTSpendExists: is ancestor "+
|
|
"block %v tspend %v", v, tspend)
|
|
return fmt.Errorf("tspend has already been mined on this "+
|
|
"chain %v", tspend)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CheckTSpendExists verifies that the provided TSpend has not been mined in a
|
|
// block on the chain of the provided block hash.
|
|
func (b *BlockChain) CheckTSpendExists(prevHash, tspend chainhash.Hash) error {
|
|
prevNode := b.index.LookupNode(&prevHash)
|
|
if prevNode == nil {
|
|
return nil
|
|
}
|
|
return b.checkTSpendExists(prevNode, tspend)
|
|
}
|
|
|
|
// getVotes returns yes and no votes for the provided hash.
|
|
func getVotes(votes []stake.TreasuryVoteTuple, hash *chainhash.Hash) (yes int, no int) {
|
|
if votes == nil {
|
|
return
|
|
}
|
|
|
|
for _, v := range votes {
|
|
if !hash.IsEqual(&v.Hash) {
|
|
continue
|
|
}
|
|
|
|
switch v.Vote {
|
|
case stake.TreasuryVoteYes:
|
|
yes++
|
|
case stake.TreasuryVoteNo:
|
|
no++
|
|
default:
|
|
// Can't happen.
|
|
trsyLog.Criticalf("getVotes: invalid vote 0x%v", v.Vote)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// tspendVotes is a structure that contains a treasury vote tally for a given
|
|
// window.
|
|
type tspendVotes struct {
|
|
start uint32 // Start block
|
|
end uint32 // End block
|
|
yes int // Yes vote tally
|
|
no int // No vote tally
|
|
}
|
|
|
|
// tSpendCountVotes returns the vote tally for a given tspend up to the
|
|
// specified block. Note that this function errors if the block is outside the
|
|
// voting window for the given tspend.
|
|
func (b *BlockChain) tSpendCountVotes(prevNode *blockNode, tspend *dcrutil.Tx) (*tspendVotes, error) {
|
|
trsyLog.Tracef("tSpendCountVotes: processing tspend %v", tspend.Hash())
|
|
|
|
var (
|
|
t tspendVotes
|
|
err error
|
|
)
|
|
|
|
expiry := tspend.MsgTx().Expiry
|
|
t.start, t.end, err = standalone.CalcTSpendWindow(expiry,
|
|
b.chainParams.TreasuryVoteInterval,
|
|
b.chainParams.TreasuryVoteIntervalMultiplier)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nextHeight := prevNode.height + 1
|
|
trsyLog.Tracef(" tSpendCountVotes: nextHeight %v start %v expiry %v",
|
|
nextHeight, t.start, t.end)
|
|
|
|
// Ensure tspend is within the window.
|
|
if !standalone.InsideTSpendWindow(nextHeight,
|
|
expiry, b.chainParams.TreasuryVoteInterval,
|
|
b.chainParams.TreasuryVoteIntervalMultiplier) {
|
|
err = fmt.Errorf("tspend outside of window: nextHeight %v "+
|
|
"start %v expiry %v", nextHeight, t.start, expiry)
|
|
return nil, err
|
|
}
|
|
|
|
// Walk prevNode back to the start of the window and count votes.
|
|
node := prevNode
|
|
for {
|
|
trsyLog.Tracef(" tSpendCountVotes height %v start %v",
|
|
node.height, t.start)
|
|
if node.height < int64(t.start) {
|
|
break
|
|
}
|
|
|
|
trsyLog.Tracef(" tSpendCountVotes count votes: %v",
|
|
node.hash)
|
|
|
|
// Find SSGen and peel out votes.
|
|
var xblock *dcrutil.Block
|
|
xblock, err = b.fetchBlockByNode(node)
|
|
if err != nil {
|
|
// Should not happen.
|
|
return nil, err
|
|
}
|
|
for _, v := range xblock.STransactions() {
|
|
votes, err := stake.CheckSSGenVotes(v.MsgTx())
|
|
if err != nil {
|
|
// Not an SSGEN
|
|
continue
|
|
}
|
|
|
|
// Find our vote bits.
|
|
yes, no := getVotes(votes, tspend.Hash())
|
|
t.yes += yes
|
|
t.no += no
|
|
}
|
|
|
|
node = node.parent
|
|
if node == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
return &t, nil
|
|
}
|
|
|
|
// TSpendCountVotes tallies the votes given for the specified tspend during its
|
|
// voting interval, up to the specified block. It returns the number of yes and
|
|
// no votes found between the passed block's height and the start of voting.
|
|
//
|
|
// Note that this function errors if the block _after_ the specified block is
|
|
// outside the tpsend voting window. In particular, calling this function for
|
|
// the TVI block that ends the voting interval for a given tspend fails, since
|
|
// the next block is outside the voting interval.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (b *BlockChain) TSpendCountVotes(blockHash *chainhash.Hash, tspend *dcrutil.Tx) (yesVotes, noVotes int64, err error) {
|
|
b.index.RLock()
|
|
defer b.index.RUnlock()
|
|
|
|
prevNode := b.index.lookupNode(blockHash)
|
|
if prevNode == nil {
|
|
return 0, 0, unknownBlockError(blockHash)
|
|
}
|
|
tv, err := b.tSpendCountVotes(prevNode, tspend)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
return int64(tv.yes), int64(tv.no), nil
|
|
}
|
|
|
|
// checkTSpendHasVotes verifies that the provided TSpend has enough votes to be
|
|
// included in a block _after_ the provided block node. Such child node MUST be
|
|
// on a TVI.
|
|
func (b *BlockChain) checkTSpendHasVotes(prevNode *blockNode, tspend *dcrutil.Tx) error {
|
|
t, err := b.tSpendCountVotes(prevNode, tspend)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Passing criteria are 20% quorum and 60% yes.
|
|
maxVotes := uint32(b.chainParams.TicketsPerBlock) * (t.end - t.start)
|
|
quorum := uint64(maxVotes) * b.chainParams.TreasuryVoteQuorumMultiplier /
|
|
b.chainParams.TreasuryVoteQuorumDivisor
|
|
numVotesCast := uint64(t.yes + t.no)
|
|
if numVotesCast < quorum {
|
|
return fmt.Errorf("quorum not met: yes %v no %v "+
|
|
"quorum %v max %v", t.yes, t.no, quorum, maxVotes)
|
|
}
|
|
|
|
// Calculate max possible votes that are left in this window.
|
|
curBlockHeight := uint32(prevNode.height + 1)
|
|
remainingBlocks := t.end - curBlockHeight
|
|
maxRemainingVotes := uint64(remainingBlocks *
|
|
uint32(b.chainParams.TicketsPerBlock))
|
|
|
|
// Treat maxRemainingVotes as possible no votes. This enables short
|
|
// circuiting the overall TSpend vote but can only pass if the yes'
|
|
// can't drop below the required threshold.
|
|
requiredVotes := (numVotesCast + maxRemainingVotes) *
|
|
b.chainParams.TreasuryVoteRequiredMultiplier /
|
|
b.chainParams.TreasuryVoteRequiredDivisor
|
|
if uint64(t.yes) < requiredVotes {
|
|
return fmt.Errorf("not enough yes votes: yes %v no %v "+
|
|
"quorum %v max %v required %v maxRemainingVotes %v",
|
|
t.yes, t.no, quorum, maxVotes, requiredVotes,
|
|
maxRemainingVotes)
|
|
}
|
|
|
|
trsyLog.Debugf("Treasury spend %v passed (yes %v, no %v, quorum %v, "+
|
|
"required %v)", tspend.Hash(), t.yes, t.no, quorum, requiredVotes)
|
|
|
|
return nil
|
|
}
|
|
|
|
// CheckTSpendHasVotes checks whether the given tspend has enough votes to be
|
|
// included in a block _after_ the specified prevHash block.
|
|
//
|
|
// Such child block MUST be on a TVI, otherwise the result of this function may
|
|
// not correspond to the behavior of the consensus rules.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (b *BlockChain) CheckTSpendHasVotes(prevHash chainhash.Hash, tspend *dcrutil.Tx) error {
|
|
prevNode := b.index.LookupNode(&prevHash)
|
|
if prevNode == nil {
|
|
return unknownBlockError(&prevHash)
|
|
}
|
|
return b.checkTSpendHasVotes(prevNode, tspend)
|
|
}
|