Don't add a genesis block by default.

This commit modifies the way initial database creation is handled.

Previously, the genesis for the main chain was inserted automatically upon
creation of the database.  However, that approach caused an issue since
other networks such as the test network don't use the same genesis block
as the main network.

The new approach introduced by this commit is to leave it up to the caller
to insert the desired genesis block.  In order to support this, the
InsertBlock function has been modified to allow the first (and only the
first) block to be inserted without having an existing parent.  Also, the
NewestSha function has been modified to return a zero hash, -1 for the
height, and no error when the database does not yet have any blocks.  This
allows the caller to determine the difference between no blocks and only
the genesis block (in which case the return values would be the genesis
hash and 0 for the height).
This commit is contained in:
Dave Collins 2013-07-25 16:44:18 -05:00
parent 584481280c
commit 25684a2ccb
7 changed files with 78 additions and 66 deletions

View file

@ -13,9 +13,30 @@ change.
## Sample Use ## Sample Use
```Go ```Go
db, err := btcdb.CreateDB("sqlite", "dbexample") // Import packages.
import (
"github.com/conformal/btcdb"
_ "github.com/conformal/btcdb/sqlite3"
"github.com/conformal/btcutil"
"github.com/conformal/btcwire"
)
// Create a database and schedule it to be closed on exit.
dbName := "example.db"
db, err := btcdb.CreateDB("sqlite", dbName)
if err != nil {
// Log and handle the error
}
defer db.Close()
// Insert the main network genesis block.
pver := btcwire.ProtocolVersion
genesis := btcutil.NewBlock(&btcwire.GenesisBlock, pver)
newHeight, err := db.InsertBlock(block) newHeight, err := db.InsertBlock(block)
db.Sync() if err != nil {
// Log and handle the error
}
``` ```
## Documentation ## Documentation

10
db.go
View file

@ -96,7 +96,9 @@ type Db interface {
FetchTxUsedBySha(txsha *btcwire.ShaHash) (spentbuf []byte, err error) FetchTxUsedBySha(txsha *btcwire.ShaHash) (spentbuf []byte, err error)
// InsertBlock inserts raw block and transaction data from a block // InsertBlock inserts raw block and transaction data from a block
// into the database. // into the database. The first block inserted into the database
// will be treated as the genesis block. Every subsequent block insert
// requires the referenced parent block to already exist.
InsertBlock(block *btcutil.Block) (height int64, err error) InsertBlock(block *btcutil.Block) (height int64, err error)
// InsertBlockData stores a block hash and its associated data block // InsertBlockData stores a block hash and its associated data block
@ -119,8 +121,10 @@ type Db interface {
// NewIterateBlocks returns an iterator for all blocks in database. // NewIterateBlocks returns an iterator for all blocks in database.
NewIterateBlocks() (pbi BlockIterator, err error) NewIterateBlocks() (pbi BlockIterator, err error)
// NewestSha provides an interface to quickly look up the hash of // NewestSha returns the hash and block height of the most recent (end)
// the most recent (end) block of the block chain. // block of the block chain. It will return the zero hash, -1 for
// the block height, and no error (nil) if there are not any blocks in
// the database yet.
NewestSha() (sha *btcwire.ShaHash, height int64, err error) NewestSha() (sha *btcwire.ShaHash, height int64, err error)
// RollbackClose discards the recent database changes to the previously // RollbackClose discards the recent database changes to the previously

16
doc.go
View file

@ -21,18 +21,22 @@ although a block can have a variable number of transactions. Along
with these two items, several convenience functions for dealing with with these two items, several convenience functions for dealing with
the database are provided as well as functions to query specific items the database are provided as well as functions to query specific items
that may be present in a block or tx (although many of these are in that may be present in a block or tx (although many of these are in
the db_sqlite subpackage). the sqlite3 subpackage).
Usage Usage
At the highest level, the use of this packages just requires that you At the highest level, the use of this packages just requires that you
import it, setup a database, insert some data into it, and optionally, import it, setup a database, insert some data into it, and optionally,
query the data back. In a more concrete example: query the data back. The first block inserted into the database will be
treated as the genesis block. Every subsequent block insert requires the
referenced parent block to already exist. In a more concrete example:
// Import packages. // Import packages.
import ( import (
"github.com/conformal/btcdb" "github.com/conformal/btcdb"
_ "github.com/conformal/btcdb/sqlite3" _ "github.com/conformal/btcdb/sqlite3"
"github.com/conformal/btcutil"
"github.com/conformal/btcwire"
) )
// Create a database and schedule it to be closed on exit. // Create a database and schedule it to be closed on exit.
@ -43,13 +47,13 @@ query the data back. In a more concrete example:
} }
defer db.Close() defer db.Close()
// Insert a block.
// Insert the main network genesis block.
pver := btcwire.ProtocolVersion
genesis := btcutil.NewBlock(&btcwire.GenesisBlock, pver)
newHeight, err := db.InsertBlock(block) newHeight, err := db.InsertBlock(block)
if err != nil { if err != nil {
// Log and handle the error // Log and handle the error
} }
// Sync the database.
db.Sync()
*/ */
package btcdb package btcdb

