Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions packages/btcindexer/db/migrations/0001_initial_schema.sql
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
-- This table tracks the blocks received from the relayer (queue for cron job)
CREATE TABLE processed_blocks (
CREATE TABLE btc_blocks (
height INTEGER PRIMARY KEY,
hash TEXT NOT NULL UNIQUE,
processed_at INTEGER DEFAULT unixepoch('subsec')
processed_at REAL DEFAULT (unixepoch('subsec')),
status TEXT NOT NULL DEFAULT 'new' -- 'new' | 'scanned'
) STRICT;

-- This table tracks the nBTC deposit txs
CREATE TABLE nbtc_txs (
-- This table tracks the nBTC deposit txs (minting)
CREATE TABLE nbtc_minting (
tx_id TEXT PRIMARY KEY,
block_hash TEXT NOT NULL,
block_height INTEGER NOT NULL,
vout INTEGER NOT NULL,
sui_recipient TEXT NOT NULL,
amount_sats INTEGER NOT NULL,
status TEXT NOT NULL, -- 'broadcasting' | 'confirming' | 'finalized' | 'minting' | 'minted' | 'reorg'
created_at INTEGER DEFAULT unixepoch('subsec'),
updated_at INTEGER DEFAULT unixepoch('subsec')
created_at REAL DEFAULT (unixepoch('subsec')),
updated_at REAL DEFAULT (unixepoch('subsec'))
) STRICT;

-- Indexes
CREATE INDEX nbtc_txs_status ON nbtc_txs (status);
CREATE INDEX nbtc_txs_sui_recipient ON nbtc_txs (sui_recipient);
CREATE INDEX processed_blocks_height ON processed_blocks (height);
CREATE INDEX nbtc_minting_status ON nbtc_minting (status);
CREATE INDEX nbtc_minting_sui_recipient ON nbtc_minting (sui_recipient);
CREATE INDEX btc_blocks_height ON btc_blocks (height);
CREATE INDEX btc_blocks_status ON btc_blocks (status);
38 changes: 19 additions & 19 deletions packages/btcindexer/src/btcindexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ export class Indexer implements Storage {
}

const insertBlockStmt = this.d1.prepare(
`INSERT INTO processed_blocks (height, hash) VALUES (?, ?)
`INSERT INTO btc_blocks (height, hash, status) VALUES (?, ?, 'new')
ON CONFLICT(height) DO UPDATE SET hash = excluded.hash
WHERE processed_blocks.hash IS NOT excluded.hash`,
WHERE btc_blocks.hash IS NOT excluded.hash`,
);

// TODO: store in KV
Expand Down Expand Up @@ -111,7 +111,9 @@ export class Indexer implements Storage {
async scanNewBlocks(): Promise<void> {
console.log("Cron: Running scanNewBlocks");
const blocksToProcess = await this.d1
.prepare("SELECT height, hash FROM processed_blocks ORDER BY height ASC LIMIT 10")
.prepare(
"SELECT height, hash FROM btc_blocks WHERE status = 'new' ORDER BY height ASC LIMIT 10",
)
.all<{ height: number; hash: string }>();

if (!blocksToProcess.results || blocksToProcess.results.length === 0) {
Expand All @@ -124,7 +126,7 @@ export class Indexer implements Storage {
const nbtcTxStatements: D1PreparedStatement[] = [];

const insertNbtcTxStmt = this.d1.prepare(
"INSERT INTO nbtc_txs (tx_id, block_hash, block_height, vout, sui_recipient, amount_sats, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
"INSERT INTO nbtc_minting (tx_id, block_hash, block_height, vout, sui_recipient, amount_sats, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
);

for (const blockInfo of blocksToProcess.results) {
Expand Down Expand Up @@ -168,10 +170,10 @@ export class Indexer implements Storage {
await this.blocksDB.put("chain_tip", latestHeightProcessed.toString());
console.log(`Cron: Updated chain_tip to ${latestHeightProcessed}`);

const heightsToDelete = blocksToProcess.results.map((r) => r.height);
const heights = heightsToDelete.join(",");
const deleteStmt = `DELETE FROM processed_blocks WHERE height IN (${heights})`;
await this.d1.prepare(deleteStmt).run();
const heightsToUpdate = blocksToProcess.results.map((r) => r.height);
const heights = heightsToUpdate.join(",");
const updateStmt = `UPDATE btc_blocks SET status = 'scanned' WHERE height IN (${heights})`;
await this.d1.prepare(updateStmt).run();
}

findNbtcDeposits(tx: Transaction): Deposit[] {
Expand Down Expand Up @@ -205,7 +207,7 @@ export class Indexer implements Storage {
async processFinalizedTransactions(): Promise<void> {
const finalizedTxs = await this.d1
.prepare(
"SELECT tx_id, block_hash, block_height as height FROM nbtc_txs WHERE status = 'finalized'",
"SELECT tx_id, block_hash, block_height as height FROM nbtc_minting WHERE status = 'finalized'",
)
.all<BlockRecord>();

Expand Down Expand Up @@ -275,10 +277,10 @@ export class Indexer implements Storage {
}
}
const setMintedStmt = this.d1.prepare(
"UPDATE nbtc_txs SET status = 'minted', updated_at = CURRENT_TIMESTAMP WHERE tx_id = ?",
"UPDATE nbtc_minting SET status = 'minted', updated_at = unixepoch('subsec') WHERE tx_id = ?",
);
const setFailedStmt = this.d1.prepare(
"UPDATE nbtc_txs SET status = 'failed', updated_at = CURRENT_TIMESTAMP WHERE tx_id = ?",
"UPDATE nbtc_minting SET status = 'failed', updated_at = unixepoch('subsec') WHERE tx_id = ?",
);
const updates = processedTxIds.map((p) =>
p.success ? setMintedStmt.bind(p.tx_id) : setFailedStmt.bind(p.tx_id),
Expand Down Expand Up @@ -312,7 +314,7 @@ export class Indexer implements Storage {
async updateConfirmationsAndFinalize(latestHeight: number): Promise<void> {
const pendingTxs = await this.d1
.prepare(
"SELECT tx_id, block_hash, block_height FROM nbtc_txs WHERE status = 'confirming'",
"SELECT tx_id, block_hash, block_height FROM nbtc_minting WHERE status = 'confirming'",
)
.all<{ tx_id: string; block_hash: string; block_height: number }>();

Expand Down Expand Up @@ -344,11 +346,9 @@ export class Indexer implements Storage {
): Promise<{ reorgUpdates: D1PreparedStatement[]; reorgedTxIds: string[] }> {
const reorgUpdates: D1PreparedStatement[] = [];
const reorgedTxIds: string[] = [];
const reorgCheckStmt = this.d1.prepare(
"SELECT hash FROM processed_blocks WHERE height = ?",
);
const reorgCheckStmt = this.d1.prepare("SELECT hash FROM btc_blocks WHERE height = ?");
const reorgStmt = this.d1.prepare(
"UPDATE nbtc_txs SET status = 'reorg', updated_at = CURRENT_TIMESTAMP WHERE tx_id = ?",
"UPDATE nbtc_minting SET status = 'reorg', updated_at = unixepoch('subsec') WHERE tx_id = ?",
);

for (const tx of pendingTxs) {
Expand All @@ -372,7 +372,7 @@ export class Indexer implements Storage {
selectFinalizedNbtcTxs(pendingTxs: PendingTx[], latestHeight: number): D1PreparedStatement[] {
const updates: D1PreparedStatement[] = [];
const finalizeStmt = this.d1.prepare(
"UPDATE nbtc_txs SET status = 'finalized', updated_at = CURRENT_TIMESTAMP WHERE tx_id = ?",
"UPDATE nbtc_minting SET status = 'finalized', updated_at = unixepoch('subsec') WHERE tx_id = ?",
);

for (const tx of pendingTxs) {
Expand All @@ -392,7 +392,7 @@ export class Indexer implements Storage {
const latestHeight = latestHeightStr ? parseInt(latestHeightStr, 10) : 0;

const tx = await this.d1
.prepare("SELECT * FROM nbtc_txs WHERE tx_id = ?")
.prepare("SELECT * FROM nbtc_minting WHERE tx_id = ?")
.bind(txid)
.first<NbtcTxD1Row>();

Expand All @@ -418,7 +418,7 @@ export class Indexer implements Storage {
const latestHeight = latestHeightStr ? parseInt(latestHeightStr, 10) : 0;

const dbResult = await this.d1
.prepare("SELECT * FROM nbtc_txs WHERE sui_recipient = ? ORDER BY created_at DESC")
.prepare("SELECT * FROM nbtc_minting WHERE sui_recipient = ? ORDER BY created_at DESC")
.bind(suiAddress)
.all<NbtcTxD1Row>();

Expand Down
2 changes: 1 addition & 1 deletion packages/btcindexer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default {
const d1 = env.DB;
// TODO: move this to the indexer directly
const latestBlock = await d1
.prepare("SELECT MAX(height) as latest_height FROM processed_blocks")
.prepare("SELECT MAX(height) as latest_height FROM btc_blocks")
.first<{ latest_height: number }>();

const indexer = indexerFromEnv(env);
Expand Down
2 changes: 1 addition & 1 deletion packages/btcindexer/worker-configuration.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable */
// Generated by Wrangler by running `wrangler types` (hash: d59ad962bdbac33b5f4bb27cacf2d2e2)
// Generated by Wrangler by running `wrangler types` (hash: 29360ac1d239ef988298d2c230492dcd)
// Runtime types generated with workerd@1.20250712.0 2025-06-20 nodejs_compat
declare namespace Cloudflare {
interface Env {
Expand Down
Loading