Draft Inverse Bloom Filter

This commit is contained in:
Jim Hamill
2025-01-20 07:47:07 +00:00
parent d6a04f4350
commit 1aebc6da42
21 changed files with 1285 additions and 381 deletions
+142
View File
@@ -0,0 +1,142 @@
import { describe, it, expect } from 'vitest';
import { Address } from '~/src/address';
import { Bytes } from '~/src/bytes';
describe('Success cases', () => {
// Test cases with different address representations
type AddressCase = {
raw: {
type: 'P2PKH' | 'P2SH20' | 'P2SH32' | 'P2PK';
payload: Uint8Array;
};
representations: {
cashAddr: string;
legacy: string;
tokenAddr: string;
lockscriptBytes: Uint8Array;
lockscriptHex: string;
lockscriptASM: string;
hash160Bytes: Uint8Array;
hash160Hex: string;
};
};
const testCases: AddressCase[] = [
{
raw: {
type: 'P2PKH',
payload: Bytes.fromHex(
'626025854e5a67ca5eb122e213f409ec842d45e4',
).toBytes(),
},
representations: {
cashAddr: 'bitcoincash:qp3xqfv9fedx0jj7ky3wyyl5p8kggt29usvlhrrxws',
legacy: '19yAQBS32jTcMFutTVCyp5e1y38JA9Qq8j',
tokenAddr: 'bitcoincash:zp3xqfv9fedx0jj7ky3wyyl5p8kggt29ust4yadq3r',
lockscriptBytes: Bytes.fromHex(
'76a914626025854e5a67ca5eb122e213f409ec842d45e488ac',
).toBytes(),
lockscriptHex: '76a914626025854e5a67ca5eb122e213f409ec842d45e488ac',
lockscriptASM:
'OP_DUP OP_HASH160 OP_PUSHBYTES_20 0x626025854e5a67ca5eb122e213f409ec842d45e4 OP_EQUALVERIFY OP_CHECKSIG',
hash160Bytes: Bytes.fromHex(
'626025854e5a67ca5eb122e213f409ec842d45e4',
).toBytes(),
hash160Hex: '626025854e5a67ca5eb122e213f409ec842d45e4',
},
},
];
// Test from methods
const fromMethods = [
{ name: 'fromCashAddr', key: 'cashAddr' },
{ name: 'fromLegacy', key: 'legacy' },
{ name: 'fromLockscriptBytes', key: 'lockscriptBytes' },
{ name: 'fromLockscriptHex', key: 'lockscriptHex' },
{ name: 'fromHash160Bytes', key: 'hash160Bytes' },
{ name: 'fromHash160Hex', key: 'hash160Hex' },
] as const;
fromMethods.forEach(({ name, key }) => {
it.each(testCases)(`${name}()`, ({ raw, representations }) => {
const value = representations[key];
const address = Address[name](value);
expect(address.toRaw()).toStrictEqual(raw);
});
});
// Test to methods
const toMethods = [
{ name: 'toCashAddr', key: 'cashAddr' },
{ name: 'toLegacy', key: 'legacy' },
{ name: 'toTokenAddress', key: 'tokenAddr' },
{ name: 'toLockscriptBytes', key: 'lockscriptBytes' },
{ name: 'toLockscriptHex', key: 'lockscriptHex' },
{ name: 'toLockscriptAsm', key: 'lockscriptASM' },
{ name: 'toHash160Bytes', key: 'hash160Bytes' },
{ name: 'toHash160Hex', key: 'hash160Hex' },
] as const;
toMethods.forEach(({ name, key }) => {
it.each(testCases)(`${name}()`, ({ raw, representations }) => {
const address = Address.fromRaw(raw);
const expected = representations[key];
expect(address[name]()).toStrictEqual(expected);
});
});
// Test generic from method
it.each(testCases)('from(cashAddr)', ({ raw, representations }) => {
const address = Address.from(representations.cashAddr);
expect(address.toRaw()).toStrictEqual(raw);
});
it.each(testCases)('from(legacy)', ({ raw, representations }) => {
const address = Address.from(representations.legacy);
expect(address.toRaw()).toStrictEqual(raw);
});
// Test validation methods
it.each(testCases)('isValid() for CashAddr', ({ representations }) => {
expect(Address.isValid(representations.cashAddr)).toBe(true);
});
it.each(testCases)('isValid() for Legacy', ({ representations }) => {
expect(Address.isValid(representations.legacy)).toBe(true);
});
it.each(testCases)('isValidCashAddr()', ({ representations }) => {
expect(Address.isValidCashAddr(representations.cashAddr)).toBe(true);
});
it.each(testCases)('isValidLegacy()', ({ representations }) => {
expect(Address.isValidLegacy(representations.legacy)).toBe(true);
});
});
describe('Error cases', () => {
const invalidCases = [
{
method: 'fromCashAddr',
input: 'invalid:address',
},
{
method: 'fromLegacy',
input: 'invalid_legacy_address',
},
{
method: 'fromHash160Hex',
input: 'invalid_hex',
},
{
method: 'fromLockscriptHex',
input: 'invalid_hex',
},
];
invalidCases.forEach(({ method, input }) => {
it(`${method}() throws on invalid input`, () => {
expect(() => Address[method](input)).toThrow();
});
});
});
@@ -0,0 +1,158 @@
import { describe, it, expect } from 'vitest';
import { BlockHeader } from '~/src/block-header';
import { Bytes } from '~/src/bytes';
describe('Success cases', () => {
// Test cases with different block header representations
type BlockHeaderCase = {
raw: {
version: number;
previousBlockHash: Uint8Array;
merkleRoot: Uint8Array;
timestamp: number;
bits: number;
nonce: number;
};
representations: {
bytes: Uint8Array;
hex: string;
};
};
const testCases: BlockHeaderCase[] = [
{
raw: {
version: 592633856,
previousBlockHash: Bytes.fromHex(
'df4da87b838b2dde9aaf26cb08380f15a57f32d99f2007000000000000000000',
).toBytes(),
merkleRoot: Bytes.fromHex(
'5e8a3ccc570da3eddb5c67853385e72aad4c830b2eedb4f23943d09513a0b476',
).toBytes(),
timestamp: 1737338908,
bits: 402807112,
nonce: 3478133258,
},
representations: {
bytes: Bytes.fromHex(
'00e05223df4da87b838b2dde9aaf26cb08380f15a57f32d99f20070000000000000000005e8a3ccc570da3eddb5c67853385e72aad4c830b2eedb4f23943d09513a0b4761cb08d67485902180a1a50cf',
).toBytes(),
hex: '00e05223df4da87b838b2dde9aaf26cb08380f15a57f32d99f20070000000000000000005e8a3ccc570da3eddb5c67853385e72aad4c830b2eedb4f23943d09513a0b4761cb08d67485902180a1a50cf',
},
},
];
// Test from methods with their respective types
const fromMethods = [
{ name: 'fromBytes', key: 'bytes' },
{ name: 'fromHex', key: 'hex' },
] as const;
fromMethods.forEach(({ name, key }) => {
it.each(testCases)(`${name}()`, ({ raw, representations }) => {
const blockHeader = BlockHeader[name](representations[key]);
expect(blockHeader.toRaw()).toStrictEqual(raw);
});
});
// Test generic from method
it.each(testCases)('from(Uint8Array)', ({ raw, representations }) => {
const blockHeader = BlockHeader.from(representations.bytes);
expect(blockHeader.toRaw()).toStrictEqual(raw);
});
it.each(testCases)('from(string)', ({ raw, representations }) => {
const blockHeader = BlockHeader.from(representations.hex);
expect(blockHeader.toRaw()).toStrictEqual(raw);
});
// Test to methods with their respective types
const toMethods = [
{ name: 'toBytes', key: 'bytes' },
{ name: 'toHex', key: 'hex' },
] as const;
toMethods.forEach(({ name, key }) => {
it.each(testCases)(`${name}()`, ({ raw, representations }) => {
const blockHeader = BlockHeader.fromRaw(raw);
expect(blockHeader[name]()).toStrictEqual(representations[key]);
});
});
// Test getter methods
it.each(testCases)('getter methods return correct values', ({ raw }) => {
const blockHeader = BlockHeader.fromRaw(raw);
expect(blockHeader.getVersion()).toBe(raw.version);
expect(blockHeader.getPreviousBlockHash()).toStrictEqual(
raw.previousBlockHash,
);
expect(blockHeader.getMerkleRoot()).toStrictEqual(raw.merkleRoot);
expect(blockHeader.getTimestamp()).toBe(raw.timestamp);
expect(blockHeader.getBits()).toBe(raw.bits);
expect(blockHeader.getNonce()).toBe(raw.nonce);
});
// Test hex getter methods
it.each(testCases)('hex getter methods return correct values', ({ raw }) => {
const blockHeader = BlockHeader.fromRaw(raw);
expect(blockHeader.getPreviousBlockHashHex()).toBe(
Bytes.fromBytes(raw.previousBlockHash).toHex(),
);
expect(blockHeader.getMerkleRootHex()).toBe(
Bytes.fromBytes(raw.merkleRoot).toHex(),
);
});
// Test hash methods
it.each(testCases)('getHash() returns correct hash', ({ raw }) => {
const blockHeader = BlockHeader.fromRaw(raw);
const hash = blockHeader.getHash();
expect(hash).toBeInstanceOf(Uint8Array);
expect(hash.length).toBe(32);
});
it.each(testCases)('getHashHex() returns correct hash hex', ({ raw }) => {
const blockHeader = BlockHeader.fromRaw(raw);
const hashHex = blockHeader.getHashHex();
expect(typeof hashHex).toBe('string');
expect(hashHex.length).toBe(64);
});
// Test validation method
it.each(testCases)(
'isValid() returns true for valid inputs',
({ representations }) => {
expect(BlockHeader.isValid(representations.bytes)).toBe(true);
expect(BlockHeader.isValid(representations.hex)).toBe(true);
},
);
});
describe('Error cases', () => {
const invalidCases = [
{
method: 'fromBytes',
input: new Uint8Array(79), // Wrong length (should be 80 bytes)
},
{
method: 'fromHex',
input: 'invalid_hex',
},
{
method: 'fromHex',
input: '00'.repeat(79), // Wrong length (should be 80 bytes / 160 hex chars)
},
];
invalidCases.forEach(({ method, input }) => {
it(`${method}() throws on invalid input`, () => {
expect(() => BlockHeader[method](input)).toThrow();
});
});
it('isValid() returns false for invalid inputs', () => {
expect(BlockHeader.isValid(new Uint8Array(79))).toBe(false);
expect(BlockHeader.isValid('invalid_hex')).toBe(false);
expect(BlockHeader.isValid('00'.repeat(79))).toBe(false);
});
});
@@ -0,0 +1,71 @@
import { describe, it, expect } from 'vitest';
import { Satoshis } from '~/src/satoshis';
describe('Success cases', () => {
// Test cases with single representation for each unit
type ConversionCase = {
raw: bigint;
units: {
sats: number | bigint;
satoshis: number | bigint;
bits: number;
mBCH: number;
bch: number;
};
};
const testCases: ConversionCase[] = [
{
raw: 1n,
units: {
sats: 1,
satoshis: 1,
bits: 0.01,
mBCH: 0.00001,
bch: 0.00000001,
},
},
{
raw: 333333333n,
units: {
sats: 333333333,
satoshis: 333333333,
bits: 3333333.33,
mBCH: 3333.33333,
bch: 3.33333333,
},
},
];
// Test from methods with their respective types
const fromMethods = [
{ name: 'fromSats', key: 'sats' },
{ name: 'fromSatoshis', key: 'satoshis' },
{ name: 'fromBits', key: 'bits' },
{ name: 'fromMBCH', key: 'mBCH' },
{ name: 'fromBCH', key: 'bch' },
] as const;
fromMethods.forEach(({ name, key }) => {
it.each(testCases)(`${name}()`, ({ raw, units }) => {
const satoshis = Satoshis[name](units[key]);
expect(satoshis.toRaw()).toStrictEqual(raw);
});
});
// Test to methods with their respective types
const toMethods = [
{ name: 'toSats', key: 'sats' },
{ name: 'toSatoshis', key: 'satoshis' },
{ name: 'toBits', key: 'bits' },
{ name: 'toMBCH', key: 'mBCH' },
{ name: 'toBCH', key: 'bch' },
] as const;
toMethods.forEach(({ name, key }) => {
it.each(testCases)(`${name}()`, ({ raw, units }) => {
const satoshis = Satoshis.fromRaw(raw);
expect(satoshis[name]()).toStrictEqual(units[key]);
});
});
});
+94
View File
@@ -0,0 +1,94 @@
import { describe, it, expect } from 'vitest';
import { Bytes } from '~/src/bytes';
import { Script } from '~/src/script';
describe('Success cases', () => {
// Test cases with different script representations
type ScriptCase = {
raw: Uint8Array;
representations: {
bytes: Uint8Array;
asm: string;
hex: string;
};
};
const testCases: ScriptCase[] = [
{
raw: Bytes.fromHex(
'76a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac',
).toBytes(),
representations: {
bytes: Bytes.fromHex(
'76a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac',
).toBytes(),
asm: 'OP_DUP\nOP_HASH160\nOP_PUSHBYTES_20\n0x876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d6\nOP_EQUALVERIFY\nOP_CHECKSIG',
hex: '76a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac',
},
},
];
// Test from methods with their respective types
const fromMethods = [
{ name: 'fromBytes', key: 'bytes' },
{ name: 'fromASM', key: 'asm' },
{ name: 'fromHex', key: 'hex' },
] as const;
fromMethods.forEach(({ name, key }) => {
it.each(testCases)(`${name}()`, ({ raw, representations }) => {
const script = Script[name](representations[key]);
expect(script.toRaw()).toStrictEqual(raw);
});
});
// Test generic from method
it.each(testCases)('from(Uint8Array)', ({ raw, representations }) => {
const script = Script.from(representations.bytes);
expect(script.toRaw()).toStrictEqual(raw);
});
it.each(testCases)('from(string)', ({ raw, representations }) => {
const script = Script.from(representations.hex);
expect(script.toRaw()).toStrictEqual(raw);
});
// Test to methods with their respective types
const toMethods = [
{ name: 'toBytes', key: 'bytes' },
{ name: 'toASM', key: 'asm' },
{ name: 'toHex', key: 'hex' },
] as const;
toMethods.forEach(({ name, key }) => {
it.each(testCases)(`${name}()`, ({ raw, representations }) => {
const script = Script.fromRaw(raw);
expect(script[name]()).toStrictEqual(representations[key]);
});
});
// Test fromRaw specifically
it.each(testCases)('fromRaw() creates correct instance', ({ raw }) => {
const script = Script.fromRaw(raw);
expect(script.toRaw()).toStrictEqual(raw);
});
});
describe('Error cases', () => {
const invalidCases = [
{
method: 'fromASM',
input: 'OP_INVALID_OPCODE',
},
{
method: 'fromHex',
input: 'INVALID_HEX',
},
];
invalidCases.forEach(({ method, input }) => {
it(`${method}() throws on invalid input`, () => {
expect(() => Script[method](input)).toThrow();
});
});
});