// 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 }