This implements the agenda for voting on changing the hash function to BLAKE3, resetting the target difficulty, and changing the difficulty algorithm to ASERT (Absolutely Scheduled Exponentially weighted Rising Targets) as defined in DCP0011 along with consensus tests. In terms of the overall effects, this includes updates to: - The validation logic for the proof of work hash and required block difficulty - Enforcement of block submission via the getwork and submitblock RPCs - Mining template generation - The output of the getwork and notifywork RPCs - The internal CPU miner Also note that this does not implement the block version bump that will ultimately be needed by the mining code since there are multiple consensus votes gated behind it and will therefore be done separately. The following is an overview of the changes: - Introduce convenience function for determining if the vote passed and is now active - Introduce convenience function for determining whether or not the agenda is forced active on networks other than the main network - Modify block validation to enforce BLAKE3 as the proof of work hash algorithm in accordance with the state of the vote - Modify block validation to enforce target difficulties per the ASERT algorithm based on a reset initial starting difficulty in accordance with the state of the vote - Update internal CPU miner to solve blocks with either BLAKE256 or BLAKE3 in accordance with the state of the vote - Update the getwork, notifywork, and submitblock RPCs to support BLAKE3 in accordance with the state of the vote - Add tests for determining if the agenda is active for both mainnet and testnet - Add tests for the getwork RPC including submission of a block solved with BLAKE3 - Add tests to ensure proper behavior for the hash function and difficulty semantics before and after the vote agenda is active
674 lines
22 KiB
Go
674 lines
22 KiB
Go
// Copyright (c) 2020-2023 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package mining
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
"github.com/decred/dcrd/chaincfg/v3"
|
|
"github.com/decred/dcrd/dcrutil/v4"
|
|
"github.com/decred/dcrd/internal/blockchain"
|
|
"github.com/decred/dcrd/txscript/v4"
|
|
"github.com/decred/dcrd/txscript/v4/stdaddr"
|
|
"github.com/decred/dcrd/wire"
|
|
)
|
|
|
|
// TestNewBlockTemplateBasicErrorScenarios tests various basic error scenarios
|
|
// that can occur during new block template generation.
|
|
func TestNewBlockTemplateBasicErrorScenarios(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Create a new mining harness instance.
|
|
harness, _, err := newMiningHarness(chaincfg.MainNetParams())
|
|
if err != nil {
|
|
t.Fatalf("error creating mining harness: %v", err)
|
|
}
|
|
|
|
// Create a test address for use in template generation.
|
|
address, err := stdaddr.DecodeAddress("Dsi8CRt85xYyempXs7ZPL1rBxvDdAGZmgsg",
|
|
harness.chainParams)
|
|
if err != nil {
|
|
t.Fatalf("error decoding address: %v", err)
|
|
}
|
|
|
|
// Test error retrieving standard verify flags.
|
|
standardVerifyFlags := harness.policy.StandardVerifyFlags
|
|
var errFlags = errors.New("error retrieving standard verify flags")
|
|
harness.policy.StandardVerifyFlags = func() (txscript.ScriptFlags, error) {
|
|
return 0, errFlags
|
|
}
|
|
_, err = harness.generator.NewBlockTemplate(address)
|
|
if !errors.Is(err, errFlags) {
|
|
t.Fatalf("unexpected error retrieving standard verify flags -- got %v, "+
|
|
"want %v", err, errFlags)
|
|
}
|
|
harness.policy.StandardVerifyFlags = standardVerifyFlags
|
|
|
|
// Test error retrieving treasury agenda.
|
|
var errTreasuryAgenda = errors.New("error retrieving treasury agenda")
|
|
harness.chain.isTreasuryAgendaActiveErr = errTreasuryAgenda
|
|
_, err = harness.generator.NewBlockTemplate(address)
|
|
if !errors.Is(err, errTreasuryAgenda) {
|
|
t.Fatalf("unexpected error retrieving treasury agenda -- got %v, want %v",
|
|
err, errTreasuryAgenda)
|
|
}
|
|
harness.chain.isTreasuryAgendaActiveErr = nil
|
|
}
|
|
|
|
// TestNewBlockTemplate tests the generation of a new block template containing
|
|
// regular and vote transactions.
|
|
func TestNewBlockTemplate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Create a new mining harness instance.
|
|
harness, spendableOuts, err := newMiningHarness(chaincfg.MainNetParams())
|
|
if err != nil {
|
|
t.Fatalf("error creating mining harness: %v", err)
|
|
}
|
|
|
|
// Create a test address for use in template generation.
|
|
address, err := stdaddr.DecodeAddress("Dsi8CRt85xYyempXs7ZPL1rBxvDdAGZmgsg",
|
|
harness.chainParams)
|
|
if err != nil {
|
|
t.Fatalf("error decoding address: %v", err)
|
|
}
|
|
|
|
// Define a munger to apply transaction fees.
|
|
applyTxFee := func(fee int64) func(*wire.MsgTx) {
|
|
return func(tx *wire.MsgTx) {
|
|
tx.TxOut[0].Value -= fee
|
|
}
|
|
}
|
|
|
|
// Create additional transactions from the first spendable output provided by
|
|
// the harness.
|
|
const numTxs = 6
|
|
txs := make([]*dcrutil.Tx, numTxs)
|
|
baseTx, err := harness.CreateSignedTx(spendableOuts, uint32(numTxs))
|
|
if err != nil {
|
|
t.Fatalf("unable to create transaction: %v", err)
|
|
}
|
|
harness.AddFakeUTXO(baseTx, harness.chain.bestState.Height, 1,
|
|
harness.chain.isTreasuryAgendaActive)
|
|
for i := 0; i < numTxs; i++ {
|
|
tx, err := harness.CreateSignedTx([]spendableOutput{
|
|
txOutToSpendableOut(baseTx, uint32(i), wire.TxTreeRegular)}, 1,
|
|
applyTxFee(5000))
|
|
if err != nil {
|
|
t.Fatalf("unable to create transaction: %v", err)
|
|
}
|
|
txs[i] = tx
|
|
}
|
|
|
|
// Create ticket purchase transactions spending the outputs of the prior
|
|
// regular transactions.
|
|
const numVotes = 5
|
|
tickets := make([]*dcrutil.Tx, numVotes)
|
|
ticketHashes := make([]chainhash.Hash, numVotes)
|
|
for i := 0; i < numVotes; i++ {
|
|
ticket, err := harness.CreateTicketPurchase(txs[i], 40000)
|
|
if err != nil {
|
|
t.Fatalf("unable to create ticket purchase transaction: %v", err)
|
|
}
|
|
tickets[i] = ticket
|
|
ticketHashes[i] = ticket.MsgTx().TxHash()
|
|
}
|
|
|
|
// Add the ticket outputs as utxos to fake their existence. Use one after
|
|
// the stake enabled height for the height of the fake utxos to ensure they
|
|
// are mature for the votes cast at stake validation height below.
|
|
harness.chain.bestState.Height = harness.chainParams.StakeEnabledHeight + 1
|
|
for i, ticket := range tickets {
|
|
harness.AddFakeUTXO(ticket, harness.chain.bestState.Height, uint32(i+1),
|
|
harness.chain.isTreasuryAgendaActive)
|
|
}
|
|
|
|
// Create votes on a block at stake validation height using the previously
|
|
// created tickets.
|
|
harness.chain.bestState.Height = harness.chainParams.StakeValidationHeight
|
|
harness.chain.bestState.NextWinningTickets = ticketHashes
|
|
votes := make([]*dcrutil.Tx, numVotes)
|
|
for i, ticket := range tickets {
|
|
vote, err := harness.CreateVote(ticket)
|
|
if err != nil {
|
|
t.Fatalf("unable to create vote: %v", err)
|
|
}
|
|
votes[i] = vote
|
|
}
|
|
|
|
// Add vote transactions to the tx source.
|
|
for _, vote := range votes {
|
|
_, err = harness.AddTransactionToTxSource(vote)
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to the tx source: %v", err)
|
|
}
|
|
}
|
|
|
|
// Add remaining regular transactions to the tx source.
|
|
for i := numVotes; i < numTxs; i++ {
|
|
_, err = harness.AddTransactionToTxSource(txs[i])
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to the tx source: %v", err)
|
|
}
|
|
}
|
|
|
|
// Generate a new block template.
|
|
blockTemplate, err := harness.generator.NewBlockTemplate(address)
|
|
if err != nil {
|
|
t.Fatalf("unexpected err generating block template: %v", err)
|
|
}
|
|
|
|
// Validate the number of transactions in the generated block template.
|
|
gotTx := len(blockTemplate.Block.Transactions)
|
|
wantTx := numTxs - numVotes + 1 // + 1 for coinbase.
|
|
if gotTx != wantTx {
|
|
t.Fatalf("unexpected number of transactions in template -- got %v, want %v",
|
|
gotTx, wantTx)
|
|
}
|
|
|
|
// Validate the number of stake transactions in the generated block template.
|
|
gotStx := len(blockTemplate.Block.STransactions)
|
|
wantStx := numVotes + 1 // + 1 for stakebase.
|
|
if gotStx != wantStx {
|
|
t.Fatalf("unexpected number of stake transactions in template -- got %v, "+
|
|
"want %v", gotStx, wantStx)
|
|
}
|
|
|
|
// Validate that the block is sane. These checks are context free.
|
|
block := dcrutil.NewBlock(blockTemplate.Block)
|
|
err = blockchain.CheckBlockSanity(block, harness.generator.cfg.TimeSource,
|
|
harness.chainParams)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error when checking block sanity: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestNewBlockTemplateAutoRevocations tests the generation of a new block with
|
|
// automatic ticket revocations enabled.
|
|
func TestNewBlockTemplateAutoRevocations(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Create a new mining harness instance.
|
|
harness, spendableOuts, err := newMiningHarness(chaincfg.MainNetParams())
|
|
if err != nil {
|
|
t.Fatalf("error creating mining harness: %v", err)
|
|
}
|
|
|
|
// Activate the automatic ticket revocations agenda.
|
|
harness.chain.isAutoRevocationsAgendaActive = true
|
|
|
|
// Create a test address for use in template generation.
|
|
address, err := stdaddr.DecodeAddress("Dsi8CRt85xYyempXs7ZPL1rBxvDdAGZmgsg",
|
|
harness.chainParams)
|
|
if err != nil {
|
|
t.Fatalf("error decoding address: %v", err)
|
|
}
|
|
|
|
// Define a munger to apply transaction fees.
|
|
applyTxFee := func(fee int64) func(*wire.MsgTx) {
|
|
return func(tx *wire.MsgTx) {
|
|
tx.TxOut[0].Value -= fee
|
|
}
|
|
}
|
|
|
|
// Create additional transactions from the first spendable output provided by
|
|
// the harness.
|
|
const numTxs = 11
|
|
txs := make([]*dcrutil.Tx, numTxs)
|
|
baseTx, err := harness.CreateSignedTx(spendableOuts, uint32(numTxs))
|
|
if err != nil {
|
|
t.Fatalf("unable to create transaction: %v", err)
|
|
}
|
|
harness.AddFakeUTXO(baseTx, harness.chain.bestState.Height, 1,
|
|
harness.chain.isTreasuryAgendaActive)
|
|
for i := 0; i < numTxs; i++ {
|
|
tx, err := harness.CreateSignedTx([]spendableOutput{
|
|
txOutToSpendableOut(baseTx, uint32(i), wire.TxTreeRegular)}, 1,
|
|
applyTxFee(5000))
|
|
if err != nil {
|
|
t.Fatalf("unable to create transaction: %v", err)
|
|
}
|
|
txs[i] = tx
|
|
}
|
|
|
|
// Create ticket purchase transactions spending the outputs of the prior
|
|
// regular transactions.
|
|
const numTickets = 10
|
|
tickets := make([]*dcrutil.Tx, numTickets)
|
|
ticketHashes := make([]chainhash.Hash, numTickets)
|
|
for i := 0; i < numTickets; i++ {
|
|
ticket, err := harness.CreateTicketPurchase(txs[i], 40000)
|
|
if err != nil {
|
|
t.Fatalf("unable to create ticket purchase transaction: %v", err)
|
|
}
|
|
tickets[i] = ticket
|
|
ticketHashes[i] = ticket.MsgTx().TxHash()
|
|
}
|
|
|
|
// Add the ticket outputs as utxos to fake their existence. Use one after
|
|
// the stake enabled height for the height of the fake utxos to ensure they
|
|
// are mature for the votes cast at stake validation height below.
|
|
harness.chain.bestState.Height = harness.chainParams.StakeEnabledHeight + 1
|
|
for i, ticket := range tickets {
|
|
harness.AddFakeUTXO(ticket, harness.chain.bestState.Height, uint32(i+1),
|
|
harness.chain.isTreasuryAgendaActive)
|
|
}
|
|
|
|
// Create votes on a block at stake validation height using the previously
|
|
// created tickets.
|
|
//
|
|
// Create votes for 3 out of 5 winning tickets. The 2 remaining winning
|
|
// tickets will be missed and will need to be revoked.
|
|
winningTickets := ticketHashes[:5]
|
|
voteTickets := winningTickets[:3]
|
|
missedThisBlock := winningTickets[3:]
|
|
harness.chain.bestState.Height = harness.chainParams.StakeValidationHeight
|
|
harness.chain.bestState.NextWinningTickets = winningTickets
|
|
numVotes := len(voteTickets)
|
|
votes := make([]*dcrutil.Tx, numVotes)
|
|
for i, ticket := range tickets[:numVotes] {
|
|
vote, err := harness.CreateVote(ticket)
|
|
if err != nil {
|
|
t.Fatalf("unable to create vote: %v", err)
|
|
}
|
|
votes[i] = vote
|
|
}
|
|
|
|
// Add vote transactions to the tx source.
|
|
for _, vote := range votes {
|
|
_, err = harness.AddTransactionToTxSource(vote)
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to the tx source: %v", err)
|
|
}
|
|
}
|
|
|
|
// Add 2 of the remaining non-winning tickets to the list of tickets expiring
|
|
// next block to simulate expiring tickets that will need to be revoked.
|
|
expiredThisBlock := ticketHashes[5:7]
|
|
harness.chain.bestState.NextExpiringTickets = expiredThisBlock
|
|
|
|
// Add 2 of the remaining non-winning tickets to the list of tickets that were
|
|
// already missed.
|
|
missedPreviously := ticketHashes[7:9]
|
|
harness.chain.bestState.MissedTickets = missedPreviously
|
|
|
|
// Use 1 ticket as a ticket that is ineligible to vote or be revoked.
|
|
ineligibleTicket := ticketHashes[9]
|
|
|
|
// Add an additional vote transaction with a bad input. This ensures that a
|
|
// revocation is still created for a missed ticket due to a vote transaction
|
|
// failing validation.
|
|
vote, err := harness.CreateVote(tickets[numVotes])
|
|
if err != nil {
|
|
t.Fatalf("unable to create vote: %v", err)
|
|
}
|
|
_, err = harness.AddTransactionToTxSource(vote)
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to the tx source: %v", err)
|
|
}
|
|
vote.MsgTx().TxIn[0].ValueIn = 0
|
|
|
|
// Get the best header bytes.
|
|
bestStateHash := harness.chain.bestState.Hash
|
|
bestHeader, err := harness.chain.HeaderByHash(&bestStateHash)
|
|
if err != nil {
|
|
t.Fatalf("unable to get tip block header %v: %v", bestStateHash, err)
|
|
}
|
|
bestHeaderBytes, err := bestHeader.Bytes()
|
|
if err != nil {
|
|
t.Fatalf("failed to serialize header for block %v: %v", bestStateHash,
|
|
err)
|
|
}
|
|
|
|
// Add revocations for tickets that were previously missed.
|
|
for _, ticketHash := range harness.chain.bestState.MissedTickets {
|
|
txDesc, err := harness.generator.createRevocationFromTicket(&ticketHash,
|
|
harness.chain.utxos, bestHeaderBytes,
|
|
harness.chain.isTreasuryAgendaActive)
|
|
if err != nil {
|
|
t.Fatalf("failed to create revocation for ticket %v: %v", ticketHash, err)
|
|
}
|
|
_, err = harness.AddTransactionToTxSource(txDesc.Tx)
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to the tx source: %v", err)
|
|
}
|
|
}
|
|
|
|
// Add a revocation that tries to spend an ineligible ticket.
|
|
txDesc, err := harness.generator.createRevocationFromTicket(&ineligibleTicket,
|
|
harness.chain.utxos, bestHeaderBytes, harness.chain.isTreasuryAgendaActive)
|
|
if err != nil {
|
|
t.Fatalf("failed to create revocation for ticket %v: %v", ineligibleTicket,
|
|
err)
|
|
}
|
|
_, err = harness.AddTransactionToTxSource(txDesc.Tx)
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to the tx source: %v", err)
|
|
}
|
|
|
|
// Add remaining regular transactions to the tx source.
|
|
for i := numVotes; i < numTxs; i++ {
|
|
_, err = harness.AddTransactionToTxSource(txs[i])
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to the tx source: %v", err)
|
|
}
|
|
}
|
|
|
|
// Generate a new block template.
|
|
blockTemplate, err := harness.generator.NewBlockTemplate(address)
|
|
if err != nil {
|
|
t.Fatalf("unexpected err generating block template: %v", err)
|
|
}
|
|
|
|
// Validate the number of transactions in the generated block template.
|
|
gotTx := len(blockTemplate.Block.Transactions)
|
|
wantTx := numTxs - numVotes + 1 // + 1 for coinbase.
|
|
if gotTx != wantTx {
|
|
t.Fatalf("unexpected number of transactions in template -- got %v, want %v",
|
|
gotTx, wantTx)
|
|
}
|
|
|
|
// Determine the expected number of revocation transactions.
|
|
numAutoRevocations := len(missedThisBlock) + len(expiredThisBlock)
|
|
numTxSourceRevocations := len(missedPreviously)
|
|
numRevocations := numAutoRevocations + numTxSourceRevocations
|
|
|
|
// Validate the number of stake transactions in the generated block template.
|
|
gotStx := len(blockTemplate.Block.STransactions)
|
|
wantStx := numVotes + numRevocations + 1 // + 1 for stakebase.
|
|
if gotStx != wantStx {
|
|
t.Fatalf("unexpected number of stake transactions in template -- got %v, "+
|
|
"want %v", gotStx, wantStx)
|
|
}
|
|
|
|
// Validate that the block is sane. These checks are context free.
|
|
block := dcrutil.NewBlock(blockTemplate.Block)
|
|
err = blockchain.CheckBlockSanity(block, harness.generator.cfg.TimeSource,
|
|
harness.chainParams)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error when checking block sanity: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestNewBlockTemplateAutoRevocationsVotesOnly tests that auto revocations are
|
|
// properly added to blocks that only contain vote transactions.
|
|
func TestNewBlockTemplateAutoRevocationsVotesOnly(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Create a new mining harness instance.
|
|
harness, spendableOuts, err := newMiningHarness(chaincfg.MainNetParams())
|
|
if err != nil {
|
|
t.Fatalf("error creating mining harness: %v", err)
|
|
}
|
|
|
|
// Activate the automatic ticket revocations agenda.
|
|
harness.chain.isAutoRevocationsAgendaActive = true
|
|
|
|
// Create a test address for use in template generation.
|
|
address, err := stdaddr.DecodeAddress("Dsi8CRt85xYyempXs7ZPL1rBxvDdAGZmgsg",
|
|
harness.chainParams)
|
|
if err != nil {
|
|
t.Fatalf("error decoding address: %v", err)
|
|
}
|
|
|
|
// Define a munger to apply transaction fees.
|
|
applyTxFee := func(fee int64) func(*wire.MsgTx) {
|
|
return func(tx *wire.MsgTx) {
|
|
tx.TxOut[0].Value -= fee
|
|
}
|
|
}
|
|
|
|
// Create additional transactions from the first spendable output provided by
|
|
// the harness.
|
|
const numTxs = 6
|
|
txs := make([]*dcrutil.Tx, numTxs)
|
|
baseTx, err := harness.CreateSignedTx(spendableOuts, uint32(numTxs))
|
|
if err != nil {
|
|
t.Fatalf("unable to create transaction: %v", err)
|
|
}
|
|
harness.AddFakeUTXO(baseTx, harness.chain.bestState.Height, 1,
|
|
harness.chain.isTreasuryAgendaActive)
|
|
for i := 0; i < numTxs; i++ {
|
|
tx, err := harness.CreateSignedTx([]spendableOutput{
|
|
txOutToSpendableOut(baseTx, uint32(i), wire.TxTreeRegular)}, 1,
|
|
applyTxFee(5000))
|
|
if err != nil {
|
|
t.Fatalf("unable to create transaction: %v", err)
|
|
}
|
|
txs[i] = tx
|
|
}
|
|
|
|
// Create ticket purchase transactions spending the outputs of the prior
|
|
// regular transactions.
|
|
const numTickets = 5
|
|
tickets := make([]*dcrutil.Tx, numTickets)
|
|
ticketHashes := make([]chainhash.Hash, numTickets)
|
|
for i := 0; i < numTickets; i++ {
|
|
ticket, err := harness.CreateTicketPurchase(txs[i], 40000)
|
|
if err != nil {
|
|
t.Fatalf("unable to create ticket purchase transaction: %v", err)
|
|
}
|
|
tickets[i] = ticket
|
|
ticketHashes[i] = ticket.MsgTx().TxHash()
|
|
}
|
|
|
|
// Add the ticket outputs as utxos to fake their existence. Use one after
|
|
// the stake enabled height for the height of the fake utxos to ensure they
|
|
// are mature for the votes cast at stake validation height below.
|
|
harness.chain.bestState.Height = harness.chainParams.StakeEnabledHeight + 1
|
|
for i, ticket := range tickets {
|
|
harness.AddFakeUTXO(ticket, harness.chain.bestState.Height, uint32(i+1),
|
|
harness.chain.isTreasuryAgendaActive)
|
|
}
|
|
|
|
// Create votes on a block at stake validation height using the previously
|
|
// created tickets.
|
|
//
|
|
// Create votes for 3 out of 5 winning tickets. The 2 remaining winning
|
|
// tickets will be missed and will need to be revoked.
|
|
winningTickets := ticketHashes[:5]
|
|
voteTickets := winningTickets[:3]
|
|
missedThisBlock := winningTickets[3:]
|
|
harness.chain.bestState.Height = harness.chainParams.StakeValidationHeight
|
|
harness.chain.bestState.NextWinningTickets = winningTickets
|
|
numVotes := len(voteTickets)
|
|
votes := make([]*dcrutil.Tx, numVotes)
|
|
for i, ticket := range tickets[:numVotes] {
|
|
vote, err := harness.CreateVote(ticket)
|
|
if err != nil {
|
|
t.Fatalf("unable to create vote: %v", err)
|
|
}
|
|
votes[i] = vote
|
|
}
|
|
|
|
// Add vote transactions to the tx source.
|
|
for _, vote := range votes {
|
|
_, err = harness.AddTransactionToTxSource(vote)
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to the tx source: %v", err)
|
|
}
|
|
}
|
|
|
|
// Generate a new block template.
|
|
blockTemplate, err := harness.generator.NewBlockTemplate(address)
|
|
if err != nil {
|
|
t.Fatalf("unexpected err generating block template: %v", err)
|
|
}
|
|
|
|
// Determine the expected number of revocation transactions.
|
|
numAutoRevocations := len(missedThisBlock)
|
|
|
|
// Validate the number of stake transactions in the generated block template.
|
|
gotStx := len(blockTemplate.Block.STransactions)
|
|
wantStx := numVotes + numAutoRevocations + 1 // + 1 for stakebase.
|
|
if gotStx != wantStx {
|
|
t.Fatalf("unexpected number of stake transactions in template -- got %v, "+
|
|
"want %v", gotStx, wantStx)
|
|
}
|
|
|
|
// Validate that the block is sane. These checks are context free.
|
|
block := dcrutil.NewBlock(blockTemplate.Block)
|
|
err = blockchain.CheckBlockSanity(block, harness.generator.cfg.TimeSource,
|
|
harness.chainParams)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error when checking block sanity: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestSortParentsByVotes ensures the function that sorts parent blocks based on
|
|
// the number of votes available for them and the current tip block works as
|
|
// intended, including reorg prevention for an equal number of votes.
|
|
func TestSortParentsByVotes(t *testing.T) {
|
|
params := chaincfg.MainNetParams()
|
|
|
|
tests := []struct {
|
|
name string // test description
|
|
numBlocks uint8 // number of sibling blocks to generate
|
|
votes []uint8 // mock votes to generate per block
|
|
curTip uint8 // index of the current tip block
|
|
want []uint8 // indexes of expected sorted blocks
|
|
}{{
|
|
name: "0 blocks, 0 votes (sanity check for impossible condition)",
|
|
numBlocks: 0,
|
|
votes: nil,
|
|
curTip: 0,
|
|
want: nil,
|
|
}, {
|
|
name: "1 block, 5 votes",
|
|
numBlocks: 1,
|
|
votes: []uint8{5},
|
|
curTip: 0,
|
|
want: []uint8{0},
|
|
}, {
|
|
name: "1 block, 4 votes",
|
|
numBlocks: 1,
|
|
votes: []uint8{4},
|
|
curTip: 0,
|
|
want: []uint8{0},
|
|
}, {
|
|
name: "1 block, 3 votes",
|
|
numBlocks: 1,
|
|
votes: []uint8{3},
|
|
curTip: 0,
|
|
want: []uint8{0},
|
|
}, {
|
|
name: "1 block, 2 votes (no useful blocks)",
|
|
numBlocks: 1,
|
|
votes: []uint8{2},
|
|
curTip: 0,
|
|
want: nil,
|
|
}, {
|
|
name: "2 blocks, votes{0:5, 1:5}, tip block 0 (no reorg)",
|
|
numBlocks: 2,
|
|
votes: []uint8{5, 5},
|
|
curTip: 0,
|
|
want: []uint8{0, 1},
|
|
}, {
|
|
name: "2 blocks, votes{0:5, 1:5}, tip block 1 (no reorg)",
|
|
numBlocks: 2,
|
|
votes: []uint8{5, 5},
|
|
curTip: 1,
|
|
want: []uint8{1, 0},
|
|
}, {
|
|
name: "2 blocks, votes{0:4, 1:5}, tip block 0",
|
|
numBlocks: 2,
|
|
votes: []uint8{4, 5},
|
|
curTip: 0,
|
|
want: []uint8{1, 0},
|
|
}, {
|
|
name: "2 blocks, votes{0:4, 1:5}, tip block 1",
|
|
numBlocks: 2,
|
|
votes: []uint8{4, 5},
|
|
curTip: 1,
|
|
want: []uint8{1, 0},
|
|
}, {
|
|
name: "3 blocks, votes{0:4, 1:5, 2:2}, tip block 0 (2 not useful)",
|
|
numBlocks: 3,
|
|
votes: []uint8{4, 5, 2},
|
|
curTip: 0,
|
|
want: []uint8{1, 0},
|
|
}, {
|
|
name: "3 blocks, votes{0:3, 1:5, 2:4}, tip block 0",
|
|
numBlocks: 3,
|
|
votes: []uint8{3, 5, 4},
|
|
curTip: 0,
|
|
want: []uint8{1, 2, 0},
|
|
}, {
|
|
name: "3 blocks, votes{0:3, 1:5, 2:4}, tip block 2",
|
|
numBlocks: 3,
|
|
votes: []uint8{3, 5, 4},
|
|
curTip: 2,
|
|
want: []uint8{1, 2, 0},
|
|
}, {
|
|
name: "3 blocks, votes{0:3, 1:3, 2:4}, tip block 1",
|
|
numBlocks: 3,
|
|
votes: []uint8{3, 3, 4},
|
|
curTip: 1,
|
|
want: []uint8{2, 0, 1},
|
|
}}
|
|
|
|
// nextHash is a convenience function that generates a unique hash each time
|
|
// it is called.
|
|
var genHashIndex uint32
|
|
nextHash := func() chainhash.Hash {
|
|
var buf [4]byte
|
|
binary.LittleEndian.PutUint32(buf[:], genHashIndex)
|
|
genHashIndex++
|
|
|
|
return chainhash.HashH(buf[:])
|
|
}
|
|
|
|
for _, test := range tests {
|
|
// Generate mock blocks and votes based on test data.
|
|
blockHashes := make([]chainhash.Hash, test.numBlocks)
|
|
for i := uint8(0); i < test.numBlocks; i++ {
|
|
blockHashes[i] = nextHash()
|
|
}
|
|
votes := make(map[chainhash.Hash][]VoteDesc, len(test.votes))
|
|
for i, numVotes := range test.votes {
|
|
blockVotedOn := blockHashes[i]
|
|
voteDescs := make([]VoteDesc, 0, numVotes)
|
|
for i := uint8(0); i < numVotes; i++ {
|
|
voteDescs = append(voteDescs, VoteDesc{
|
|
VoteHash: nextHash(),
|
|
TicketHash: nextHash(),
|
|
ApprovesParent: true,
|
|
})
|
|
}
|
|
votes[blockVotedOn] = voteDescs
|
|
}
|
|
var curTip chainhash.Hash
|
|
if len(blockHashes) == 0 {
|
|
curTip = nextHash()
|
|
} else {
|
|
curTip = blockHashes[test.curTip]
|
|
}
|
|
var want []chainhash.Hash
|
|
if len(test.want) > 0 {
|
|
want = make([]chainhash.Hash, 0, len(test.want))
|
|
for _, blockIndex := range test.want {
|
|
want = append(want, blockHashes[blockIndex])
|
|
}
|
|
}
|
|
|
|
// Create a fake transaction source instance populated with the votes.
|
|
txSource := &fakeTxSource{
|
|
votes: votes,
|
|
}
|
|
|
|
// Ensure the result matches the expected values.
|
|
got := SortParentsByVotes(txSource, curTip, blockHashes, params)
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("%q: unexpected result -- got %v, want %v", test.name, got,
|
|
want)
|
|
continue
|
|
}
|
|
}
|
|
}
|