Skip to content

Commit

Permalink
add header encodings
Browse files Browse the repository at this point in the history
  • Loading branch information
samrock5000 committed Apr 11, 2024
1 parent ade0151 commit 1594ea5
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 0 deletions.
59 changes: 59 additions & 0 deletions src/lib/message/block-header-encoding.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import test from 'ava';
import { hexToBin } from '../lib.js';
import { decodeHeader, encodeHeader } from './block-header-encoding.js';
import type { BlockHeader } from './block-header-encoding.js'

export const uahfHeader = hexToBin(
"02000020e42980330b7294bef6527af576e5cfe2c97d55f9c19beb0000000000000000004a88016082f466735a0f4bc9e5e42725fbc3d0ac28d4ab9547bf18654f14655b1e7f80593547011816dd5975",
);

const genesisHeader = hexToBin(
"0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c",
);
const genesisDecoded: BlockHeader = {
version: 1,
previousBlockHash: hexToBin("0000000000000000000000000000000000000000000000000000000000000000"),
merkleRootHash: hexToBin("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"),
time: 1231006505,
difficultyTarget: 486604799,
nonce: 2083236893,
};
const uahfDecoded: BlockHeader = {
version: 536870914,
previousBlockHash: hexToBin("000000000000000000eb9bc1f9557dc9e2cfe576f57a52f6be94720b338029e4"),
merkleRootHash: hexToBin("5b65144f6518bf4795abd428acd0c3fb2527e4e5c94b0f5a7366f4826001884a"),
time: 1501593374,
difficultyTarget: 402736949,
nonce: 1968823574,
};
test("decodeHeader genesis", (t) => {
t.deepEqual(decodeHeader(genesisHeader), genesisDecoded)
})
test("encodeHeader genesis", (t) => {
t.deepEqual(encodeHeader(genesisDecoded), genesisHeader)
})
test("decodeHeader uahf", (t) => {
t.deepEqual(decodeHeader(uahfHeader), uahfDecoded)
})
test("encodeHeader uahf", (t) => {
t.deepEqual(encodeHeader(uahfDecoded), uahfHeader)
})

test("decodeHeader invalid version byte length", (t) => {
t.deepEqual(decodeHeader(Uint8Array.from([])), "Error reading header. Error reading Uint32LE: requires 4 bytes. Remaining bytes: 0")
})
test("decodeHeader invalid previousHash byte length", (t) => {
t.deepEqual(decodeHeader(Uint8Array.from([0, 0, 0, 0, 0])), "Error reading header. Error reading bytes: insufficient length. Bytes requested: 32; remaining bytes: 1")
})
test("decodeHeader invalid merkle byte length", (t) => {
t.deepEqual(decodeHeader(new Uint8Array(40)), "Error reading header. Error reading bytes: insufficient length. Bytes requested: 32; remaining bytes: 4")
})
test("decodeHeader invalid time length", (t) => {
t.deepEqual(decodeHeader(new Uint8Array(68)), "Error reading header. Error reading Uint32LE: requires 4 bytes. Remaining bytes: 0")
})
test("decodeHeader invalid target length", (t) => {
t.deepEqual(decodeHeader(new Uint8Array(72)), "Error reading header. Error reading Uint32LE: requires 4 bytes. Remaining bytes: 0")
})
test("decodeHeader invalid nonce length", (t) => {
t.deepEqual(decodeHeader(new Uint8Array(76)), "Error reading header. Error reading Uint32LE: requires 4 bytes. Remaining bytes: 0")
})
147 changes: 147 additions & 0 deletions src/lib/message/block-header-encoding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import {
flattenBinArray,
formatError,
numberToBinUint32LE,
readMultiple
} from "../lib.js";
import {
type MaybeReadResult,
type ReadPosition,
} from "../lib.js"
import {
readBytes,
readUint32LE,
} from "./read-components.js";

const SHA256HASHLEN = 32;

