Merge branch '70-feat-blockchain-add-method-to-get-the-current-blockheight' into 'development'
Resolve "feat(blockchain): Add method to get the current blockheight" Closes #70 See merge request GeneralProtocols/xo/stack!107
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
|||||||
type BlockchainUTXOs,
|
type BlockchainUTXOs,
|
||||||
type BlockchainTransactions,
|
type BlockchainTransactions,
|
||||||
type BlockchainUnsubscribeCallback,
|
type BlockchainUnsubscribeCallback,
|
||||||
|
type BlockchainChainTip,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
|
||||||
import { BlockHeader, Transaction, TransactionHash } from '@xocash/primitives';
|
import { BlockHeader, Transaction, TransactionHash } from '@xocash/primitives';
|
||||||
@@ -89,6 +90,14 @@ export abstract class BaseBlockchain<
|
|||||||
*/
|
*/
|
||||||
abstract fetchBlockHeader(blockHeight: number): Promise<BlockHeader>;
|
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.
|
* Retrieves detailed information about a specific transaction.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
fetchHistory,
|
fetchHistory,
|
||||||
fetchTransaction,
|
fetchTransaction,
|
||||||
subscribeToBlockheaderUpdates,
|
subscribeToBlockheaderUpdates,
|
||||||
|
fetchCurrentChainTip,
|
||||||
} from '@electrum-cash/protocol';
|
} from '@electrum-cash/protocol';
|
||||||
|
|
||||||
import { hexToBin } from '@bitauth/libauth';
|
import { hexToBin } from '@bitauth/libauth';
|
||||||
@@ -181,8 +182,11 @@ export class BlockchainElectrum extends BaseBlockchain<
|
|||||||
this.handleTransactionStatusUpdates.bind(this),
|
this.handleTransactionStatusUpdates.bind(this),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Subscribe to block header notifications.
|
// Subscribe to block header notifications and fetch the chain tip.
|
||||||
await subscribeToBlockheaderUpdates(this.electrumClient);
|
await Promise.all([
|
||||||
|
subscribeToBlockheaderUpdates(this.electrumClient),
|
||||||
|
this.fetchChainTip(),
|
||||||
|
]);
|
||||||
|
|
||||||
// Return to prevent further execution.
|
// Return to prevent further execution.
|
||||||
return;
|
return;
|
||||||
@@ -338,6 +342,37 @@ export class BlockchainElectrum extends BaseBlockchain<
|
|||||||
return blockHeader;
|
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(
|
async broadcastTransaction(
|
||||||
tx: Transaction | Uint8Array | string,
|
tx: Transaction | Uint8Array | string,
|
||||||
opts: BroadcastOpts = { timeoutMs: 10_000, waitUntilSeen: true },
|
opts: BroadcastOpts = { timeoutMs: 10_000, waitUntilSeen: true },
|
||||||
@@ -546,13 +581,19 @@ export class BlockchainElectrum extends BaseBlockchain<
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handleNewBlockchainHeaders(
|
protected async handleNewBlockchainHeaders(
|
||||||
notification: HeadersSubscribeNotification,
|
notification: HeadersSubscribeNotification,
|
||||||
) {
|
) {
|
||||||
// Extract height from the first param.
|
// Extract height and hex from the first param.
|
||||||
// NOTE: This is an object.
|
// NOTE: This is an object.
|
||||||
// https://electrum-cash-protocol.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe
|
// 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.
|
// Assign the block height.
|
||||||
this.blockHeight = height;
|
this.blockHeight = height;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
type BlockchainTransactions,
|
type BlockchainTransactions,
|
||||||
type BlockchainUTXO,
|
type BlockchainUTXO,
|
||||||
type BlockchainUTXOs,
|
type BlockchainUTXOs,
|
||||||
|
type BlockchainChainTip,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -103,6 +104,12 @@ export class BlockchainInMemory extends BaseBlockchain {
|
|||||||
return blockHeader;
|
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(
|
async broadcastTransaction(
|
||||||
tx: Transaction | Uint8Array | string,
|
tx: Transaction | Uint8Array | string,
|
||||||
opts: { skipValidation: boolean } = { skipValidation: false },
|
opts: { skipValidation: boolean } = { skipValidation: false },
|
||||||
|
|||||||
@@ -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> = (
|
export type BlockchainCallback<Payload = undefined> = (
|
||||||
/** The transaction hash, address, lockscript or scriptHash of this subscription. */
|
/** The transaction hash, address, lockscript or scriptHash of this subscription. */
|
||||||
@@ -43,3 +49,8 @@ export type BlockHeightPayload = {
|
|||||||
type: 'block-height';
|
type: 'block-height';
|
||||||
height: number | null;
|
height: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BlockchainChainTip = {
|
||||||
|
height: number;
|
||||||
|
blockHeader: BlockHeader;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user