Files
brk/website_next/explore/block/preview/data.js
T
2026-07-08 11:24:52 +02:00

238 lines
6.3 KiB
JavaScript

import { brk } from "../../../utils/client.js";
import { FILTERS, getFilterBit, getTypeBit } from "./filters/model.js";
const VERSION_BITS = new Map([
[1, getFilterBit("version:1")],
[2, getFilterBit("version:2")],
[3, getFilterBit("version:3")],
]);
const RBF_YES_BIT = getFilterBit("rbf:yes");
const RBF_NO_BIT = getFilterBit("rbf:no");
const INPUT_ONE_BIT = getFilterBit("input:one");
const INPUT_MULTI_BIT = getFilterBit("input:multi");
const OUTPUT_ONE_BIT = getFilterBit("output:one");
const OUTPUT_MULTI_BIT = getFilterBit("output:multi");
/**
* @template T
* @param {{
* slice: (
* start: number,
* end: number,
* ) => {
* fetch: (
* options: { signal: AbortSignal, memCache: false },
* ) => Promise<{ data: T[] }>,
* },
* }} series
* @param {number} start
* @param {number} end
* @param {AbortSignal} signal
* @returns {Promise<{ data: T[] }>}
*/
function fetchSeriesSlice(series, start, end, signal) {
return series.slice(start, end).fetch({ signal, memCache: false });
}
/**
* @param {import("../../../modules/brk-client/index.js").BlockInfoV1} block
* @param {AbortSignal} signal
*/
async function loadBlockPreviewRange(block, signal) {
const firstTxIndex = (
await fetchSeriesSlice(
brk.series.transactions.raw.firstTxIndex.by.height,
block.height,
block.height + 1,
signal,
)
).data[0];
signal.throwIfAborted();
const start = /** @type {number} */ (firstTxIndex);
const end = start + block.txCount;
return { start, end };
}
/**
* @param {import("../../../modules/brk-client/index.js").BlockInfoV1} block
* @param {AbortSignal} signal
*/
export async function loadBlockPreview(block, signal) {
const { start, end } = await loadBlockPreviewRange(block, signal);
const tx = brk.series.transactions;
const [weights, feeRates] = await Promise.all([
fetchSeriesSlice(tx.size.weight.txIndex.by.tx_index, start, end, signal),
fetchSeriesSlice(tx.fees.feeRate.by.tx_index, start, end, signal),
]);
signal.throwIfAborted();
return {
range: { start, end },
transactions: weights.data.map((weight, index) => ({
txIndex: start + index,
weight: /** @type {number} */ (weight),
feeRate: /** @type {number} */ (feeRates.data[index]),
})),
};
}
/**
* @param {number} txIndex
* @param {AbortSignal} signal
*/
export async function loadBlockPreviewTxid(txIndex, signal) {
const txid = (
await brk.series.transactions.raw.txid.by.tx_index
.get(txIndex)
.fetch({ signal, memCache: false })
).data[0];
signal.throwIfAborted();
return /** @type {string} */ (txid);
}
/**
* @param {number[]} starts
* @param {number[]} counts
*/
function rangeEnd(starts, counts) {
const last = starts.length - 1;
return starts[last] + counts[last];
}
/**
* @param {Uint32Array} counts
* @param {number} mask
*/
function countMask(counts, mask) {
for (const { bit, index } of FILTERS) {
if (mask & bit) counts[index] += 1;
}
}
/**
* @param {readonly string[]} types
* @param {number} start
* @param {number} end
*/
function getTypesMask(types, start, end) {
let mask = 0;
for (let index = start; index < end; index += 1) {
mask |= getTypeBit(types[index]);
}
return mask;
}
/**
* @param {BlockPreviewRange} range
* @param {AbortSignal} signal
*/
export async function loadBlockPreviewFilters(range, signal) {
const { start, end } = range;
const tx = brk.series.transactions;
const indexes = brk.series.indexes.txIndex;
const [
versions,
rbfs,
inputCounts,
outputCounts,
firstInputs,
firstOutputs,
] = await Promise.all([
fetchSeriesSlice(tx.raw.txVersion.by.tx_index, start, end, signal),
fetchSeriesSlice(tx.raw.isExplicitlyRbf.by.tx_index, start, end, signal),
fetchSeriesSlice(indexes.inputCount.by.tx_index, start, end, signal),
fetchSeriesSlice(indexes.outputCount.by.tx_index, start, end, signal),
fetchSeriesSlice(tx.raw.firstTxinIndex.by.tx_index, start, end, signal),
fetchSeriesSlice(tx.raw.firstTxoutIndex.by.tx_index, start, end, signal),
]);
signal.throwIfAborted();
const inputStart = /** @type {number} */ (firstInputs.data[0]);
const outputStart = /** @type {number} */ (firstOutputs.data[0]);
const [inputTypes, outputTypes] = await Promise.all([
fetchSeriesSlice(
brk.series.inputs.raw.outputType.by.txin_index,
inputStart,
rangeEnd(
/** @type {number[]} */ (firstInputs.data),
/** @type {number[]} */ (inputCounts.data),
),
signal,
),
fetchSeriesSlice(
brk.series.outputs.raw.outputType.by.txout_index,
outputStart,
rangeEnd(
/** @type {number[]} */ (firstOutputs.data),
/** @type {number[]} */ (outputCounts.data),
),
signal,
),
]);
signal.throwIfAborted();
const masks = new Uint32Array(end - start);
const counts = new Uint32Array(FILTERS.length);
versions.data.forEach((version, index) => {
const inputCount = /** @type {number} */ (inputCounts.data[index]);
const outputCount = /** @type {number} */ (outputCounts.data[index]);
const inputTypeStart = /** @type {number} */ (firstInputs.data[index]);
const inputTypeEnd = inputTypeStart + inputCount;
const outputTypeStart = /** @type {number} */ (firstOutputs.data[index]);
const outputTypeEnd = outputTypeStart + outputCount;
const mask =
(VERSION_BITS.get(/** @type {number} */ (version)) ?? 0) |
(rbfs.data[index] ? RBF_YES_BIT : RBF_NO_BIT) |
(inputCount === 1 ? INPUT_ONE_BIT : INPUT_MULTI_BIT) |
(outputCount === 1 ? OUTPUT_ONE_BIT : OUTPUT_MULTI_BIT) |
getTypesMask(
/** @type {string[]} */ (inputTypes.data),
inputTypeStart - inputStart,
inputTypeEnd - inputStart,
) |
getTypesMask(
/** @type {string[]} */ (outputTypes.data),
outputTypeStart - outputStart,
outputTypeEnd - outputStart,
);
masks[index] = mask;
countMask(counts, mask);
});
return { counts, masks, start };
}
/**
* @typedef {Object} BlockPreviewRange
* @property {number} start
* @property {number} end
*/
/**
* @typedef {Object} BlockPreviewTransaction
* @property {number} txIndex
* @property {number} weight
* @property {number} feeRate
*/
/**
* @typedef {Object} BlockPreviewFilterState
* @property {number} start
* @property {Uint32Array} masks
* @property {Uint32Array} counts
*/