export enum HeaderDecodingError {
version = "Error reading version.",
previousBlock = "Error reading previous block.",
merkleRootHash = "Error reading merkle root hash",
time = "Error reading time",
difficultyTarget = "Error reading difficulty target",
nonce = "Error reading nonce",
generic = "Error reading header.",
endsWithUnexpectedBytes = "Error decoding header: the provided header includes unexpected bytes.",
}

/**
* Represents the header of a block in a blockchain.
*/
export type BlockHeader = {
/**
* The version of the block.
*/
version: number;

/**
* The hash of the previous block in the blockchain.
*/
previousBlockHash: Uint8Array;

/**
* The hash of the Merkle root of the transactions in the block.
*/
merkleRootHash: Uint8Array;

/**
* The Unix epoch time at which the block was created.
*/
time: number;

/**
* The target value for the block's proof-of-work.
*/
difficultyTarget: number;

/**
* A random value used in the proof-of-work calculation.
*/
nonce: number;
};

/**
* Attempts to read a BlockHeader from the provided binary data at the given position.
*
* @param {ReadPosition} position - The position in the binary data from which to start reading.
* @returns {MaybeReadResult<BlockHeader>} A parsed BlockHeader object if successful, or an error message if not.
*/
export const readHeader = (
position: ReadPosition,
): MaybeReadResult<BlockHeader> => {
const headerRead = readMultiple(position, [
readUint32LE,
readBytes(SHA256HASHLEN), // previous block hash
readBytes(SHA256HASHLEN), // merkle root
readUint32LE, // Unix epoch time
readUint32LE, // target difficulty A.K.A bits
readUint32LE, // nonce
]);
if (typeof headerRead === "string") {
return formatError(HeaderDecodingError.generic, headerRead);
}
const {
position: nextPosition,
result: [
version,
previousBlockHash,
merkleRootHash,
time,
difficultyTarget,
nonce,
],
} = headerRead;
return {
position: nextPosition,
result: {
version,
previousBlockHash: previousBlockHash.reverse(),
merkleRootHash: merkleRootHash.reverse(),
time,
difficultyTarget,
nonce,
},
};
};

/**
* Decodes a BlockHeader from a given Uint8Array containing its binary representation.
*
* @param {Uint8Array} bin - The binary data containing the encoded BlockHeader.
* @returns {BlockHeader | string} A parsed BlockHeader object if successful, or an error message if not.
*/
export const decodeHeader = (bin: Uint8Array): BlockHeader | string => {
const headerRead = readHeader({ bin, index: 0 });
if (typeof headerRead === "string") {
return headerRead;
}
if (headerRead.position.index !== bin.length) {
return formatError(
HeaderDecodingError.endsWithUnexpectedBytes,
`Encoded header ends at index ${headerRead.position.index - 1}, leaving ${bin.length - headerRead.position.index
} remaining bytes.`,
);
}

Check warning on line 125 in src/lib/message/block-header-encoding.ts

View check run for this annotation

Codecov / codecov/patch

src/lib/message/block-header-encoding.ts#L120-L125

Added lines #L120 - L125 were not covered by tests
return headerRead.result;
};

/**
* Encodes a BlockHeader object into its binary representation.
*
* This function takes a `BlockHeader` object and returns a new `Uint8Array` containing its
* serialized form. The encoding process follows the little-endian convention for all numerical
* values (version, time, difficultyTarget, and nonce).
*
* @param {BlockHeader} header - The BlockHeader object to encode.
* @returns {Uint8Array} A new Uint8Array containing the binary representation of the BlockHeader.
*/
export const encodeHeader = (header: BlockHeader) =>
flattenBinArray([
numberToBinUint32LE(header.version),
header.previousBlockHash.reverse(),
header.merkleRootHash.reverse(),
numberToBinUint32LE(header.time),
numberToBinUint32LE(header.difficultyTarget),
numberToBinUint32LE(header.nonce),
]);

0 comments on commit 1594ea5

Please sign in to comment.