[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() bluestParent := parents.bluest()
blockHeight := parents.maxHeight() + 1 blockHeight := int32(0)
if !block.IsGenesis() {
blockHeight = parents.maxHeight() + 1
}
block.SetHeight(blockHeight) block.SetHeight(blockHeight)
// The block must pass all of the validation rules which depend on the // 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). // 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 { 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 // No warnings about unknown rules or versions until the DAG is
// current. // current.
@ -650,6 +644,9 @@ func (dag *BlockDAG) LastFinalityPointHash() *daghash.Hash {
} }
func (dag *BlockDAG) checkFinalityRulesAndGetFinalityPointCandidate(node *blockNode) (*blockNode, error) { func (dag *BlockDAG) checkFinalityRulesAndGetFinalityPointCandidate(node *blockNode) (*blockNode, error) {
if node.isGenesis() {
return node, nil
}
var finalityPointCandidate *blockNode var finalityPointCandidate *blockNode
finalityErr := ruleError(ErrFinality, "The last finality point is not in the selected chain of this block") 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 { func (node *blockNode) validateFeeTransaction(dag *BlockDAG, acceptedTxData []*TxWithBlockHash, nodeTransactions []*util.Tx) error {
if node.isGenesis() {
return nil
}
expectedFeeTransaction, err := node.buildFeeTransaction(dag, acceptedTxData) expectedFeeTransaction, err := node.buildFeeTransaction(dag, acceptedTxData)
if err != nil { if err != nil {
return err return err
@ -968,8 +968,23 @@ type TxWithBlockHash struct {
InBlock *daghash.Hash 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 // 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) { 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) pastUTXO, err = node.selectedParent.restoreUTXO(virtual)
if err != nil { if err != nil {
return nil, nil, err 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). // This function MUST be called with the chain state lock held (for reads).
func (dag *BlockDAG) isCurrent() bool { func (dag *BlockDAG) isCurrent() bool {
// Not current if the latest main (best) chain height is before the // Not current if the virtual's selected tip height is less than
// latest known good checkpoint (when checkpoints are enabled). // the latest known good checkpoint (when checkpoints are enabled).
checkpoint := dag.LatestCheckpoint() checkpoint := dag.LatestCheckpoint()
if checkpoint != nil && dag.selectedTip().height < checkpoint.Height { if checkpoint != nil && dag.selectedTip().height < checkpoint.Height {
return false return false
} }
// Not current if the latest best block has a timestamp before 24 hours // Not current if the virtual's selected parent has a timestamp
// ago. // 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. // 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() 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 // 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 { for i := range config.Checkpoints {
checkpoint := &config.Checkpoints[i] checkpoint := &config.Checkpoints[i]
if checkpoint.Height <= prevCheckpointHeight { if checkpoint.Height <= prevCheckpointHeight {
return nil, AssertError("blockchain.New " + return nil, AssertError("blockdag.New " +
"checkpoints are not sorted by height") "checkpoints are not sorted by height")
} }
@ -1707,7 +1730,7 @@ func New(config *Config) (*BlockDAG, error) {
prevOrphans: make(map[daghash.Hash][]*orphanBlock), prevOrphans: make(map[daghash.Hash][]*orphanBlock),
warningCaches: newThresholdCaches(vbNumBits), warningCaches: newThresholdCaches(vbNumBits),
deploymentCaches: newThresholdCaches(dagconfig.DefinedDeployments), deploymentCaches: newThresholdCaches(dagconfig.DefinedDeployments),
blockCount: 1, blockCount: 0,
SubnetworkStore: newSubnetworkStore(config.DB), SubnetworkStore: newSubnetworkStore(config.DB),
subnetworkID: config.SubnetworkID, subnetworkID: config.SubnetworkID,
} }
@ -1719,11 +1742,6 @@ func New(config *Config) (*BlockDAG, error) {
return nil, err 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 // Initialize and catch up all of the currently active optional indexes
// as needed. // as needed.
if config.IndexManager != nil { 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. // Initialize rule change threshold state caches.
if err := dag.initThresholdCaches(); err != nil { if err := dag.initThresholdCaches(); err != nil {
return nil, err return nil, err

View File

@ -190,7 +190,7 @@ func TestHaveBlock(t *testing.T) {
{hash: dagconfig.SimNetParams.GenesisHash.String(), want: true}, {hash: dagconfig.SimNetParams.GenesisHash.String(), want: true},
// Block 3b should be present (as a second child of Block 2). // 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). // Block 100000 should be present (as an orphan).
{hash: "25d5494f3e1f895774c58034f1bd50f7b279e75db6007514affec8573ace4389", want: true}, {hash: "25d5494f3e1f895774c58034f1bd50f7b279e75db6007514affec8573ace4389", want: true},
@ -895,6 +895,7 @@ func TestValidateFeeTransaction(t *testing.T) {
for i, tx := range transactions { for i, tx := range transactions {
utilTxs[i] = util.NewTx(tx) utilTxs[i] = util.NewTx(tx)
} }
daghash.Sort(parentHashes)
msgBlock := &wire.MsgBlock{ msgBlock := &wire.MsgBlock{
Header: wire.BlockHeader{ Header: wire.BlockHeader{
Bits: dag.genesis.Header().Bits, 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 // createDAGState initializes both the database and the DAG state to the
// genesis block. This includes creating the necessary buckets and inserting // genesis block. This includes creating the necessary buckets, so it
// the genesis block, so it must only be called on an uninitialized database. // must only be called on an uninitialized database.
func (dag *BlockDAG) createDAGState() error { func (dag *BlockDAG) createDAGState() error {
// Create a new node from the genesis block and set it as the DAG. // Create the initial the database DAG state including creating the
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
// necessary index buckets and inserting the genesis block. // necessary index buckets and inserting the genesis block.
err := dag.db.Update(func(dbTx database.Tx) error { err := dag.db.Update(func(dbTx database.Tx) error {
meta := dbTx.Metadata() meta := dbTx.Metadata()
@ -506,35 +480,13 @@ func (dag *BlockDAG) createDAGState() error {
if err != nil { if err != nil {
return err return err
} }
return nil
// 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 err if err != nil {
return err
}
return nil
} }
// initDAGState attempts to load and initialize the DAG state from the // initDAGState attempts to load and initialize the DAG state from the

View File

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

View File

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

View File

@ -6,11 +6,12 @@ package blockdag_test
import ( import (
"fmt" "fmt"
"github.com/daglabs/btcd/wire"
"math/big" "math/big"
"os" "os"
"path/filepath" "path/filepath"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/blockdag" "github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/dagconfig" "github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/database" "github.com/daglabs/btcd/database"
@ -70,7 +71,7 @@ func ExampleBlockDAG_ProcessBlock() {
fmt.Printf("Block accepted. Is it an orphan?: %v", isOrphan) fmt.Printf("Block accepted. Is it an orphan?: %v", isOrphan)
// Output: // 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 // 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 // Increment the internal block ID to use for the block being connected
// and add all of the transactions in the block to the index. // and add all of the transactions in the block to the index.
newBlockID := idx.curBlockID + 1 newBlockID := idx.curBlockID + 1
if block.MsgBlock().Header.IsGenesis() {
newBlockID = 0
}
if err := dbAddTxIndexEntries(dbTx, block, newBlockID, acceptedTxsData); err != nil { if err := dbAddTxIndexEntries(dbTx, block, newBlockID, acceptedTxsData); err != nil {
return err 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 return err
} }
err = checkBlockParentsOrder(header) if len(header.ParentHashes) == 0 {
if err != nil { if header.BlockHash() != *dag.dagParams.GenesisHash {
return err 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. // 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") "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 "+ return ruleError(ErrSecondTxNotFeeTransaction, "second transaction in "+
"block is not a fee transaction") "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 // A block must not have more than one coinbase. And transactions must be
// ordered by subnetwork // ordered by subnetwork
for i, tx := range transactions[2:] { for i, tx := range transactions[txOffset:] {
if IsCoinBase(tx) { if IsCoinBase(tx) {
str := fmt.Sprintf("block contains second coinbase at "+ str := fmt.Sprintf("block contains second coinbase at "+
"index %d", i+2) "index %d", i+2)
@ -678,13 +690,15 @@ func (dag *BlockDAG) checkBlockHeaderContext(header *wire.BlockHeader, bluestPar
return ruleError(ErrUnexpectedDifficulty, str) return ruleError(ErrUnexpectedDifficulty, str)
} }
// Ensure the timestamp for the block header is not before the if !header.IsGenesis() {
// median time of the last several blocks (medianTimeBlocks). // Ensure the timestamp for the block header is not before the
medianTime := bluestParent.CalcPastMedianTime() // median time of the last several blocks (medianTimeBlocks).
if header.Timestamp.Before(medianTime) { medianTime := bluestParent.CalcPastMedianTime()
str := "block timestamp of %v is not after expected %v" if header.Timestamp.Before(medianTime) {
str = fmt.Sprintf(str, header.Timestamp, medianTime) str := "block timestamp of %s is not after expected %s"
return ruleError(ErrTimeTooOld, str) 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 fastAdd := flags&BFFastAdd == BFFastAdd
if !fastAdd { if !fastAdd {
blockTime := bluestParent.CalcPastMedianTime() blockTime := header.Timestamp
if !block.IsGenesis() {
blockTime = bluestParent.CalcPastMedianTime()
}
// Ensure all transactions in the block are finalized. // Ensure all transactions in the block are finalized.
for _, tx := range block.Transactions() { for _, tx := range block.Transactions() {
@ -1048,9 +1065,12 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
scriptFlags := txscript.ScriptNoFlags scriptFlags := txscript.ScriptNoFlags
// We obtain the MTP of the *previous* block in order to // We obtain the MTP of the *previous* block (unless it's genesis block)
// determine if transactions in the current block are final. // in order to determine if transactions in the current block are final.
medianTime := block.selectedParent.CalcPastMedianTime() medianTime := block.Header().Timestamp
if !block.isGenesis() {
medianTime = block.selectedParent.CalcPastMedianTime()
}
// We also enforce the relative sequence number based // We also enforce the relative sequence number based
// lock-times within the inputs of all transactions in this // 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 // genesisHash is the hash of the first block in the block chain for the main
// network (genesis block). // network (genesis block).
var genesisHash = daghash.Hash([daghash.HashSize]byte{ // Make go vet happy. var genesisHash = daghash.Hash([daghash.HashSize]byte{ // Make go vet happy.
0x53, 0xb8, 0xf9, 0x4b, 0xec, 0x3f, 0xae, 0x0a, 0xb8, 0xb0, 0x7e, 0x33, 0x56, 0x47, 0xb9, 0xe2,
0x7c, 0x79, 0x7a, 0x8c, 0x87, 0xfb, 0x4c, 0x37, 0x19, 0x9f, 0xff, 0x44, 0xa8, 0xba, 0x3c, 0x62,
0xff, 0x68, 0xed, 0xdb, 0x4a, 0x96, 0xd6, 0xbd, 0xc7, 0xd5, 0x68, 0xe9, 0x3b, 0x2a, 0xd9, 0x3d,
0x36, 0xf0, 0x28, 0x93, 0xe7, 0x09, 0xc3, 0xcc, 0xab, 0xf0, 0x98, 0x7b, 0x49, 0xbe, 0x0f, 0x4f,
}) })
// genesisMerkleRoot is the hash of the first transaction in the genesis block // genesisMerkleRoot is the hash of the first transaction in the genesis block
@ -69,7 +69,7 @@ var genesisBlock = wire.MsgBlock{
IDMerkleRoot: genesisMerkleRoot, IDMerkleRoot: genesisMerkleRoot,
Timestamp: time.Unix(0x5c3cafec, 0), Timestamp: time.Unix(0x5c3cafec, 0),
Bits: 0x207fffff, Bits: 0x207fffff,
Nonce: 0xbffffffffffffffa, Nonce: 0,
}, },
Transactions: []*wire.MsgTx{&genesisCoinbaseTx}, Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
} }

View File

@ -131,8 +131,8 @@ var genesisBlockBytes = []byte{
0xc3, 0x54, 0xc9, 0xa7, 0x06, 0x8c, 0x23, 0x24, 0xc3, 0x54, 0xc9, 0xa7, 0x06, 0x8c, 0x23, 0x24,
0x3c, 0x53, 0x6d, 0x56, 0x23, 0xec, 0xaf, 0x3c, 0x3c, 0x53, 0x6d, 0x56, 0x23, 0xec, 0xaf, 0x3c,
0x5c, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x5c, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f,
0x20, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xbf, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 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, 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 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 // NewBlock returns a new instance of a bitcoin block given an underlying
// wire.MsgBlock. See Block. // wire.MsgBlock. See Block.
func NewBlock(msgBlock *wire.MsgBlock) *Block { func NewBlock(msgBlock *wire.MsgBlock) *Block {