[DEV-259] Allow to spend genesis coinbase, and use ProcessBlock to ad… (#187)

* [DEV-259] Allow to spend genesis coinbase, and use ProcessBlock to add genesis to the DAG like any other block

* [DEV-259] fix IsCurrent to check genesis timestamp if needed

* [DEV-259] add genesisPastUTXO as separate function
This commit is contained in:
Ori Newman 2019-02-14 18:20:49 +02:00 committed by Svarog
parent f06513aad7
commit f615298453
17 changed files with 129 additions and 102 deletions

View File

@ -29,7 +29,10 @@ func (dag *BlockDAG) maybeAcceptBlock(block *util.Block, flags BehaviorFlags) er
}
bluestParent := parents.bluest()
blockHeight := parents.maxHeight() + 1
blockHeight := int32(0)
if !block.IsGenesis() {
blockHeight = parents.maxHeight() + 1
}
block.SetHeight(blockHeight)
// The block must pass all of the validation rules which depend on the

View File

@ -498,12 +498,6 @@ func (dag *BlockDAG) connectToDAG(node *blockNode, parentNodes blockSet, block *
//
// This function MUST be called with the chain state lock held (for writes).
func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, fastAdd bool) error {
// The coinbase for the Genesis block is not spendable, so just return
// an error now.
if node.hash.IsEqual(dag.dagParams.GenesisHash) {
str := "the coinbase for the genesis block is not spendable"
return ruleError(ErrMissingTxOut, str)
}
// No warnings about unknown rules or versions until the DAG is
// current.
@ -650,6 +644,9 @@ func (dag *BlockDAG) LastFinalityPointHash() *daghash.Hash {
}
func (dag *BlockDAG) checkFinalityRulesAndGetFinalityPointCandidate(node *blockNode) (*blockNode, error) {
if node.isGenesis() {
return node, nil
}
var finalityPointCandidate *blockNode
finalityErr := ruleError(ErrFinality, "The last finality point is not in the selected chain of this block")
@ -925,6 +922,9 @@ func (dag *BlockDAG) getTXO(outpointBlockNode *blockNode, outpoint wire.OutPoint
}
func (node *blockNode) validateFeeTransaction(dag *BlockDAG, acceptedTxData []*TxWithBlockHash, nodeTransactions []*util.Tx) error {
if node.isGenesis() {
return nil
}
expectedFeeTransaction, err := node.buildFeeTransaction(dag, acceptedTxData)
if err != nil {
return err
@ -968,8 +968,23 @@ type TxWithBlockHash struct {
InBlock *daghash.Hash
}
func genesisPastUTXO(virtual *virtualBlock) UTXOSet {
// The genesis has no past UTXO, so we create an empty UTXO
// set by creating a diff UTXO set with the virtual UTXO
// set, and adding all of its entries in toRemove
diff := NewUTXODiff()
for outPoint, entry := range virtual.utxoSet.utxoCollection {
diff.toRemove[outPoint] = entry
}
genesisPastUTXO := UTXOSet(NewDiffUTXOSet(virtual.utxoSet, diff))
return genesisPastUTXO
}
// pastUTXO returns the UTXO of a given block's past
func (node *blockNode) pastUTXO(virtual *virtualBlock, db database.DB) (pastUTXO UTXOSet, acceptedTxData []*TxWithBlockHash, err error) {
if node.isGenesis() {
return genesisPastUTXO(virtual), nil, nil
}
pastUTXO, err = node.selectedParent.restoreUTXO(virtual)
if err != nil {
return nil, nil, err
@ -1099,20 +1114,28 @@ func updateTipsUTXO(tips blockSet, virtual *virtualBlock, virtualUTXO UTXOSet) e
//
// This function MUST be called with the chain state lock held (for reads).
func (dag *BlockDAG) isCurrent() bool {
// Not current if the latest main (best) chain height is before the
// latest known good checkpoint (when checkpoints are enabled).
// Not current if the virtual's selected tip height is less than
// the latest known good checkpoint (when checkpoints are enabled).
checkpoint := dag.LatestCheckpoint()
if checkpoint != nil && dag.selectedTip().height < checkpoint.Height {
return false
}
// Not current if the latest best block has a timestamp before 24 hours
// ago.
// Not current if the virtual's selected parent has a timestamp
// before 24 hours ago. If the DAG is empty, we take the genesis
// block timestamp.
//
// The chain appears to be current if none of the checks reported
// The DAG appears to be current if none of the checks reported
// otherwise.
var dagTimestamp int64
selectedTip := dag.selectedTip()
if selectedTip == nil {
dagTimestamp = dag.dagParams.GenesisBlock.Header.Timestamp.Unix()
} else {
dagTimestamp = selectedTip.timestamp
}
minus24Hours := dag.timeSource.AdjustedTime().Add(-24 * time.Hour).Unix()
return dag.selectedTip().timestamp >= minus24Hours
return dagTimestamp >= minus24Hours
}
// IsCurrent returns whether or not the chain believes it is current. Several
@ -1676,7 +1699,7 @@ func New(config *Config) (*BlockDAG, error) {
for i := range config.Checkpoints {
checkpoint := &config.Checkpoints[i]
if checkpoint.Height <= prevCheckpointHeight {
return nil, AssertError("blockchain.New " +
return nil, AssertError("blockdag.New " +
"checkpoints are not sorted by height")
}
@ -1707,7 +1730,7 @@ func New(config *Config) (*BlockDAG, error) {
prevOrphans: make(map[daghash.Hash][]*orphanBlock),
warningCaches: newThresholdCaches(vbNumBits),
deploymentCaches: newThresholdCaches(dagconfig.DefinedDeployments),
blockCount: 1,
blockCount: 0,
SubnetworkStore: newSubnetworkStore(config.DB),
subnetworkID: config.SubnetworkID,
}
@ -1719,11 +1742,6 @@ func New(config *Config) (*BlockDAG, error) {
return nil, err
}
// Save a reference to the genesis block. Note that we may only get
// an index reference to it here because the index is uninitialized
// before initDAGState.
dag.genesis = index.LookupNode(params.GenesisHash)
// Initialize and catch up all of the currently active optional indexes
// as needed.
if config.IndexManager != nil {
@ -1733,6 +1751,23 @@ func New(config *Config) (*BlockDAG, error) {
}
}
genesis := index.LookupNode(params.GenesisHash)
if genesis == nil {
genesisBlock := util.NewBlock(dag.dagParams.GenesisBlock)
isOrphan, err := dag.ProcessBlock(genesisBlock, BFNone)
if err != nil {
return nil, err
}
if isOrphan {
return nil, errors.New("Genesis block is unexpectedly orphan")
}
genesis = index.LookupNode(params.GenesisHash)
}
// Save a reference to the genesis block.
dag.genesis = genesis
// Initialize rule change threshold state caches.
if err := dag.initThresholdCaches(); err != nil {
return nil, err

View File

@ -190,7 +190,7 @@ func TestHaveBlock(t *testing.T) {
{hash: dagconfig.SimNetParams.GenesisHash.String(), want: true},
// Block 3b should be present (as a second child of Block 2).
{hash: "7592764c1af29786ad0bdd21bd8ada1efe7f78f42f5f26a5c58d43531b39f0c4", want: true},
{hash: "08a3f0182ac8ff0326497f592d2e28b8b3b2b7e3fd77c7cb6f31ca872536cf7b", want: true},
// Block 100000 should be present (as an orphan).
{hash: "25d5494f3e1f895774c58034f1bd50f7b279e75db6007514affec8573ace4389", want: true},
@ -895,6 +895,7 @@ func TestValidateFeeTransaction(t *testing.T) {
for i, tx := range transactions {
utilTxs[i] = util.NewTx(tx)
}
daghash.Sort(parentHashes)
msgBlock := &wire.MsgBlock{
Header: wire.BlockHeader{
Bits: dag.genesis.Header().Bits,

View File

@ -433,36 +433,10 @@ func dbPutDAGState(dbTx database.Tx, state *dagState) error {
}
// createDAGState initializes both the database and the DAG state to the
// genesis block. This includes creating the necessary buckets and inserting
// the genesis block, so it must only be called on an uninitialized database.
// genesis block. This includes creating the necessary buckets, so it
// must only be called on an uninitialized database.
func (dag *BlockDAG) createDAGState() error {
// Create a new node from the genesis block and set it as the DAG.
genesisBlock := util.NewBlock(dag.dagParams.GenesisBlock)
genesisBlock.SetHeight(0)
header := &genesisBlock.MsgBlock().Header
node := newBlockNode(header, newSet(), dag.dagParams.K)
node.status = statusDataStored | statusValid
genesisCoinbase := genesisBlock.Transactions()[0].MsgTx()
genesisCoinbaseTxIn := genesisCoinbase.TxIn[0]
genesisCoinbaseTxOut := genesisCoinbase.TxOut[0]
genesisCoinbaseOutpoint := *wire.NewOutPoint(&genesisCoinbaseTxIn.PreviousOutPoint.TxID, genesisCoinbaseTxIn.PreviousOutPoint.Index)
genesisCoinbaseUTXOEntry := NewUTXOEntry(genesisCoinbaseTxOut, true, 0)
node.diff = &UTXODiff{
toAdd: utxoCollection{genesisCoinbaseOutpoint: genesisCoinbaseUTXOEntry},
toRemove: utxoCollection{},
}
dag.virtual.utxoSet.AddTx(genesisCoinbase, 0)
dag.virtual.SetTips(setFromSlice(node))
// Add the new node to the index which is used for faster lookups.
dag.index.addNode(node)
// Initiate the last finality point to the genesis block
dag.lastFinalityPoint = node
// Create the initial the database chain state including creating the
// Create the initial the database DAG state including creating the
// necessary index buckets and inserting the genesis block.
err := dag.db.Update(func(dbTx database.Tx) error {
meta := dbTx.Metadata()
@ -506,35 +480,13 @@ func (dag *BlockDAG) createDAGState() error {
if err != nil {
return err
}
// Save the genesis block to the block index database.
err = dbStoreBlockNode(dbTx, node)
if err != nil {
return err
}
// Add the genesis block hash to height and height to hash
// mappings to the index.
err = dbPutBlockIndex(dbTx, &node.hash, node.height)
if err != nil {
return err
}
// Store the current DAG state into the database.
state := &dagState{
TipHashes: dag.TipHashes(),
LastFinalityPoint: *genesisBlock.Hash(),
}
err = dbPutDAGState(dbTx, state)
if err != nil {
return err
}
// Store the genesis block into the database.
return dbStoreBlock(dbTx, genesisBlock)
return nil
})
return err
if err != nil {
return err
}
return nil
}
// initDAGState attempts to load and initialize the DAG state from the

View File

@ -60,6 +60,9 @@ const (
// the current time.
ErrTimeTooNew
// ErrNoParents indicates that the block is missing parents
ErrNoParents
// ErrWrongParentsOrder indicates that the block's parents are not ordered by hash, as expected
ErrWrongParentsOrder
@ -244,6 +247,8 @@ var errorCodeStrings = map[ErrorCode]string{
ErrInvalidTime: "ErrInvalidTime",
ErrTimeTooOld: "ErrTimeTooOld",
ErrTimeTooNew: "ErrTimeTooNew",
ErrNoParents: "ErrNoParents",
ErrWrongParentsOrder: "ErrWrongParentsOrder",
ErrDifficultyTooLow: "ErrDifficultyTooLow",
ErrUnexpectedDifficulty: "ErrUnexpectedDifficulty",
ErrHighHash: "ErrHighHash",

View File

@ -21,6 +21,8 @@ func TestErrorCodeStringer(t *testing.T) {
{ErrInvalidTime, "ErrInvalidTime"},
{ErrTimeTooOld, "ErrTimeTooOld"},
{ErrTimeTooNew, "ErrTimeTooNew"},
{ErrNoParents, "ErrNoParents"},
{ErrWrongParentsOrder, "ErrWrongParentsOrder"},
{ErrDifficultyTooLow, "ErrDifficultyTooLow"},
{ErrUnexpectedDifficulty, "ErrUnexpectedDifficulty"},
{ErrHighHash, "ErrHighHash"},

View File

@ -6,11 +6,12 @@ package blockdag_test
import (
"fmt"
"github.com/daglabs/btcd/wire"
"math/big"
"os"
"path/filepath"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/database"
@ -70,7 +71,7 @@ func ExampleBlockDAG_ProcessBlock() {
fmt.Printf("Block accepted. Is it an orphan?: %v", isOrphan)
// Output:
// Failed to process block: already have block ccc309e79328f036bdd6964adbed68ff374cfb878c7a797c0aae3fec4bf9b853
// Failed to process block: already have block 4f0fbe497b98f0ab3dd92a3be968d5c7623cbaa844ff9f19e2b94756337eb0b8
}
// This example demonstrates how to convert the compact "bits" in a block header

View File

@ -429,6 +429,9 @@ func (idx *TxIndex) ConnectBlock(dbTx database.Tx, block *util.Block, _ *blockda
// Increment the internal block ID to use for the block being connected
// and add all of the transactions in the block to the index.
newBlockID := idx.curBlockID + 1
if block.MsgBlock().Header.IsGenesis() {
newBlockID = 0
}
if err := dbAddTxIndexEntries(dbTx, block, newBlockID, acceptedTxsData); err != nil {
return err
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -409,9 +409,15 @@ func (dag *BlockDAG) checkBlockHeaderSanity(header *wire.BlockHeader, flags Beha
return err
}
err = checkBlockParentsOrder(header)
if err != nil {
return err
if len(header.ParentHashes) == 0 {
if header.BlockHash() != *dag.dagParams.GenesisHash {
return ruleError(ErrNoParents, "block has no parents")
}
} else {
err = checkBlockParentsOrder(header)
if err != nil {
return err
}
}
// A block timestamp must not have a greater precision than one second.
@ -497,14 +503,20 @@ func (dag *BlockDAG) checkBlockSanity(block *util.Block, flags BehaviorFlags) er
"block is not a coinbase")
}
if !IsFeeTransaction(transactions[1]) {
isGenesis := block.MsgBlock().Header.IsGenesis()
if !isGenesis && !IsFeeTransaction(transactions[1]) {
return ruleError(ErrSecondTxNotFeeTransaction, "second transaction in "+
"block is not a fee transaction")
}
txOffset := 2
if isGenesis {
txOffset = 1
}
// A block must not have more than one coinbase. And transactions must be
// ordered by subnetwork
for i, tx := range transactions[2:] {
for i, tx := range transactions[txOffset:] {
if IsCoinBase(tx) {
str := fmt.Sprintf("block contains second coinbase at "+
"index %d", i+2)
@ -678,13 +690,15 @@ func (dag *BlockDAG) checkBlockHeaderContext(header *wire.BlockHeader, bluestPar
return ruleError(ErrUnexpectedDifficulty, str)
}
// Ensure the timestamp for the block header is not before the
// median time of the last several blocks (medianTimeBlocks).
medianTime := bluestParent.CalcPastMedianTime()
if header.Timestamp.Before(medianTime) {
str := "block timestamp of %v is not after expected %v"
str = fmt.Sprintf(str, header.Timestamp, medianTime)
return ruleError(ErrTimeTooOld, str)
if !header.IsGenesis() {
// Ensure the timestamp for the block header is not before the
// median time of the last several blocks (medianTimeBlocks).
medianTime := bluestParent.CalcPastMedianTime()
if header.Timestamp.Before(medianTime) {
str := "block timestamp of %s is not after expected %s"
str = fmt.Sprintf(str, header.Timestamp.String(), medianTime.String())
return ruleError(ErrTimeTooOld, str)
}
}
}
@ -776,7 +790,10 @@ func (dag *BlockDAG) checkBlockContext(block *util.Block, parents blockSet, blue
fastAdd := flags&BFFastAdd == BFFastAdd
if !fastAdd {
blockTime := bluestParent.CalcPastMedianTime()
blockTime := header.Timestamp
if !block.IsGenesis() {
blockTime = bluestParent.CalcPastMedianTime()
}
// Ensure all transactions in the block are finalized.
for _, tx := range block.Transactions() {
@ -1048,9 +1065,12 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
scriptFlags := txscript.ScriptNoFlags
// We obtain the MTP of the *previous* block in order to
// determine if transactions in the current block are final.
medianTime := block.selectedParent.CalcPastMedianTime()
// We obtain the MTP of the *previous* block (unless it's genesis block)
// in order to determine if transactions in the current block are final.
medianTime := block.Header().Timestamp
if !block.isGenesis() {
medianTime = block.selectedParent.CalcPastMedianTime()
}
// We also enforce the relative sequence number based
// lock-times within the inputs of all transactions in this

View File

@ -44,10 +44,10 @@ var genesisCoinbaseTx = wire.MsgTx{
// genesisHash is the hash of the first block in the block chain for the main
// network (genesis block).
var genesisHash = daghash.Hash([daghash.HashSize]byte{ // Make go vet happy.
0x53, 0xb8, 0xf9, 0x4b, 0xec, 0x3f, 0xae, 0x0a,
0x7c, 0x79, 0x7a, 0x8c, 0x87, 0xfb, 0x4c, 0x37,
0xff, 0x68, 0xed, 0xdb, 0x4a, 0x96, 0xd6, 0xbd,
0x36, 0xf0, 0x28, 0x93, 0xe7, 0x09, 0xc3, 0xcc,
0xb8, 0xb0, 0x7e, 0x33, 0x56, 0x47, 0xb9, 0xe2,
0x19, 0x9f, 0xff, 0x44, 0xa8, 0xba, 0x3c, 0x62,
0xc7, 0xd5, 0x68, 0xe9, 0x3b, 0x2a, 0xd9, 0x3d,
0xab, 0xf0, 0x98, 0x7b, 0x49, 0xbe, 0x0f, 0x4f,
})
// genesisMerkleRoot is the hash of the first transaction in the genesis block
@ -69,7 +69,7 @@ var genesisBlock = wire.MsgBlock{
IDMerkleRoot: genesisMerkleRoot,
Timestamp: time.Unix(0x5c3cafec, 0),
Bits: 0x207fffff,
Nonce: 0xbffffffffffffffa,
Nonce: 0,
},
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
}

View File

@ -131,8 +131,8 @@ var genesisBlockBytes = []byte{
0xc3, 0x54, 0xc9, 0xa7, 0x06, 0x8c, 0x23, 0x24,
0x3c, 0x53, 0x6d, 0x56, 0x23, 0xec, 0xaf, 0x3c,
0x5c, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f,
0x20, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xbf, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

View File

@ -194,6 +194,11 @@ func (b *Block) SetHeight(height int32) {
b.blockHeight = height
}
// IsGenesis returns whether or not this block is the genesis block.
func (b *Block) IsGenesis() bool {
return b.MsgBlock().Header.IsGenesis()
}
// NewBlock returns a new instance of a bitcoin block given an underlying
// wire.MsgBlock. See Block.
func NewBlock(msgBlock *wire.MsgBlock) *Block {