stake: Introduce StakeParams interface.

This introduces a new interface named StakeParams and updates the
functions that currently take a pointer to a chaincfg.Params struct to
accept the interface instead.

This removes the tight coupling between the two packages at the API
boundary and allows callers to easily provide custom values if desired.

It also updates the tests to make use of the interface by adding and
using a mock stake params struct the implements the new interface.  The
new struct is defined in terms of a chaincfg.Params for now for
convenience, but the interface design means it is possible to define
them independently in the future as needed.
This commit is contained in:
Dave Collins 2019-07-26 13:18:29 -05:00
parent a6e7a5f26c
commit b2adbbceee
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
2 changed files with 99 additions and 33 deletions

View File

@ -10,7 +10,6 @@ import (
"github.com/decred/dcrd/blockchain/stake/internal/dbnamespace"
"github.com/decred/dcrd/blockchain/stake/internal/ticketdb"
"github.com/decred/dcrd/blockchain/stake/internal/tickettreap"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/wire"
@ -20,6 +19,29 @@ import (
// stored in memory in the node.
type UndoTicketDataSlice []ticketdb.UndoTicketData
// StakeParams defines an interface that is used to provides the parameters
// required working with stake nodes. These values are typically well-defined
// and unique per network.
type StakeParams interface {
// VotesPerBlock returns the maximum number of votes a block must contain to
// receive full subsidy.
VotesPerBlock() uint16
// StakeValidationBeginHeight returns the height at which votes become
// required to extend a block. This height is the first that will be voted
// on, but will not include any votes itself.
StakeValidationBeginHeight() int64
// StakeEnableHeight returns the height at which the first ticket could
// possibly mature.
StakeEnableHeight() int64
// TicketExpiryBlocks returns the number of blocks after maturity that
// tickets expire. This will be >= (StakeEnableHeight() +
// StakeValidationBeginHeight()).
TicketExpiryBlocks() uint32
}
// Node is in-memory stake data for a node. It contains a list of database
// updates to be written in the case that the block is inserted in the main
// chain database. Because of its use of immutable treap data structures, it
@ -58,8 +80,8 @@ type Node struct {
// tickets pool.
finalState [6]byte
// params is the blockchain parameters.
params *chaincfg.Params
// params is the blockchain stake parameters.
params StakeParams
}
// UndoData returns the stored UndoTicketDataSlice used to remove this node
@ -212,7 +234,7 @@ func (sn *Node) Height() uint32 {
// genesisNode returns a pointer to the initialized ticket database for the
// genesis block.
func genesisNode(params *chaincfg.Params) *Node {
func genesisNode(params StakeParams) *Node {
return &Node{
height: 0,
liveTickets: &tickettreap.Immutable{},
@ -226,7 +248,7 @@ func genesisNode(params *chaincfg.Params) *Node {
}
// ResetDatabase resets the ticket database back to the genesis block.
func ResetDatabase(dbTx database.Tx, params *chaincfg.Params) error {
func ResetDatabase(dbTx database.Tx, params StakeParams, genesisHash *chainhash.Hash) error {
// Remove all of the database buckets.
err := ticketdb.DbRemoveAllBuckets(dbTx)
if err != nil {
@ -234,13 +256,13 @@ func ResetDatabase(dbTx database.Tx, params *chaincfg.Params) error {
}
// Initialize the database.
_, err = InitDatabaseState(dbTx, params)
_, err = InitDatabaseState(dbTx, params, genesisHash)
return err
}
// InitDatabaseState initializes the chain with the best state being the
// genesis block.
func InitDatabaseState(dbTx database.Tx, params *chaincfg.Params) (*Node, error) {
func InitDatabaseState(dbTx database.Tx, params StakeParams, genesisHash *chainhash.Hash) (*Node, error) {
// Create the database.
err := ticketdb.DbCreate(dbTx)
if err != nil {
@ -263,14 +285,14 @@ func InitDatabaseState(dbTx database.Tx, params *chaincfg.Params) (*Node, error)
}
// Write the new best state to the database.
nextWinners := make([]chainhash.Hash, int(genesis.params.TicketsPerBlock))
nextWinners := make([]chainhash.Hash, int(genesis.params.VotesPerBlock()))
err = ticketdb.DbPutBestState(dbTx, ticketdb.BestChainState{
Hash: *params.GenesisHash,
Hash: *genesisHash,
Height: genesis.height,
Live: uint32(genesis.liveTickets.Len()),
Missed: uint64(genesis.missedTickets.Len()),
Revoked: uint64(genesis.revokedTickets.Len()),
PerBlock: genesis.params.TicketsPerBlock,
PerBlock: genesis.params.VotesPerBlock(),
NextWinners: nextWinners,
})
if err != nil {
@ -287,7 +309,7 @@ func InitDatabaseState(dbTx database.Tx, params *chaincfg.Params) (*Node, error)
// checks to ensure that the database has not failed the upgrade process and
// reports the current version. Upgrades are also handled by this function,
// when they become applicable.
func LoadBestNode(dbTx database.Tx, height uint32, blockHash chainhash.Hash, header wire.BlockHeader, params *chaincfg.Params) (*Node, error) {
func LoadBestNode(dbTx database.Tx, height uint32, blockHash chainhash.Hash, header wire.BlockHeader, params StakeParams) (*Node, error) {
info, err := ticketdb.DbFetchDatabaseInfo(dbTx)
if err != nil {
return nil, err
@ -352,7 +374,7 @@ func LoadBestNode(dbTx database.Tx, height uint32, blockHash chainhash.Hash, hea
// Restore the next winners for the node.
node.nextWinners = make([]chainhash.Hash, 0)
if node.height >= uint32(node.params.StakeValidationHeight-1) {
if node.height >= uint32(params.StakeValidationBeginHeight()-1) {
node.nextWinners = make([]chainhash.Hash, len(state.NextWinners))
for i := range state.NextWinners {
node.nextWinners[i] = state.NextWinners[i]
@ -360,7 +382,7 @@ func LoadBestNode(dbTx database.Tx, height uint32, blockHash chainhash.Hash, hea
// Calculate the final state from the block header.
stateBuffer := make([]byte, 0,
(node.params.TicketsPerBlock+1)*chainhash.HashSize)
(params.VotesPerBlock()+1)*chainhash.HashSize)
for _, ticketHash := range node.nextWinners {
stateBuffer = append(stateBuffer, ticketHash[:]...)
}
@ -370,7 +392,7 @@ func LoadBestNode(dbTx database.Tx, height uint32, blockHash chainhash.Hash, hea
}
prng := NewHash256PRNG(hB)
_, err = findTicketIdxs(node.liveTickets.Len(),
node.params.TicketsPerBlock, prng)
params.VotesPerBlock(), prng)
if err != nil {
return nil, err
}
@ -468,7 +490,7 @@ func connectNode(node *Node, lotteryIV chainhash.Hash, ticketsVoted, revokedTick
// We only have to deal with vote-related issues and expiry after
// StakeEnabledHeight.
var err error
if connectedNode.height >= uint32(connectedNode.params.StakeEnabledHeight) {
if connectedNode.height >= uint32(connectedNode.params.StakeEnableHeight()) {
// Basic sanity check.
for i := range ticketsVoted {
if !hashInSlice(ticketsVoted[i], node.nextWinners) {
@ -533,9 +555,9 @@ func connectNode(node *Node, lotteryIV chainhash.Hash, ticketsVoted, revokedTick
// the winners are from the cached information in the previous block, so
// no drop the results of that here.
toExpireHeight := uint32(0)
if connectedNode.height > connectedNode.params.TicketExpiry {
if connectedNode.height > connectedNode.params.TicketExpiryBlocks() {
toExpireHeight = connectedNode.height -
connectedNode.params.TicketExpiry
connectedNode.params.TicketExpiryBlocks()
}
connectedNode.liveTickets.ForEachByHeight(toExpireHeight+1, func(treapKey tickettreap.Key, value *tickettreap.Value) bool {
@ -636,17 +658,17 @@ func connectNode(node *Node, lotteryIV chainhash.Hash, ticketsVoted, revokedTick
// The first block voted on is at StakeValidationHeight, so begin calculating
// winners at the block before StakeValidationHeight.
if connectedNode.height >=
uint32(connectedNode.params.StakeValidationHeight-1) {
uint32(connectedNode.params.StakeValidationBeginHeight()-1) {
// Find the next set of winners.
prng := NewHash256PRNGFromIV(lotteryIV)
idxs, err := findTicketIdxs(connectedNode.liveTickets.Len(),
connectedNode.params.TicketsPerBlock, prng)
connectedNode.params.VotesPerBlock(), prng)
if err != nil {
return nil, err
}
stateBuffer := make([]byte, 0,
(connectedNode.params.TicketsPerBlock+1)*chainhash.HashSize)
(connectedNode.params.VotesPerBlock()+1)*chainhash.HashSize)
nextWinnersKeys, err := fetchWinners(idxs, connectedNode.liveTickets)
if err != nil {
return nil, err
@ -726,7 +748,7 @@ func disconnectNode(node *Node, parentLotteryIV chainhash.Hash, parentUtds UndoT
// changes to the respective treap, reversing all the changes
// added when the child block was added to the chain.
stateBuffer := make([]byte, 0,
(node.params.TicketsPerBlock+1)*chainhash.HashSize)
(node.params.VotesPerBlock()+1)*chainhash.HashSize)
for _, undo := range node.databaseUndoUpdate {
var err error
k := tickettreap.Key(undo.TicketHash)
@ -810,10 +832,10 @@ func disconnectNode(node *Node, parentLotteryIV chainhash.Hash, parentUtds UndoT
}
}
if node.height >= uint32(node.params.StakeValidationHeight) {
if node.height >= uint32(node.params.StakeValidationBeginHeight()) {
prng := NewHash256PRNGFromIV(parentLotteryIV)
_, err := findTicketIdxs(restoredNode.liveTickets.Len(),
node.params.TicketsPerBlock, prng)
node.params.VotesPerBlock(), prng)
if err != nil {
return nil, err
}
@ -915,8 +937,8 @@ func WriteConnectedBestNode(dbTx database.Tx, node *Node, hash chainhash.Hash) e
}
// Write the new best state to the database.
nextWinners := make([]chainhash.Hash, int(node.params.TicketsPerBlock))
if node.height >= uint32(node.params.StakeValidationHeight-1) {
nextWinners := make([]chainhash.Hash, int(node.params.VotesPerBlock()))
if node.height >= uint32(node.params.StakeValidationBeginHeight()-1) {
for i := range nextWinners {
nextWinners[i] = node.nextWinners[i]
}
@ -928,7 +950,7 @@ func WriteConnectedBestNode(dbTx database.Tx, node *Node, hash chainhash.Hash) e
Live: uint32(node.liveTickets.Len()),
Missed: uint64(node.missedTickets.Len()),
Revoked: uint64(node.revokedTickets.Len()),
PerBlock: node.params.TicketsPerBlock,
PerBlock: node.params.VotesPerBlock(),
NextWinners: nextWinners,
})
}
@ -1046,8 +1068,9 @@ func WriteDisconnectedBestNode(dbTx database.Tx, node *Node, hash chainhash.Hash
}
// Write the new best state to the database.
nextWinners := make([]chainhash.Hash, int(node.params.TicketsPerBlock))
if node.height >= uint32(node.params.StakeValidationHeight-1) {
params := node.params
nextWinners := make([]chainhash.Hash, int(params.VotesPerBlock()))
if node.height >= uint32(params.StakeValidationBeginHeight()-1) {
for i := range nextWinners {
nextWinners[i] = node.nextWinners[i]
}
@ -1059,7 +1082,7 @@ func WriteDisconnectedBestNode(dbTx database.Tx, node *Node, hash chainhash.Hash
Live: uint32(node.liveTickets.Len()),
Missed: uint64(node.missedTickets.Len()),
Revoked: uint64(node.revokedTickets.Len()),
PerBlock: node.params.TicketsPerBlock,
PerBlock: params.VotesPerBlock(),
NextWinners: nextWinners,
})
}

View File

@ -40,6 +40,49 @@ func calcHash256PRNGIVFromHeader(header *wire.BlockHeader) (chainhash.Hash, erro
return CalcHash256PRNGIV(hB), nil
}
// mockStakeParams implements the StakeParams interface and is used throughout
// the tests to mock networks.
type mockStakeParams chaincfg.Params
// VotesPerBlock returns the value associated with the mock params for the
// maximum number of votes a block must contain to receive full subsidy
//
// This is part of the StakeParams interface.
func (s *mockStakeParams) VotesPerBlock() uint16 {
return s.TicketsPerBlock
}
// StakeValidationBeginHeight returns the value associated with the mock params
// for the height at which votes become required to extend a block.
//
// This is part of the StakeParams interface.
func (s *mockStakeParams) StakeValidationBeginHeight() int64 {
return s.StakeValidationHeight
}
// StakeEnableHeight returns the value associated with the mock params for the
// height at which the first ticket could possibly mature.
//
// This is part of the StakeParams interface.
func (s *mockStakeParams) StakeEnableHeight() int64 {
return s.StakeEnabledHeight
}
// TicketExpiryBlocks returns the value associated with the mock params for the
// number of blocks after maturity that tickets expire.
//
// This is part of the StakeParams interface.
func (s *mockStakeParams) TicketExpiryBlocks() uint32 {
return s.TicketExpiry
}
// mockRegNetParams returns mock regression test stake parameters to use
// throughout the tests. They match the Decred regression test network params
// as of the time this comment was written.
func mockRegNetParams() *mockStakeParams {
return (*mockStakeParams)(&chaincfg.RegNetParams)
}
// copyNode copies a stake node so that it can be manipulated for tests.
func copyNode(n *Node) *Node {
liveTickets := new(tickettreap.Immutable)
@ -208,7 +251,7 @@ func nodesEqual(a *Node, b *Node) error {
func TestTicketDBLongChain(t *testing.T) {
// Declare some useful variables.
params := &chaincfg.RegNetParams
params := mockRegNetParams()
testBCHeight := int64(1001)
filename := filepath.Join("testdata", "testexpiry.bz2")
fi, err := os.Open(filename)
@ -417,7 +460,7 @@ func TestTicketDBLongChain(t *testing.T) {
// Load the genesis block and begin testing exported functions.
err = testDb.Update(func(dbTx database.Tx) error {
var errLocal error
bestNode, errLocal = InitDatabaseState(dbTx, params)
bestNode, errLocal = InitDatabaseState(dbTx, params, &params.GenesisHash)
if errLocal != nil {
return errLocal
}
@ -588,7 +631,7 @@ func TestTicketDBLongChain(t *testing.T) {
func TestTicketDBGeneral(t *testing.T) {
// Declare some useful variables.
params := &chaincfg.RegNetParams
params := mockRegNetParams()
testBCHeight := int64(168)
filename := filepath.Join("testdata", "blocks0to168.bz2")
fi, err := os.Open(filename)
@ -637,7 +680,7 @@ func TestTicketDBGeneral(t *testing.T) {
var bestNode *Node
err = testDb.Update(func(dbTx database.Tx) error {
var errLocal error
bestNode, errLocal = InitDatabaseState(dbTx, params)
bestNode, errLocal = InitDatabaseState(dbTx, params, params.GenesisHash)
return errLocal
})
if err != nil {