View file

@ -76,7 +76,7 @@ func testUnspentInsert(t *testing.T, mode int) {
blocks := loadblocks(t) blocks := loadblocks(t)
endtest: endtest:
for height := int64(1); height < int64(len(blocks)); height++ { for height := int64(0); height < int64(len(blocks)); height++ {
block := blocks[height] block := blocks[height]
// look up inputs to this x // look up inputs to this x

View file

@ -81,7 +81,7 @@ func testOperationalMode(t *testing.T, mode int) {
err = nil err = nil
out: out:
for height := int64(1); height < int64(len(blocks)); height++ { for height := int64(0); height < int64(len(blocks)); height++ {
block := blocks[height] block := blocks[height]
if mode != dbTmNoVerify { if mode != dbTmNoVerify {
// except for NoVerify which does not allow lookups check inputs // except for NoVerify which does not allow lookups check inputs
@ -203,7 +203,7 @@ func testBackout(t *testing.T, mode int) {
} }
err = nil err = nil
for height := int64(1); height < int64(len(blocks)); height++ { for height := int64(0); height < int64(len(blocks)); height++ {
if height == 100 { if height == 100 {
t.Logf("Syncing at block height 100") t.Logf("Syncing at block height 100")
db.Sync() db.Sync()
@ -305,10 +305,11 @@ func loadBlocks(t *testing.T, file string) (blocks []*btcutil.Block, err error)
} }
}() }()
var block *btcutil.Block // Set the first block as the genesis block.
// block 0 isn't really there, put in nil genesis := btcutil.NewBlock(&btcwire.GenesisBlock, btcwire.ProtocolVersion)
blocks = append(blocks, block) blocks = append(blocks, genesis)
var block *btcutil.Block
err = nil err = nil
for height := int64(1); err == nil; height++ { for height := int64(1); err == nil; height++ {
var rintbuf uint32 var rintbuf uint32

View file

@ -178,12 +178,6 @@ func createDB(db *sql.DB) error {
} }
} }
// Insert the genesis block.
err := insertGenesisBlock(db)
if err != nil {
return err
}
return nil return nil
} }
@ -610,8 +604,10 @@ func (db *SqliteDb) DropAfterBlockBySha(sha *btcwire.ShaHash) (err error) {
return return
} }
// InsertBlock inserts the block data and transaction data from a block // InsertBlock inserts raw block and transaction data from a block into the
// into the database. // database. The first block inserted into the database will be treated as the
// genesis block. Every subsequent block insert requires the referenced parent
// block to already exist.
func (db *SqliteDb) InsertBlock(block *btcutil.Block) (height int64, err error) { func (db *SqliteDb) InsertBlock(block *btcutil.Block) (height int64, err error) {
db.dbLock.Lock() db.dbLock.Lock()
defer db.dbLock.Unlock() defer db.dbLock.Unlock()

View file

@ -5,36 +5,12 @@
package sqlite3 package sqlite3
import ( import (
"bytes"
"database/sql" "database/sql"
"github.com/conformal/btcdb" "github.com/conformal/btcdb"
"github.com/conformal/btcwire" "github.com/conformal/btcwire"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
) )
// insertGenesisBlock inserts the genesis block of the block chain into the
// database.
func insertGenesisBlock(db *sql.DB) error {
// Encode the genesis block to raw bytes.
pver := uint32(btcwire.ProtocolVersion)
var buf bytes.Buffer
err := btcwire.GenesisBlock.BtcEncode(&buf, pver)
if err != nil {
return err
}
// Insert the genesis block along with its hash and protocol encoding
// version.
sql := blkqueries[blkInsertSha]
sha := btcwire.GenesisHash
_, err = db.Exec(sql, sha.Bytes(), pver, buf.Bytes())
if err != nil {
return err
}
return nil
}
// InsertBlockData stores a block hash and its associated data block with a // InsertBlockData stores a block hash and its associated data block with a
// previous sha of `prevSha' and a version of `pver'. // previous sha of `prevSha' and a version of `pver'.
func (db *SqliteDb) InsertBlockData(sha *btcwire.ShaHash, prevSha *btcwire.ShaHash, pver uint32, buf []byte) (blockid int64, err error) { func (db *SqliteDb) InsertBlockData(sha *btcwire.ShaHash, prevSha *btcwire.ShaHash, pver uint32, buf []byte) (blockid int64, err error) {
@ -56,20 +32,26 @@ func (db *SqliteDb) insertBlockData(sha *btcwire.ShaHash, prevSha *btcwire.ShaHa
} }
} }
var prevOk bool // It is an error if the previous block does not already exist in the
var blkid int64 // database, unless there are no blocks at all.
if prevOk := db.blkExistsSha(prevSha); !prevOk {
prevOk = db.blkExistsSha(prevSha) // exists -> ok var numBlocks uint64
if !prevOk { querystr := "SELECT COUNT(blockid) FROM block;"
err := db.sqldb.QueryRow(querystr).Scan(&numBlocks)
if err != nil {
return 0, err
}
if numBlocks != 0 {
return 0, btcdb.PrevShaMissing return 0, btcdb.PrevShaMissing
} }
}
result, err := db.blkStmts[blkInsertSha].Exec(sha.Bytes(), pver, buf) result, err := db.blkStmts[blkInsertSha].Exec(sha.Bytes(), pver, buf)
if err != nil { if err != nil {
return return
} }
blkid, err = result.LastInsertId() blkid, err := result.LastInsertId()
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -218,8 +200,9 @@ func (db *SqliteDb) FetchHeightRange(startHeight, endHeight int64) (rshalist []b
return return
} }
// NewestSha provides an interface to quickly look up the sha of // NewestSha returns the hash and block height of the most recent (end) block of
// the most recent (end) of the block chain. // the block chain. It will return the zero hash, -1 for the block height, and
// no error (nil) if there are not any blocks in the database yet.
func (db *SqliteDb) NewestSha() (sha *btcwire.ShaHash, blkid int64, err error) { func (db *SqliteDb) NewestSha() (sha *btcwire.ShaHash, blkid int64, err error) {
var row *sql.Row var row *sql.Row
var blockidx int64 var blockidx int64
@ -245,6 +228,9 @@ func (db *SqliteDb) NewestSha() (sha *btcwire.ShaHash, blkid int64, err error) {
var shabytes []byte var shabytes []byte
err = row.Scan(&shabytes, &blockidx) err = row.Scan(&shabytes, &blockidx)
if err == sql.ErrNoRows {
return &btcwire.ShaHash{}, -1, nil
}
if err == nil { if err == nil {
var retsha btcwire.ShaHash var retsha btcwire.ShaHash
retsha.SetBytes(shabytes) retsha.SetBytes(shabytes)