mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-09 18:18:14 -07:00
website_next: move hash logic to clients
This commit is contained in:
@@ -1,24 +1,15 @@
|
||||
import { rapidHashV3Prefix } from "./hash.js";
|
||||
|
||||
const MIN_PREFIX_NIBBLES = 4;
|
||||
const MAX_PREFIX_NIBBLES = 16;
|
||||
|
||||
/**
|
||||
* @typedef {import("../../modules/brk-client/index.js").AddrHashPrefixMatches} AddrHashPrefixMatches
|
||||
* @typedef {import("../derive/index.js").AddressType} AddressType
|
||||
* @typedef {import("../derive/index.js").GeneratedAddress} GeneratedAddress
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} AddrHashPrefixMatches
|
||||
* @property {AddressType} addrType
|
||||
* @property {string} prefix
|
||||
* @property {boolean} truncated
|
||||
* @property {string[]} addresses
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} AddressClient
|
||||
* @property {(addrType: AddressType, prefix: string, options?: { cache?: boolean }) => Promise<unknown>} getAddressHashPrefixMatches
|
||||
* @property {(addrType: AddressType, payload: Uint8Array, nibbles: number, options?: { cache?: boolean }) => Promise<AddrHashPrefixMatches>} getAddressPayloadHashPrefixMatches
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -28,10 +19,8 @@ const MAX_PREFIX_NIBBLES = 16;
|
||||
* @returns {Promise<AddrHashPrefixMatches>}
|
||||
*/
|
||||
async function fetchPrefixMatches(client, generated, nibbles) {
|
||||
const prefix = rapidHashV3Prefix(generated.payload, nibbles);
|
||||
|
||||
return /** @type {AddrHashPrefixMatches} */ (
|
||||
await client.getAddressHashPrefixMatches(generated.addrType, prefix, {
|
||||
await client.getAddressPayloadHashPrefixMatches(generated.addrType, generated.payload, nibbles, {
|
||||
cache: false,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
const MASK_64 = 0xffffffffffffffffn;
|
||||
const DEFAULT_SECRETS = /** @type {const} */ ([
|
||||
0x2d358dccaa6c78a5n,
|
||||
0x8bb84b93962eacc9n,
|
||||
0x4b33a62ed433d4a3n,
|
||||
0x4d5a2da51de1aa47n,
|
||||
0xa0761d6478bd642fn,
|
||||
0xe7037ed1a0b428dbn,
|
||||
0x90ed1765281c388cn,
|
||||
]);
|
||||
const DEFAULT_SEED = rapidHashSeed(0n);
|
||||
|
||||
/**
|
||||
* @param {bigint} value
|
||||
*/
|
||||
function u64(value) {
|
||||
return value & MASK_64;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {bigint} left
|
||||
* @param {bigint} right
|
||||
*/
|
||||
function rapidMix(left, right) {
|
||||
const result = u64(left) * u64(right);
|
||||
|
||||
return u64(result) ^ u64(result >> 64n);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {bigint} left
|
||||
* @param {bigint} right
|
||||
* @returns {[bigint, bigint]}
|
||||
*/
|
||||
function rapidMum(left, right) {
|
||||
const result = u64(left) * u64(right);
|
||||
|
||||
return [u64(result), u64(result >> 64n)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {bigint} seed
|
||||
*/
|
||||
function rapidHashSeed(seed) {
|
||||
return u64(seed ^ rapidMix(seed ^ DEFAULT_SECRETS[2], DEFAULT_SECRETS[1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {number} offset
|
||||
*/
|
||||
function readU64(bytes, offset) {
|
||||
return (
|
||||
BigInt(bytes[offset]) |
|
||||
(BigInt(bytes[offset + 1]) << 8n) |
|
||||
(BigInt(bytes[offset + 2]) << 16n) |
|
||||
(BigInt(bytes[offset + 3]) << 24n) |
|
||||
(BigInt(bytes[offset + 4]) << 32n) |
|
||||
(BigInt(bytes[offset + 5]) << 40n) |
|
||||
(BigInt(bytes[offset + 6]) << 48n) |
|
||||
(BigInt(bytes[offset + 7]) << 56n)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} bytes
|
||||
*/
|
||||
function rapidHashV3(bytes) {
|
||||
const length = bytes.length;
|
||||
|
||||
if (length <= 16) {
|
||||
throw new Error("Expected more than 16 bytes");
|
||||
}
|
||||
|
||||
if (length > 32) {
|
||||
throw new Error("Expected at most 32 bytes");
|
||||
}
|
||||
|
||||
let seed = rapidMix(
|
||||
readU64(bytes, 0) ^ DEFAULT_SECRETS[2],
|
||||
readU64(bytes, 8) ^ DEFAULT_SEED,
|
||||
);
|
||||
let a = readU64(bytes, length - 16) ^ BigInt(length);
|
||||
let b = readU64(bytes, length - 8);
|
||||
|
||||
a ^= DEFAULT_SECRETS[1];
|
||||
b ^= seed;
|
||||
|
||||
[a, b] = rapidMum(a, b);
|
||||
|
||||
return rapidMix(a ^ 0xaaaaaaaaaaaaaaaan, b ^ DEFAULT_SECRETS[1] ^ BigInt(length));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} bytes
|
||||
*/
|
||||
function rapidHashV3Hex(bytes) {
|
||||
return rapidHashV3(bytes).toString(16).padStart(16, "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {number} nibbles
|
||||
*/
|
||||
export function rapidHashV3Prefix(bytes, nibbles) {
|
||||
return rapidHashV3Hex(bytes).slice(0, nibbles);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ const LOOKUP_CONCURRENCY = 8;
|
||||
|
||||
/**
|
||||
* @typedef {import("./stats.js").AddressStats} AddressStats
|
||||
* @typedef {import("./bucket.js").AddrHashPrefixMatches} AddrHashPrefixMatches
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -37,8 +38,8 @@ const LOOKUP_CONCURRENCY = 8;
|
||||
/**
|
||||
* @typedef {Object} AddressClient
|
||||
* @property {string} domain
|
||||
* @property {(address: string, options?: { cache?: boolean }) => Promise<unknown>} getAddress
|
||||
* @property {(addrType: AddressType, prefix: string, options?: { cache?: boolean }) => Promise<unknown>} getAddressHashPrefixMatches
|
||||
* @property {(address: string, options?: { cache?: boolean }) => Promise<AddressStats>} getAddress
|
||||
* @property {(addrType: AddressType, payload: Uint8Array, nibbles: number, options?: { cache?: boolean }) => Promise<AddrHashPrefixMatches>} getAddressPayloadHashPrefixMatches
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -115,9 +116,7 @@ async function fetchBucketMetadata(client, addresses, cache) {
|
||||
if (!cache.has(address)) {
|
||||
cache.set(
|
||||
address,
|
||||
client.getAddress(address, { cache: false }).then(
|
||||
(stats) => /** @type {AddressStats} */ (stats),
|
||||
),
|
||||
client.getAddress(address, { cache: false }),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -132,9 +131,7 @@ async function fetchBucketMetadata(client, addresses, cache) {
|
||||
*/
|
||||
async function fetchDirectWalletAddress(client, generated) {
|
||||
try {
|
||||
const stats = /** @type {AddressStats} */ (
|
||||
await client.getAddress(generated.address, { cache: false })
|
||||
);
|
||||
const stats = await client.getAddress(generated.address, { cache: false });
|
||||
const historyAddresses =
|
||||
getAddressTxCount(stats) > 0 ? [generated.address] : [];
|
||||
|
||||
|
||||
@@ -8,17 +8,10 @@ const MAX_SCANNED_ADDRESSES = 1_000;
|
||||
|
||||
/**
|
||||
* @typedef {import("../derive/address.js").AddressScript} AddressScript
|
||||
* @typedef {import("../derive/index.js").AddressType} AddressType
|
||||
* @typedef {import("../lookup/index.js").AddressClient} AddressClient
|
||||
* @typedef {import("../lookup/index.js").WalletAddress} WalletAddress
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} AddressClient
|
||||
* @property {string} domain
|
||||
* @property {(address: string, options?: { cache?: boolean }) => Promise<unknown>} getAddress
|
||||
* @property {(addrType: AddressType, prefix: string, options?: { cache?: boolean }) => Promise<unknown>} getAddressHashPrefixMatches
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ScanProgress
|
||||
* @property {number} scannedCount
|
||||
|
||||
@@ -25,17 +25,10 @@ const descriptorBranches = /** @type {const} */ ([
|
||||
|
||||
/**
|
||||
* @typedef {import("../derive/address.js").AddressScript} AddressScript
|
||||
* @typedef {import("../derive/index.js").AddressType} AddressType
|
||||
* @typedef {import("../lookup/index.js").AddressClient} AddressClient
|
||||
* @typedef {import("./branch.js").WalletAddress} WalletAddress
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} AddressClient
|
||||
* @property {string} domain
|
||||
* @property {(address: string, options?: { cache?: boolean }) => Promise<unknown>} getAddress
|
||||
* @property {(addrType: AddressType, prefix: string, options?: { cache?: boolean }) => Promise<unknown>} getAddressHashPrefixMatches
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {WalletAddress & {
|
||||
* branchId: WalletBranchId,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { addressScripts } from "../derive/script.js";
|
||||
|
||||
/**
|
||||
* @typedef {import("../derive/address.js").AddressScript} AddressScript
|
||||
* @typedef {import("../derive/index.js").AddressType} AddressType
|
||||
* @typedef {import("../lookup/index.js").AddressClient} AddressClient
|
||||
* @typedef {Awaited<ReturnType<typeof scanBranches>>["addresses"][number]} WalletAddress
|
||||
* @typedef {Awaited<ReturnType<typeof scanBranches>>} ScriptScan
|
||||
*/
|
||||
@@ -18,11 +18,9 @@ import { addressScripts } from "../derive/script.js";
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} WalletScanClient
|
||||
* @property {string} domain
|
||||
* @property {(address: string, options?: { cache?: boolean }) => Promise<unknown>} getAddress
|
||||
* @property {(addrType: AddressType, prefix: string, options?: { cache?: boolean }) => Promise<unknown>} getAddressHashPrefixMatches
|
||||
* @property {(options?: { cache?: boolean }) => Promise<unknown>} getLivePrice
|
||||
* @typedef {AddressClient & {
|
||||
* getLivePrice(options?: { cache?: boolean }): Promise<number>,
|
||||
* }} WalletScanClient
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -135,9 +133,7 @@ export async function scanWalletAddresses({
|
||||
|
||||
const addresses = scans.flatMap((scan) => scan.addresses)
|
||||
.sort(compareWalletAddresses);
|
||||
const btcUsdPrice = /** @type {number} */ (
|
||||
await client.getLivePrice({ cache: false })
|
||||
);
|
||||
const btcUsdPrice = await client.getLivePrice({ cache: false });
|
||||
|
||||
return {
|
||||
addresses,
|
||||
|
||||
@@ -4,20 +4,21 @@ const HISTORY_CONCURRENCY = 4;
|
||||
const MAX_SELECTED_ADDRESS_TXS = 100;
|
||||
|
||||
const historyByBucketKey =
|
||||
/** @type {Map<string, Promise<Map<string, unknown[]>>>} */ (new Map());
|
||||
/** @type {Map<string, Promise<Map<string, ApiTransaction[]>>>} */ (new Map());
|
||||
|
||||
/**
|
||||
* @typedef {import("../../scan/index.js").WalletAddress} WalletAddress
|
||||
* @typedef {import("./transaction.js").ApiTransaction} ApiTransaction
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} AddressHistoryClient
|
||||
* @property {(address: string, options?: { cache?: boolean }) => Promise<unknown>} getAddressTxs
|
||||
* @property {(address: string, options?: { cache?: boolean }) => Promise<ApiTransaction[]>} getAddressTxs
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} AddressHistory
|
||||
* @property {unknown[]} transactions
|
||||
* @property {ApiTransaction[]} transactions
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -30,16 +31,14 @@ function createBucketKey(addresses) {
|
||||
/**
|
||||
* @param {AddressHistoryClient} client
|
||||
* @param {readonly string[]} addresses
|
||||
* @returns {Promise<Map<string, unknown[]>>}
|
||||
* @returns {Promise<Map<string, ApiTransaction[]>>}
|
||||
*/
|
||||
async function fetchBucketHistory(client, addresses) {
|
||||
const entries = await mapConcurrent(
|
||||
addresses,
|
||||
HISTORY_CONCURRENCY,
|
||||
async (address) => {
|
||||
const transactions = /** @type {unknown[]} */ (
|
||||
await client.getAddressTxs(address, { cache: false })
|
||||
);
|
||||
const transactions = await client.getAddressTxs(address, { cache: false });
|
||||
|
||||
return /** @type {const} */ ([address, transactions]);
|
||||
},
|
||||
|
||||
@@ -3,12 +3,13 @@ import { readWalletTransaction } from "./transaction.js";
|
||||
|
||||
/**
|
||||
* @typedef {import("../../scan/index.js").WalletAddress} WalletAddress
|
||||
* @typedef {import("./transaction.js").ApiTransaction} ApiTransaction
|
||||
* @typedef {import("./transaction.js").WalletTransaction} WalletTransaction
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} TransactionClient
|
||||
* @property {(address: string, options?: { cache?: boolean }) => Promise<unknown>} getAddressTxs
|
||||
* @property {(address: string, options?: { cache?: boolean }) => Promise<ApiTransaction[]>} getAddressTxs
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* @typedef {import("../../scan/index.js").WalletAddress} WalletAddress
|
||||
* @typedef {Record<string, unknown>} ApiTransaction
|
||||
*
|
||||
* @typedef {Object} WalletTransactionAddress
|
||||
* @property {WalletAddress} walletAddress
|
||||
@@ -186,7 +187,7 @@ function getExternalOutputValue(transaction, walletAddressSet) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} transaction
|
||||
* @param {ApiTransaction} transaction
|
||||
* @param {readonly WalletAddress[]} walletAddresses
|
||||
* @returns {WalletTransaction}
|
||||
*/
|
||||
|
||||
@@ -3,20 +3,31 @@ import { mapConcurrent } from "../../concurrent.js";
|
||||
const UTXO_CONCURRENCY = 4;
|
||||
|
||||
const utxosByBucketKey =
|
||||
/** @type {Map<string, Promise<Map<string, unknown[]>>>} */ (new Map());
|
||||
/** @type {Map<string, Promise<Map<string, ApiUtxo[]>>>} */ (new Map());
|
||||
|
||||
/**
|
||||
* @typedef {import("../../scan/index.js").WalletAddress} WalletAddress
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ApiUtxoStatus
|
||||
* @property {boolean} confirmed
|
||||
*
|
||||
* @typedef {Object} ApiUtxo
|
||||
* @property {string} txid
|
||||
* @property {number} vout
|
||||
* @property {number} value
|
||||
* @property {ApiUtxoStatus} status
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} UtxoClient
|
||||
* @property {(address: string, options?: { cache?: boolean }) => Promise<unknown>} getAddressUtxos
|
||||
* @property {(address: string, options?: { cache?: boolean }) => Promise<ApiUtxo[]>} getAddressUtxos
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} AddressUtxos
|
||||
* @property {unknown[]} utxos
|
||||
* @property {ApiUtxo[]} utxos
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -39,12 +50,11 @@ function createBucketKey(addresses) {
|
||||
/**
|
||||
* @param {UtxoClient} client
|
||||
* @param {string} address
|
||||
* @returns {Promise<ApiUtxo[]>}
|
||||
*/
|
||||
async function fetchAddressUtxos(client, address) {
|
||||
try {
|
||||
return /** @type {unknown[]} */ (
|
||||
await client.getAddressUtxos(address, { cache: false })
|
||||
);
|
||||
return await client.getAddressUtxos(address, { cache: false });
|
||||
} catch (error) {
|
||||
if (isNotFound(error)) return [];
|
||||
|
||||
@@ -55,7 +65,7 @@ async function fetchAddressUtxos(client, address) {
|
||||
/**
|
||||
* @param {UtxoClient} client
|
||||
* @param {readonly string[]} addresses
|
||||
* @returns {Promise<Map<string, unknown[]>>}
|
||||
* @returns {Promise<Map<string, ApiUtxo[]>>}
|
||||
*/
|
||||
async function fetchBucketUtxos(client, addresses) {
|
||||
const entries = await mapConcurrent(
|
||||
|
||||
Reference in New Issue
Block a user