Skip to content

Commit

Permalink
Merge pull request #448 from hirosystems/develop
Browse files Browse the repository at this point in the history
release to beta
  • Loading branch information
rafaelcr authored Feb 25, 2025
2 parents 01a99c8 + 6815878 commit 03a72da
Show file tree
Hide file tree
Showing 25 changed files with 664 additions and 257 deletions.
28 changes: 13 additions & 15 deletions api/ordinals/src/pg/brc20/brc20-pg-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,38 +82,36 @@ export class Brc20PgStore extends BasePgStore {
): Promise<DbPaginatedResult<DbBrc20Balance>> {
const ticker = sqlOr(
this.sql,
args.ticker?.map(t => this.sql`d.ticker LIKE LOWER(${t}) || '%'`)
args.ticker?.map(t => this.sql`b.ticker LIKE LOWER(${t}) || '%'`)
);
// Change selection table depending if we're filtering by block height or not.
const results = await this.sql<(DbBrc20Balance & { total: number })[]>`
SELECT
b.ticker, (SELECT decimals FROM tokens WHERE ticker = b.ticker) AS decimals,
b.avail_balance, b.trans_balance, b.total_balance, COUNT(*) OVER() as total
${
args.block_height
? this.sql`
SELECT
d.ticker, d.decimals,
SUM(b.avail_balance) AS avail_balance,
SUM(b.trans_balance) AS trans_balance,
SUM(b.avail_balance + b.trans_balance) AS total_balance,
COUNT(*) OVER() as total
FROM operations AS b
INNER JOIN tokens AS d ON d.ticker = b.ticker
FROM balances_history b
INNER JOIN (
SELECT ticker, address, MAX(block_height) as max_block_height
FROM balances_history
WHERE address = ${args.address} AND block_height <= ${args.block_height}
GROUP BY ticker, address
) latest ON b.ticker = latest.ticker AND b.address = latest.address AND b.block_height = latest.max_block_height
WHERE
b.address = ${args.address}
AND b.block_height <= ${args.block_height}
b.total_balance > 0
${ticker ? this.sql`AND ${ticker}` : this.sql``}
GROUP BY d.ticker, d.decimals
HAVING SUM(b.avail_balance + b.trans_balance) > 0
`
: this.sql`
SELECT d.ticker, d.decimals, b.avail_balance, b.trans_balance, b.total_balance, COUNT(*) OVER() as total
FROM balances AS b
INNER JOIN tokens AS d ON d.ticker = b.ticker
WHERE
b.total_balance > 0
AND b.address = ${args.address}
${ticker ? this.sql`AND ${ticker}` : this.sql``}
`
}
ORDER BY b.total_balance DESC
LIMIT ${args.limit}
OFFSET ${args.offset}
`;
Expand Down
227 changes: 227 additions & 0 deletions api/ordinals/tests/brc-20/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1400,4 +1400,231 @@ describe('BRC-20 API', () => {
expect(response.statusCode).toBe(404);
});
});

