Resolve "feat(blockchain): Add method to get the current blockheight"

This commit is contained in:
2025-07-07 02:13:22 +00:00
committed by Jim Hamill
parent 39a1efc84b
commit 4ec83c36fb
4 changed files with 74 additions and 6 deletions
@@ -5,6 +5,7 @@ import {
type BlockchainUTXOs,
type BlockchainTransactions,
type BlockchainUnsubscribeCallback,
type BlockchainChainTip,
} from './types.js';
import { BlockHeader, Transaction, TransactionHash } from '@xocash/primitives';
@@ -89,6 +90,14 @@ export abstract class BaseBlockchain<
*/
abstract fetchBlockHeader(blockHeight: number): Promise<BlockHeader>;
/**
* Retrieves the current chain tip block.
*
* @returns {Promise<BlockchainChainTip>} A promise that resolves with the current chain tip
* @throws {Error} If the chain tip cannot be retrieved
*/
abstract fetchChainTip(): Promise<BlockchainChainTip>;
/**
* Retrieves detailed information about a specific transaction.
*
@@ -32,6 +32,7 @@ import {
fetchHistory,
fetchTransaction,
subscribeToBlockheaderUpdates,
fetchCurrentChainTip,
} from '@electrum-cash/protocol';
import { hexToBin } from '@bitauth/libauth';
@@ -181,8 +182,11 @@ export class BlockchainElectrum extends BaseBlockchain<
this.handleTransactionStatusUpdates.bind(this),
);
// Subscribe to block header notifications.
await subscribeToBlockheaderUpdates(this.electrumClient);
// Subscribe to block header notifications and fetch the chain tip.
await Promise.all([
subscribeToBlockheaderUpdates(this.electrumClient),
this.fetchChainTip(),
]);
// Return to prevent further execution.
return;
@@ -338,6 +342,37 @@ export class BlockchainElectrum extends BaseBlockchain<
return blockHeader;
}
// TODO: ElectrumClient provides a chainHeight property that is populated after a `blockchain.headers.subscribe` call.
// We may be able to leverage this side-effect to avoid fetching the chain tip.
async fetchChainTip() {
// Wait for connection to be established before attempting to subscribe.
// TODO: Consider doing this at a higher level.
if (!this.isConnected) {
await this.waitFor('isConnectedUpdated', (isConnected) => isConnected);
}
// If we don't currently have a block height, fetch the current chain tip.
// NOTE: We don't use our cache here because the key will be the same and so on-boot, we will have a stale value.
if (!this.blockHeight) {
const chainTip = await fetchCurrentChainTip(this.electrumClient);
this.blockHeight = chainTip.height;
}
// Get the block header
// NOTE: If we didn't have the block height, we are performing double-fetch here
const blockHeader = await this.fetchBlockHeader(this.blockHeight);
// Create a chain tip object.
const chainTip = {
height: this.blockHeight,
blockHeader: blockHeader,
};
// Return the chain tip.
return chainTip;
}
async broadcastTransaction(
tx: Transaction | Uint8Array | string,
opts: BroadcastOpts = { timeoutMs: 10_000, waitUntilSeen: true },
@@ -546,13 +581,19 @@ export class BlockchainElectrum extends BaseBlockchain<
return response;
}
protected handleNewBlockchainHeaders(
protected async handleNewBlockchainHeaders(
notification: HeadersSubscribeNotification,
) {
// Extract height from the first param.
// Extract height and hex from the first param.
// NOTE: This is an object.
// https://electrum-cash-protocol.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe
const { height } = notification.params[0];
const { height, hex } = notification.params[0];
// Decode the raw data into our Block Header primitive.
const blockHeader = BlockHeader.fromHex(hex);
// Update the cache with the new block header.
await this.store.set(`blockHeader.${height}`, blockHeader.toRaw());
// Assign the block height.
this.blockHeight = height;
@@ -5,6 +5,7 @@ import {
type BlockchainTransactions,
type BlockchainUTXO,
type BlockchainUTXOs,
type BlockchainChainTip,
} from './types.js';
import {
@@ -103,6 +104,12 @@ export class BlockchainInMemory extends BaseBlockchain {
return blockHeader;
}
// TODO: Implement a method for fetching the chain tip.
// This is a placeholder for now.
async fetchChainTip(): Promise<BlockchainChainTip> {
throw new Error('Not implemented yet');
}
async broadcastTransaction(
tx: Transaction | Uint8Array | string,
opts: { skipValidation: boolean } = { skipValidation: false },
+12 -1
View File
@@ -1,4 +1,10 @@
import { Transaction, TransactionHash, UTXO, ExtMap } from '@xocash/primitives';
import {
Transaction,
TransactionHash,
UTXO,
ExtMap,
BlockHeader,
} from '@xocash/primitives';
export type BlockchainCallback<Payload = undefined> = (
/** The transaction hash, address, lockscript or scriptHash of this subscription. */
@@ -43,3 +49,8 @@ export type BlockHeightPayload = {
type: 'block-height';
height: number | null;
};
export type BlockchainChainTip = {
height: number;
blockHeader: BlockHeader;
};