From f61529845313e286b484851ca8d0de54b5afc3c6 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Thu, 14 Feb 2019 18:20:49 +0200 Subject: [PATCH] =?UTF-8?q?[DEV-259]=20Allow=20to=20spend=20genesis=20coin?= =?UTF-8?q?base,=20and=20use=20ProcessBlock=20to=20ad=E2=80=A6=20(#187)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [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 --- blockdag/accept.go | 5 ++- blockdag/dag.go | 73 +++++++++++++++++++++++-------- blockdag/dag_test.go | 3 +- blockdag/dagio.go | 64 ++++----------------------- blockdag/error.go | 5 +++ blockdag/error_test.go | 2 + blockdag/example_test.go | 5 ++- blockdag/indexers/txindex.go | 3 ++ blockdag/testdata/blk_0_to_4.dat | Bin 1788 -> 1788 bytes blockdag/testdata/blk_3A.dat | Bin 425 -> 425 bytes blockdag/testdata/blk_3B.dat | Bin 312 -> 312 bytes blockdag/testdata/blk_3C.dat | Bin 344 -> 344 bytes blockdag/testdata/blk_3D.dat | Bin 470 -> 470 bytes blockdag/validate.go | 52 +++++++++++++++------- dagconfig/genesis.go | 10 ++--- dagconfig/genesis_test.go | 4 +- util/block.go | 5 +++ 17 files changed, 129 insertions(+), 102 deletions(-) diff --git a/blockdag/accept.go b/blockdag/accept.go index ec6251d9d..fa391d2ed 100644 --- a/blockdag/accept.go +++ b/blockdag/accept.go @@ -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 diff --git a/blockdag/dag.go b/blockdag/dag.go index 49be30d2c..845478d83 100644 --- a/blockdag/dag.go +++ b/blockdag/dag.go @@ -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 diff --git a/blockdag/dag_test.go b/blockdag/dag_test.go index 946e3706a..dccd31bca 100644 --- a/blockdag/dag_test.go +++ b/blockdag/dag_test.go @@ -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, diff --git a/blockdag/dagio.go b/blockdag/dagio.go index d037e0916..a0f1610a3 100644 --- a/blockdag/dagio.go +++ b/blockdag/dagio.go @@ -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 diff --git a/blockdag/error.go b/blockdag/error.go index fc6a9459c..26ebd1df0 100644 --- a/blockdag/error.go +++ b/blockdag/error.go @@ -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", diff --git a/blockdag/error_test.go b/blockdag/error_test.go index f340619c5..055db3d5c 100644 --- a/blockdag/error_test.go +++ b/blockdag/error_test.go @@ -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"}, diff --git a/blockdag/example_test.go b/blockdag/example_test.go index 44b16064a..2331441d7 100644 --- a/blockdag/example_test.go +++ b/blockdag/example_test.go @@ -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 diff --git a/blockdag/indexers/txindex.go b/blockdag/indexers/txindex.go index 46e74743d..459738e0f 100644 --- a/blockdag/indexers/txindex.go +++ b/blockdag/indexers/txindex.go @@ -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 } diff --git a/blockdag/testdata/blk_0_to_4.dat b/blockdag/testdata/blk_0_to_4.dat index e31d984bfe65395e76c6373ff13dd729ea5ced74..1c7af6f107fdbee9d085314b91d65179aad31549 100644 GIT binary patch delta 627 zcmeyv`-gWzBnJZ&OiV1T-?5?2ILv+LBgy&yT~_R}NjiQtkCbC$E&)X!%*1dH9|wMV8S3p@_gS!G4J=F`@d4vOXix%&N0jFQ)=nRXbt~?l4m6nO zd1zzmnZ2rNC;2t6-M1*d5x1bceSgr=3_X(#D#_XPtGBNIvtZTNxtF@PZJo1a|{U)6&y@yL=o~Ji4i6CxIjGNZ_CE3DZ4bExCRTSV=@eM1_P<8c~EiNMb}q1;+*XtS`x0 gzv+l+>B3~0OFQ$=l-2T|4*FIeamn;%DK#Ss0326Gz5oCK diff --git a/blockdag/testdata/blk_3A.dat b/blockdag/testdata/blk_3A.dat index 35430b95fb807325616cf7dec973df402dbd71d6..e8ea33276fc978c5e7ec48cd7192dc37d1d82115 100644 GIT binary patch delta 172 zcmZ3WC0oG0*hjlCNpAIyb=Xq#j>Y2T&YA5+MuidvOz7e;e zT>Qe>duhJ29?F)e?b0*2@Oj~r>$?)dcP-}N)OeaXXGJX``SVn8@D#xDJ)o6u=V|ie>L&4C+`J1s3mM& zw%p_AiS0Rmq!~BMiK#4#+m>{x(_T;ZKv+)6dqVQ>*W1K^Z2JGdUI9dd029b^hy%cU z?8JW{;0q4p)(*A&tgZt9 D8JT4G literal 312 zcmeylZ_7;sMg|ZNVDvn|+Ueu4ZpHo6fd=zD4{c06vsYE^B){gh`xeDF;ue%YkGh#3 zp&ygFB=)|?#5C*r#Av(!^Xxt^Tk-tS>Ob!m`4EzSzuqPWWYhou^$H*w1eidULmU9+ zV<-Lt0bg(!w|;<8u!nw9NpgxlNC*TN89uRUfei_S>BVLoSOp;ug3JTD4gz4NVRan< Dvny$; diff --git a/blockdag/testdata/blk_3C.dat b/blockdag/testdata/blk_3C.dat index 09b044322d99e7f1af8163e0814381b92565a2dd..bb0c2bd9769a3b23c2ee76ee36f5a089c88a063b 100644 GIT binary patch literal 344 zcmeylZ_CX9Mg|ZNU}E0A;q%$eje&LkA17RlR1DdsU1%fUd8Nox&+vn4@q6FNfzzV3 zZZS`v7beK{cgNlm|B}Ljg#}yRfB07uFMIM{pvvY89ge9w#Ze04m6{fgJ!*$k&OKye z5pp&DoBjS()i*-&@7LSJfL!wbf4u@LhyeslAjd%*2bY literal 344 zcmeylZ_CX9Mg|ZNUl%GC$KlN_v#gC8057_9cm+g*p-}F_5 zUCE_JsrY=-4A0qwWC0oG0*hjlCNpAIyb=Xq#j>Y2T&YA5+MuidvOz7e;e zTi7c0GAt$Sl=2cFBz^w%y9f;^MB8ZE+?f|9-tq49KSc|LYZ4Kn#Y7 O_4x{fY@N)