Browse Source

storage: store full transactions and decouple storage from rpc

wip_broken_chromebook
George Tankersley 6 years ago
parent
commit
5c2e5479a3
  1. 142
      storage/sqlite3.go
  2. 15
      storage/sqlite3_test.go

142
storage/sqlite3.go

@ -5,8 +5,6 @@ import (
"database/sql"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/gtank/ctxd/rpc"
"github.com/pkg/errors"
)
@ -36,9 +34,28 @@ func CreateTables(conn *sql.DB) error {
);
`
_, err = conn.Exec(blockTable)
if err != nil {
return err
}
txTable := `
CREATE TABLE IF NOT EXISTS transactions (
block_height INTEGER,
block_hash TEXT,
tx_index INTEGER,
tx_hash TEXT,
tx_bytes BLOB,
FOREIGN KEY (block_height) REFERENCES blocks (height),
FOREIGN KEY (block_hash) REFERENCES blocks (hash)
);
`
_, err = conn.Exec(txTable)
return err
}
// TODO consider max/count queries instead of state table. bit of a coupling assumption though.
func GetCurrentHeight(ctx context.Context, db *sql.DB) (int, error) {
var height int
query := "SELECT current_height FROM state WHERE rowid = 1"
@ -46,60 +63,28 @@ func GetCurrentHeight(ctx context.Context, db *sql.DB) (int, error) {
return height, err
}
func SetCurrentHeight(conn *sql.DB, height int) error {
update := "UPDATE state SET current_height=?, timestamp=CURRENT_TIMESTAMP WHERE rowid = 1"
result, err := conn.Exec(update, height)
if err != nil {
return errors.Wrap(err, "updating state row")
}
rowCount, err := result.RowsAffected()
if err != nil {
return errors.Wrap(err, "checking if state row exists")
}
if rowCount == 0 {
// row does not yet exist
insert := "INSERT OR IGNORE INTO state (rowid, current_height) VALUES (1, ?)"
result, err = conn.Exec(insert, height)
if err != nil {
return errors.Wrap(err, "on state row insert")
}
rowCount, err = result.RowsAffected()
if err != nil {
return errors.Wrap(err, "checking if state row exists")
}
if rowCount != 1 {
return errors.New("totally failed to update current height state")
}
}
return nil
}
func GetBlock(ctx context.Context, db *sql.DB, height int) (*rpc.CompactBlock, error) {
func GetBlock(ctx context.Context, db *sql.DB, height int) ([]byte, error) {
var blockBytes []byte // avoid a copy with *RawBytes
query := "SELECT compact_encoding from blocks WHERE height = ?"
err := db.QueryRowContext(ctx, query, height).Scan(&blockBytes)
if err != nil {
return nil, err
}
compactBlock := &rpc.CompactBlock{}
err = proto.Unmarshal(blockBytes, compactBlock)
return compactBlock, err
return blockBytes, err
}
func GetBlockByHash(ctx context.Context, db *sql.DB, hash string) (*rpc.CompactBlock, error) {
func GetBlockByHash(ctx context.Context, db *sql.DB, hash string) ([]byte, error) {
var blockBytes []byte // avoid a copy with *RawBytes
query := "SELECT compact_encoding from blocks WHERE hash = ?"
err := db.QueryRowContext(ctx, query, hash).Scan(&blockBytes)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("getting block with hash %s", hash))
}
compactBlock := &rpc.CompactBlock{}
err = proto.Unmarshal(blockBytes, compactBlock)
return compactBlock, err
return blockBytes, err
}
// [start, end]
func GetBlockRange(conn *sql.DB, start, end int) ([]*rpc.CompactBlock, error) {
func GetBlockRange(conn *sql.DB, start, end int) ([][]byte, error) {
// TODO sanity check range bounds
query := "SELECT compact_encoding from blocks WHERE (height BETWEEN ? AND ?)"
result, err := conn.Query(query, start, end)
@ -108,19 +93,14 @@ func GetBlockRange(conn *sql.DB, start, end int) ([]*rpc.CompactBlock, error) {
}
defer result.Close()
compactBlocks := make([]*rpc.CompactBlock, 0, (end-start)+1)
compactBlocks := make([][]byte, 0, (end-start)+1)
for result.Next() {
var blockBytes []byte // avoid a copy with *RawBytes
err = result.Scan(&blockBytes)
if err != nil {
return nil, err
}
newBlock := &rpc.CompactBlock{}
err = proto.Unmarshal(blockBytes, newBlock)
if err != nil {
return nil, err
}
compactBlocks = append(compactBlocks, newBlock)
compactBlocks = append(compactBlocks, blockBytes)
}
err = result.Err()
@ -147,3 +127,73 @@ func StoreBlock(conn *sql.DB, height int, hash string, sapling bool, encoded []b
}
return err
}
func SetCurrentHeight(conn *sql.DB, height int) error {
update := "UPDATE state SET current_height=?, timestamp=CURRENT_TIMESTAMP WHERE rowid = 1"
result, err := conn.Exec(update, height)
if err != nil {
return errors.Wrap(err, "updating state row")
}
rowCount, err := result.RowsAffected()
if err != nil {
return errors.Wrap(err, "checking if state row exists")
}
if rowCount == 0 {
// row does not yet exist
insert := "INSERT OR IGNORE INTO state (rowid, current_height) VALUES (1, ?)"
result, err = conn.Exec(insert, height)
if err != nil {
return errors.Wrap(err, "on state row insert")
}
rowCount, err = result.RowsAffected()
if err != nil {
return errors.Wrap(err, "checking if state row exists")
}
if rowCount != 1 {
return errors.New("totally failed to update current height state")
}
}
return nil
}
func StoreFullTx(db *sql.DB, blockHeight int, blockHash string, txIndex int, txHash string, txBytes []byte) error {
insertTx := "INSERT INTO transactions (block_height, block_hash, tx_index, tx_hash, tx_bytes) VALUES (?,?,?,?,?)"
_, err := db.Exec(insertTx, blockHeight, blockHash, txIndex, txHash, txBytes)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("storing tx %x", txHash))
}
return nil
}
// GetFulTxByHash retrieves a full transaction by its little-endian hash.
func GetFullTxByHash(ctx context.Context, db *sql.DB, txHash string) ([]byte, error) {
var txBytes []byte // avoid a copy with *RawBytes
query := "SELECT bytes from transactions WHERE tx_hash = ?"
err := db.QueryRowContext(ctx, query, txHash).Scan(&txBytes)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("getting tx with hash %s", txHash))
}
return txBytes, nil
}
// GetFullTxByHeightAndIndex retrieves a full transaction by its parent block height and index
func GetFullTxByHeightAndIndex(ctx context.Context, db *sql.DB, blockHeight, txIndex int) ([]byte, error) {
var txBytes []byte // avoid a copy with *RawBytes
query := "SELECT bytes from transactions WHERE (block_height = ? AND tx_index = ?)"
err := db.QueryRowContext(ctx, query, blockHeight, txIndex).Scan(&txBytes)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("getting tx (%d, %d)", blockHeight, txIndex))
}
return txBytes, nil
}
// GetFullTxByHashAndIndex retrieves a full transaction by its parent block hash and index
func GetFullTxByHashAndIndex(ctx context.Context, db *sql.DB, blockHash string, txIndex int) ([]byte, error) {
var txBytes []byte // avoid a copy with *RawBytes
query := "SELECT bytes from transactions WHERE (block_hash = ? AND tx_index = ?)"
err := db.QueryRowContext(ctx, query, blockHash, txIndex).Scan(&txBytes)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("getting tx (%x, %d)", blockHash, txIndex))
}
return txBytes, nil
}

15
storage/sqlite3_test.go

@ -9,10 +9,12 @@ import (
"io/ioutil"
"testing"
protobuf "github.com/golang/protobuf/proto"
"github.com/gtank/ctxd/parser"
"github.com/golang/protobuf/proto"
_ "github.com/mattn/go-sqlite3"
"github.com/pkg/errors"
"github.com/gtank/ctxd/parser"
"github.com/gtank/ctxd/rpc"
)
func TestSqliteStorage(t *testing.T) {
@ -58,7 +60,7 @@ func TestSqliteStorage(t *testing.T) {
hash := hex.EncodeToString(block.GetEncodableHash())
hasSapling := block.HasSaplingTransactions()
protoBlock := block.ToCompact()
marshaled, _ := protobuf.Marshal(protoBlock)
marshaled, _ := proto.Marshal(protoBlock)
err = StoreBlock(conn, height, hash, hasSapling, marshaled)
if err != nil {
@ -92,8 +94,13 @@ func TestSqliteStorage(t *testing.T) {
if err != nil {
t.Error(errors.Wrap(err, "retrieving stored block"))
}
cblock := &rpc.CompactBlock{}
err = proto.Unmarshal(retBlock, cblock)
if err != nil {
t.Fatal(err)
}
if int(retBlock.Height) != lastBlockTest.BlockHeight {
if int(cblock.Height) != lastBlockTest.BlockHeight {
t.Error("incorrect retrieval")
}

Loading…
Cancel
Save