mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-04 13:16:43 +00:00
[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:
parent
f06513aad7
commit
f615298453
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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"},
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
BIN
blockdag/testdata/blk_0_to_4.dat
vendored
BIN
blockdag/testdata/blk_0_to_4.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3A.dat
vendored
BIN
blockdag/testdata/blk_3A.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3B.dat
vendored
BIN
blockdag/testdata/blk_3B.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3C.dat
vendored
BIN
blockdag/testdata/blk_3C.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3D.dat
vendored
BIN
blockdag/testdata/blk_3D.dat
vendored
Binary file not shown.
@ -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
|
||||
|
@ -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},
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user