describe('/brc-20/balances', () => {
test('address balance history is accurate', async () => {
// Setup
const numbers = incrementing(0);
const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz';
const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4';

// A deploys pepe
let transferHash = randomHash();
let blockHash = randomHash();
await brc20TokenDeploy(brc20Db.sql, {
ticker: 'pepe',
display_ticker: 'pepe',
inscription_id: `${transferHash}i0`,
inscription_number: numbers.next().value.toString(),
block_height: '780000',
block_hash: blockHash,
tx_id: transferHash,
tx_index: 0,
address: addressA,
max: '21000000000000000000000000',
limit: '21000000000000000000000000',
decimals: 18,
self_mint: false,
minted_supply: '0',
tx_count: 1,
timestamp: 1677803510,
operation: 'deploy',
ordinal_number: '20000',
output: `${transferHash}:0`,
offset: '0',
to_address: null,
amount: '0',
});
// A mints 10000 pepe
transferHash = randomHash();
blockHash = randomHash();
await brc20Operation(brc20Db.sql, {
ticker: 'pepe',
operation: 'mint',
inscription_id: `${transferHash}i0`,
inscription_number: numbers.next().value.toString(),
ordinal_number: '200000',
block_height: '780050',
block_hash: blockHash,
tx_id: transferHash,
tx_index: 0,
output: `${transferHash}:0`,
offset: '0',
timestamp: 1677803510,
address: addressA,
to_address: null,
amount: '10000000000000000000000',
});
// A mints 10000 pepe again
transferHash = randomHash();
blockHash = randomHash();
await brc20Operation(brc20Db.sql, {
ticker: 'pepe',
operation: 'mint',
inscription_id: `${transferHash}i0`,
inscription_number: numbers.next().value.toString(),
ordinal_number: '200000',
block_height: '780060',
block_hash: blockHash,
tx_id: transferHash,
tx_index: 0,
output: `${transferHash}:0`,
offset: '0',
timestamp: 1677803510,
address: addressA,
to_address: null,
amount: '10000000000000000000000',
});
// B mints 10000 pepe
transferHash = randomHash();
blockHash = randomHash();
await brc20Operation(brc20Db.sql, {
ticker: 'pepe',
operation: 'mint',
inscription_id: `${transferHash}i0`,
inscription_number: numbers.next().value.toString(),
ordinal_number: '200000',
block_height: '780070',
block_hash: blockHash,
tx_id: transferHash,
tx_index: 0,
output: `${transferHash}:0`,
offset: '0',
timestamp: 1677803510,
address: addressB,
to_address: null,
amount: '10000000000000000000000',
});

// A deploys test
transferHash = randomHash();
blockHash = randomHash();
await brc20TokenDeploy(brc20Db.sql, {
ticker: 'test',
display_ticker: 'test',
inscription_id: `${transferHash}i0`,
inscription_number: numbers.next().value.toString(),
block_height: '780100',
block_hash: blockHash,
tx_id: transferHash,
tx_index: 0,
address: addressA,
max: '21000000000000000000000000',
limit: '21000000000000000000000000',
decimals: 18,
self_mint: false,
minted_supply: '0',
tx_count: 1,
timestamp: 1677803510,
operation: 'deploy',
ordinal_number: '20000',
output: `${transferHash}:0`,
offset: '0',
to_address: null,
amount: '0',
});
// A mints 10000 test
transferHash = randomHash();
blockHash = randomHash();
await brc20Operation(brc20Db.sql, {
ticker: 'test',
operation: 'mint',
inscription_id: `${transferHash}i0`,
inscription_number: numbers.next().value.toString(),
ordinal_number: '200000',
block_height: '780200',
block_hash: blockHash,
tx_id: transferHash,
tx_index: 0,
output: `${transferHash}:0`,
offset: '0',
timestamp: 1677803510,
address: addressA,
to_address: null,
amount: '10000000000000000000000',
});

// Verify balance history across block intervals
let response = await fastify.inject({
method: 'GET',
url: `/ordinals/brc-20/balances/${addressA}`,
});
expect(response.statusCode).toBe(200);
let json = response.json();
expect(json.total).toBe(2);
expect(json.results).toEqual(
expect.arrayContaining([
{
available_balance: '20000.000000000000000000',
overall_balance: '20000.000000000000000000',
ticker: 'pepe',
transferrable_balance: '0.000000000000000000',
},
{
available_balance: '10000.000000000000000000',
overall_balance: '10000.000000000000000000',
ticker: 'test',
transferrable_balance: '0.000000000000000000',
},
])
);
response = await fastify.inject({
method: 'GET',
url: `/ordinals/brc-20/balances/${addressA}?block_height=780200`,
});
expect(response.statusCode).toBe(200);
json = response.json();
expect(json.total).toBe(2);
expect(json.results).toEqual(
expect.arrayContaining([
{
available_balance: '20000.000000000000000000',
overall_balance: '20000.000000000000000000',
ticker: 'pepe',
transferrable_balance: '0.000000000000000000',
},
{
available_balance: '10000.000000000000000000',
overall_balance: '10000.000000000000000000',
ticker: 'test',
transferrable_balance: '0.000000000000000000',
},
])
);
response = await fastify.inject({
method: 'GET',
url: `/ordinals/brc-20/balances/${addressA}?block_height=780200&ticker=te`,
});
expect(response.statusCode).toBe(200);
json = response.json();
expect(json.total).toBe(1);
expect(json.results).toEqual(
expect.arrayContaining([
{
available_balance: '10000.000000000000000000',
overall_balance: '10000.000000000000000000',
ticker: 'test',
transferrable_balance: '0.000000000000000000',
},
])
);
response = await fastify.inject({
method: 'GET',
url: `/ordinals/brc-20/balances/${addressA}?block_height=780050`,
});
expect(response.statusCode).toBe(200);
json = response.json();
expect(json.total).toBe(1);
expect(json.results).toEqual(
expect.arrayContaining([
{
available_balance: '10000.000000000000000000',
overall_balance: '10000.000000000000000000',
ticker: 'pepe',
transferrable_balance: '0.000000000000000000',
},
])
);
});
});
});
14 changes: 14 additions & 0 deletions api/ordinals/tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,20 @@ export async function brc20Operation(sql: PgSqlClient, operation: TestBrc20Opera
trans_balance = balances.trans_balance + EXCLUDED.trans_balance,
total_balance = balances.avail_balance + EXCLUDED.total_balance
`;
await sql`
INSERT INTO balances_history
(ticker, address, block_height, avail_balance, trans_balance, total_balance)
(
SELECT ticker, address, ${operation.block_height} AS block_height, avail_balance,
trans_balance, total_balance
FROM balances
WHERE address = ${operation.address} AND ticker = ${operation.ticker}
)
ON CONFLICT (address, block_height, ticker) DO UPDATE SET
avail_balance = EXCLUDED.avail_balance,
trans_balance = EXCLUDED.trans_balance,
total_balance = EXCLUDED.total_balance
`;
}

/** Generate a random hash like string for testing */
Expand Down
5 changes: 3 additions & 2 deletions components/chainhook-sdk/src/indexer/bitcoin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use bitcoincore_rpc::jsonrpc::error::RpcError;
use bitcoincore_rpc_json::GetRawTransactionResultVoutScriptPubKey;
use chainhook_types::bitcoin::{OutPoint, TxIn, TxOut};
use chainhook_types::{
BitcoinBlockData, BitcoinBlockMetadata, BitcoinNetwork, BitcoinTransactionData,
BitcoinTransactionMetadata, BlockHeader, BlockIdentifier, TransactionIdentifier,
BitcoinBlockData, BitcoinBlockMetadata, BitcoinNetwork,
BitcoinTransactionData,BitcoinTransactionMetadata, BlockHeader, BlockIdentifier,
TransactionIdentifier,
};
use hiro_system_kit::slog;
use reqwest::Client as HttpClient;
Expand Down
1 change: 1 addition & 0 deletions components/chainhook-sdk/src/indexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub struct IndexerConfig {
pub bitcoind_rpc_username: String,
pub bitcoind_rpc_password: String,
pub bitcoin_block_signaling: BitcoinBlockSignaling,
pub prometheus_monitoring_port: Option<u16>,
}

pub struct Indexer {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
use std::{thread::sleep, time::Duration};

use chainhook_sdk::{
bitcoincore_rpc::{Auth, Client, RpcApi},
utils::Context,
};
use bitcoincore_rpc::{Auth, Client, RpcApi};
use hiro_system_kit::slog;
use crate::utils::Context;
use crate::indexer::IndexerConfig;

use crate::{config::Config, try_error, try_info};
use crate::{try_error, try_info};

fn bitcoind_get_client(config: &Config, ctx: &Context) -> Client {
fn bitcoind_get_client(config: &IndexerConfig, ctx: &Context) -> Client {
loop {
let auth = Auth::UserPass(
config.network.bitcoind_rpc_username.clone(),
config.network.bitcoind_rpc_password.clone(),
config.bitcoind_rpc_username.clone(),
config.bitcoind_rpc_password.clone(),
);
match Client::new(&config.network.bitcoind_rpc_url, auth) {
match Client::new(&config.bitcoind_rpc_url, auth) {
Ok(con) => {
return con;
}
Expand All @@ -26,7 +26,7 @@ fn bitcoind_get_client(config: &Config, ctx: &Context) -> Client {
}

/// Retrieves the block height from bitcoind.
pub fn bitcoind_get_block_height(config: &Config, ctx: &Context) -> u64 {
pub fn bitcoind_get_block_height(config: &IndexerConfig, ctx: &Context) -> u64 {
let bitcoin_rpc = bitcoind_get_client(config, ctx);
loop {
match bitcoin_rpc.get_blockchain_info() {
Expand All @@ -46,7 +46,7 @@ pub fn bitcoind_get_block_height(config: &Config, ctx: &Context) -> u64 {
}

/// Checks if bitcoind is still synchronizing blocks and waits until it's finished if that is the case.
pub fn bitcoind_wait_for_chain_tip(config: &Config, ctx: &Context) {
pub fn bitcoind_wait_for_chain_tip(config: &IndexerConfig, ctx: &Context) {
let bitcoin_rpc = bitcoind_get_client(config, ctx);
let mut confirmations = 0;
loop {
Expand Down
2 changes: 2 additions & 0 deletions components/chainhook-sdk/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod bitcoind;

use std::{
collections::{BTreeSet, VecDeque},
fs::{self, OpenOptions},
Expand Down
1 change: 1 addition & 0 deletions components/chainhook-types-rs/src/ordinals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub struct OrdinalInscriptionRevealData {
pub satpoint_post_inscription: String,
pub curse_type: Option<OrdinalInscriptionCurseType>,
pub charms: u16,
pub unbound_sequence: Option<i64>,
}

impl OrdinalInscriptionNumber {
Expand Down
3 changes: 2 additions & 1 deletion components/ordhook-cli/src/config/file.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use chainhook_types::{BitcoinBlockSignaling, BitcoinNetwork};
use chainhook_sdk::indexer::IndexerConfig;
use ordhook::config::{
Config, IndexerConfig, LogConfig, MetaProtocolsConfig, ResourcesConfig, SnapshotConfig,
Config, LogConfig, MetaProtocolsConfig, ResourcesConfig, SnapshotConfig,
SnapshotConfigDownloadUrls, StorageConfig, DEFAULT_BITCOIND_RPC_THREADS,
DEFAULT_BITCOIND_RPC_TIMEOUT, DEFAULT_BRC20_LRU_CACHE_SIZE, DEFAULT_MEMORY_AVAILABLE,
DEFAULT_ULIMIT,
Expand Down
Loading

0 comments on commit 03a72da

Please sign in to comment.