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