-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ade0151
commit 1594ea5
Showing
2 changed files
with
206 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.`, | ||
); | ||
} | ||
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), | ||
]); |