313 lines
7.4 KiB
Go
313 lines
7.4 KiB
Go
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||
|
// Use of this source code is governed by an ISC
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package sqlite3
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"database/sql"
|
||
|
"github.com/conformal/btcdb"
|
||
|
"github.com/conformal/btcwire"
|
||
|
_ "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
|
||
|
// 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) {
|
||
|
db.dbLock.Lock()
|
||
|
defer db.dbLock.Unlock()
|
||
|
|
||
|
return db.insertBlockData(sha, prevSha, pver, buf)
|
||
|
}
|
||
|
|
||
|
// insertSha stores a block hash and its associated data block with a
|
||
|
// previous sha of `prevSha' and a version of `pver'.
|
||
|
// insertSha shall be called with db lock held
|
||
|
func (db *SqliteDb) insertBlockData(sha *btcwire.ShaHash, prevSha *btcwire.ShaHash, pver uint32, buf []byte) (blockid int64, err error) {
|
||
|
tx := &db.txState
|
||
|
if tx.tx == nil {
|
||
|
err = db.startTx()
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var prevOk bool
|
||
|
var blkid int64
|
||
|
|
||
|
prevOk = db.blkExistsSha(prevSha) // exists -> ok
|
||
|
if !prevOk {
|
||
|
return 0, btcdb.PrevShaMissing
|
||
|
}
|
||
|
|
||
|
result, err := db.blkStmts[blkInsertSha].Exec(sha.Bytes(), pver, buf)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
blkid, err = result.LastInsertId()
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
blkid -= 1 // skew between btc blockid and sql
|
||
|
|
||
|
// Because we don't know know what the last idx is, we don't
|
||
|
// cache unless already cached
|
||
|
if db.lastBlkShaCached == true {
|
||
|
db.lastBlkSha = *sha
|
||
|
db.lastBlkIdx++
|
||
|
}
|
||
|
|
||
|
bid := tBlockInsertData{*sha, pver, buf}
|
||
|
tx.txInsertList = append(tx.txInsertList, bid)
|
||
|
tx.txDataSz += len(buf)
|
||
|
|
||
|
blockid = blkid
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// fetchSha returns the datablock and pver for the given ShaHash.
|
||
|
func (db *SqliteDb) fetchSha(sha btcwire.ShaHash) (buf []byte, pver uint32,
|
||
|
blkid int64, err error) {
|
||
|
|
||
|
db.dbLock.Lock()
|
||
|
defer db.dbLock.Unlock()
|
||
|
|
||
|
row := db.blkStmts[blkFetchSha].QueryRow(sha.Bytes())
|
||
|
|
||
|
var blockidx int64
|
||
|
var databytes []byte
|
||
|
err = row.Scan(&pver, &databytes, &blockidx)
|
||
|
if err == sql.ErrNoRows {
|
||
|
return // no warning
|
||
|
}
|
||
|
if err != nil {
|
||
|
log.Warnf("fail 2 %v", err)
|
||
|
return
|
||
|
}
|
||
|
buf = databytes
|
||
|
blkid = blockidx - 1 // skew between btc blockid and sql
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// ExistsSha looks up the given block hash
|
||
|
// returns true if it is present in the database.
|
||
|
func (db *SqliteDb) ExistsSha(sha *btcwire.ShaHash) (exists bool) {
|
||
|
db.dbLock.Lock()
|
||
|
defer db.dbLock.Unlock()
|
||
|
|
||
|
_, exists = db.fetchBlockCache(sha)
|
||
|
if exists {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// not in cache, try database
|
||
|
exists = db.blkExistsSha(sha)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// blkExistsSha looks up the given block hash
|
||
|
// returns true if it is present in the database.
|
||
|
// CALLED WITH LOCK HELD
|
||
|
func (db *SqliteDb) blkExistsSha(sha *btcwire.ShaHash) bool {
|
||
|
var pver uint32
|
||
|
|
||
|
row := db.blkStmts[blkExistsSha].QueryRow(sha.Bytes())
|
||
|
err := row.Scan(&pver)
|
||
|
|
||
|
if err == sql.ErrNoRows {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
// ignore real errors?
|
||
|
log.Warnf("blkExistsSha: fail %v", err)
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// FetchBlockShaByIdx returns a block sha based on its height in the blockchain.
|
||
|
func (db *SqliteDb) FetchBlockShaByIdx(blkid int64) (sha *btcwire.ShaHash, err error) {
|
||
|
var row *sql.Row
|
||
|
db.dbLock.Lock()
|
||
|
defer db.dbLock.Unlock()
|
||
|
|
||
|
blockidx := blkid + 1 // skew between btc blockid and sql
|
||
|
|
||
|
row = db.blkStmts[blkFetchIdx].QueryRow(blockidx)
|
||
|
|
||
|
var shabytes []byte
|
||
|
err = row.Scan(&shabytes)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
var shaval btcwire.ShaHash
|
||
|
shaval.SetBytes(shabytes)
|
||
|
return &shaval, nil
|
||
|
}
|
||
|
|
||
|
// FetchIdxRange looks up a range of block by the start and ending ids.
|
||
|
// Fetch is inclusive of the start id and exclusive of the ending id. If the
|
||
|
// special id `AllShas' is provided as endid then FetchIdxRange will fetch all
|
||
|
// shas from startid until no more shas are present.
|
||
|
func (db *SqliteDb) FetchIdxRange(startid, endid int64) (rshalist []btcwire.ShaHash, err error) {
|
||
|
db.dbLock.Lock()
|
||
|
defer db.dbLock.Unlock()
|
||
|
|
||
|
startidx := startid + 1 // skew between btc blockid and sql
|
||
|
|
||
|
var endidx int64
|
||
|
if endid == btcdb.AllShas {
|
||
|
endidx = btcdb.AllShas // no skew if asking for all
|
||
|
} else {
|
||
|
endidx = endid + 1 // skew between btc blockid and sql
|
||
|
}
|
||
|
rows, err := db.blkStmts[blkFetchIdxList].Query(startidx, endidx)
|
||
|
if err != nil {
|
||
|
log.Warnf("query failed %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var shalist []btcwire.ShaHash
|
||
|
for rows.Next() {
|
||
|
var sha btcwire.ShaHash
|
||
|
var shabytes []byte
|
||
|
err = rows.Scan(&shabytes)
|
||
|
if err != nil {
|
||
|
log.Warnf("wtf? %v", err)
|
||
|
break
|
||
|
}
|
||
|
sha.SetBytes(shabytes)
|
||
|
shalist = append(shalist, sha)
|
||
|
}
|
||
|
rows.Close()
|
||
|
if err == nil {
|
||
|
rshalist = shalist
|
||
|
}
|
||
|
log.Tracef("FetchIdxRange idx %v %v returned %v shas err %v", startid, endid, len(shalist), err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// NewestSha provides an interface to quickly look up the sha of
|
||
|
// the most recent (end) of the block chain.
|
||
|
func (db *SqliteDb) NewestSha() (sha *btcwire.ShaHash, blkid int64, err error) {
|
||
|
var row *sql.Row
|
||
|
var blockidx int64
|
||
|
db.dbLock.Lock()
|
||
|
defer db.dbLock.Unlock()
|
||
|
|
||
|
// answer may be cached
|
||
|
if db.lastBlkShaCached == true {
|
||
|
shacopy := db.lastBlkSha
|
||
|
sha = &shacopy
|
||
|
blkid = db.lastBlkIdx - 1 // skew between btc blockid and sql
|
||
|
return
|
||
|
}
|
||
|
|
||
|
querystr := "SELECT key, blockid FROM block ORDER BY blockid DESC;"
|
||
|
|
||
|
tx := &db.txState
|
||
|
if tx.tx != nil {
|
||
|
row = tx.tx.QueryRow(querystr)
|
||
|
} else {
|
||
|
row = db.sqldb.QueryRow(querystr)
|
||
|
}
|
||
|
|
||
|
var shabytes []byte
|
||
|
err = row.Scan(&shabytes, &blockidx)
|
||
|
if err == nil {
|
||
|
var retsha btcwire.ShaHash
|
||
|
retsha.SetBytes(shabytes)
|
||
|
sha = &retsha
|
||
|
blkid = blockidx - 1 // skew between btc blockid and sql
|
||
|
|
||
|
db.lastBlkSha = retsha
|
||
|
db.lastBlkIdx = blockidx
|
||
|
db.lastBlkShaCached = true
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
type SqliteBlockIterator struct {
|
||
|
rows *sql.Rows
|
||
|
stmt *sql.Stmt
|
||
|
db *SqliteDb
|
||
|
}
|
||
|
|
||
|
// NextRow iterates thru all blocks in database.
|
||
|
func (bi *SqliteBlockIterator) NextRow() bool {
|
||
|
return bi.rows.Next()
|
||
|
}
|
||
|
|
||
|
// Row returns row data for block iterator.
|
||
|
func (bi *SqliteBlockIterator) Row() (key *btcwire.ShaHash, pver uint32,
|
||
|
buf []byte, err error) {
|
||
|
var keybytes []byte
|
||
|
|
||
|
err = bi.rows.Scan(&keybytes, &pver, &buf)
|
||
|
if err == nil {
|
||
|
var retkey btcwire.ShaHash
|
||
|
retkey.SetBytes(keybytes)
|
||
|
key = &retkey
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Close shuts down the iterator when done walking blocks in the database.
|
||
|
func (bi *SqliteBlockIterator) Close() {
|
||
|
bi.rows.Close()
|
||
|
bi.stmt.Close()
|
||
|
}
|
||
|
|
||
|
// NewIterateBlocks prepares iterator for all blocks in database.
|
||
|
func (db *SqliteDb) NewIterateBlocks() (btcdb.BlockIterator, error) {
|
||
|
var bi SqliteBlockIterator
|
||
|
db.dbLock.Lock()
|
||
|
defer db.dbLock.Unlock()
|
||
|
|
||
|
stmt, err := db.sqldb.Prepare("SELECT key, pver, data FROM block ORDER BY blockid;")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
tx := &db.txState
|
||
|
if tx.tx != nil {
|
||
|
txstmt := tx.tx.Stmt(stmt)
|
||
|
stmt.Close()
|
||
|
stmt = txstmt
|
||
|
}
|
||
|
bi.stmt = stmt
|
||
|
|
||
|
bi.rows, err = bi.stmt.Query()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
bi.db = db
|
||
|
|
||
|
return &bi, nil
|
||
|
}
|