├── .gitignore
├── LICENSE
├── README.md
├── babel.config.cjs
├── jest.config.cjs
├── package-lock.json
├── package.json
├── src
├── MiniProtocol
│ ├── MiniProtocol.ts
│ └── index.ts
├── common
│ ├── AddEvtListenerOpts.ts
│ └── ErrorListener.ts
├── index.ts
├── multiplexer
│ ├── Multiplexer.ts
│ ├── SocketLike.ts
│ ├── __tests__
│ │ └── multiplexerMessage.test.ts
│ ├── index.ts
│ └── multiplexerMessage.ts
└── protocols
│ ├── block-fetch
│ ├── BlockFetchClient.ts
│ ├── BlockFetchMessage.ts
│ ├── BlockFetchSever.ts
│ ├── __tests__
│ │ └── blockFetch.tenBlocks.test.ts
│ ├── block-fetch.cddl
│ ├── block-fetch.md
│ ├── index.ts
│ └── messages
│ │ ├── BlockFetchBatchDone.ts
│ │ ├── BlockFetchBlock.ts
│ │ ├── BlockFetchClientDone.ts
│ │ ├── BlockFetchNoBlocks.ts
│ │ ├── BlockFetchRequestRange.ts
│ │ ├── BlockFetchStartBatch.ts
│ │ └── index.ts
│ ├── chain-sync
│ ├── ChainSyncClient.ts
│ ├── ChainSyncMessage.ts
│ ├── ChainSyncServer.ts
│ ├── __tests__
│ │ └── ChainSync.splitMsg.test.ts
│ ├── chain-sync.cddl
│ ├── index.ts
│ └── messages
│ │ ├── ChainSyncAwaitReply.ts
│ │ ├── ChainSyncFindIntersect.ts
│ │ ├── ChainSyncIntersectFound.ts
│ │ ├── ChainSyncIntersectNotFound.ts
│ │ ├── ChainSyncMessageDone.ts
│ │ ├── ChainSyncRequestNext.ts
│ │ ├── ChainSyncRollBackwards.ts
│ │ ├── ChainSyncRollForward.ts
│ │ └── index.ts
│ ├── handshake
│ ├── HandshakeClient.ts
│ ├── HandshakeVersionTable
│ │ ├── HandshakeVersionTable.ts
│ │ ├── NetworkMagic.ts
│ │ ├── VersionData.ts
│ │ ├── VersionNumber.ts
│ │ └── index.ts
│ ├── cddls
│ │ ├── handshake-node-to-client.cddl
│ │ ├── handshake-node-to-node-v11-12.cddl
│ │ ├── handshake-node-to-node-v13.cddl
│ │ └── handshake-node-to-node.cddl
│ ├── index.ts
│ └── messages
│ │ ├── HandshakeAcceptVersion.ts
│ │ ├── HandshakeMessage.ts
│ │ ├── HandshakeProposeVersion.ts
│ │ ├── HandshakeQueryReply.ts
│ │ ├── HandshakeRefuse.ts
│ │ ├── RefuseReason
│ │ ├── RefuseReason.ts
│ │ ├── RefuseReasonHandshakeDecodeError.ts
│ │ ├── RefuseReasonRefuse.ts
│ │ ├── RefuseReasonVersionMismatch.ts
│ │ └── index.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── interfaces
│ └── IChainDb.ts
│ ├── keep-alive
│ ├── KeepAliveClient.ts
│ ├── KeepAliveMessage.ts
│ ├── index.ts
│ ├── keep-alive.cddl
│ └── messages
│ │ ├── KeepAliveDone.ts
│ │ ├── KeepAliveRequest.ts
│ │ ├── KeepAliveResponse.ts
│ │ └── index.ts
│ ├── local-state-query
│ ├── LocalStateQueryClient.ts
│ ├── QryMessage.ts
│ ├── index.ts
│ ├── local-state-query.cddl
│ ├── local-state-query.md
│ ├── messages
│ │ ├── QryAcquire.ts
│ │ ├── QryAcquired.ts
│ │ ├── QryDone.ts
│ │ ├── QryFailure.ts
│ │ ├── QryQuery.ts
│ │ ├── QryReAcquire.ts
│ │ ├── QryRelease.ts
│ │ ├── QryResult.ts
│ │ └── index.ts
│ └── query.cddl
│ ├── local-tx-monitor
│ ├── LocalTxMonitorClient.ts
│ ├── TxMonitorMessage.ts
│ ├── index.ts
│ ├── local-tx-monitor.cddl
│ ├── local-tx-monitor.md
│ └── messages
│ │ ├── TxMonitorAcquire.ts
│ │ ├── TxMonitorAcquired.ts
│ │ ├── TxMonitorDone.ts
│ │ ├── TxMonitorGetSizes.ts
│ │ ├── TxMonitorHasTx.ts
│ │ ├── TxMonitorNextTx.ts
│ │ ├── TxMonitorRelease.ts
│ │ ├── TxMonitorReplyGetSizes.ts
│ │ ├── TxMonitorReplyHasTx.ts
│ │ ├── TxMonitorReplyNextTx.ts
│ │ └── index.ts
│ ├── local-tx-submit
│ ├── LocalTxSubmitClient.ts
│ ├── LocalTxSubmitMessage.ts
│ ├── index.ts
│ ├── local-tx-submission.cddl
│ ├── local-tx-submit.md
│ └── messages
│ │ ├── LocalTxSubmitAccept.ts
│ │ ├── LocalTxSubmitDone.ts
│ │ ├── LocalTxSubmitReject.ts
│ │ ├── LocalTxSubmitSubmit.ts
│ │ └── index.ts
│ ├── peer-sharing
│ ├── PeerAddress
│ │ ├── PeerAddress.ts
│ │ ├── PeerAddressIPv4.ts
│ │ ├── PeerAddressIPv6.ts
│ │ └── index.ts
│ ├── PeerSharingClient.ts
│ ├── PeerSharingMessage.ts
│ ├── index.ts
│ ├── messages
│ │ ├── PeerSharingDone.ts
│ │ ├── PeerSharingRequest.ts
│ │ ├── PeerSharingResponse.ts
│ │ └── index.ts
│ ├── peer-sharing-v11-12.cddl
│ └── peer-sharing-v13.cddl
│ ├── tx-submission
│ ├── TxSubmitClient.ts
│ ├── TxSubmitMessage.ts
│ ├── TxSubmitServer.ts
│ ├── index.ts
│ ├── interfaces
│ │ ├── IMempool.ts
│ │ ├── index.ts
│ │ └── types
│ │ │ ├── IndexedHash.ts
│ │ │ ├── MempoolAppendResult.ts
│ │ │ ├── MempoolIndex.ts
│ │ │ ├── MempoolTx.ts
│ │ │ ├── MempoolTxHash.ts
│ │ │ ├── SupportedMempoolSize.ts
│ │ │ └── index.ts
│ ├── messages
│ │ ├── TxSubmitDone.ts
│ │ ├── TxSubmitInit.ts
│ │ ├── TxSubmitReplyIds.ts
│ │ ├── TxSubmitReplyTxs.ts
│ │ ├── TxSubmitRequestIds.ts
│ │ ├── TxSubmitRequestTxs.ts
│ │ └── index.ts
│ └── tx-submission2.cddl
│ ├── types
│ ├── AsOptions.ts
│ ├── ChainPoint.ts
│ ├── ChainTip.ts
│ ├── Definitely.ts
│ ├── OptField.ts
│ ├── RealPoint.ts
│ ├── index.ts
│ └── ints.ts
│ └── utils
│ ├── assert.ts
│ ├── bool.ts
│ ├── getSubCborRef.ts
│ ├── isByte.ts
│ ├── isWord16.ts
│ ├── isWord32.ts
│ └── safeParseInt.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @harmoniclabs/ouroboros-miniprotocols-ts
2 |
3 | ## What is this? Where am I?
4 |
5 | In this repository you'll find a typescript implementation of the [ouroboros networking protocol](https://ouroboros-network.cardano.intersectmbo.org/pdfs/network-spec/network-spec.pdf).
6 |
7 | To be precise here you find:
8 | - the data-types with respective CBOR encoding/decoding functions (`toCbor` methods and `fromCbor` static methods)
9 | - all needed for the multiplexer (the `Multiplexer` class and the more low level `wrapMultiplexerMessage` and `unwrapMultiplexerMessage`)
10 | - some protocol-specific enums (`MiniProtcol` and handshake versions).
11 |
12 | ## Why isn't it called `ouroboros-network-ts`?
13 |
14 | The ouroboros network component should also take care of connections with peers.
15 |
16 | This package doesn't do that.
17 |
18 | The reason is very simple: we might want differnt connections types depending on what is establishing the connection.
19 |
20 | example; a cardano node typescript implementation would be designed to operate only in a server environment; in that case a TCP socket would be used to connect to other peers, or a UNIX socket for node-to-client protocols (things like `cardano-cli` or `cardano-db-sync`);
21 |
22 | however, a light node implementation, meant to be able to run in browsers, wouldn't be able to have succ connecitons;
23 | the best a browser can offer are `WebSockets`¹ which are a different type of connection; but the data sent and received is the same.
24 |
25 |
26 | ¹currently not supported by the haskell implementaiton of the `cardano-node` but still possible in other implementaitons
--------------------------------------------------------------------------------
/babel.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@babel/preset-typescript",['@babel/preset-env', {targets: {node: 'current'}}]],
3 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@harmoniclabs/ouroboros-miniprotocols-ts",
3 | "version": "0.0.5-dev3",
4 | "main": "./dist/index.js",
5 | "types": "./dist/index.d.ts",
6 | "files": [
7 | "dist"
8 | ],
9 | "scripts": {
10 | "build": "tsc --project ./tsconfig.json && tsc-alias -p ./tsconfig.json",
11 | "start": "npm run build && node ./dist/index.js",
12 | "test": "jest",
13 | "ci": "npm run test && npm run build",
14 | "pub": "npm run ci && npm pub"
15 | },
16 | "dependencies": {
17 | "@harmoniclabs/cbor": "^1.6.5",
18 | "@harmoniclabs/obj-utils": "^1.0.0",
19 | "@harmoniclabs/uint8array-utils": "^1.0.3",
20 | "@types/node": "^20.1.7"
21 | },
22 | "devDependencies": {
23 | "@babel/preset-env": "^7.22.9",
24 | "@babel/preset-typescript": "^7.22.5",
25 | "@types/jest": "^29.5.3",
26 | "jest": "^29.6.2",
27 | "tsc-alias": "^1.8.7",
28 | "typescript": "^5.1.6"
29 | },
30 | "author": "Harmonic Laboratories",
31 | "license": "Apache-2.0",
32 | "funding": "https://github.com/sponsors/HarmonicLabs"
33 | }
34 |
--------------------------------------------------------------------------------
/src/MiniProtocol/MiniProtocol.ts:
--------------------------------------------------------------------------------
1 | export enum MiniProtocol {
2 | Handshake = 0,
3 | /** Node-to-Node ChainSync */
4 | ChainSync = 2,
5 | /** Node-to-Client ChainSync */
6 | LocalChainSync = 5,
7 | BlockFetch = 3,
8 | /** Node-to-Node TxSubmission */
9 | TxSubmission = 4,
10 | /** Node-to-Client TxSubmission */
11 | LocalTxSubmission = 6,
12 | LocalStateQuery = 7,
13 | KeepAlive = 8,
14 | LocalTxMonitor = 9,
15 | PeerSharing = 10
16 | }
17 |
18 | Object.freeze( MiniProtocol );
19 |
20 | export type MiniProtocolStr
21 | = "Handshake"
22 | | "ChainSync"
23 | | "LocalChainSync"
24 | | "BlockFetch"
25 | | "TxSubmission"
26 | | "LocalTxSubmission"
27 | | "LocalStateQuery"
28 | | "KeepAlive"
29 | | "LocalTxMonitor"
30 | | "PeerSharing";
31 |
32 | export function isMiniProtocolStr( thing: any ): thing is MiniProtocolStr
33 | {
34 | return (
35 | thing === "Handshake" ||
36 | thing === "ChainSync" ||
37 | thing === "LocalChainSync" ||
38 | thing === "BlockFetch" ||
39 | thing === "TxSubmission" ||
40 | thing === "LocalTxSubmission" ||
41 | thing === "LocalStateQuery" ||
42 | thing === "KeepAlive" ||
43 | thing === "LocalTxMonitor" ||
44 | thing === "PeerSharing"
45 | );
46 | }
47 |
48 | export type MiniProtocolNum
49 | = 0 // "Handshake"
50 | | 2 // "ChainSync"
51 | | 5 // "LocalChainSync"
52 | | 3 // "BlockFetch"
53 | | 4 // "TxSubmission"
54 | | 6 // "LocalTxSubmission"
55 | | 7 // "LocalStateQuery"
56 | | 8 // "KeepAlive"
57 | | 9 // "LocalTxMonitor"
58 | | 10 // "PeerSharing";
59 |
60 | export function isMiniProtocolNum( thing: any ): thing is MiniProtocolNum
61 | {
62 | return (
63 | thing === 0 || // "Handshake"
64 | thing === 2 || // "ChainSync"
65 | thing === 5 || // "LocalChainSync"
66 | thing === 3 || // "BlockFetch"
67 | thing === 4 || // "TxSubmission"
68 | thing === 6 || // "LocalTxSubmission"
69 | thing === 7 || // "LocalStateQuery"
70 | thing === 8 || // "KeepAlive";
71 | thing === 9 || // "LocalTxMonitor";
72 | thing === 10 // "PeerSharing";
73 | );
74 | }
75 |
76 | export function miniProtocolToNumber( protocol: number | string ): MiniProtocolNum
77 | {
78 | return typeof protocol === "string" ? MiniProtocol[protocol as any] as any as MiniProtocolNum : Number( protocol ) as MiniProtocolNum;
79 | }
80 |
81 | export function miniProtocolToString( protocol: number | string ): string
82 | {
83 | return typeof protocol === "number" ? MiniProtocol[protocol] : String( protocol );
84 | }
85 |
86 | export function isMiniProtocol( protocol: number | string ): boolean
87 | {
88 | if( typeof protocol === "number" )
89 | {
90 | return (
91 | protocol === MiniProtocol.BlockFetch ||
92 | protocol === MiniProtocol.ChainSync ||
93 | protocol === MiniProtocol.Handshake ||
94 | protocol === MiniProtocol.KeepAlive ||
95 | protocol === MiniProtocol.LocalChainSync ||
96 | protocol === MiniProtocol.LocalStateQuery ||
97 | protocol === MiniProtocol.LocalTxSubmission ||
98 | protocol === MiniProtocol.TxSubmission ||
99 | protocol === MiniProtocol.LocalTxMonitor ||
100 | protocol === MiniProtocol.PeerSharing
101 | );
102 | }
103 | else if( typeof protocol === "string" )
104 | {
105 | return isMiniProtocol( miniProtocolToNumber( protocol ) )
106 | }
107 | return false;
108 | }
--------------------------------------------------------------------------------
/src/MiniProtocol/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./MiniProtocol";
--------------------------------------------------------------------------------
/src/common/AddEvtListenerOpts.ts:
--------------------------------------------------------------------------------
1 | export interface AddEvtListenerOpts {
2 | once?: boolean
3 | }
--------------------------------------------------------------------------------
/src/common/ErrorListener.ts:
--------------------------------------------------------------------------------
1 | import type { MultiplexerHeader } from "../multiplexer";
2 |
3 | export type ErrorListener = (err: Error) => void;
4 | export type DataListener = (data: Uint8Array) => void;
5 | export type SendListener = (payload: Uint8Array, header: MultiplexerHeader) => void;
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./MiniProtocol";
2 | export * from "./multiplexer";
3 | export * from "./protocols";
--------------------------------------------------------------------------------
/src/multiplexer/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./multiplexerMessage";
2 | export * from "./Multiplexer";
--------------------------------------------------------------------------------
/src/multiplexer/multiplexerMessage.ts:
--------------------------------------------------------------------------------
1 | import { toHex } from "@harmoniclabs/uint8array-utils";
2 | import { MiniProtocol } from "../MiniProtocol";
3 |
4 | export interface MultiplexerHeaderInfos {
5 | hasAgency: boolean,
6 | protocol: MiniProtocol,
7 | transmissionTime?: number,
8 | payloadLength?: number
9 | }
10 |
11 | export interface MultiplexerHeader {
12 | hasAgency: boolean,
13 | protocol: MiniProtocol,
14 | transmissionTime: number,
15 | payloadLength: number
16 | }
17 |
18 | export function wrapMultiplexerMessage(
19 | payload: Uint8Array,
20 | {
21 | protocol,
22 | hasAgency
23 | }: MultiplexerHeaderInfos
24 | ): Uint8Array
25 | {
26 | // Mini Protocol ID The unique ID of the mini protocol as in tables 2.2 and 2.3.
27 |
28 | /**
29 | * - 4 bytes (32 bits) for transmission time (calculated only at the end)
30 | * - 1 bit for _mode_:
31 | * - `0` if the sender has agency (initiates the mini protocol)
32 | * - `1` otherwhise
33 | * - 15 bits for the mini protocol ID (see `MiniProtocol` enum above)
34 | * - 16 bits for the payload-length
35 | * - payload
36 | **/
37 | const buff = new ArrayBuffer( payload.length + 8 )
38 | const result = new Uint8Array( buff );
39 | const view = new DataView( buff );
40 |
41 | view.setUint16(
42 | 4, // byteOffset
43 | /**
44 | * - 1 bit for _mode_:
45 | * - `0` if the sender has agency (initiates the mini protocol)
46 | * - `1` otherwhise
47 | */
48 | ( hasAgency ? 0 : 1 << 15 ) |
49 | // - 15 bits for the mini protocol ID (see `MiniProtocol` enum above)
50 | ( protocol & 0xffff ),
51 | false, // littleEndian = false
52 | );
53 |
54 | view.setUint16(
55 | 6, // byteOffset
56 | // - 16 bits for the payload-length
57 | payload.length & 0xffff,
58 | false, // littleEndian = false
59 | );
60 |
61 | // - payload
62 | result.set( payload, 8 );
63 |
64 | // - 4 bytes (32 bits) for transmission time (calculated only at the end)
65 | //
66 | // Transmission Time
67 | // The transmission time is a time stamp based
68 | // the **lower 32 bits** of the sender’s **monotonic clock**
69 | // with a resolution of one microsecond.
70 | view.setInt32(
71 | 0,
72 | Math.ceil( performance.timeOrigin * 1000 + performance.now() * 1000 ) & 0xffffffff,
73 | false, // littleEndian = false
74 | );
75 | return result;
76 | }
77 |
78 | const agencyMask = 0x8000; // ( 1 << 15 )
79 | const protoclMask = 0x7fff; // ~agencyMask & 0xffff;
80 |
81 | export type MultiplexerMessage = {
82 | header: MultiplexerHeader,
83 | payload: Uint8Array
84 | };
85 |
86 | /**
87 | * @deprecated use `unwrapMultiplexerMessages`
88 | */
89 | export function unwrapMultiplexerMessage(
90 | message: Uint8Array
91 | ): MultiplexerMessage
92 | {
93 | const agencyAndProtocol = message[4] << 8 | message[5];
94 | const payloadLen = message[6] << 8 | message[7];
95 | return {
96 | header: {
97 | transmissionTime: (message[0] << 24) | (message[1] << 16) | (message[2] << 8) | message[3],
98 | hasAgency: (agencyAndProtocol & agencyMask) > 0,
99 | protocol: agencyAndProtocol & protoclMask,
100 | payloadLength: payloadLen
101 | },
102 | payload: message.slice( 8, 8 + payloadLen )
103 | };
104 | }
105 |
106 | export function unwrapMultiplexerMessages(
107 | message: Uint8Array
108 | ): MultiplexerMessage[]
109 | {
110 | const messages: MultiplexerMessage[] = [];
111 | while( message.length >= 8 )
112 | {
113 | const view = new DataView( message.buffer );
114 |
115 | // bitwise opeartor (or and shift) implicitly convert to signed int32
116 | // but these are 2 bytes long numbers, hence always positive
117 | const agencyAndProtocol = message[4] << 8 | message[5];
118 | const payloadLen = message[6] << 8 | message[7];
119 |
120 | messages.push({
121 | header: {
122 | transmissionTime: view.getUint32( 0, false ),
123 | hasAgency: (agencyAndProtocol & agencyMask) > 0,
124 | protocol: agencyAndProtocol & protoclMask,
125 | payloadLength: payloadLen
126 | },
127 | payload: Uint8Array.prototype.slice.call( message, 8, 8 + payloadLen )
128 | });
129 |
130 | message = Uint8Array.prototype.slice.call( message, 8 + payloadLen );
131 | }
132 | return messages
133 | }
--------------------------------------------------------------------------------
/src/protocols/block-fetch/BlockFetchMessage.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "@harmoniclabs/obj-utils";
2 | import { BlockFetchBatchDone, IBlockFetchBatchDone } from "./messages/BlockFetchBatchDone";
3 | import { BlockFetchBlock, IBlockFetchBlock } from "./messages/BlockFetchBlock";
4 | import { BlockFetchClientDone, IBlockFetchClientDone } from "./messages/BlockFetchClientDone";
5 | import { BlockFetchNoBlocks, IBlockFetchNoBlocks } from "./messages/BlockFetchNoBlocks";
6 | import { BlockFetchRequestRange, IBlockFetchRequestRange } from "./messages/BlockFetchRequestRange";
7 | import { BlockFetchStartBatch, IBlockFetchStartBatch } from "./messages/BlockFetchStartBatch";
8 | import { CanBeCborString, Cbor, CborArray, CborObj, CborUInt, forceCborString } from "@harmoniclabs/cbor";
9 |
10 | export type BlockFetchMessage
11 | = BlockFetchRequestRange
12 | | BlockFetchClientDone
13 | | BlockFetchStartBatch
14 | | BlockFetchNoBlocks
15 | | BlockFetchBlock
16 | | BlockFetchBatchDone;
17 |
18 | export function isBlockFetchMessage( stuff: any ): stuff is BlockFetchMessage
19 | {
20 | return isObject( stuff ) && (
21 | stuff instanceof BlockFetchRequestRange ||
22 | stuff instanceof BlockFetchClientDone ||
23 | stuff instanceof BlockFetchStartBatch ||
24 | stuff instanceof BlockFetchNoBlocks ||
25 | stuff instanceof BlockFetchBlock ||
26 | stuff instanceof BlockFetchBatchDone
27 | );
28 | }
29 |
30 | export type IBlockFetchMessage
31 | = IBlockFetchRequestRange
32 | | IBlockFetchClientDone
33 | | IBlockFetchStartBatch
34 | | IBlockFetchNoBlocks
35 | | IBlockFetchBlock
36 | | IBlockFetchBatchDone;
37 |
38 | export function blockFetchMessageFromCbor( cbor: CanBeCborString ): BlockFetchMessage
39 | {
40 | return blockFetchMessageFromCborObj(
41 | Cbor.parse(
42 | cbor instanceof Uint8Array ?
43 | cbor :
44 | forceCborString( cbor )
45 | )
46 | );
47 | }
48 |
49 | export function blockFetchMessageFromCborObj( cbor: CborObj ): BlockFetchMessage
50 | {
51 | if(!(
52 | cbor instanceof CborArray &&
53 | cbor.array.length >= 1 &&
54 | cbor.array[0] instanceof CborUInt
55 | )) throw new Error("invalid cbor for 'BlockFetchMessage'");
56 |
57 | const idx = Number( cbor.array[0].num );
58 |
59 | if( idx === 0 ) return BlockFetchRequestRange.fromCborObj( cbor );
60 | if( idx === 1 ) return new BlockFetchClientDone();
61 | if( idx === 2 ) return new BlockFetchStartBatch();
62 | if( idx === 3 ) return new BlockFetchNoBlocks();
63 | if( idx === 4 ) return BlockFetchBlock.fromCborObj( cbor );
64 | if( idx === 5 ) return new BlockFetchBatchDone();
65 |
66 | throw new Error("invalid cbor for 'BlockFetchMessage'; unknown index: " + idx);
67 | }
--------------------------------------------------------------------------------
/src/protocols/block-fetch/block-fetch.cddl:
--------------------------------------------------------------------------------
1 | ;
2 | ; BlockFetch mini-protocol
3 | ;
4 |
5 | ; reference implementation of the codec in :
6 | ; ouroboros-network/src/Ouroboros/Network/Protocol/BlockFetch/Codec.hs
7 |
8 | blockFetchMessage
9 | = msgRequestRange
10 | / msgClientDone
11 | / msgStartBatch
12 | / msgNoBlocks
13 | / msgBlock
14 | / msgBatchDone
15 |
16 | msgRequestRange = [0, point, point]
17 | msgClientDone = [1]
18 | msgStartBatch = [2]
19 | msgNoBlocks = [3]
20 | msgBlock = [4, block]
21 | msgBatchDone = [5]
--------------------------------------------------------------------------------
/src/protocols/block-fetch/block-fetch.md:
--------------------------------------------------------------------------------
1 | ```mermaid
2 | flowchart TD
3 | Idle
4 | Done
5 | Busy
6 | Streaming
7 |
8 | Idle --"ClientDone"--> Done
9 | Idle --"RequestRange"--> Busy
10 | Busy --"NoBlocks"--> Idle
11 | Busy --"StartBatch"--> Streaming
12 | Streaming --"Block"--> Streaming
13 | Streaming --"BatchDone"--> Idle
14 | ```
--------------------------------------------------------------------------------
/src/protocols/block-fetch/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./BlockFetchMessage";
2 | export * from "./messages";
3 | export * from "./BlockFetchClient";
--------------------------------------------------------------------------------
/src/protocols/block-fetch/messages/BlockFetchBatchDone.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IBlockFetchBatchDone {}
5 |
6 | export function isIBlockFetchBatchDone( stuff: any ): stuff is IBlockFetchBatchDone
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class BlockFetchBatchDone
12 | implements ToCbor, ToCborObj, IBlockFetchBatchDone
13 | {
14 | readonly cborRef: SubCborRef | undefined = undefined;
15 | constructor() {};
16 |
17 | toJSON() { return this.toJson(); }
18 | toJson() { return {}; }
19 |
20 | toCborBytes(): Uint8Array
21 | {
22 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
23 | return this.toCbor().toBuffer();
24 | }
25 | toCbor(): CborString
26 | {
27 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
28 | return Cbor.encode( this.toCborObj() );
29 | }
30 | toCborObj(): CborArray
31 | {
32 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
33 | return new CborArray([ new CborUInt(5) ]);
34 | }
35 |
36 | static fromCbor( cbor: CanBeCborString ): BlockFetchBatchDone
37 | {
38 | return BlockFetchBatchDone.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
39 | }
40 | static fromCborObj( cbor: CborObj ): BlockFetchBatchDone
41 | {
42 | if(!(
43 | cbor instanceof CborArray &&
44 | cbor.array[0] instanceof CborUInt &&
45 | cbor.array[0].num === BigInt(5)
46 | )) throw new Error("invalid CBOR for 'BlockFetchBatchDone");
47 |
48 | return new BlockFetchBatchDone();
49 | }
50 | }
--------------------------------------------------------------------------------
/src/protocols/block-fetch/messages/BlockFetchBlock.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborBytes, CborObj, CborString, CborTag, CborUInt, LazyCborArray, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { LazyCborTag } from "@harmoniclabs/cbor/dist/LazyCborObj/LazyCborTag";
3 | import { hasOwn, isObject } from "@harmoniclabs/obj-utils";
4 | import { getSubCborRef, subCborRefOrUndef } from "../../utils/getSubCborRef";
5 |
6 | export interface IBlockFetchBlock {
7 | blockData: CanBeCborString
8 | }
9 |
10 | export function isIBlockFetchBlock( stuff: any ): stuff is IBlockFetchBlock
11 | {
12 | return isObject( stuff ) && (
13 | hasOwn( stuff, "blockData" ) &&
14 | (
15 | CborString.isValidHexValue( stuff.blockData ) ||
16 | stuff.blockData instanceof Uint8Array ||
17 | stuff.blockData instanceof CborString
18 | )
19 | );
20 | }
21 |
22 | export class BlockFetchBlock
23 | implements ToCbor, ToCborObj, IBlockFetchBlock
24 | {
25 | readonly blockData: Uint8Array;
26 |
27 | constructor(
28 | blk: IBlockFetchBlock,
29 | readonly cborRef: SubCborRef | undefined = undefined
30 | )
31 | {
32 | if(!(
33 | isIBlockFetchBlock( blk )
34 | )) throw new Error("invalid interface for 'BlockFetchBlock'");
35 |
36 | this.blockData = blk.blockData instanceof Uint8Array ? blk.blockData : forceCborString( blk.blockData ).toBuffer();
37 | this.cborRef = cborRef ?? subCborRefOrUndef( blk );
38 | };
39 |
40 | toCborBytes(): Uint8Array
41 | {
42 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
43 | return this.toCbor().toBuffer();
44 | }
45 | toCbor(): CborString
46 | {
47 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
48 | return Cbor.encode( this.toCborObj() );
49 | }
50 | toCborObj(): CborArray
51 | {
52 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
53 | return new CborArray([
54 | new CborUInt(4),
55 | new CborTag(
56 | 24,
57 | new CborBytes( this.blockData )
58 | )
59 | ]);
60 | }
61 |
62 | /**
63 | * @returns {Uint8Array}
64 | * the bytes of `this.blockData` as present on `this.cborBytes`
65 | * (using `Cbor.parseLazy`)
66 | */
67 | getBlockBytes(): Uint8Array
68 | {
69 | const msgData = this.toCborBytes();
70 | const lazy = Cbor.parseLazy( msgData );
71 | if(!( lazy instanceof LazyCborArray )) throw new Error("invalid 'BlockFetchBlock' cbor found");
72 |
73 | const tagBytes = lazy.array[1];
74 | const lazyTag = Cbor.parseLazy( tagBytes );
75 | if(!( lazyTag instanceof LazyCborTag )) throw new Error("invalid 'BlockFetchBlock' cbor found");
76 |
77 | const taggedElem = lazyTag.data;
78 | if(!( taggedElem instanceof CborBytes )) throw new Error("invalid 'BlockFetchBlock' cbor found");
79 |
80 | return taggedElem.bytes;
81 | }
82 |
83 | static fromCbor( cbor: CanBeCborString ): BlockFetchBlock
84 | {
85 | const buff = cbor instanceof Uint8Array ?
86 | cbor:
87 | forceCborString( cbor ).toBuffer();
88 |
89 | return BlockFetchBlock.fromCborObj(
90 | Cbor.parse( buff, { keepRef: true } ),
91 | buff
92 | );
93 | }
94 | static fromCborObj(
95 | cbor: CborObj,
96 | originalBytes: Uint8Array | undefined = undefined
97 | ): BlockFetchBlock
98 | {
99 | if(!(
100 | // is array
101 | cbor instanceof CborArray &&
102 | // with at least two elements
103 | cbor.array.length >= 2 &&
104 | // of which the first is the `BlockFetchBlock` index
105 | cbor.array[0] instanceof CborUInt &&
106 | cbor.array[0].num === BigInt(4)
107 | )) throw new Error("invalid CBOR for 'BlockFetchBlock");
108 |
109 | let arg = cbor.array[1];
110 |
111 | if(
112 | arg instanceof CborTag &&
113 | arg.tag === BigInt(24) &&
114 | arg.data instanceof CborBytes
115 | )
116 | {
117 | arg = arg.data
118 | }
119 |
120 | if( !( arg instanceof CborBytes ) )
121 | throw new Error("invalid CBOR for 'BlockFetchBlock");
122 |
123 | return new BlockFetchBlock({
124 | blockData: arg.bytes
125 | }, getSubCborRef( cbor, originalBytes ));
126 | }
127 | }
--------------------------------------------------------------------------------
/src/protocols/block-fetch/messages/BlockFetchClientDone.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IBlockFetchClientDone {}
5 |
6 | export function isIBlockFetchClientDone( stuff: any ): stuff is IBlockFetchClientDone
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class BlockFetchClientDone
12 | implements ToCbor, ToCborObj, IBlockFetchClientDone
13 | {
14 | readonly cborRef: SubCborRef | undefined = undefined;
15 | constructor() {};
16 |
17 | toJSON() { return this.toJson(); }
18 | toJson() { return {}; }
19 |
20 | toCborBytes(): Uint8Array
21 | {
22 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
23 | return this.toCbor().toBuffer();
24 | }
25 | toCbor(): CborString
26 | {
27 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
28 | return Cbor.encode( this.toCborObj() );
29 | }
30 | toCborObj(): CborArray
31 | {
32 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
33 | return new CborArray([ new CborUInt(1) ]);
34 | }
35 |
36 | static fromCbor( cbor: CanBeCborString ): BlockFetchClientDone
37 | {
38 | return BlockFetchClientDone.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
39 | }
40 | static fromCborObj( cbor: CborObj ): BlockFetchClientDone
41 | {
42 | if(!(
43 | cbor instanceof CborArray &&
44 | cbor.array[0] instanceof CborUInt &&
45 | cbor.array[0].num === BigInt(1)
46 | )) throw new Error("invalid CBOR for 'BlockFetchClientDone");
47 |
48 | return new BlockFetchClientDone();
49 | }
50 | }
--------------------------------------------------------------------------------
/src/protocols/block-fetch/messages/BlockFetchNoBlocks.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IBlockFetchNoBlocks {}
5 |
6 | export function isIBlockFetchNoBlocks( stuff: any ): stuff is IBlockFetchNoBlocks
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class BlockFetchNoBlocks
12 | implements ToCbor, ToCborObj, IBlockFetchNoBlocks
13 | {
14 | readonly cborRef: SubCborRef | undefined = undefined;
15 | constructor() {};
16 |
17 | toJSON() { return this.toJson(); }
18 | toJson() { return {}; }
19 |
20 | toCborBytes(): Uint8Array
21 | {
22 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
23 | return this.toCbor().toBuffer();
24 | }
25 | toCbor(): CborString
26 | {
27 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
28 | return Cbor.encode( this.toCborObj() );
29 | }
30 | toCborObj(): CborArray
31 | {
32 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
33 | return new CborArray([ new CborUInt(3) ]);
34 | }
35 |
36 | static fromCbor( cbor: CanBeCborString ): BlockFetchNoBlocks
37 | {
38 | return BlockFetchNoBlocks.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
39 | }
40 | static fromCborObj( cbor: CborObj ): BlockFetchNoBlocks
41 | {
42 | if(!(
43 | cbor instanceof CborArray &&
44 | cbor.array[0] instanceof CborUInt &&
45 | cbor.array[0].num === BigInt(3)
46 | )) throw new Error("invalid CBOR for 'BlockFetchNoBlocks");
47 |
48 | return new BlockFetchNoBlocks();
49 | }
50 | }
--------------------------------------------------------------------------------
/src/protocols/block-fetch/messages/BlockFetchRequestRange.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { ChainPoint, IChainPoint, isIChainPoint } from "../../types/ChainPoint";
3 | import { getSubCborRef, subCborRefOrUndef } from "../../utils/getSubCborRef";
4 |
5 | export interface IBlockFetchRequestRange {
6 | from: IChainPoint,
7 | to: IChainPoint
8 | }
9 |
10 | export class BlockFetchRequestRange
11 | implements ToCbor, ToCborObj, IBlockFetchRequestRange
12 | {
13 | readonly from: ChainPoint;
14 | readonly to: ChainPoint;
15 |
16 | constructor(
17 | range: IBlockFetchRequestRange,
18 | readonly cborRef: SubCborRef | undefined = undefined
19 | )
20 | {
21 | const { from, to } = range;
22 | if(!(
23 | isIChainPoint( from ) &&
24 | isIChainPoint( to )
25 | )) throw new Error("invalid chain points for 'BlockFetchRequestRange'");
26 |
27 | this.from = from instanceof ChainPoint ? from : new ChainPoint( from );
28 | this.to = to instanceof ChainPoint ? to : new ChainPoint( to );
29 | this.cborRef = cborRef ?? subCborRefOrUndef( range );
30 | }
31 |
32 | toCborBytes(): Uint8Array
33 | {
34 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
35 | return this.toCbor().toBuffer();
36 | }
37 | toCbor(): CborString
38 | {
39 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
40 | return Cbor.encode( this.toCborObj() );
41 | }
42 | toCborObj(): CborArray
43 | {
44 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
45 | return new CborArray([
46 | new CborUInt(0),
47 | this.from.toCborObj(),
48 | this.to.toCborObj()
49 | ]);
50 | }
51 |
52 | static fromcCbor( cbor: CanBeCborString ): BlockFetchRequestRange
53 | {
54 | const bytes = cbor instanceof Uint8Array ? cbor : forceCborString( cbor ).toBuffer();
55 | return BlockFetchRequestRange.fromCborObj(
56 | Cbor.parse( bytes, { keepRef: true } ),
57 | bytes
58 | );
59 | }
60 | static fromCborObj(
61 | cbor: CborObj,
62 | originalBytes: Uint8Array | undefined = undefined
63 | ): BlockFetchRequestRange
64 | {
65 | if(!(
66 | cbor instanceof CborArray &&
67 | cbor.array.length >= 3 &&
68 | cbor.array[0] instanceof CborUInt &&
69 | cbor.array[0].num === BigInt(0)
70 | )) throw new Error("invalid CBOR for 'BlockFetchRequestRange'");
71 |
72 | const [ _idx, fromCbor, toCbor ] = cbor.array;
73 |
74 | return new BlockFetchRequestRange({
75 | from: ChainPoint.fromCborObj( fromCbor ),
76 | to: ChainPoint.fromCborObj( toCbor )
77 | }, getSubCborRef( cbor, originalBytes ));
78 | }
79 | }
--------------------------------------------------------------------------------
/src/protocols/block-fetch/messages/BlockFetchStartBatch.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IBlockFetchStartBatch {}
5 |
6 | export function isIBlockFetchStartBatch( stuff: any ): stuff is IBlockFetchStartBatch
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class BlockFetchStartBatch
12 | implements ToCbor, ToCborObj, IBlockFetchStartBatch
13 | {
14 | readonly cborRef: SubCborRef | undefined = undefined;
15 |
16 | constructor() {};
17 |
18 | toJSON() { return this.toJson(); }
19 | toJson() { return {}; }
20 |
21 | toCborBytes(): Uint8Array
22 | {
23 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
24 | return this.toCbor().toBuffer();
25 | }
26 | toCbor(): CborString
27 | {
28 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
29 | return Cbor.encode( this.toCborObj() );
30 | }
31 | toCborObj(): CborArray
32 | {
33 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
34 | return new CborArray([ new CborUInt(2) ]);
35 | }
36 |
37 | static fromCbor( cbor: CanBeCborString ): BlockFetchStartBatch
38 | {
39 | return BlockFetchStartBatch.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
40 | }
41 | static fromCborObj( cbor: CborObj ): BlockFetchStartBatch
42 | {
43 | if(!(
44 | cbor instanceof CborArray &&
45 | cbor.array[0] instanceof CborUInt &&
46 | cbor.array[0].num === BigInt(2)
47 | )) throw new Error("invalid CBOR for 'BlockFetchStartBatch");
48 |
49 | return new BlockFetchStartBatch();
50 | }
51 | }
--------------------------------------------------------------------------------
/src/protocols/block-fetch/messages/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./BlockFetchBatchDone";
2 | export * from "./BlockFetchBlock";
3 | export * from "./BlockFetchClientDone";
4 | export * from "./BlockFetchNoBlocks";
5 | export * from "./BlockFetchRequestRange";
6 | export * from "./BlockFetchStartBatch";
--------------------------------------------------------------------------------
/src/protocols/chain-sync/ChainSyncMessage.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "@harmoniclabs/obj-utils";
2 | import { CanBeCborString, Cbor, CborArray, CborObj, CborUInt, forceCborString } from "@harmoniclabs/cbor";
3 | import { ChainSyncRequestNext, ChainSyncAwaitReply, ChainSyncRollForward, ChainSyncRollBackwards, ChainSyncFindIntersect, ChainSyncIntersectFound, ChainSyncIntersectNotFound, ChainSyncMessageDone, IChainSyncRequestNext, IChainSyncAwaitReply, IChainSyncRollForward, IChainSyncRollBackwards, IChainSyncFindIntersect, IChainSyncIntersectFound, IChainSyncIntersectNotFound, IChainSyncMessageDone } from "./messages";
4 |
5 | export type ChainSyncMessage
6 | = ChainSyncRequestNext
7 | | ChainSyncAwaitReply
8 | | ChainSyncRollForward
9 | | ChainSyncRollBackwards
10 | | ChainSyncFindIntersect
11 | | ChainSyncIntersectFound
12 | | ChainSyncIntersectNotFound
13 | | ChainSyncMessageDone;
14 |
15 | export function isChainSyncMessage( stuff: any ): stuff is ChainSyncMessage
16 | {
17 | return isObject( stuff ) && (
18 | stuff instanceof ChainSyncRequestNext ||
19 | stuff instanceof ChainSyncAwaitReply ||
20 | stuff instanceof ChainSyncRollForward ||
21 | stuff instanceof ChainSyncRollBackwards ||
22 | stuff instanceof ChainSyncFindIntersect ||
23 | stuff instanceof ChainSyncIntersectFound ||
24 | stuff instanceof ChainSyncIntersectNotFound ||
25 | stuff instanceof ChainSyncMessageDone
26 | );
27 | }
28 |
29 | export type IChainSyncMessage
30 | = IChainSyncRequestNext // {}
31 | | IChainSyncAwaitReply // {}
32 | | IChainSyncRollForward
33 | | IChainSyncRollBackwards
34 | | IChainSyncFindIntersect
35 | | IChainSyncIntersectFound
36 | | IChainSyncIntersectNotFound
37 | | IChainSyncMessageDone; // {}
38 |
39 |
40 | export function isIChainSyncMessage( stuff: any ): stuff is IChainSyncMessage
41 | {
42 | return isObject( stuff ); // empty object satisfies some of the ChainSync messages
43 | }
44 |
45 | export function chainSyncMessageFromCbor( cbor: CanBeCborString ): ChainSyncMessage
46 | {
47 | const buff = cbor instanceof Uint8Array ?
48 | cbor :
49 | forceCborString( cbor ).toBuffer();
50 |
51 | const msg = chainSyncMessageFromCborObj( Cbor.parse( buff ) );
52 |
53 | // @ts-ignore Cannot assign to 'cborBytes' because it is a read-only property.ts(2540)
54 | msg.cborBytes = buff;
55 |
56 | return msg;
57 | }
58 | export function chainSyncMessageFromCborObj( cbor: CborObj ): ChainSyncMessage
59 | {
60 | if(!(
61 | cbor instanceof CborArray &&
62 | cbor.array.length >= 1 &&
63 | cbor.array[0] instanceof CborUInt
64 | )) throw new Error("invalid cbor for 'ChainSyncMessage'");
65 |
66 | const idx = Number( cbor.array[0].num );
67 |
68 | if( idx === 0 ) return new ChainSyncRequestNext();
69 | if( idx === 1 ) return new ChainSyncAwaitReply();
70 | if( idx === 2 ) return ChainSyncRollForward.fromCborObj( cbor );
71 | if( idx === 3 ) return ChainSyncRollBackwards.fromCborObj( cbor );
72 | if( idx === 4 ) return ChainSyncFindIntersect.fromCborObj( cbor );
73 | if( idx === 5 ) return ChainSyncIntersectFound.fromCborObj( cbor );
74 | if( idx === 6 ) return ChainSyncIntersectNotFound.fromCborObj( cbor );
75 | if( idx === 7 ) return new ChainSyncMessageDone();
76 |
77 | throw new Error("invalid cbor for 'ChainSyncMessage'; unknown index: " + idx);
78 | }
--------------------------------------------------------------------------------
/src/protocols/chain-sync/__tests__/ChainSync.splitMsg.test.ts:
--------------------------------------------------------------------------------
1 | import { connect } from "node:net";
2 | import { Multiplexer } from "../../../multiplexer/Multiplexer";
3 | import { ChainSyncClient } from "../ChainSyncClient";
4 | import { N2CHandshakeVersion, N2CMessageAcceptVersion, N2CMessageProposeVersion, n2cHandshakeMessageFromCbor } from "../../handshake";
5 | import { MiniProtocol } from "../../../MiniProtocol";
6 | import { toHex } from "@harmoniclabs/uint8array-utils";
7 | import { chainSyncMessageFromCbor } from "../ChainSyncMessage";
8 | import { ChainSyncRollForward } from "../ChainSyncRollForward";
9 |
10 | jest.setTimeout( 15_000 );
11 |
12 | test.skip("ChainSync", async () => {
13 |
14 | const mplexer = new Multiplexer(
15 | {
16 | connect: () => connect({ path: process.env.CARDANO_NODE_SOCKET_PATH ?? "" },),
17 | protocolType: "node-to-client"
18 | }
19 | );
20 |
21 | const chainSyncClient = new ChainSyncClient( mplexer );
22 |
23 | mplexer.on("Handshake",( chunk, header ) => {
24 | const msg = n2cHandshakeMessageFromCbor( chunk );
25 | console.log( msg );
26 | });
27 |
28 | const msgs: ChainSyncRollForward[] = [];
29 | let i = 0;
30 | chainSyncClient.on("rollForward", ( msg ) => {
31 | msgs.push( msg );
32 | });
33 |
34 | // handshake
35 | await new Promise(( resolve => {
36 | mplexer.on("Handshake", chunk => {
37 |
38 | const msg = n2cHandshakeMessageFromCbor( chunk );
39 |
40 | if( msg instanceof N2CMessageAcceptVersion )
41 | {
42 | mplexer.clearListeners( MiniProtocol.Handshake );
43 | resolve();
44 | }
45 | else {
46 | throw new Error("TODO: handle rejection")
47 | }
48 | });
49 |
50 | mplexer.send(
51 | new N2CMessageProposeVersion({
52 | versionTable: [
53 | {
54 | version: N2CHandshakeVersion.v10,
55 | data: {
56 | networkMagic: 1
57 | }
58 | }
59 | ]
60 | }).toCbor().toBuffer(),
61 | {
62 | hasAgency: true,
63 | protocol: MiniProtocol.Handshake
64 | }
65 | );
66 | }));
67 |
68 | for( let i = 0; i < 100; i++ )
69 | {
70 | chainSyncClient.requestNext();
71 | }
72 |
73 | await new Promise( res => setTimeout(() => {
74 | mplexer.close();
75 | console.log( "tot roll froward", msgs.length );
76 | res();
77 | }, 5000 ) );
78 |
79 |
80 | })
--------------------------------------------------------------------------------
/src/protocols/chain-sync/chain-sync.cddl:
--------------------------------------------------------------------------------
1 | chainSyncMessage
2 | = msgRequestNext
3 | / msgAwaitReply
4 | / msgRollForward
5 | / msgRollBackward
6 | / msgFindIntersect
7 | / msgIntersectFound
8 | / msgIntersectNotFound
9 | / chainSyncMsgDone
10 |
11 | msgRequestNext = [0]
12 | msgAwaitReply = [1]
13 | msgRollForward = [2, wrappedHeader, tip]
14 | msgRollBackward = [3, point, tip]
15 | msgFindIntersect = [4, points]
16 | msgIntersectFound = [5, point, tip]
17 | msgIntersectNotFound = [6, tip]
18 | chainSyncMsgDone = [7]
19 |
20 | wrappedHeader = #6.24(bytes .cbor blockHeader)
21 | tip = [point, uint]
22 |
23 | points = [ *point ]
24 |
--------------------------------------------------------------------------------
/src/protocols/chain-sync/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ChainSyncMessage";
2 | export * from "./ChainSyncClient";
3 | export * from "./messages";
--------------------------------------------------------------------------------
/src/protocols/chain-sync/messages/ChainSyncAwaitReply.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IChainSyncAwaitReply {};
5 |
6 | export function isIChainSyncAwaitReply( stuff: any ): stuff is IChainSyncAwaitReply
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class ChainSyncAwaitReply
12 | implements ToCbor, ToCborObj, IChainSyncAwaitReply
13 | {
14 | constructor(
15 | readonly cborRef: SubCborRef | undefined = undefined
16 | ){};
17 |
18 | toJSON() { return this.toJson(); }
19 | toJson() { return {}; }
20 |
21 | toCborBytes(): Uint8Array
22 | {
23 | // if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
24 | return this.toCbor().toBuffer();
25 | }
26 | toCbor(): CborString
27 | {
28 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
29 | return Cbor.encode( this.toCborObj() );
30 | }
31 | toCborObj(): CborArray
32 | {
33 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
34 | return new CborArray([ new CborUInt(1) ]);
35 | }
36 |
37 | static fromCbor( cbor: CanBeCborString ): ChainSyncAwaitReply
38 | {
39 | const buff = cbor instanceof Uint8Array ?
40 | cbor:
41 | forceCborString( cbor ).toBuffer();
42 |
43 | return ChainSyncAwaitReply.fromCborObj( Cbor.parse( buff ) );
44 | }
45 | static fromCborObj( cbor: CborObj ): ChainSyncAwaitReply
46 | {
47 | if(!(
48 | cbor instanceof CborArray &&
49 | cbor.array[0] instanceof CborUInt &&
50 | cbor.array[0].num === BigInt(1)
51 | )) throw new Error("invalid CBOR for 'ChainSyncAwaitReply");
52 |
53 | return new ChainSyncAwaitReply();
54 | }
55 | }
--------------------------------------------------------------------------------
/src/protocols/chain-sync/messages/ChainSyncFindIntersect.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { ChainPoint, IChainPoint, isIChainPoint } from "../../types/ChainPoint";
3 | import { getSubCborRef, subCborRefOrUndef } from "../../utils/getSubCborRef";
4 |
5 | export interface IChainSyncFindIntersect {
6 | points: readonly IChainPoint[]
7 | }
8 |
9 | export class ChainSyncFindIntersect
10 | implements ToCbor, ToCborObj, IChainSyncFindIntersect
11 | {
12 | readonly points: readonly ChainPoint[];
13 |
14 | constructor(
15 | ask: IChainSyncFindIntersect,
16 | readonly cborRef: SubCborRef | undefined = undefined
17 | )
18 | {
19 | const { points } = ask;
20 | if(!(
21 | Array.isArray( points ) && points.every( isIChainPoint )
22 | )) throw new Error("invalid IMessageFindIntesect interface");
23 |
24 | this.points = points.map( p => p instanceof ChainPoint ? p : new ChainPoint( p ) );
25 | this.cborRef = cborRef ?? subCborRefOrUndef( ask );
26 | }
27 |
28 | toJSON() { return this.toJson(); }
29 | toJson()
30 | {
31 | return {
32 | points: this.points.map( p => p.toJson() )
33 | }
34 | }
35 |
36 | toCborBytes(): Uint8Array
37 | {
38 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
39 | return this.toCbor().toBuffer();
40 | }
41 | toCbor(): CborString
42 | {
43 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
44 | return Cbor.encode( this.toCborObj() );
45 | }
46 | toCborObj(): CborArray
47 | {
48 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
49 | return new CborArray([
50 | new CborUInt( 4 ),
51 | new CborArray( this.points.map( p => p.toCborObj() ) )
52 | ])
53 | }
54 |
55 |
56 | static fromCbor( cbor: CanBeCborString ): ChainSyncFindIntersect
57 | {
58 | const buff = cbor instanceof Uint8Array ?
59 | cbor:
60 | forceCborString( cbor ).toBuffer();
61 |
62 | return ChainSyncFindIntersect.fromCborObj( Cbor.parse( buff ), buff );
63 | }
64 | static fromCborObj(
65 | cbor: CborObj,
66 | originalBytes: Uint8Array | undefined = undefined
67 | ): ChainSyncFindIntersect
68 | {
69 | if(!(
70 | cbor instanceof CborArray &&
71 | cbor.array.length >= 2 &&
72 | cbor.array[0] instanceof CborUInt &&
73 | cbor.array[0].num === BigInt(4) &&
74 | cbor.array[1] instanceof CborArray
75 | )) throw new Error("invalid CBOR for 'ChainSyncAwaitReply");
76 |
77 | const pointsCbor = cbor.array[1].array;
78 |
79 | return new ChainSyncFindIntersect({
80 | points: pointsCbor.map( ChainPoint.fromCborObj )
81 | }, getSubCborRef( cbor, originalBytes ));
82 | }
83 | }
--------------------------------------------------------------------------------
/src/protocols/chain-sync/messages/ChainSyncIntersectFound.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborBytes, CborObj, CborString, CborTag, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { ChainPoint, IChainPoint, isIChainPoint } from "../../types/ChainPoint";
3 | import { ChainTip, IChainTip, isIChainTip } from "../../types/ChainTip";
4 | import { getSubCborRef, subCborRefOrUndef } from "../../utils/getSubCborRef";
5 |
6 | export interface IChainSyncIntersectFound {
7 | point: IChainPoint,
8 | tip: IChainTip
9 | }
10 |
11 | export class ChainSyncIntersectFound
12 | implements ToCbor, ToCborObj, IChainSyncIntersectFound
13 | {
14 | readonly point: ChainPoint;
15 | readonly tip: ChainTip;
16 |
17 | constructor(
18 | intersect: IChainSyncIntersectFound,
19 | readonly cborRef: SubCborRef | undefined = undefined
20 | )
21 | {
22 | const { point, tip } = intersect;
23 | if(!(
24 | isIChainPoint( point ) &&
25 | isIChainTip( tip )
26 | )) throw new Error("invalid IChainSyncIntersectFound interface");
27 |
28 | this.point = point instanceof ChainPoint ? point : new ChainPoint( point );
29 | this.tip = tip instanceof ChainTip ? tip : new ChainTip( tip );
30 | this.cborRef = cborRef ?? subCborRefOrUndef( intersect );
31 | };
32 |
33 | toJSON() { return this.toJson(); }
34 | toJson()
35 | {
36 | return {
37 | point: this.point.toJson(),
38 | tip: this.tip.toJson()
39 | };
40 | }
41 |
42 | toCborBytes(): Uint8Array
43 | {
44 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
45 | return this.toCbor().toBuffer();
46 | }
47 | toCbor(): CborString
48 | {
49 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
50 | return Cbor.encode( this.toCborObj() );
51 | }
52 | toCborObj(): CborArray
53 | {
54 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
55 | return new CborArray([
56 | new CborUInt(5),
57 | this.point.toCborObj(),
58 | this.tip.toCborObj()
59 | ]);
60 | }
61 |
62 |
63 | static fromCbor( cbor: CanBeCborString ): ChainSyncIntersectFound
64 | {
65 | const buff = cbor instanceof Uint8Array ?
66 | cbor:
67 | forceCborString( cbor ).toBuffer();
68 |
69 | return ChainSyncIntersectFound.fromCborObj( Cbor.parse( buff ), buff );
70 | }
71 | static fromCborObj(
72 | cbor: CborObj,
73 | originalBytes: Uint8Array | undefined = undefined
74 | ): ChainSyncIntersectFound
75 | {
76 | if(!(
77 | cbor instanceof CborArray &&
78 | cbor.array.length >= 3 &&
79 | cbor.array[0] instanceof CborUInt &&
80 | cbor.array[0].num === BigInt(5)
81 | )) throw new Error("invalid CBOR for 'ChainSyncIntersectFound");
82 |
83 | const [ _idx, pointCbor, tipCbor ] = cbor.array;
84 |
85 | return new ChainSyncIntersectFound({
86 | point: ChainPoint.fromCborObj( pointCbor ),
87 | tip: ChainTip.fromCborObj( tipCbor )
88 | }, getSubCborRef( cbor, originalBytes ));
89 | }
90 | }
--------------------------------------------------------------------------------
/src/protocols/chain-sync/messages/ChainSyncIntersectNotFound.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { IChainTip, ChainTip, isIChainTip } from "../../types";
3 | import { getSubCborRef, subCborRefOrUndef } from "../../utils/getSubCborRef";
4 |
5 | export interface IChainSyncIntersectNotFound {
6 | tip: IChainTip
7 | }
8 |
9 | export class ChainSyncIntersectNotFound
10 | implements ToCbor, ToCborObj, IChainSyncIntersectNotFound
11 | {
12 | readonly tip: ChainTip;
13 |
14 | constructor(
15 | intersect: IChainSyncIntersectNotFound,
16 | readonly cborRef: SubCborRef | undefined = undefined
17 | )
18 | {
19 | const { tip } = intersect;
20 | if(!(
21 | isIChainTip( tip )
22 | )) throw new Error("invalid IChainSyncIntersectNotFound interface");
23 |
24 | this.tip = tip instanceof ChainTip ? tip : new ChainTip( tip );
25 | this.cborRef = cborRef ?? subCborRefOrUndef( intersect );
26 | };
27 |
28 | toJSON() { return this.toJson(); }
29 | toJson()
30 | {
31 | return {
32 | tip: this.tip.toJson()
33 | }
34 | }
35 |
36 | toCborBytes(): Uint8Array
37 | {
38 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
39 | return this.toCbor().toBuffer();
40 | }
41 | toCbor(): CborString
42 | {
43 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
44 | return Cbor.encode( this.toCborObj() );
45 | }
46 | toCborObj(): CborArray
47 | {
48 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
49 | return new CborArray([
50 | new CborUInt(6),
51 | this.tip.toCborObj()
52 | ]);
53 | }
54 |
55 |
56 | static fromCbor( cbor: CanBeCborString ): ChainSyncIntersectNotFound
57 | {
58 | const buff = cbor instanceof Uint8Array ?
59 | cbor:
60 | forceCborString( cbor ).toBuffer();
61 |
62 | return ChainSyncIntersectNotFound.fromCborObj( Cbor.parse( buff ), buff );
63 | }
64 | static fromCborObj(
65 | cbor: CborObj,
66 | originalBytes: Uint8Array | undefined = undefined
67 | ): ChainSyncIntersectNotFound
68 | {
69 | if(!(
70 | cbor instanceof CborArray &&
71 | cbor.array.length >= 2 &&
72 | cbor.array[0] instanceof CborUInt &&
73 | cbor.array[0].num === BigInt(6)
74 | )) throw new Error("invalid CBOR for 'ChainSyncIntersectNotFound");
75 |
76 | const [ _idx, tipCbor ] = cbor.array;
77 |
78 | return new ChainSyncIntersectNotFound({
79 | tip: ChainTip.fromCborObj( tipCbor )
80 | }, getSubCborRef( cbor, originalBytes ));
81 | }
82 | }
--------------------------------------------------------------------------------
/src/protocols/chain-sync/messages/ChainSyncMessageDone.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IChainSyncMessageDone {}
5 |
6 | export function isIChainSyncMessageDone( stuff: any ): stuff is IChainSyncMessageDone
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class ChainSyncMessageDone
12 | implements ToCbor, ToCborObj, IChainSyncMessageDone
13 | {
14 | readonly cborRef: SubCborRef | undefined = undefined;
15 | constructor() {};
16 |
17 | toJSON() { return this.toJson(); }
18 | toJson() { return {}; }
19 |
20 | toCborBytes(): Uint8Array
21 | {
22 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
23 | return this.toCbor().toBuffer();
24 | }
25 | toCbor(): CborString
26 | {
27 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
28 | return Cbor.encode( this.toCborObj() );
29 | }
30 | toCborObj(): CborArray
31 | {
32 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
33 | return new CborArray([ new CborUInt(7) ]);
34 | }
35 |
36 |
37 | static fromCbor( cbor: CanBeCborString ): ChainSyncMessageDone
38 | {
39 | const buff = cbor instanceof Uint8Array ?
40 | cbor:
41 | forceCborString( cbor ).toBuffer();
42 |
43 | return ChainSyncMessageDone.fromCborObj( Cbor.parse( buff ), buff );
44 | }
45 | static fromCborObj(
46 | cbor: CborObj,
47 | originalBytes: Uint8Array | undefined = undefined
48 | ): ChainSyncMessageDone
49 | {
50 | if(!(
51 | cbor instanceof CborArray &&
52 | cbor.array[0] instanceof CborUInt &&
53 | cbor.array[0].num === BigInt(7)
54 | )) throw new Error("invalid CBOR for 'ChainSyncMessageDone");
55 |
56 | return new ChainSyncMessageDone();
57 | }
58 | }
--------------------------------------------------------------------------------
/src/protocols/chain-sync/messages/ChainSyncRequestNext.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IChainSyncRequestNext{}
5 |
6 | export function isIChainSyncRequestNext( stuff: any ): stuff is IChainSyncRequestNext
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class ChainSyncRequestNext
12 | implements ToCbor, ToCborObj, IChainSyncRequestNext
13 | {
14 | readonly cborRef: SubCborRef | undefined = undefined;
15 | constructor()
16 | {
17 | };
18 |
19 | toJSON() { return this.toJson(); }
20 | toJson() { return {}; }
21 |
22 | toCborBytes(): Uint8Array
23 | {
24 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
25 | return this.toCbor().toBuffer();
26 | }
27 | toCbor(): CborString
28 | {
29 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
30 | return Cbor.encode( this.toCborObj() );
31 | }
32 | toCborObj(): CborArray
33 | {
34 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
35 | return new CborArray([ new CborUInt(0) ]);
36 | }
37 |
38 |
39 | static fromCbor( cbor: CanBeCborString ): ChainSyncRequestNext
40 | {
41 | const buff = cbor instanceof Uint8Array ?
42 | cbor:
43 | forceCborString( cbor ).toBuffer();
44 |
45 | return ChainSyncRequestNext.fromCborObj( Cbor.parse( buff ), buff );
46 | }
47 | static fromCborObj(
48 | cbor: CborObj,
49 | originalBytes: Uint8Array | undefined = undefined
50 | ): ChainSyncRequestNext
51 | {
52 | if(!(
53 | cbor instanceof CborArray &&
54 | cbor.array[0] instanceof CborUInt &&
55 | cbor.array[0].num === BigInt(0)
56 | )) throw new Error("invalid CBOR for 'ChainSyncRequestNext");
57 |
58 | return new ChainSyncRequestNext();
59 | }
60 | }
--------------------------------------------------------------------------------
/src/protocols/chain-sync/messages/ChainSyncRollBackwards.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborBytes, CborObj, CborString, CborTag, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { ChainPoint, IChainPoint, isIChainPoint } from "../../types/ChainPoint";
3 | import { ChainTip, IChainTip, isIChainTip } from "../../types/ChainTip";
4 | import { getSubCborRef, subCborRefOrUndef } from "../../utils/getSubCborRef";
5 |
6 | export interface IChainSyncRollBackwards {
7 | point: IChainPoint,
8 | tip: IChainTip
9 | }
10 |
11 | export class ChainSyncRollBackwards
12 | implements ToCbor, ToCborObj, IChainSyncRollBackwards
13 | {
14 | readonly point: ChainPoint;
15 | readonly tip: ChainTip;
16 |
17 | constructor(
18 | rollback: IChainSyncRollBackwards,
19 | readonly cborRef: SubCborRef | undefined = undefined
20 | )
21 | {
22 | const { point, tip } = rollback;
23 | if(!(
24 | isIChainPoint( point ) &&
25 | isIChainTip( tip )
26 | )) throw new Error("invalid IChainSyncRollBackwards interface");
27 |
28 | this.point = point instanceof ChainPoint ? point : new ChainPoint( point );
29 | this.tip = tip instanceof ChainTip ? tip : new ChainTip( tip );
30 | this.cborRef = cborRef ?? subCborRefOrUndef( rollback );
31 | };
32 |
33 | toJSON() { return this.toJson(); }
34 | toJson()
35 | {
36 | return {
37 | point: this.point.toJson(),
38 | tip: this.tip.toJson()
39 | };
40 | }
41 |
42 | toCborBytes(): Uint8Array
43 | {
44 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
45 | return this.toCbor().toBuffer();
46 | }
47 | toCbor(): CborString
48 | {
49 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
50 | return Cbor.encode( this.toCborObj() );
51 | }
52 | toCborObj(): CborArray
53 | {
54 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
55 | return new CborArray([
56 | new CborUInt(3),
57 | this.point.toCborObj(),
58 | this.tip.toCborObj()
59 | ]);
60 | }
61 |
62 | static fromCbor( cbor: CanBeCborString ): ChainSyncRollBackwards
63 | {
64 | const buff = cbor instanceof Uint8Array ?
65 | cbor:
66 | forceCborString( cbor ).toBuffer();
67 |
68 | return ChainSyncRollBackwards.fromCborObj( Cbor.parse( buff ), buff );
69 | }
70 | static fromCborObj(
71 | cbor: CborObj,
72 | originalBytes: Uint8Array | undefined = undefined
73 | ): ChainSyncRollBackwards
74 | {
75 | if(!(
76 | cbor instanceof CborArray &&
77 | cbor.array.length >= 3 &&
78 | cbor.array[0] instanceof CborUInt &&
79 | cbor.array[0].num === BigInt(3)
80 | )) throw new Error("invalid CBOR for 'ChainSyncRollBackwards");
81 |
82 | const [ _idx, pointCbor, tipCbor ] = cbor.array;
83 |
84 | return new ChainSyncRollBackwards({
85 | point: ChainPoint.fromCborObj( pointCbor ),
86 | tip: ChainTip.fromCborObj( tipCbor )
87 | }, getSubCborRef( cbor, originalBytes ));
88 | }
89 | }
--------------------------------------------------------------------------------
/src/protocols/chain-sync/messages/ChainSyncRollForward.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborBytes, CborObj, CborString, CborTag, CborUInt, LazyCborArray, SubCborRef, ToCbor, ToCborObj, forceCborString, isCborObj } from "@harmoniclabs/cbor";
2 | import { ChainTip, IChainTip, isIChainTip } from "../../types/ChainTip";
3 | import { getSubCborRef, subCborRefOrUndef } from "../../utils/getSubCborRef";
4 | import { toHex } from "@harmoniclabs/uint8array-utils";
5 |
6 | export interface IChainSyncRollForward {
7 | data: CborObj
8 | tip: IChainTip
9 | }
10 |
11 | export class ChainSyncRollForward
12 | implements ToCbor, ToCborObj, IChainSyncRollForward
13 | {
14 | readonly data: CborObj;
15 | readonly tip: ChainTip;
16 |
17 | constructor(
18 | forward: IChainSyncRollForward,
19 | readonly cborRef: SubCborRef | undefined = undefined
20 | )
21 | {
22 | const { data, tip } = forward;
23 | if(!(
24 | isCborObj( data ) &&
25 | isIChainTip( tip )
26 | )) throw new Error("invalid IChainSyncRollForward interface");
27 |
28 | this.data = data;
29 | this.tip = tip instanceof ChainTip ? tip : new ChainTip( tip );
30 | this.cborRef = cborRef ?? subCborRefOrUndef( forward );
31 | };
32 |
33 | toJSON() { return this.toJson(); }
34 | toJson()
35 | {
36 | return {
37 | data: toHex( this.toCborBytes() ),
38 | tip: this.tip.toJson()
39 | };
40 | }
41 | toString(): string
42 | {
43 | return `(roll forward: (header: ${Cbor.encode(this.data).toString()}) ${this.tip.toString()} )`
44 | }
45 |
46 | toCborBytes(): Uint8Array
47 | {
48 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
49 | return this.toCbor().toBuffer();
50 | }
51 | toCbor(): CborString
52 | {
53 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
54 | return Cbor.encode( this.toCborObj() );
55 | }
56 | toCborObj(): CborArray
57 | {
58 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
59 | return new CborArray([
60 | new CborUInt(2),
61 | this.data,
62 | this.tip.toCborObj()
63 | ]);
64 | }
65 |
66 | /**
67 | * @returns {Uint8Array}
68 | * the bytes of `this.data` as present on `this.cborBytes`
69 | * (using `Cbor.parseLazy`)
70 | */
71 | getDataBytes(): Uint8Array
72 | {
73 | const msgData = this.toCborBytes();
74 | const lazy = Cbor.parseLazy( msgData ) as LazyCborArray;
75 | return lazy.array[1];
76 | }
77 |
78 | static fromCbor( cbor: CanBeCborString ): ChainSyncRollForward
79 | {
80 | const buff = cbor instanceof Uint8Array ?
81 | cbor:
82 | forceCborString( cbor ).toBuffer();
83 |
84 | return ChainSyncRollForward.fromCborObj( Cbor.parse( buff ), buff );
85 | }
86 | static fromCborObj(
87 | cbor: CborObj,
88 | originalBytes: Uint8Array | undefined = undefined
89 | ): ChainSyncRollForward
90 | {
91 | if(!(
92 | cbor instanceof CborArray &&
93 | cbor.array.length >= 3 &&
94 | cbor.array[0] instanceof CborUInt &&
95 | cbor.array[0].num === BigInt(2)
96 | )) throw new Error("invalid CBOR for 'ChainSyncRollForward");
97 |
98 | const [ _idx, headerCbor, tipCbor ] = cbor.array;
99 |
100 | return new ChainSyncRollForward({
101 | data: headerCbor,
102 | tip: ChainTip.fromCborObj( tipCbor )
103 | }, getSubCborRef( cbor, originalBytes ));
104 | }
105 | }
--------------------------------------------------------------------------------
/src/protocols/chain-sync/messages/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ChainSyncAwaitReply";
2 | export * from "./ChainSyncFindIntersect";
3 | export * from "./ChainSyncIntersectFound";
4 | export * from "./ChainSyncIntersectNotFound";
5 | export * from "./ChainSyncMessageDone";
6 | export * from "./ChainSyncRequestNext";
7 | export * from "./ChainSyncRollBackwards";
8 | export * from "./ChainSyncRollForward";
--------------------------------------------------------------------------------
/src/protocols/handshake/HandshakeVersionTable/NetworkMagic.ts:
--------------------------------------------------------------------------------
1 |
2 | export type NetworkMagic = number;
3 |
4 | export enum CardanoNetworkMagic {
5 | Preprod = 1,
6 | Preview = 2,
7 | Sanchonet = 4,
8 | Mainnet = 764824073
9 | }
10 |
11 | Object.freeze( CardanoNetworkMagic );
12 |
13 | export function isNetworkMagic( stuff: any ): stuff is NetworkMagic
14 | {
15 | return (
16 | Number.isSafeInteger( stuff ) &&
17 | // unsigned integer 32 bits
18 | stuff === (stuff >>> 0)
19 | );
20 | }
--------------------------------------------------------------------------------
/src/protocols/handshake/HandshakeVersionTable/VersionNumber.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export type VersionNumber = number;
4 |
5 | export function VersionNumber( thing: any ): VersionNumber
6 | {
7 | if( typeof thing === "bigint" ) return VersionNumber( Number( thing ) );
8 |
9 | if( !Number.isSafeInteger( thing ) || thing < 0 )
10 | return 0;
11 |
12 | return thing & 0x7fff;
13 | }
14 |
15 | export function toClientVersionNumber( thing: VersionNumber ): number
16 | {
17 | return VersionNumber( thing ) | 0x8000;
18 | }
19 |
20 | export function adaptVersionNumberToMode( thing: VersionNumber, n2n: boolean ): number
21 | {
22 | return n2n ? VersionNumber( thing ) : toClientVersionNumber( thing );
23 | }
24 |
25 | export function isVersionNumber( thing: any ): thing is VersionNumber
26 | {
27 | if( typeof thing === "bigint" ) return isVersionNumber( Number( thing ) );
28 | return Number.isSafeInteger( thing ) && thing === (thing & 0x7fff);
29 | }
30 |
31 | export function isExtendedVersionNumber( thing: any ): thing is VersionNumber
32 | {
33 | if( typeof thing === "bigint" ) return isExtendedVersionNumber( Number( thing ) );
34 | return Number.isSafeInteger( thing ) && thing === (thing & 0xffff);
35 | }
--------------------------------------------------------------------------------
/src/protocols/handshake/HandshakeVersionTable/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./HandshakeVersionTable";
2 | export * from "./NetworkMagic";
3 | export * from "./VersionData";
4 | export * from "./VersionNumber";
--------------------------------------------------------------------------------
/src/protocols/handshake/cddls/handshake-node-to-client.cddl:
--------------------------------------------------------------------------------
1 | ;
2 | ; NodeToClient Handshake
3 | ;
4 |
5 | handshakeMessage
6 | = msgProposeVersions
7 | / msgAcceptVersion
8 | / msgRefuse
9 | / msgQueryReply
10 |
11 | msgProposeVersions = [0, versionTable]
12 | msgAcceptVersion = [1, versionNumber, nodeToClientVersionData]
13 | msgRefuse = [2, refuseReason]
14 | msgQueryReply = [3, versionTable]
15 |
16 | ; Entries must be sorted by version number. For testing, this is handled in `handshakeFix`.
17 | versionTable = { * versionNumber => nodeToClientVersionData }
18 |
19 |
20 | ; as of version 2 (which is no longer supported) we set 15th bit to 1
21 | ; 16 / 17 / 18 / 19
22 | versionNumber = 32784 / 32785 / 32786 / 32787
23 |
24 | ; As of version 15 and higher
25 | nodeToClientVersionData = [networkMagic, query]
26 |
27 | ; version 14 and before
28 | oldNodeToClientVersionData = networkMagic
29 |
30 | networkMagic = uint
31 | query = bool
32 |
33 | refuseReason
34 | = refuseReasonVersionMismatch
35 | / refuseReasonHandshakeDecodeError
36 | / refuseReasonRefused
37 |
38 | refuseReasonVersionMismatch = [0, [ *versionNumber ] ]
39 | refuseReasonHandshakeDecodeError = [1, versionNumber, tstr]
40 | refuseReasonRefused = [2, versionNumber, tstr]
--------------------------------------------------------------------------------
/src/protocols/handshake/cddls/handshake-node-to-node-v11-12.cddl:
--------------------------------------------------------------------------------
1 | ;
2 | ; NodeToNode Handshake, v11 to v12
3 | ;
4 | handshakeMessage
5 | = msgProposeVersions
6 | / msgAcceptVersion
7 | / msgRefuse
8 | / msgQueryReply
9 |
10 | msgProposeVersions = [0, versionTable]
11 | msgAcceptVersion = [1, versionNumber, nodeToNodeVersionData]
12 | msgRefuse = [2, refuseReason]
13 | msgQueryReply = [3, versionTable]
14 |
15 | versionTable = { * versionNumber => nodeToNodeVersionData }
16 |
17 | versionNumber = 11 / 12
18 |
19 | nodeToNodeVersionData = [ networkMagic, initiatorOnlyDiffusionMode, peerSharing, query ]
20 |
21 | ; range between 0 and 0xffffffff
22 | networkMagic = 0..4294967295
23 | initiatorOnlyDiffusionMode = bool
24 | ; range between 0 and 2
25 | peerSharing = 0..2
26 | query = bool
27 |
28 | refuseReason
29 | = refuseReasonVersionMismatch
30 | / refuseReasonHandshakeDecodeError
31 | / refuseReasonRefused
32 |
33 | refuseReasonVersionMismatch = [0, [ *versionNumber ] ]
34 | refuseReasonHandshakeDecodeError = [1, versionNumber, tstr]
35 | refuseReasonRefused = [2, versionNumber, tstr]
36 |
--------------------------------------------------------------------------------
/src/protocols/handshake/cddls/handshake-node-to-node-v13.cddl:
--------------------------------------------------------------------------------
1 | ;
2 | ; NodeToNode Handshake, v13
3 | ;
4 | handshakeMessage
5 | = msgProposeVersions
6 | / msgAcceptVersion
7 | / msgRefuse
8 | / msgQueryReply
9 |
10 | msgProposeVersions = [0, versionTable]
11 | msgAcceptVersion = [1, versionNumber, nodeToNodeVersionData]
12 | msgRefuse = [2, refuseReason]
13 | msgQueryReply = [3, versionTable]
14 |
15 | versionTable = { * versionNumber => nodeToNodeVersionData }
16 |
17 | versionNumber = 13 / 14
18 |
19 | nodeToNodeVersionData = [ networkMagic, initiatorOnlyDiffusionMode, peerSharing, query ]
20 |
21 | ; range between 0 and 0xffffffff
22 | networkMagic = 0..4294967295
23 | initiatorOnlyDiffusionMode = bool
24 | ; range between 0 and 1
25 | peerSharing = 0..1
26 | query = bool
27 |
28 | refuseReason
29 | = refuseReasonVersionMismatch
30 | / refuseReasonHandshakeDecodeError
31 | / refuseReasonRefused
32 |
33 | refuseReasonVersionMismatch = [0, [ *versionNumber ] ]
34 | refuseReasonHandshakeDecodeError = [1, versionNumber, tstr]
35 | refuseReasonRefused = [2, versionNumber, tstr]
--------------------------------------------------------------------------------
/src/protocols/handshake/cddls/handshake-node-to-node.cddl:
--------------------------------------------------------------------------------
1 | ;
2 | ; NodeToNode Handshake, v7 to v10
3 | ;
4 |
5 | handshakeMessage
6 | = msgProposeVersions
7 | / msgAcceptVersion
8 | / msgRefuse
9 |
10 | msgProposeVersions = [0, versionTable]
11 | msgAcceptVersion = [1, versionNumber, nodeToNodeVersionData]
12 | msgRefuse = [2, refuseReason]
13 |
14 | versionTable = { * versionNumber => nodeToNodeVersionData }
15 |
16 | versionNumber = 7 / 8 / 9 / 10
17 |
18 | nodeToNodeVersionData = [ networkMagic, initiatorOnlyDiffusionMode ]
19 |
20 | ; range between 0 and 0xffffffff
21 | networkMagic = 0..4294967295
22 | initiatorOnlyDiffusionMode = bool
23 |
24 | refuseReason
25 | = refuseReasonVersionMismatch
26 | / refuseReasonHandshakeDecodeError
27 | / refuseReasonRefused
28 |
29 | refuseReasonVersionMismatch = [0, [ *versionNumber ] ]
30 | refuseReasonHandshakeDecodeError = [1, versionNumber, tstr]
31 | refuseReasonRefused = [2, versionNumber, tstr]
--------------------------------------------------------------------------------
/src/protocols/handshake/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./HandshakeClient";
2 | export * from "./HandshakeVersionTable";
3 | export * from "./messages";
--------------------------------------------------------------------------------
/src/protocols/handshake/messages/HandshakeAcceptVersion.ts:
--------------------------------------------------------------------------------
1 | import { CborString, Cbor, CborArray, CborUInt, CanBeCborString, forceCborString, CborObj } from "@harmoniclabs/cbor";
2 | import { VersionData } from "../HandshakeVersionTable/VersionData";
3 | import { adaptVersionNumberToMode, toClientVersionNumber, VersionNumber } from "../HandshakeVersionTable/VersionNumber";
4 | import { bool } from "../../utils/bool";
5 |
6 | export interface IHandshakeAcceptVersion {
7 | versionNumber: VersionNumber,
8 | versionData: VersionData
9 | }
10 |
11 | export class HandshakeAcceptVersion
12 | implements IHandshakeAcceptVersion
13 | {
14 | readonly versionNumber: VersionNumber;
15 | readonly versionData: VersionData;
16 |
17 | readonly isN2N: boolean = true;
18 |
19 | constructor(
20 | { versionNumber, versionData }: IHandshakeAcceptVersion,
21 | n2n: boolean = true
22 | )
23 | {
24 | this.versionNumber = versionNumber;
25 | this.versionData = versionData;
26 |
27 | this.isN2N = bool( n2n, true );
28 | }
29 |
30 | toCborBytes(): Uint8Array
31 | {
32 | return this.toCbor().toBuffer();
33 | }
34 | toCbor(): CborString
35 | {
36 | return Cbor.encode( this.toCborObj() );
37 | }
38 | toCborObj(): CborArray
39 | {
40 | return new CborArray([
41 | new CborUInt(1),
42 | new CborUInt( adaptVersionNumberToMode( this.versionNumber, this.isN2N ) ),
43 | this.versionData.toCborObj()
44 | ]);
45 | }
46 |
47 | static fromCbor( cbor: CanBeCborString ): HandshakeAcceptVersion
48 | {
49 | return HandshakeAcceptVersion.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
50 | }
51 | static fromCborObj( cbor: CborObj ): HandshakeAcceptVersion
52 | {
53 | if(!(
54 | cbor instanceof CborArray &&
55 | cbor.array.length >= 3 &&
56 | cbor.array[0] instanceof CborUInt &&
57 | Number(cbor.array[0].num) === 1 &&
58 | cbor.array[1] instanceof CborUInt
59 | )) throw new Error("invalid CBOR for 'HandshakeAcceptVersion'");
60 |
61 | return new HandshakeAcceptVersion({
62 | versionNumber: VersionNumber( Number( cbor.array[1].num ) ),
63 | versionData: VersionData.fromCborObj( cbor.array[2] )
64 | });
65 | }
66 | }
--------------------------------------------------------------------------------
/src/protocols/handshake/messages/HandshakeMessage.ts:
--------------------------------------------------------------------------------
1 | import { CborArray, CborObj, CborUInt } from "@harmoniclabs/cbor";
2 | import { HandshakeAcceptVersion } from "./HandshakeAcceptVersion";
3 | import { HandshakeQueryReply } from "./HandshakeQueryReply";
4 | import { HandshakeRefuse } from "./HandshakeRefuse";
5 | import { HandshakeProposeVersion } from "./HandshakeProposeVersion";
6 |
7 | export type HandshakeMessage = HandshakeProposeVersion | HandshakeAcceptVersion | HandshakeRefuse | HandshakeQueryReply;
8 |
9 | export function isHandshakeMessage( thing: any ): thing is HandshakeMessage
10 | {
11 | return (
12 | thing instanceof HandshakeProposeVersion ||
13 | thing instanceof HandshakeAcceptVersion ||
14 | thing instanceof HandshakeRefuse ||
15 | thing instanceof HandshakeQueryReply
16 | );
17 | }
18 |
19 | export function handshakeMessageFromCborObj( cObj: CborObj ): HandshakeMessage
20 | {
21 | if(!(
22 | cObj instanceof CborArray &&
23 | cObj.array.length >= 1 &&
24 | cObj.array[0] instanceof CborUInt
25 | )) throw new Error("invalid CBOR for 'HandshakeMessage'");
26 |
27 | const idx = Number( cObj.array[0].num );
28 |
29 | if( idx === 0 ) return HandshakeProposeVersion.fromCborObj( cObj );
30 | if( idx === 1 ) return HandshakeAcceptVersion.fromCborObj( cObj );
31 | if( idx === 2 ) return HandshakeRefuse.fromCborObj( cObj );
32 | if( idx === 3 ) return HandshakeQueryReply.fromCborObj( cObj );
33 |
34 | throw new Error("invalid CBOR for 'HandshakeMessage'; invalid index");
35 | }
36 |
--------------------------------------------------------------------------------
/src/protocols/handshake/messages/HandshakeProposeVersion.ts:
--------------------------------------------------------------------------------
1 | import { CborString, Cbor, CborUInt, CanBeCborString, forceCborString, CborObj, CborArray } from "@harmoniclabs/cbor";
2 | import { normalizeVersionTableMap, versionTableFromCborObj, VersionTableMap, versionTableToCborObj } from "../HandshakeVersionTable/HandshakeVersionTable";
3 |
4 | export interface IHandshakeProposeVersion {
5 | versionTable: VersionTableMap
6 | }
7 |
8 | export class HandshakeProposeVersion
9 | implements IHandshakeProposeVersion
10 | {
11 | readonly versionTable: VersionTableMap;
12 | readonly isN2N: boolean = true;
13 |
14 | constructor(
15 | { versionTable }: IHandshakeProposeVersion,
16 | n2n: boolean = true
17 | )
18 | {
19 | this.versionTable = normalizeVersionTableMap( versionTable );
20 | this.isN2N = n2n;
21 | }
22 |
23 | toCborBytes(): Uint8Array
24 | {
25 | return this.toCbor().toBuffer();
26 | }
27 | toCbor(): CborString
28 | {
29 | return Cbor.encode( this.toCborObj() );
30 | }
31 | toCborObj(): CborArray
32 | {
33 | return new CborArray([
34 | new CborUInt(0),
35 | versionTableToCborObj( this.versionTable, this.isN2N )
36 | ]);
37 | }
38 |
39 | static fromCbor( cbor: CanBeCborString, n2n: boolean = true ): HandshakeProposeVersion
40 | {
41 | return HandshakeProposeVersion.fromCborObj( Cbor.parse( forceCborString( cbor ) ), n2n );
42 | }
43 | static fromCborObj( cbor: CborObj, n2n: boolean = true ): HandshakeProposeVersion
44 | {
45 | if(!(
46 | cbor instanceof CborArray &&
47 | cbor.array.length >= 2 &&
48 | cbor.array[0] instanceof CborUInt &&
49 | Number(cbor.array[0].num) === 0
50 | )) throw new Error("invalid CBOR for 'N2NVersionTable'");
51 |
52 | return new HandshakeProposeVersion({
53 | versionTable: versionTableFromCborObj( cbor.array[1], n2n )
54 | }, n2n);
55 | }
56 | }
--------------------------------------------------------------------------------
/src/protocols/handshake/messages/HandshakeQueryReply.ts:
--------------------------------------------------------------------------------
1 | import { CborString, Cbor, CborUInt, CanBeCborString, forceCborString, CborObj, CborArray } from "@harmoniclabs/cbor";
2 | import { normalizeVersionTableMap, versionTableFromCborObj, VersionTableMap, versionTableToCborObj } from "../HandshakeVersionTable/HandshakeVersionTable";
3 |
4 | export interface IHandshakeQueryReply {
5 | versionTable: VersionTableMap
6 | }
7 |
8 | export class HandshakeQueryReply
9 | implements IHandshakeQueryReply
10 | {
11 | readonly versionTable: VersionTableMap;
12 | readonly isN2N: boolean = true;
13 |
14 | constructor(
15 | { versionTable }: IHandshakeQueryReply,
16 | n2n: boolean = true
17 | )
18 | {
19 | this.versionTable = normalizeVersionTableMap( versionTable );
20 | this.isN2N = n2n;
21 | }
22 |
23 | toCborBytes(): Uint8Array
24 | {
25 | return this.toCbor().toBuffer();
26 | }
27 | toCbor(): CborString
28 | {
29 | return Cbor.encode( this.toCborObj() );
30 | }
31 | toCborObj(): CborArray
32 | {
33 | return new CborArray([
34 | new CborUInt(3),
35 | versionTableToCborObj( this.versionTable, this.isN2N )
36 | ]);
37 | }
38 |
39 | static fromCbor( cbor: CanBeCborString, n2n: boolean = true ): HandshakeQueryReply
40 | {
41 | return HandshakeQueryReply.fromCborObj( Cbor.parse( forceCborString( cbor ) ), n2n );
42 | }
43 | static fromCborObj( cbor: CborObj, n2n: boolean = true ): HandshakeQueryReply
44 | {
45 | if(!(
46 | cbor instanceof CborArray &&
47 | cbor.array.length >= 2 &&
48 | cbor.array[0] instanceof CborUInt &&
49 | Number(cbor.array[0].num) === 3
50 | )) throw new Error("invalid CBOR for 'N2NVersionTable'");
51 |
52 | return new HandshakeQueryReply({
53 | versionTable: versionTableFromCborObj( cbor.array[1], n2n )
54 | }, n2n);
55 | }
56 | }
--------------------------------------------------------------------------------
/src/protocols/handshake/messages/HandshakeRefuse.ts:
--------------------------------------------------------------------------------
1 | import { CborString, Cbor, CborArray, CborUInt, CanBeCborString, CborObj, forceCborString, ToCborBytes, SubCborRef, ToCborObj, ToCborString } from "@harmoniclabs/cbor";
2 | import { RefuseReason, refuseReasonFromCborObj } from "./RefuseReason";
3 |
4 | export interface IHandshakeRefuse {
5 | reason: RefuseReason
6 | }
7 |
8 | export class HandshakeRefuse
9 | implements ToCborObj, ToCborString, ToCborBytes, IHandshakeRefuse
10 | {
11 | readonly reason: RefuseReason;
12 |
13 | readonly isN2N: boolean = true;
14 |
15 | constructor(
16 | refuse: IHandshakeRefuse,
17 | n2n: boolean = true
18 | )
19 | {
20 | this.reason = refuse.reason;
21 | this.isN2N = n2n;
22 | }
23 |
24 | toCborBytes(): Uint8Array
25 | {
26 | return this.toCbor().toBuffer();
27 | }
28 | toCbor(): CborString
29 | {
30 | return Cbor.encode( this.toCborObj() )
31 | }
32 | toCborObj(): CborArray
33 | {
34 | return new CborArray([
35 | new CborUInt( 2 ),
36 | this.reason.toCborObj()
37 | ]);
38 | }
39 |
40 | static fromCbor( cbor: CanBeCborString, n2n: boolean = true ): HandshakeRefuse
41 | {
42 | return HandshakeRefuse.fromCborObj( Cbor.parse( forceCborString( cbor ) ), n2n );
43 | }
44 | static fromCborObj( cbor: CborObj, n2n: boolean = true ): HandshakeRefuse
45 | {
46 | if(!(cbor instanceof CborArray)) throw new Error("invalid CBOR for 'HandshakeRefuse'");
47 |
48 | const [ idx, _reason ] = cbor.array;
49 |
50 | if(!(
51 | idx instanceof CborUInt &&
52 | idx.num === BigInt(2)
53 | )) throw new Error("invalid CBOR for 'HandshakeRefuse'; invalid reason index");
54 |
55 | return new HandshakeRefuse({
56 | reason: refuseReasonFromCborObj( _reason )
57 | }, n2n);
58 | }
59 | }
--------------------------------------------------------------------------------
/src/protocols/handshake/messages/RefuseReason/RefuseReason.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "@harmoniclabs/obj-utils";
2 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, forceCborString } from "@harmoniclabs/cbor";
3 | import { RefuseReasonHandshakeDecodeError } from "./RefuseReasonHandshakeDecodeError";
4 | import { RefuseReasonRefuse } from "./RefuseReasonRefuse";
5 | import { RefuseReasonVersionMismatch } from "./RefuseReasonVersionMismatch";
6 |
7 | export type RefuseReason
8 | = RefuseReasonVersionMismatch
9 | | RefuseReasonHandshakeDecodeError
10 | | RefuseReasonRefuse;
11 |
12 | export function isRefuseReason( stuff: any ): stuff is RefuseReason
13 | {
14 | return (
15 | isObject( stuff ) &&
16 | (
17 | stuff instanceof RefuseReasonVersionMismatch ||
18 | stuff instanceof RefuseReasonHandshakeDecodeError ||
19 | stuff instanceof RefuseReasonRefuse
20 | )
21 | );
22 | }
23 |
24 | export function refuseReasonToCbor( refuseReason: RefuseReason ): CborString
25 | {
26 | return refuseReason.toCbor();
27 | }
28 | export function refuseReasonToCborObj( refuseReason: RefuseReason ): CborArray
29 | {
30 | return refuseReason.toCborObj();
31 | }
32 |
33 | export function refuseReasonFromCbor( cbor: CanBeCborString, n2n: boolean = true ): RefuseReason
34 | {
35 | return refuseReasonFromCborObj( Cbor.parse( forceCborString( cbor ) ), n2n );
36 | }
37 | export function refuseReasonFromCborObj( cbor: CborObj, n2n: boolean = true ): RefuseReason
38 | {
39 | if(!(
40 | cbor instanceof CborArray &&
41 | cbor.array.length > 0 &&
42 | cbor.array[0] instanceof CborUInt
43 | )) throw new Error("invalid CBOR for RefuseReason");
44 |
45 | const idx = Number( cbor.array[0].num );
46 |
47 | if( idx === 0 ) return RefuseReasonVersionMismatch.fromCborObj( cbor, n2n );
48 | if( idx === 1 ) return RefuseReasonHandshakeDecodeError.fromCborObj( cbor, n2n );
49 | if( idx === 2 ) return RefuseReasonRefuse.fromCborObj( cbor, n2n );
50 |
51 | throw new Error("invalid CBOR for RefuseReason; unknown reason index: " + idx)
52 | }
--------------------------------------------------------------------------------
/src/protocols/handshake/messages/RefuseReason/RefuseReasonHandshakeDecodeError.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborText, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { adaptVersionNumberToMode, isExtendedVersionNumber, isVersionNumber, VersionNumber } from "../../HandshakeVersionTable/VersionNumber";
3 |
4 | export interface IRefuseReasonHandshakeDecodeError {
5 | version: VersionNumber,
6 | decodeError: string
7 | }
8 |
9 | export class RefuseReasonHandshakeDecodeError
10 | implements ToCborString, ToCborObj, IRefuseReasonHandshakeDecodeError
11 | {
12 | readonly version: VersionNumber;
13 | readonly decodeError: string;
14 |
15 | readonly isN2N: boolean = true;
16 |
17 | constructor(
18 | { version, decodeError }: IRefuseReasonHandshakeDecodeError,
19 | n2n: boolean = true
20 | )
21 | {
22 | if(!isExtendedVersionNumber( version ))
23 | throw new Error("invalid 'validVerisons' for 'RefuseReasonHandshakeDecodeError'");
24 |
25 | this.version = VersionNumber( version );
26 | this.decodeError = String( decodeError );
27 |
28 | this.isN2N = n2n;
29 | }
30 |
31 | toCborBytes(): Uint8Array
32 | {
33 | return this.toCbor().toBuffer();
34 | }
35 | toCbor(): CborString
36 | {
37 | return Cbor.encode( this.toCborObj() )
38 | }
39 | toCborObj(): CborArray
40 | {
41 | return new CborArray([
42 | new CborUInt( 1 ),
43 | new CborUInt( adaptVersionNumberToMode( this.version, this.isN2N ) ),
44 | new CborText( this.decodeError )
45 | ]);
46 | }
47 |
48 | static fromCbor( cbor: CanBeCborString, n2n: boolean = true ): RefuseReasonHandshakeDecodeError
49 | {
50 | return RefuseReasonHandshakeDecodeError.fromCborObj( Cbor.parse( forceCborString( cbor ) ), n2n );
51 | }
52 | static fromCborObj( cbor: CborObj, n2n: boolean = true ): RefuseReasonHandshakeDecodeError
53 | {
54 | if(!(cbor instanceof CborArray)) throw new Error("invalid CBOR for 'RefuseReasonHandshakeDecodeError'");
55 |
56 | const [ idx, _v, _txt ] = cbor.array;
57 |
58 | if(!(
59 | idx instanceof CborUInt &&
60 | idx.num === BigInt(1)
61 | )) throw new Error("invalid CBOR for 'RefuseReasonHandshakeDecodeError'; invalid reason index");
62 |
63 | if(!(
64 | _v instanceof CborUInt &&
65 | isExtendedVersionNumber( _v.num )
66 | )) throw new Error("invalid CBOR for 'RefuseReasonHandshakeDecodeError'; invalid old version");
67 |
68 | if(!(
69 | _txt instanceof CborText
70 | )) throw new Error("invalid CBOR for 'RefuseReasonHandshakeDecodeError'; invalid error message");
71 |
72 | return new RefuseReasonHandshakeDecodeError({
73 | version: VersionNumber( _v.num ),
74 | decodeError: _txt.text
75 | }, n2n);
76 | }
77 | }
--------------------------------------------------------------------------------
/src/protocols/handshake/messages/RefuseReason/RefuseReasonRefuse.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborText, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { adaptVersionNumberToMode, isExtendedVersionNumber, VersionNumber } from "../../HandshakeVersionTable/VersionNumber";
3 |
4 | export interface IRefuseReasonRefuse {
5 | version: VersionNumber,
6 | errorMessage: string
7 | }
8 |
9 | export class RefuseReasonRefuse
10 | implements ToCborString, ToCborObj, IRefuseReasonRefuse
11 | {
12 | readonly version: VersionNumber;
13 | readonly errorMessage: string;
14 |
15 | readonly isN2N: boolean = true;
16 |
17 | constructor(
18 | { version, errorMessage }: IRefuseReasonRefuse,
19 | n2n: boolean = true
20 | )
21 | {
22 | if(!isExtendedVersionNumber( version ))
23 | throw new Error("invalid 'validVerisons' for 'RefuseReasonRefuse'");
24 |
25 | this.version = VersionNumber( version );
26 | this.errorMessage = String( errorMessage );
27 |
28 | this.isN2N = n2n;
29 | }
30 |
31 | toCborBytes(): Uint8Array
32 | {
33 | return this.toCbor().toBuffer();
34 | }
35 | toCbor(): CborString
36 | {
37 | return Cbor.encode( this.toCborObj() )
38 | }
39 | toCborObj(): CborArray
40 | {
41 | return new CborArray([
42 | new CborUInt( 2 ),
43 | new CborUInt( adaptVersionNumberToMode( this.version, this.isN2N ) ),
44 | new CborText( this.errorMessage )
45 | ]);
46 | }
47 |
48 | static fromCbor( cbor: CanBeCborString, n2n: boolean = true ): RefuseReasonRefuse
49 | {
50 | return RefuseReasonRefuse.fromCborObj( Cbor.parse( forceCborString( cbor ) ), n2n );
51 | }
52 | static fromCborObj( cbor: CborObj, n2n: boolean = true ): RefuseReasonRefuse
53 | {
54 | if(!(cbor instanceof CborArray)) throw new Error("invalid CBOR for 'RefuseReasonRefuse'");
55 |
56 | const [ idx, _v, _txt ] = cbor.array;
57 |
58 | if(!(
59 | idx instanceof CborUInt &&
60 | idx.num === BigInt(2)
61 | )) throw new Error("invalid CBOR for 'RefuseReasonRefuse'; invalid reason index");
62 |
63 | if(!(
64 | _v instanceof CborUInt &&
65 | isExtendedVersionNumber( _v.num )
66 | )) throw new Error("invalid CBOR for 'RefuseReasonRefuse'; invalid old version");
67 |
68 | if(!(
69 | _txt instanceof CborText
70 | )) throw new Error("invalid CBOR for 'RefuseReasonRefuse'; invalid error message");
71 |
72 | return new RefuseReasonRefuse({
73 | version: VersionNumber( _v.num ),
74 | errorMessage: _txt.text
75 | }, n2n);
76 | }
77 | }
--------------------------------------------------------------------------------
/src/protocols/handshake/messages/RefuseReason/RefuseReasonVersionMismatch.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { adaptVersionNumberToMode, isExtendedVersionNumber, VersionNumber } from "../../HandshakeVersionTable/VersionNumber";
3 |
4 | export interface IRefuseReasonVersionMismatch {
5 | validVersions: VersionNumber[]
6 | }
7 |
8 | export class RefuseReasonVersionMismatch
9 | implements ToCborString, ToCborObj, IRefuseReasonVersionMismatch
10 | {
11 | readonly validVersions: VersionNumber[];
12 |
13 | readonly isN2N: boolean = true;
14 |
15 | constructor(
16 | validVersions: VersionNumber[],
17 | n2n: boolean = true
18 | )
19 | {
20 | if(!(
21 | Array.isArray( validVersions ) &&
22 | validVersions.every( isExtendedVersionNumber )
23 | ))
24 | throw new Error("invalid 'validVerisons' for 'RefuseReasonVersionMismatch'");
25 |
26 | this.validVersions = validVersions.filter( ( v, i, thisArr ) => thisArr.indexOf(v) === i);
27 | this.isN2N = n2n;
28 | }
29 |
30 | toCborBytes(): Uint8Array
31 | {
32 | return this.toCbor().toBuffer();
33 | }
34 | toCbor(): CborString
35 | {
36 | return Cbor.encode( this.toCborObj() )
37 | }
38 | toCborObj( n2n: boolean | undefined = undefined ): CborArray
39 | {
40 | return new CborArray([
41 | new CborUInt( 0 ),
42 | new CborArray(
43 | this.validVersions.map( v =>
44 | new CborUInt(
45 | adaptVersionNumberToMode( v, this.isN2N )
46 | )
47 | )
48 | )
49 | ]);
50 | }
51 |
52 | static fromCbor( cbor: CanBeCborString, n2n: boolean = true ): RefuseReasonVersionMismatch
53 | {
54 | return RefuseReasonVersionMismatch.fromCborObj( Cbor.parse( forceCborString( cbor ) ), n2n );
55 | }
56 | static fromCborObj( cbor: CborObj, n2n: boolean = true ): RefuseReasonVersionMismatch
57 | {
58 | if(!(cbor instanceof CborArray)) throw new Error("invalid CBOR for 'RefuseReasonVersionMismatch'");
59 |
60 | const [ idx, _versions ] = cbor.array;
61 |
62 | if(!(
63 | idx instanceof CborUInt &&
64 | idx.num === BigInt(0)
65 | )) throw new Error("invalid CBOR for 'RefuseReasonVersionMismatch'; invalid reason index");
66 |
67 | if(!(
68 | _versions instanceof CborArray
69 | )) throw new Error("invalid CBOR for 'RefuseReasonVersionMismatch'; invalid versions field");
70 |
71 | return new RefuseReasonVersionMismatch(
72 | _versions.array.map( v => {
73 | if(!(
74 | v instanceof CborUInt
75 | )) throw new Error("invalid CBOR for 'VersionNumber'");
76 |
77 | return VersionNumber( v.num )
78 | }),
79 | n2n
80 | );
81 | }
82 | }
--------------------------------------------------------------------------------
/src/protocols/handshake/messages/RefuseReason/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./RefuseReason";
2 | export * from "./RefuseReasonHandshakeDecodeError";
3 | export * from "./RefuseReasonRefuse";
4 | export * from "./RefuseReasonVersionMismatch";
--------------------------------------------------------------------------------
/src/protocols/handshake/messages/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./RefuseReason";
2 | export * from "./HandshakeAcceptVersion";
3 | export * from "./HandshakeMessage";
4 | export * from "./HandshakeProposeVersion";
5 | export * from "./HandshakeQueryReply";
6 | export * from "./HandshakeRefuse";
--------------------------------------------------------------------------------
/src/protocols/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./block-fetch";
2 | export * from "./chain-sync";
3 | export * from "./handshake";
4 | export * from "./keep-alive";
5 | export * from "./local-state-query";
6 | export * from "./local-tx-monitor";
7 | export * from "./local-tx-submit";
8 | export * from "./peer-sharing";
9 | export * from "./tx-submission";
10 | export * from "./types/";
--------------------------------------------------------------------------------
/src/protocols/interfaces/IChainDb.ts:
--------------------------------------------------------------------------------
1 | import { ChainPoint, IChainPoint, IChainTip } from "../types";
2 | export interface IChainDb
3 | {
4 | /**
5 | * @returns {IChainTip} not really the tip of the chain but we need the block number
6 | */
7 | findIntersect( ...point: IChainPoint[] ): Promise;
8 | getBlockNo( blockIndex: bigint ): Promise;
9 | getTip(): Promise;
10 |
11 | /**
12 | * @returns {ChainPoint[]} if blocks are present between the range
13 | */
14 | getBlocksBetweenRange( from: IChainPoint, to: IChainPoint ): Promise;
15 |
16 | on( evtName: "extend" | "fork", cb : ( tip: IExtendData ) => any ): void;
17 | off( evtName: "extend" | "fork", cb?: ( tip: IExtendData ) => any ): void;
18 | }
19 |
20 | export interface IExtendData
21 | {
22 | tip: IChainTip;
23 | intersection: IChainTip;
24 | }
25 |
--------------------------------------------------------------------------------
/src/protocols/keep-alive/KeepAliveMessage.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "@harmoniclabs/obj-utils";
2 | import { CanBeCborString, Cbor, CborArray, CborObj, CborUInt, forceCborString } from "@harmoniclabs/cbor";
3 | import { IKeepAliveDone, KeepAliveDone } from "./messages/KeepAliveDone";
4 | import { IKeepAliveResponse, KeepAliveResponse } from "./messages/KeepAliveResponse";
5 | import { IKeepAliveRequest, KeepAliveRequest } from "./messages/KeepAliveRequest";
6 |
7 | export type KeepAliveMessage
8 | = KeepAliveRequest
9 | | KeepAliveResponse
10 | | KeepAliveDone
11 |
12 | export function isKeepAliveMessage( stuff: any ): stuff is KeepAliveMessage
13 | {
14 | return isObject( stuff ) && (
15 | stuff instanceof KeepAliveRequest ||
16 | stuff instanceof KeepAliveResponse ||
17 | stuff instanceof KeepAliveDone
18 | );
19 | }
20 |
21 | export type IKeepAliveMessage
22 | = IKeepAliveRequest // {}
23 | | IKeepAliveResponse // {}
24 | | IKeepAliveDone
25 |
26 | export function isIKeepAliveMessage( stuff: any ): stuff is IKeepAliveMessage
27 | {
28 | return isObject( stuff ); // empty object satisfies some of the KeepAlive messages
29 | }
30 |
31 | export function keepAliveMessageFromCbor( cbor: CanBeCborString ): KeepAliveMessage
32 | {
33 | const buff = cbor instanceof Uint8Array ?
34 | cbor :
35 | forceCborString( cbor ).toBuffer();
36 |
37 | const msg = keepAliveMessageFromCborObj( Cbor.parse( buff ) );
38 |
39 | // @ts-ignore Cannot assign to 'cborBytes' because it is a read-only property.ts(2540)
40 | msg.cborBytes = buff;
41 |
42 | return msg;
43 | }
44 | export function keepAliveMessageFromCborObj( cbor: CborObj ): KeepAliveMessage
45 | {
46 | if(!(
47 | cbor instanceof CborArray &&
48 | cbor.array.length >= 1 &&
49 | cbor.array[0] instanceof CborUInt
50 | )) throw new Error("invalid cbor for 'KeepAliveMessage'");
51 |
52 | const idx = Number( cbor.array[0].num );
53 |
54 | if( idx === 0 ) return KeepAliveRequest.fromCborObj( cbor );
55 | if( idx === 1 ) return KeepAliveResponse.fromCborObj( cbor );
56 | if( idx === 2 ) return new KeepAliveDone();
57 |
58 | throw new Error("invalid cbor for 'KeepAliveMessage'; unknown index: " + idx);
59 | }
--------------------------------------------------------------------------------
/src/protocols/keep-alive/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./KeepAliveMessage";
2 | export * from "./messages";
--------------------------------------------------------------------------------
/src/protocols/keep-alive/keep-alive.cddl:
--------------------------------------------------------------------------------
1 | ;
2 | ; KeepAlive Mini-Protocol
3 | ;
4 |
5 | keepAliveMessage = msgKeepAlive
6 | / msgKeepAliveResponse
7 | / msgDone
8 |
9 | msgKeepAlive = [0, cookie]
10 | msgKeepAliveResponse = [1, cookie]
11 | msgDone = [ 2 ]
12 |
13 | cookie = word16
14 | word16 = 0..65535
--------------------------------------------------------------------------------
/src/protocols/keep-alive/messages/KeepAliveDone.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IKeepAliveDone {}
5 |
6 | export function isIKeepAliveDone( stuff: any ): stuff is IKeepAliveDone
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class KeepAliveDone
12 | implements ToCborString, ToCborObj, IKeepAliveDone
13 | {
14 | constructor() {};
15 |
16 | toJSON() { return this.toJson(); }
17 | toJson() { return {}; }
18 |
19 | toCborBytes(): Uint8Array
20 | {
21 | return this.toCbor().toBuffer();
22 | }
23 | toCbor(): CborString
24 | {
25 | return Cbor.encode( this.toCborObj() );
26 | }
27 | toCborObj(): CborArray
28 | {
29 | return new CborArray([ new CborUInt(2) ]);
30 | }
31 |
32 | static fromCbor( cbor: CanBeCborString ): KeepAliveDone
33 | {
34 | const buff = cbor instanceof Uint8Array ?
35 | cbor:
36 | forceCborString( cbor ).toBuffer();
37 |
38 | return KeepAliveDone.fromCborObj( Cbor.parse( buff, { keepRef: false } ) );
39 | }
40 | static fromCborObj( cbor: CborObj ): KeepAliveDone
41 | {
42 | if(!(
43 | cbor instanceof CborArray &&
44 | cbor.array[0] instanceof CborUInt &&
45 | cbor.array[0].num === BigInt(2)
46 | )) throw new Error("invalid CBOR for 'KeepAliveDone");
47 |
48 | return new KeepAliveDone();
49 | }
50 | }
--------------------------------------------------------------------------------
/src/protocols/keep-alive/messages/KeepAliveRequest.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { isWord16 } from "../../utils/isWord16";
4 |
5 | export interface IKeepAliveRequest {
6 | cookie: number | bigint;
7 | }
8 |
9 | export function isIKeepAliveRequest( stuff: any ): stuff is IKeepAliveRequest
10 | {
11 | return isObject( stuff );
12 | }
13 |
14 | export class KeepAliveRequest
15 | implements ToCborString, ToCborObj, IKeepAliveRequest
16 | {
17 | readonly cookie: number;
18 |
19 | constructor({ cookie }: IKeepAliveRequest )
20 | {
21 | if( !isWord16( cookie ) )
22 | {
23 | throw new Error("keep alive cookie is not word 16");
24 | }
25 |
26 | this.cookie = Number( cookie ) | 0;
27 | }
28 |
29 | toJSON() { return this.toJson(); }
30 | toJson() { return {}; }
31 |
32 | toCborBytes(): Uint8Array
33 | {
34 | return this.toCbor().toBuffer();
35 | }
36 | toCbor(): CborString
37 | {
38 | return Cbor.encode( this.toCborObj() );
39 | }
40 | toCborObj(): CborArray
41 | {
42 | return new CborArray([
43 | new CborUInt( 0 ),
44 | new CborUInt( this.cookie )
45 | ]);
46 | }
47 |
48 | static fromCbor( cbor: CanBeCborString ): KeepAliveRequest
49 | {
50 | const buff = cbor instanceof Uint8Array ?
51 | cbor:
52 | forceCborString( cbor ).toBuffer();
53 |
54 | return KeepAliveRequest.fromCborObj( Cbor.parse( buff, { keepRef: false } ) );
55 | }
56 | static fromCborObj( cbor: CborObj ): KeepAliveRequest
57 | {
58 | if(!(
59 | cbor instanceof CborArray &&
60 | cbor.array[0] instanceof CborUInt &&
61 | cbor.array[1] instanceof CborUInt &&
62 | cbor.array[0].num === BigInt( 0 )
63 | )) throw new Error("invalid CBOR for 'KeepAliveRequest");
64 |
65 | return new KeepAliveRequest({
66 | cookie: cbor.array[1].num
67 | });
68 | }
69 | }
--------------------------------------------------------------------------------
/src/protocols/keep-alive/messages/KeepAliveResponse.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { isWord16 } from "../../utils/isWord16";
4 |
5 | export interface IKeepAliveResponse {
6 | cookie: number | bigint;
7 | }
8 |
9 | export function isIKeepAliveResponse( stuff: any ): stuff is IKeepAliveResponse
10 | {
11 | return isObject( stuff );
12 | }
13 |
14 | export class KeepAliveResponse
15 | implements ToCborString, ToCborObj, IKeepAliveResponse
16 | {
17 | readonly cookie: number;
18 |
19 | constructor( { cookie }: IKeepAliveResponse )
20 | {
21 | if( !isWord16( cookie ) )
22 | {
23 | throw new Error("keep alive cookie is not word 16");
24 | }
25 |
26 | this.cookie = Number( cookie ) | 0;
27 | }
28 |
29 | toJSON() { return this.toJson(); }
30 | toJson() { return {}; }
31 |
32 | toCborBytes(): Uint8Array
33 | {
34 | return this.toCbor().toBuffer();
35 | }
36 | toCbor(): CborString
37 | {
38 | return Cbor.encode( this.toCborObj() );
39 | }
40 | toCborObj(): CborArray
41 | {
42 | return new CborArray([
43 | new CborUInt( 1 ),
44 | new CborUInt( this.cookie )
45 | ]);
46 | }
47 |
48 | static fromCbor( cbor: CanBeCborString ): KeepAliveResponse
49 | {
50 | const buff = cbor instanceof Uint8Array ?
51 | cbor:
52 | forceCborString( cbor ).toBuffer();
53 |
54 | return KeepAliveResponse.fromCborObj( Cbor.parse( buff ) );
55 | }
56 | static fromCborObj( cbor: CborObj ): KeepAliveResponse
57 | {
58 | if(!(
59 | cbor instanceof CborArray &&
60 | cbor.array[0] instanceof CborUInt &&
61 | cbor.array[1] instanceof CborUInt &&
62 | cbor.array[0].num === BigInt( 1 )
63 | )) throw new Error("invalid CBOR for 'KeepAliveResponse");
64 |
65 | return new KeepAliveResponse({
66 | cookie: cbor.array[1].num
67 | });
68 | }
69 | }
--------------------------------------------------------------------------------
/src/protocols/keep-alive/messages/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./KeepAliveDone";
2 | export * from "./KeepAliveRequest";
3 | export * from "./KeepAliveResponse";
--------------------------------------------------------------------------------
/src/protocols/local-state-query/QryMessage.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "@harmoniclabs/obj-utils";
2 | import { CborArray, CborObj, CborUInt } from "@harmoniclabs/cbor";
3 | import { QryAcquire } from "./messages/QryAcquire";
4 | import { QryAcquired } from "./messages/QryAcquired";
5 | import { QryDone } from "./messages/QryDone";
6 | import { QryFailure } from "./messages/QryFailure";
7 | import { QryQuery } from "./messages/QryQuery";
8 | import { QryReAcquire } from "./messages/QryReAcquire";
9 | import { QryRelease } from "./messages/QryRelease";
10 | import { QryResult } from "./messages/QryResult";
11 |
12 | export type QryMessage
13 | = QryAcquire
14 | | QryFailure
15 | | QryAcquired
16 | | QryReAcquire
17 | | QryQuery
18 | | QryResult
19 | | QryRelease
20 | | QryDone
21 |
22 | export function isQryMessage( msg: any ): msg is QryMessage
23 | {
24 | return isObject( msg ) && (
25 | msg instanceof QryAcquire ||
26 | msg instanceof QryFailure ||
27 | msg instanceof QryAcquired ||
28 | msg instanceof QryReAcquire ||
29 | msg instanceof QryQuery ||
30 | msg instanceof QryResult ||
31 | msg instanceof QryRelease ||
32 | msg instanceof QryDone
33 | );
34 | }
35 |
36 | export function localStateQueryMessageFromCborObj( cbor: CborObj ): QryMessage
37 | {
38 | /*
39 | msgAcquire = [0 , point ] / [ 8 ]; `[8]` is shortcut for tip
40 | msgAcquired = [ 1 ]
41 | msgFailure = [2 , failure ]
42 | msgQuery = [3 , query ]; see `query.cddl`
43 | msgResult = [4 , result ]
44 | msgRelease = [5]
45 | msgReAcquire = [6 , point ] / [ 9 ]; `[9]` is shortcut for tip
46 | lsqMsgDone = [7]
47 | */
48 | if(!(
49 | cbor instanceof CborArray &&
50 | cbor.array.length >= 1 &&
51 | cbor.array[0] instanceof CborUInt
52 | )) throw new Error("invalid CBOR for `QryMessage`");
53 |
54 | const idx = Number( cbor.array[0].num );
55 |
56 | if( idx === 0 || idx === 8 ) return QryAcquire.fromCborObj( cbor );
57 | if( idx === 1 ) return new QryAcquired();
58 | if( idx === 2 ) return QryFailure.fromCborObj( cbor );
59 | if( idx === 3 ) return QryQuery.fromCborObj( cbor );
60 | if( idx === 4 ) return QryResult.fromCborObj( cbor );
61 | if( idx === 5 ) return new QryRelease();
62 | if( idx === 6 || idx === 9 ) return QryReAcquire.fromCborObj( cbor );
63 | if( idx === 7 ) return new QryDone();
64 |
65 | throw new Error("invalid CBOR for `QryMessage`; unknown index: " + idx.toString());
66 | }
--------------------------------------------------------------------------------
/src/protocols/local-state-query/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./QryMessage";
2 | export * from "./LocalStateQueryClient";
3 | export * from "./messages";
--------------------------------------------------------------------------------
/src/protocols/local-state-query/local-state-query.cddl:
--------------------------------------------------------------------------------
1 |
2 | localStateQueryMessage
3 | = msgAcquire
4 | / msgAcquired
5 | / msgFailure
6 | / msgQuery
7 | / msgResult
8 | / msgRelease
9 | / msgReAcquire
10 | / lsqMsgDone
11 |
12 | acquireFailurePointTooOld = 0
13 | acquireFailurePointNotOnChain = 1
14 |
15 | failure = acquireFailurePointTooOld / acquireFailurePointNotOnChain
16 |
17 | msgAcquire = [0 , point ] / [ 8 ]
18 | msgAcquired = [ 1 ]
19 | msgFailure = [2 , failure ]
20 | msgQuery = [3 , query ]; see `query.cddl`
21 | msgResult = [4 , result ]
22 | msgRelease = [5]
23 | msgReAcquire = [6 , point ] / [ 9 ]; `[9]` is shortcut for tip
24 | lsqMsgDone = [7]
--------------------------------------------------------------------------------
/src/protocols/local-state-query/local-state-query.md:
--------------------------------------------------------------------------------
1 | ```mermaid
2 | flowchart LR
3 | Idle
4 | Acquiring
5 | Acquired
6 | Querying
7 | Done
8 |
9 | Start --> Idle
10 |
11 | Idle -- msgAcquire --> Acquiring
12 |
13 | Acquiring -- msgFailure --> Idle
14 | Acquiring -- msgAcquired --> Acquired
15 |
16 | Acquired -- msgReAcquire --> Acquiring
17 |
18 | Acquired -- msgQuery --> Querying
19 | Querying -- msgResult --> Acquired
20 |
21 | Acquired -- msgRelease --> Idle
22 |
23 | Idle -- msgDone --> Done
24 | ```
--------------------------------------------------------------------------------
/src/protocols/local-state-query/messages/QryAcquire.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { IChainPoint, isIChainPoint, ChainPoint } from "../../types/ChainPoint";
4 |
5 | export interface IQryAcquire {
6 | point?: IChainPoint
7 | };
8 |
9 | export function isIQryAcquire( stuff: any ): stuff is IQryAcquire
10 | {
11 | return isObject( stuff ) && (stuff.point === undefined || isIChainPoint( stuff.point ));
12 | }
13 |
14 | export class QryAcquire
15 | implements ToCborString, ToCborObj, IQryAcquire
16 | {
17 | readonly point?: ChainPoint;
18 |
19 | constructor( acq: IQryAcquire )
20 | {
21 | acq = acq ?? {};
22 | if(!isIQryAcquire( acq )) throw new Error("invalid interface for 'QryAcquire'");
23 |
24 | this.point = (
25 | acq.point instanceof ChainPoint ? acq.point :
26 | isIChainPoint( acq.point ) ? new ChainPoint( acq.point ) :
27 | undefined
28 | );
29 | };
30 |
31 | toCborBytes(): Uint8Array
32 | {
33 | return this.toCbor().toBuffer();
34 | }
35 | toCbor(): CborString
36 | {
37 | return Cbor.encode( this.toCborObj() );
38 | }
39 | toCborObj()
40 | {
41 | const arr: CborObj[] = [ new CborUInt( this.point ? 0 : 8 ) ];
42 | if( this.point )
43 | {
44 | arr.push( this.point.toCborObj() )
45 | }
46 | return new CborArray( arr );
47 | }
48 |
49 | static fromCbor( cbor: CanBeCborString ): QryAcquire
50 | {
51 | const bytes = cbor instanceof Uint8Array ? cbor : forceCborString( cbor ).toBuffer();
52 | return QryAcquire.fromCborObj(
53 | Cbor.parse( bytes, { keepRef: false } )
54 | );
55 | }
56 | static fromCborObj(
57 | cbor: CborObj,
58 | originalBytes: Uint8Array | undefined = undefined
59 | ): QryAcquire
60 | {
61 | if(!(
62 | cbor instanceof CborArray &&
63 | cbor.array.length >= 1 &&
64 | cbor.array[0] instanceof CborUInt
65 | )) throw new Error("invalid CBOR for 'QryAcquire");
66 |
67 | const num = Number( cbor.array[0].num );
68 |
69 | if( num === 0 )
70 | {
71 | if( cbor.array.length < 2 )
72 | throw new Error("invalid CBOR for 'QryAcquire");
73 |
74 | return new QryAcquire({
75 | point: ChainPoint.fromCborObj(
76 | cbor.array[1]
77 | )
78 | });
79 | }
80 | if( num === 8 ) return new QryAcquire({});
81 |
82 | throw new Error("invalid CBOR for 'QryAcquire'; unknown index: " + num.toString());
83 | }
84 | }
--------------------------------------------------------------------------------
/src/protocols/local-state-query/messages/QryAcquired.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IQryAcquired {}
5 |
6 | export function isIQryAcquired( stuff: any ): stuff is IQryAcquired
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class QryAcquired
12 | implements ToCborString, ToCborObj, IQryAcquired
13 | {
14 | constructor() {};
15 |
16 | toJSON() { return this.toJson(); }
17 | toJson() { return {}; }
18 |
19 | toCborBytes(): Uint8Array
20 | {
21 | return this.toCbor().toBuffer();
22 | }
23 | toCbor(): CborString
24 | {
25 | return Cbor.encode( this.toCborObj() );
26 | }
27 | toCborObj(): CborArray
28 | {
29 | return new CborArray([ new CborUInt(1) ]);
30 | }
31 |
32 | static fromCbor( cbor: CanBeCborString ): QryAcquired
33 | {
34 | return QryAcquired.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
35 | }
36 | static fromCborObj( cbor: CborObj ): QryAcquired
37 | {
38 | if(!(
39 | cbor instanceof CborArray &&
40 | cbor.array[0] instanceof CborUInt &&
41 | cbor.array[0].num === BigInt(1)
42 | )) throw new Error("invalid CBOR for 'QryAcquired");
43 |
44 | return new QryAcquired();
45 | }
46 | }
--------------------------------------------------------------------------------
/src/protocols/local-state-query/messages/QryDone.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IQryDone {}
5 |
6 | export function isIQryDone( stuff: any ): stuff is IQryDone
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class QryDone
12 | implements ToCborString, ToCborObj, IQryDone
13 | {
14 | constructor() {};
15 |
16 | toJSON() { return this.toJson(); }
17 | toJson() { return {}; }
18 |
19 | toCborBytes(): Uint8Array
20 | {
21 | return this.toCbor().toBuffer();
22 | }
23 | toCbor(): CborString
24 | {
25 | return Cbor.encode( this.toCborObj() );
26 | }
27 | toCborObj(): CborArray
28 | {
29 | return new CborArray([ new CborUInt(7) ]);
30 | }
31 |
32 | static fromCbor( cbor: CanBeCborString ): QryDone
33 | {
34 | return QryDone.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
35 | }
36 | static fromCborObj( cbor: CborObj ): QryDone
37 | {
38 | if(!(
39 | cbor instanceof CborArray &&
40 | cbor.array[0] instanceof CborUInt &&
41 | cbor.array[0].num === BigInt(7)
42 | )) throw new Error("invalid CBOR for 'QryDone");
43 |
44 | return new QryDone();
45 | }
46 | }
--------------------------------------------------------------------------------
/src/protocols/local-state-query/messages/QryFailure.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { MiniProtocol, miniProtocolToString } from "../../../MiniProtocol";
3 |
4 | export enum QryFailureReason {
5 | pointTooOld = 0,
6 | pointNotOnChain = 1
7 | }
8 |
9 | Object.freeze( QryFailureReason );
10 |
11 | export function isQryFailureReason( stuff: any ): stuff is QryFailureReason
12 | {
13 | return stuff === 0 || stuff === 1;
14 | }
15 |
16 | export interface IQryFailure {
17 | reason: QryFailureReason
18 | }
19 |
20 | export class QryFailure
21 | implements ToCborString, ToCborObj, IQryFailure
22 | {
23 | readonly reason: QryFailureReason;
24 |
25 | constructor({ reason }: IQryFailure)
26 | {
27 | if(!(
28 | isQryFailureReason( reason )
29 | )) throw new Error("invalid IQryFailure interface");
30 |
31 | this.reason = reason;
32 | };
33 |
34 | toJSON() { return this.toJson(); }
35 | toJson()
36 | {
37 | return {
38 | protocol: miniProtocolToString( MiniProtocol.LocalStateQuery ),
39 | message: "QryFailure",
40 | data: {
41 | reason:
42 | typeof this.reason === "number" ?
43 | QryFailureReason[ this.reason ] :
44 | this.reason
45 | }
46 | }
47 | }
48 |
49 | toCborBytes(): Uint8Array
50 | {
51 | return this.toCbor().toBuffer();
52 | }
53 | toCbor(): CborString
54 | {
55 | return Cbor.encode( this.toCborObj() );
56 | }
57 | toCborObj(): CborArray
58 | {
59 | return new CborArray([
60 | new CborUInt( 2 ),
61 | new CborUInt( this.reason )
62 | ]);
63 | }
64 |
65 | static fromCbor( cbor: CanBeCborString ): QryFailure
66 | {
67 | return QryFailure.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
68 | }
69 | static fromCborObj( cbor: CborObj ): QryFailure
70 | {
71 | if(!(
72 | cbor instanceof CborArray &&
73 | cbor.array.length >= 2 &&
74 | cbor.array[0] instanceof CborUInt &&
75 | cbor.array[0].num === BigInt( 2 ) &&
76 | cbor.array[1] instanceof CborUInt
77 | )) throw new Error("invalid CBOR for 'QryFailure");
78 |
79 | const [ _idx, _reasonCbor ] = cbor.array;
80 |
81 | return new QryFailure({
82 | // constructor checks for correct interface (and correct reason)
83 | reason: Number( _reasonCbor.num )
84 | });
85 | }
86 | }
--------------------------------------------------------------------------------
/src/protocols/local-state-query/messages/QryQuery.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString, isCborObj } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { getSubCborRef, subCborRefOrUndef } from "../../utils/getSubCborRef";
4 |
5 | export interface IQryQuery {
6 | /**
7 | * not fixed in the specification;
8 | *
9 | * check `query.cddl` for valid cardano queries
10 | **/
11 | query: CborObj
12 | }
13 |
14 | export class QryQuery
15 | implements ToCbor, ToCborObj, IQryQuery
16 | {
17 | readonly query: CborObj;
18 |
19 | constructor(
20 | qry: IQryQuery,
21 | readonly cborRef: SubCborRef | undefined = undefined
22 | )
23 | {
24 | const { query } = qry;
25 | if(!(
26 | isCborObj( query )
27 | )) throw new Error("invalid IQryQuery interface");
28 |
29 | this.query = query;
30 | // only query message to keep track of the original bytes
31 | // because data might actually be hashed by the client
32 | this.cborRef = cborRef ?? subCborRefOrUndef( qry );
33 | };
34 |
35 | toCborBytes(): Uint8Array
36 | {
37 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
38 | return this.toCbor().toBuffer();
39 | }
40 | toCbor(): CborString
41 | {
42 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
43 | return Cbor.encode( this.toCborObj() );
44 | }
45 | toCborObj(): CborArray
46 | {
47 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
48 | return new CborArray([
49 | new CborUInt( 3 ),
50 | this.query
51 | ]);
52 | }
53 |
54 | static fromCbor( cbor: CanBeCborString ): QryQuery
55 | {
56 | const bytes = cbor instanceof Uint8Array ? cbor : forceCborString( cbor ).toBuffer();
57 | return QryQuery.fromCborObj( Cbor.parse( bytes, { keepRef: true } ), bytes );
58 | }
59 | static fromCborObj(
60 | cbor: CborObj,
61 | originlBytes: Uint8Array | undefined = undefined
62 | ): QryQuery
63 | {
64 | if(!(
65 | cbor instanceof CborArray &&
66 | cbor.array.length >= 2 &&
67 | cbor.array[0] instanceof CborUInt &&
68 | cbor.array[0].num === BigInt( 3 )
69 | )) throw new Error("invalid CBOR for 'QryQuery");
70 |
71 | const [ _idx, _queryCbor ] = cbor.array;
72 |
73 | return new QryQuery({
74 | query: cbor.array[1]
75 | }, getSubCborRef( cbor, originlBytes ));
76 | }
77 | }
--------------------------------------------------------------------------------
/src/protocols/local-state-query/messages/QryReAcquire.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { ChainPoint, IChainPoint, isIChainPoint } from "../../types/ChainPoint";
4 |
5 | export interface IQryReAcquire {
6 | point?: IChainPoint
7 | };
8 |
9 | export function isIQryReAcquire( stuff: any ): stuff is IQryReAcquire
10 | {
11 | return isObject( stuff ) && (stuff.point === undefined || isIChainPoint( stuff.point ));
12 | }
13 |
14 | export class QryReAcquire
15 | implements ToCborString, ToCborObj, IQryReAcquire
16 | {
17 | readonly point?: ChainPoint;
18 |
19 | constructor( acq: IQryReAcquire = {} )
20 | {
21 | acq = acq ?? {};
22 | if(!isIQryReAcquire( acq )) throw new Error("invalid interface for 'QryReAcquire'");
23 |
24 | this.point = isIChainPoint( acq.point ) ? new ChainPoint( acq.point ) : undefined;
25 | };
26 |
27 | toCborBytes(): Uint8Array
28 | {
29 | return this.toCbor().toBuffer();
30 | }
31 | toCbor(): CborString
32 | {
33 | return Cbor.encode( this.toCborObj() );
34 | }
35 | toCborObj()
36 | {
37 | const arr: CborObj[] = [ new CborUInt( this.point ? 6 : 9 ) ];
38 | if( this.point )
39 | {
40 | arr.push( this.point.toCborObj() )
41 | }
42 | return new CborArray( arr );
43 | }
44 |
45 | static fromCbor( cbor: CanBeCborString ): QryReAcquire
46 | {
47 | return QryReAcquire.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
48 | }
49 | static fromCborObj( cbor: CborObj ): QryReAcquire
50 | {
51 | if(!(
52 | cbor instanceof CborArray &&
53 | cbor.array.length >= 1 &&
54 | cbor.array[0] instanceof CborUInt
55 | )) throw new Error("invalid CBOR for 'QryReAcquire");
56 |
57 | const num = Number( cbor.array[0].num );
58 |
59 | if( num === 6 )
60 | {
61 | if( cbor.array.length < 2 )
62 | throw new Error("invalid CBOR for 'QryReAcquire");
63 |
64 | return new QryReAcquire({
65 | point: ChainPoint.fromCborObj(
66 | cbor.array[1]
67 | )
68 | });
69 | }
70 | if( num === 9 ) return new QryReAcquire({});
71 |
72 | throw new Error("invalid CBOR for 'QryReAcquire'; unknown index: " + num.toString());
73 | }
74 | }
--------------------------------------------------------------------------------
/src/protocols/local-state-query/messages/QryRelease.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IQryRelease {}
5 |
6 | export function isIQryRelease( stuff: any ): stuff is IQryRelease
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class QryRelease
12 | implements ToCborString, ToCborObj, IQryRelease
13 | {
14 | constructor() {};
15 |
16 | toJSON() { return this.toJson(); }
17 | toJson() { return {}; }
18 |
19 | toCborBytes(): Uint8Array
20 | {
21 | return this.toCbor().toBuffer();
22 | }
23 | toCbor(): CborString
24 | {
25 | return Cbor.encode( this.toCborObj() );
26 | }
27 | toCborObj(): CborArray
28 | {
29 | return new CborArray([ new CborUInt(5) ]);
30 | }
31 |
32 | static fromCbor( cbor: CanBeCborString ): QryRelease
33 | {
34 | return QryRelease.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
35 | }
36 | static fromCborObj( cbor: CborObj ): QryRelease
37 | {
38 | if(!(
39 | cbor instanceof CborArray &&
40 | cbor.array[0] instanceof CborUInt &&
41 | cbor.array[0].num === BigInt(5)
42 | )) throw new Error("invalid CBOR for 'QryRelease");
43 |
44 | return new QryRelease();
45 | }
46 | }
--------------------------------------------------------------------------------
/src/protocols/local-state-query/messages/QryResult.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString, isCborObj } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { getSubCborRef } from "../../utils/getSubCborRef";
4 |
5 | export interface IQryResult {
6 | /**
7 | * not fixed in the specification;
8 | *
9 | * check `query.cddl` for valid cardano queries' results
10 | **/
11 | result: CborObj
12 | }
13 |
14 | export class QryResult
15 | implements ToCbor, ToCborObj, IQryResult
16 | {
17 | readonly result: CborObj;
18 |
19 | constructor(
20 | qry: IQryResult,
21 | readonly cborRef: SubCborRef | undefined = undefined
22 | )
23 | {
24 | const { result } = qry;
25 | if(!(
26 | isCborObj( result )
27 | )) throw new Error("invalid IQryResult interface");
28 |
29 | this.result = result;
30 | };
31 |
32 | toCborBytes(): Uint8Array
33 | {
34 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
35 | return this.toCbor().toBuffer();
36 | }
37 | toCbor(): CborString
38 | {
39 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
40 | return Cbor.encode( this.toCborObj() );
41 | }
42 | toCborObj(): CborArray
43 | {
44 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
45 | return new CborArray([
46 | new CborUInt( 4 ),
47 | this.result
48 | ]);
49 | }
50 |
51 | static fromCbor( cbor: CanBeCborString ): QryResult
52 | {
53 | const bytes = cbor instanceof Uint8Array ? cbor : forceCborString( cbor ).toBuffer();
54 | return QryResult.fromCborObj( Cbor.parse( bytes, { keepRef: true } ), bytes );
55 | }
56 | static fromCborObj(
57 | cbor: CborObj,
58 | originalBytes: Uint8Array | undefined = undefined
59 | ): QryResult
60 | {
61 | if(!(
62 | cbor instanceof CborArray &&
63 | cbor.array.length >= 2 &&
64 | cbor.array[0] instanceof CborUInt &&
65 | cbor.array[0].num === BigInt( 4 )
66 | )) throw new Error("invalid CBOR for 'QryResult");
67 |
68 | const [ _idx, _resultCbor ] = cbor.array;
69 |
70 | return new QryResult({
71 | result: cbor.array[1]
72 | }, getSubCborRef( cbor, originalBytes ));
73 | }
74 | }
--------------------------------------------------------------------------------
/src/protocols/local-state-query/messages/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./QryAcquire";
2 | export * from "./QryAcquired";
3 | export * from "./QryDone";
4 | export * from "./QryFailure";
5 | export * from "./QryQuery";
6 | export * from "./QryReAcquire";
7 | export * from "./QryRelease";
8 | export * from "./QryResult";
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/TxMonitorMessage.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "@harmoniclabs/obj-utils";
2 | import { TxMonitorAcquire, TxMonitorAwaitAquire } from "./messages/TxMonitorAcquire";
3 | import { TxMonitorDone } from "./messages/TxMonitorDone";
4 | import { TxMonitorGetSizes } from "./messages/TxMonitorGetSizes";
5 | import { TxMonitorNextTx } from "./messages/TxMonitorNextTx";
6 | import { TxMonitorRelease } from "./messages/TxMonitorRelease";
7 | import { TxMonitorReplyGetSizes } from "./messages/TxMonitorReplyGetSizes";
8 | import { TxMonitorReplyHasTx } from "./messages/TxMonitorReplyHasTx";
9 | import { CanBeCborString, Cbor, CborArray, CborObj, CborUInt, cborObjFromRaw, forceCborString } from "@harmoniclabs/cbor";
10 | import { TxMonitorAcquired, TxMonitorReplyNextTx, TxMonitorHasTx } from "./messages";
11 |
12 | export type TxMonitorMessage
13 | = TxMonitorDone
14 | | TxMonitorAcquire
15 | | TxMonitorAcquired
16 | | TxMonitorRelease
17 | | TxMonitorNextTx
18 | | TxMonitorReplyNextTx
19 | | TxMonitorHasTx
20 | | TxMonitorReplyHasTx
21 | | TxMonitorGetSizes
22 | | TxMonitorReplyGetSizes;
23 | // | TxMonitorAwaitAquire // same as TxMonitorAcquire
24 |
25 | export function isTxMonitorMessage( stuff: any ): stuff is TxMonitorMessage
26 | {
27 | return isObject( stuff ) && (
28 | stuff instanceof TxMonitorDone ||
29 | stuff instanceof TxMonitorAcquire ||
30 | stuff instanceof TxMonitorAcquired ||
31 | stuff instanceof TxMonitorRelease ||
32 | stuff instanceof TxMonitorNextTx ||
33 | stuff instanceof TxMonitorReplyNextTx ||
34 | stuff instanceof TxMonitorHasTx ||
35 | stuff instanceof TxMonitorReplyHasTx ||
36 | stuff instanceof TxMonitorGetSizes ||
37 | stuff instanceof TxMonitorReplyGetSizes
38 | );
39 | }
40 |
41 | export function txMonitorMessageFromCbor( cbor: CanBeCborString )
42 | {
43 | return txMonitorMessageFromCborObj( Cbor.parse( forceCborString( cbor ) ) )
44 | }
45 | export function txMonitorMessageFromCborObj( cbor: CborObj )
46 | {
47 | if(!(
48 | cbor instanceof CborArray &&
49 | cbor.array.length >= 1 &&
50 | cbor.array[0] instanceof CborUInt
51 | )) throw new Error("Invalid CBOR for 'TxMonitorMessage'");
52 |
53 | const n = Number( cbor.array[0].num );
54 |
55 | if( n === 0 ) return new TxMonitorDone();
56 | if( n === 1 ) return new TxMonitorAcquire();
57 | if( n === 2 ) return TxMonitorAcquired.fromCborObj( cbor );
58 | if( n === 3 ) return new TxMonitorRelease();
59 | if( n === 4 ) throw new Error("unknown TxMonitorMessage with index 4");
60 | if( n === 5 ) return new TxMonitorNextTx();
61 | if( n === 6 ) return TxMonitorReplyNextTx.fromCborObj( cbor );
62 | if( n === 7 ) return TxMonitorHasTx.fromCborObj( cbor );
63 | if( n === 8 ) return TxMonitorReplyHasTx.fromCborObj( cbor );
64 | if( n === 9 ) return new TxMonitorGetSizes();
65 | if( n === 10 ) return TxMonitorReplyGetSizes.fromCborObj( cbor );
66 |
67 | throw new Error("unknown TxMonitorMessage with index " + n);
68 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./TxMonitorMessage";
2 | export * from "./LocalTxMonitorClient";
3 | export * from "./messages";
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/local-tx-monitor.cddl:
--------------------------------------------------------------------------------
1 | ;
2 | ; LocalTxMonitor mini-protocol.
3 | ;
4 | ;
5 | ; reference implementation of the codec in :
6 | ; ouroboros-network/src/Ouroboros/Network/Protocol/LocalTxMonitor/Codec.hs
7 |
8 | localTxMonitorMessage
9 | = msgDone
10 | / msgAcquire
11 | / msgAcquired
12 | / msgNextTx
13 | / msgReplyNextTx
14 | / msgHasTx
15 | / msgReplyHasTx
16 | / msgGetSizes
17 | / msgReplyGetSizes
18 | / msgRelease
19 |
20 | msgDone = [0]
21 |
22 | msgAcquire = [1] ; acquire latest mempool snapshot
23 | msgAcquired = [2, slotNo] ;
24 |
25 | msgAwaitAcquire = msgAcquire
26 | msgRelease = [3]
27 |
28 | msgNextTx = [5]
29 | msgReplyNextTx = [6] / [6, tx]
30 | msgHasTx = [7, txId]
31 | msgReplyHasTx = [8, bool]
32 | msgGetSizes = [9]
33 | msgReplyGetSizes = [10, [mempool_capacity, mempool_size, n_txs]]
34 |
35 | mempool_capacity = word32
36 | mempool_size = word32
37 | n_txs = word32
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/local-tx-monitor.md:
--------------------------------------------------------------------------------
1 | ```mermaid
2 | flowchart LR
3 | Idle
4 | Acquiring
5 | Acquired
6 | Busy
7 |
8 | Start --> Idle
9 | Idle -- msgAcquire --> Acquiring
10 | Acquiring -- msgAwaitAcquire --> Acquiring
11 | Acquiring -- msgAcquired --> Acquired
12 | Acquired -- msgRelease --> Idle
13 |
14 | Acquired -- msgNextTx --> Busy
15 | Busy -- msgReplyNextTx --> Acquired
16 |
17 | Acquired -- msgHasTx --> Busy
18 | Busy -- msgReplyHasTx --> Acquired
19 |
20 | Acquired -- msgGetSizes --> Busy
21 | Busy -- msgReplyGetSizes --> Acquired
22 |
23 | Idle -- msgDone --> Done
24 |
25 | ```
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/messages/TxMonitorAcquire.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { type } from "os";
4 |
5 | export interface ITxMonitorAcquire {}
6 |
7 | export function isITxMonitorAcquire( stuff: any ): stuff is ITxMonitorAcquire
8 | {
9 | return isObject( stuff );
10 | }
11 |
12 | export type TxMonitorAwaitAquire = TxMonitorAcquire;
13 |
14 | export class TxMonitorAcquire
15 | implements ToCborString, ToCborObj, ITxMonitorAcquire
16 | {
17 | constructor() {};
18 |
19 | toJSON() { return this.toJson(); }
20 | toJson() { return {}; }
21 |
22 | toCborBytes(): Uint8Array
23 | {
24 | return this.toCbor().toBuffer();
25 | }
26 | toCbor(): CborString
27 | {
28 | return Cbor.encode( this.toCborObj() );
29 | }
30 | toCborObj(): CborArray
31 | {
32 | return new CborArray([ new CborUInt(3) ]);
33 | }
34 |
35 | static fromCbor( cbor: CanBeCborString ): TxMonitorAcquire
36 | {
37 | return TxMonitorAcquire.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
38 | }
39 | static fromCborObj( cbor: CborObj ): TxMonitorAcquire
40 | {
41 | if(!(
42 | cbor instanceof CborArray &&
43 | cbor.array[0] instanceof CborUInt &&
44 | cbor.array[0].num === BigInt(3)
45 | )) throw new Error("invalid CBOR for 'TxMonitorAcquire");
46 |
47 | return new TxMonitorAcquire();
48 | }
49 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/messages/TxMonitorAcquired.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { canBeUInteger, forceBigUInt } from "../../types/ints";
4 |
5 | export interface ITxMonitorAcquired {
6 | slotNumber: number | bigint
7 | }
8 |
9 | export function isITxMonitorAcquired( stuff: any ): stuff is ITxMonitorAcquired
10 | {
11 | return isObject( stuff ) && canBeUInteger( stuff.slotNumber );
12 | }
13 |
14 | export class TxMonitorAcquired
15 | implements ToCborString, ToCborObj, ITxMonitorAcquired
16 | {
17 | readonly slotNumber: bigint;
18 |
19 | constructor({ slotNumber }: ITxMonitorAcquired )
20 | {
21 | if(!isITxMonitorAcquired({ slotNumber }))
22 | throw new Error("invalid interface for 'TxMonitorAcquired'");
23 |
24 | this.slotNumber = forceBigUInt( slotNumber );
25 | };
26 |
27 | toCborBytes(): Uint8Array
28 | {
29 | return this.toCbor().toBuffer();
30 | }
31 | toCbor(): CborString
32 | {
33 | return Cbor.encode( this.toCborObj() );
34 | }
35 | toCborObj(): CborArray
36 | {
37 | return new CborArray([
38 | new CborUInt( 2 ),
39 | new CborUInt( this.slotNumber )
40 | ]);
41 | }
42 |
43 | static fromCbor( cbor: CanBeCborString ): TxMonitorAcquired
44 | {
45 | return TxMonitorAcquired.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
46 | }
47 | static fromCborObj( cbor: CborObj ): TxMonitorAcquired
48 | {
49 | if(!(
50 | cbor instanceof CborArray &&
51 | cbor.array.length >= 2 &&
52 | cbor.array[0] instanceof CborUInt &&
53 | cbor.array[0].num === BigInt(2) &&
54 | cbor.array[1] instanceof CborUInt
55 | )) throw new Error("invalid CBOR for 'TxMonitorAcquired");
56 |
57 | return new TxMonitorAcquired({
58 | slotNumber: cbor.array[1].num
59 | });
60 | }
61 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/messages/TxMonitorDone.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface ITxMonitorDone {}
5 |
6 | export function isITxMonitorDone( stuff: any ): stuff is ITxMonitorDone
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class TxMonitorDone
12 | implements ToCborString, ToCborObj, ITxMonitorDone
13 | {
14 | constructor() {};
15 |
16 | toJSON() { return this.toJson(); }
17 | toJson() { return {}; }
18 |
19 | toCborBytes(): Uint8Array
20 | {
21 | return this.toCbor().toBuffer();
22 | }
23 | toCbor(): CborString
24 | {
25 | return Cbor.encode( this.toCborObj() );
26 | }
27 | toCborObj(): CborArray
28 | {
29 | return new CborArray([ new CborUInt(0) ]);
30 | }
31 |
32 | static fromCbor( cbor: CanBeCborString ): TxMonitorDone
33 | {
34 | return TxMonitorDone.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
35 | }
36 | static fromCborObj( cbor: CborObj ): TxMonitorDone
37 | {
38 | if(!(
39 | cbor instanceof CborArray &&
40 | cbor.array[0] instanceof CborUInt &&
41 | cbor.array[0].num === BigInt(0)
42 | )) throw new Error("invalid CBOR for 'TxMonitorDone");
43 |
44 | return new TxMonitorDone();
45 | }
46 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/messages/TxMonitorGetSizes.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface ITxMonitorGetSizes {}
5 |
6 | export function isITxMonitorGetSizes( stuff: any ): stuff is ITxMonitorGetSizes
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class TxMonitorGetSizes
12 | implements ToCborString, ToCborObj, ITxMonitorGetSizes
13 | {
14 | constructor() {};
15 |
16 | toJSON() { return this.toJson(); }
17 | toJson() { return {}; }
18 |
19 | toCborBytes(): Uint8Array
20 | {
21 | return this.toCbor().toBuffer();
22 | }
23 | toCbor(): CborString
24 | {
25 | return Cbor.encode( this.toCborObj() );
26 | }
27 | toCborObj(): CborArray
28 | {
29 | return new CborArray([ new CborUInt(3) ]);
30 | }
31 |
32 | static fromCbor( cbor: CanBeCborString ): TxMonitorGetSizes
33 | {
34 | return TxMonitorGetSizes.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
35 | }
36 | static fromCborObj( cbor: CborObj ): TxMonitorGetSizes
37 | {
38 | if(!(
39 | cbor instanceof CborArray &&
40 | cbor.array[0] instanceof CborUInt &&
41 | cbor.array[0].num === BigInt(3)
42 | )) throw new Error("invalid CBOR for 'TxMonitorGetSizes");
43 |
44 | return new TxMonitorGetSizes();
45 | }
46 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/messages/TxMonitorHasTx.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborBytes, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { hasOwn, isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface ITxMonitorHasTx {
5 | txId: Uint8Array
6 | }
7 |
8 | export function isITxMonitorHasTx( stuff: any ): stuff is ITxMonitorHasTx
9 | {
10 | return isObject( stuff ) && ( stuff.txId instanceof Uint8Array );
11 | }
12 |
13 | export class TxMonitorHasTx
14 | implements ToCborString, ToCborObj, ITxMonitorHasTx
15 | {
16 | readonly txId: Uint8Array;
17 |
18 | constructor({ txId }: ITxMonitorHasTx )
19 | {
20 | if(!isITxMonitorHasTx({ txId }))
21 | throw new Error("invalid interface for 'TxMonitorHasTx'");
22 |
23 | this.txId = txId;
24 | };
25 |
26 | toCborBytes(): Uint8Array
27 | {
28 | return this.toCbor().toBuffer();
29 | }
30 | toCbor(): CborString
31 | {
32 | return Cbor.encode( this.toCborObj() );
33 | }
34 | toCborObj(): CborArray
35 | {
36 | return new CborArray([
37 | new CborUInt( 7 ),
38 | new CborBytes( this.txId )
39 | ]);
40 | }
41 |
42 | static fromCbor( cbor: CanBeCborString ): TxMonitorHasTx
43 | {
44 | return TxMonitorHasTx.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
45 | }
46 | static fromCborObj( cbor: CborObj ): TxMonitorHasTx
47 | {
48 | if(!(
49 | cbor instanceof CborArray &&
50 | cbor.array.length >= 2 &&
51 | cbor.array[0] instanceof CborUInt &&
52 | cbor.array[0].num === BigInt(7) &&
53 | cbor.array[1] instanceof CborBytes
54 | )) throw new Error("invalid CBOR for 'TxMonitorHasTx");
55 |
56 | return new TxMonitorHasTx({
57 | txId: cbor.array[1].bytes
58 | });
59 | }
60 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/messages/TxMonitorNextTx.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { getSubCborRef } from "../../utils/getSubCborRef";
4 |
5 | export interface ITxMonitorNextTx {}
6 |
7 | export function isITxMonitorNextTx( stuff: any ): stuff is ITxMonitorNextTx
8 | {
9 | return isObject( stuff );
10 | }
11 |
12 | export class TxMonitorNextTx
13 | implements ToCborString, ToCborObj, ITxMonitorNextTx
14 | {
15 | constructor() {};
16 |
17 | toJSON() { return this.toJson(); }
18 | toJson() { return {}; }
19 |
20 | toCborBytes(): Uint8Array
21 | {
22 | return this.toCbor().toBuffer();
23 | }
24 | toCbor(): CborString
25 | {
26 | return Cbor.encode( this.toCborObj() );
27 | }
28 | toCborObj(): CborArray
29 | {
30 | return new CborArray([ new CborUInt(5) ]);
31 | }
32 |
33 | static fromCbor( cbor: CanBeCborString ): TxMonitorNextTx
34 | {
35 | const bytes = cbor instanceof Uint8Array ? cbor : forceCborString( cbor ).toBuffer();
36 | return TxMonitorNextTx.fromCborObj( Cbor.parse( bytes, { keepRef: true } ), bytes );
37 | }
38 | static fromCborObj(
39 | cbor: CborObj,
40 | originalBytes: Uint8Array | undefined = undefined
41 | ): TxMonitorNextTx
42 | {
43 | if(!(
44 | cbor instanceof CborArray &&
45 | cbor.array[0] instanceof CborUInt &&
46 | cbor.array[0].num === BigInt(5)
47 | )) throw new Error("invalid CBOR for 'TxMonitorNextTx");
48 |
49 | return new TxMonitorNextTx();
50 | }
51 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/messages/TxMonitorRelease.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface ITxMonitorRelease {}
5 |
6 | export function isITxMonitorRelease( stuff: any ): stuff is ITxMonitorRelease
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class TxMonitorRelease
12 | implements ToCborString, ToCborObj, ITxMonitorRelease
13 | {
14 | constructor() {};
15 |
16 | toJSON() { return this.toJson(); }
17 | toJson() { return {}; }
18 |
19 | toCborBytes(): Uint8Array
20 | {
21 | return this.toCbor().toBuffer();
22 | }
23 | toCbor(): CborString
24 | {
25 | return Cbor.encode( this.toCborObj() );
26 | }
27 | toCborObj(): CborArray
28 | {
29 | return new CborArray([ new CborUInt(3) ]);
30 | }
31 |
32 | static fromCbor( cbor: CanBeCborString ): TxMonitorRelease
33 | {
34 | return TxMonitorRelease.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
35 | }
36 | static fromCborObj( cbor: CborObj ): TxMonitorRelease
37 | {
38 | if(!(
39 | cbor instanceof CborArray &&
40 | cbor.array[0] instanceof CborUInt &&
41 | cbor.array[0].num === BigInt(3)
42 | )) throw new Error("invalid CBOR for 'TxMonitorRelease");
43 |
44 | return new TxMonitorRelease();
45 | }
46 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/messages/TxMonitorReplyGetSizes.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, forceCborString, ToCbor, ToCborObj, ToCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils"
3 |
4 | const roDescr = Object.freeze({
5 | writable: false,
6 | enumerable: true,
7 | configurable: false
8 | });
9 |
10 | export interface ITxMonitorReplyGetSizes
11 | {
12 | mempoolCapacity: number,
13 | mempoolSize: number,
14 | nTxs: number
15 | }
16 |
17 | export function isITxMonitorReplyGetSizes( stuff: any ): stuff is ITxMonitorReplyGetSizes
18 | {
19 | return isObject( stuff ) && (
20 | Number.isSafeInteger( stuff.mempoolCapacity ) &&
21 | Number.isSafeInteger( stuff.mempoolSize ) &&
22 | Number.isSafeInteger( stuff.nTxs )
23 | );
24 | }
25 |
26 | export class TxMonitorReplyGetSizes
27 | implements ITxMonitorReplyGetSizes, ToCborString, ToCborObj
28 | {
29 | readonly mempoolCapacity: number
30 | readonly mempoolSize: number
31 | readonly nTxs: number
32 |
33 | constructor( reply: ITxMonitorReplyGetSizes )
34 | {
35 | if( !isITxMonitorReplyGetSizes( reply ) ) throw new Error("invalid `ITxMonitorReplyGetSizes`");
36 |
37 | this.mempoolCapacity = reply.mempoolCapacity;
38 | this.mempoolSize = reply.mempoolSize;
39 | this.nTxs = reply.nTxs;
40 | }
41 |
42 | toCborBytes(): Uint8Array
43 | {
44 | return this.toCbor().toBuffer();
45 | }
46 | toCbor(): CborString
47 | {
48 | return Cbor.encode( this.toCborObj() );
49 | }
50 | toCborObj(): CborArray
51 | {
52 | return new CborArray([
53 | new CborUInt( 10 ),
54 | new CborArray([
55 | new CborUInt( this.mempoolCapacity ),
56 | new CborUInt( this.mempoolSize ),
57 | new CborUInt( this.nTxs )
58 | ])
59 | ]);
60 | }
61 |
62 | static fromCbor( cbor: CanBeCborString ): TxMonitorReplyGetSizes
63 | {
64 | return TxMonitorReplyGetSizes.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
65 | }
66 | static fromCborObj( cbor: CborObj ): TxMonitorReplyGetSizes
67 | {
68 | if(!(
69 | cbor instanceof CborArray &&
70 | cbor.array.length >= 2 &&
71 | cbor.array[0] instanceof CborUInt &&
72 | cbor.array[0].num === BigInt(10) &&
73 | cbor.array[1] instanceof CborArray &&
74 | cbor.array[1].array.length === 3 &&
75 | cbor.array[1].array.every( elem => elem instanceof CborUInt )
76 | )) throw new Error("invalid CBOR for 'TxMonitorReplyGetSizes'");
77 |
78 | const [ mempoolCapacity, mempoolSize, nTxs ] = cbor.array[1].array
79 | .map( elem => Number( (elem as CborUInt).num ));
80 |
81 | return new TxMonitorReplyGetSizes({
82 | mempoolCapacity,
83 | mempoolSize,
84 | nTxs
85 | });
86 | }
87 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/messages/TxMonitorReplyHasTx.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborSimple, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface ITxMonitorReplyHasTx {
5 | hasTx: boolean
6 | }
7 |
8 | export function isITxMonitorReplyHasTx( stuff: any ): stuff is ITxMonitorReplyHasTx
9 | {
10 | return isObject( stuff ) && ( typeof stuff.hasTx === "boolean" );
11 | }
12 |
13 | export class TxMonitorReplyHasTx
14 | implements ToCborString, ToCborObj, ITxMonitorReplyHasTx
15 | {
16 | readonly hasTx: boolean;
17 |
18 | constructor({ hasTx }: ITxMonitorReplyHasTx )
19 | {
20 | if(!isITxMonitorReplyHasTx({ hasTx }))
21 | throw new Error("invalid interface for 'TxMonitorReplyHasTx'");
22 |
23 | this.hasTx = hasTx;
24 | };
25 |
26 | toCborBytes(): Uint8Array
27 | {
28 | return this.toCbor().toBuffer();
29 | }
30 | toCbor(): CborString
31 | {
32 | return Cbor.encode( this.toCborObj() );
33 | }
34 | toCborObj(): CborArray
35 | {
36 | return new CborArray([
37 | new CborUInt( 8 ),
38 | new CborSimple( this.hasTx )
39 | ]);
40 | }
41 |
42 | static fromCbor( cbor: CanBeCborString ): TxMonitorReplyHasTx
43 | {
44 | return TxMonitorReplyHasTx.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
45 | }
46 | static fromCborObj( cbor: CborObj ): TxMonitorReplyHasTx
47 | {
48 | if(!(
49 | cbor instanceof CborArray &&
50 | cbor.array.length >= 2 &&
51 | cbor.array[0] instanceof CborUInt &&
52 | cbor.array[0].num === BigInt(8) &&
53 | cbor.array[1] instanceof CborSimple &&
54 | typeof cbor.array[1].simple === "boolean"
55 | )) throw new Error("invalid CBOR for 'TxMonitorReplyHasTx");
56 |
57 | return new TxMonitorReplyHasTx({
58 | hasTx: cbor.array[1].simple
59 | });
60 | }
61 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/messages/TxMonitorReplyNextTx.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborBytes, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { hasOwn, isObject } from "@harmoniclabs/obj-utils";
3 | import { getSubCborRef, subCborRefOrUndef } from "../../utils/getSubCborRef";
4 |
5 | export interface ITxMonitorReplyNextTx {
6 | tx?: Uint8Array
7 | }
8 |
9 | export function isITxMonitorReplyNextTx( stuff: any ): stuff is ITxMonitorReplyNextTx
10 | {
11 | return isObject( stuff ) && (stuff.tx ? stuff.tx instanceof Uint8Array : true);
12 | }
13 |
14 | export class TxMonitorReplyNextTx
15 | implements ToCbor, ToCborObj, ITxMonitorReplyNextTx
16 | {
17 | readonly tx?: Uint8Array;
18 |
19 | constructor(
20 | msg: ITxMonitorReplyNextTx,
21 | readonly cborRef: SubCborRef | undefined = undefined
22 | )
23 | {
24 | const { tx } = msg;
25 | if(!isITxMonitorReplyNextTx({ tx }))
26 | throw new Error("invalid interface for 'TxMonitorReplyNextTx'");
27 |
28 | this.tx = tx;
29 | this.cborRef = cborRef ?? subCborRefOrUndef( msg );
30 | };
31 |
32 | toCborBytes(): Uint8Array
33 | {
34 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
35 | return this.toCbor().toBuffer();
36 | }
37 | toCbor(): CborString
38 | {
39 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
40 | return Cbor.encode( this.toCborObj() );
41 | }
42 | toCborObj(): CborArray
43 | {
44 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
45 | const arr: CborObj[] = [ new CborUInt( 6 ) ];
46 | if( this.tx ) arr.push( new CborBytes( this.tx ) );
47 | return new CborArray( arr );
48 | }
49 |
50 | static fromCbor( cbor: CanBeCborString ): TxMonitorReplyNextTx
51 | {
52 | const bytes = cbor instanceof Uint8Array ? cbor : forceCborString( cbor ).toBuffer();
53 | return TxMonitorReplyNextTx.fromCborObj( Cbor.parse( bytes, { keepRef: true } ), bytes );
54 | }
55 | static fromCborObj(
56 | cbor: CborObj,
57 | originalBytes: Uint8Array | undefined = undefined
58 | ): TxMonitorReplyNextTx
59 | {
60 | if(!(
61 | cbor instanceof CborArray &&
62 | cbor.array[0] instanceof CborUInt &&
63 | cbor.array[0].num === BigInt(6)
64 | )) throw new Error("invalid CBOR for 'TxMonitorReplyNextTx");
65 |
66 | const reply: ITxMonitorReplyNextTx = {
67 | tx: undefined
68 | };
69 |
70 | if( cbor.array[1] )
71 | {
72 | if(!( cbor.array[1] instanceof CborBytes ))
73 | throw new Error("invalid CBOR for 'TxMonitorReplyNextTx");
74 |
75 | reply.tx = cbor.array[1].bytes;
76 | }
77 |
78 | return new TxMonitorReplyNextTx( reply, getSubCborRef( cbor, originalBytes ) );
79 | }
80 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-monitor/messages/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./TxMonitorAcquire";
2 | export * from "./TxMonitorAcquired";
3 | export * from "./TxMonitorDone";
4 | export * from "./TxMonitorGetSizes";
5 | export * from "./TxMonitorHasTx";
6 | export * from "./TxMonitorNextTx";
7 | export * from "./TxMonitorRelease";
8 | export * from "./TxMonitorReplyGetSizes";
9 | export * from "./TxMonitorReplyHasTx";
10 | export * from "./TxMonitorReplyNextTx";
--------------------------------------------------------------------------------
/src/protocols/local-tx-submit/LocalTxSubmitMessage.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "@harmoniclabs/obj-utils";
2 | import { LocalTxSubmitAccept } from "./messages/LocalTxSubmitAccept";
3 | import { LocalTxSubmitDone } from "./messages/LocalTxSubmitDone";
4 | import { LocalTxSubmitSubmit } from "./messages/LocalTxSubmitSubmit";
5 | import { CanBeCborString, Cbor, CborArray, CborObj, CborUInt, forceCborString } from "@harmoniclabs/cbor";
6 | import { LocalTxSubmitReject } from "./messages/LocalTxSubmitReject";
7 |
8 | export type LocalTxSubmitMessage
9 | = LocalTxSubmitSubmit
10 | | LocalTxSubmitReject
11 | | LocalTxSubmitAccept
12 | | LocalTxSubmitDone;
13 |
14 | export function isLocalTxSubmitMessage( thing: any ): thing is LocalTxSubmitMessage
15 | {
16 | return isObject( thing ) && (
17 | thing instanceof LocalTxSubmitSubmit ||
18 | thing instanceof LocalTxSubmitReject ||
19 | thing instanceof LocalTxSubmitAccept ||
20 | thing instanceof LocalTxSubmitDone
21 | );
22 | }
23 |
24 | export function LocalTxSubmitSubmitMessageFromCbor( cbor: CanBeCborString ): LocalTxSubmitMessage
25 | {
26 | return localTxSubmitMessageFromCborObj(
27 | Cbor.parse(
28 | cbor instanceof Uint8Array ? cbor :
29 | forceCborString( cbor )
30 | )
31 | );
32 | }
33 | export function localTxSubmitMessageFromCborObj( cbor: CborObj ): LocalTxSubmitMessage
34 | {
35 | if(!(
36 | cbor instanceof CborArray &&
37 | cbor.array.length >= 1 &&
38 | cbor.array[0] instanceof CborUInt
39 | )) throw new Error("invalid cbor for 'LocalTxSubmitMessage'");
40 |
41 | const idx = Number( cbor.array[0].num );
42 |
43 | if( idx === 0 ) return LocalTxSubmitSubmit.fromCborObj( cbor );
44 | if( idx === 1 ) return new LocalTxSubmitAccept();
45 | if( idx === 2 ) return LocalTxSubmitReject.fromCborObj( cbor );
46 | if( idx === 3 ) return new LocalTxSubmitDone();
47 |
48 | throw new Error("invalid cbor for 'LocalTxSubmitMessage'; unknown index: " + idx);
49 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-submit/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./LocalTxSubmitMessage";
2 | export * from "./LocalTxSubmitClient";
3 | export * from "./messages";
--------------------------------------------------------------------------------
/src/protocols/local-tx-submit/local-tx-submission.cddl:
--------------------------------------------------------------------------------
1 | ;
2 | ; LocalTxSubmission mini-protocol
3 | ;
4 |
5 |
6 | ; Reference implementation of the codec in:
7 | ; ouroboros-network/src/Ouroboros/Network/Protocol/LocalTxSubmission/Codec.hs
8 |
9 | localTxSubmissionMessage
10 | = msgSubmitTx
11 | / msgAcceptTx
12 | / msgRejectTx
13 | / ltMsgDone
14 |
15 | msgSubmitTx = [0, tx ]
16 | msgAcceptTx = [1]
17 | msgRejectTx = [2, rejectReason ]
18 | ltMsgDone = [3]
19 |
20 | rejectReason = int
--------------------------------------------------------------------------------
/src/protocols/local-tx-submit/local-tx-submit.md:
--------------------------------------------------------------------------------
1 | ```mermaid
2 | flowchart LR
3 | Idle
4 | Busy
5 |
6 | Start --> Idle
7 | subgraph "client"
8 | Idle -- msgDone --> Done
9 | end
10 | subgraph "server"
11 | Idle -- msgSubmitTx --> Busy
12 | Busy -- msgRejectTx --> Idle
13 | Busy -- msgAcceptTx --> Idle
14 | end
15 |
16 |
17 | ```
--------------------------------------------------------------------------------
/src/protocols/local-tx-submit/messages/LocalTxSubmitAccept.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface ILocalTxSubmitAccept {}
5 |
6 | export function isILocalTxSubmitAccept( stuff: any ): stuff is ILocalTxSubmitAccept
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class LocalTxSubmitAccept
12 | implements ToCborString, ToCborObj, ILocalTxSubmitAccept
13 | {
14 | constructor() {};
15 |
16 | toJSON() { return this.toJson(); }
17 | toJson() { return {}; }
18 |
19 | toCborBytes(): Uint8Array
20 | {
21 | return this.toCbor().toBuffer();
22 | }
23 | toCbor(): CborString
24 | {
25 | return Cbor.encode( this.toCborObj() );
26 | }
27 | toCborObj(): CborArray
28 | {
29 | return new CborArray([ new CborUInt(1) ]);
30 | }
31 |
32 | static fromCbor( cbor: CanBeCborString ): LocalTxSubmitAccept
33 | {
34 | return LocalTxSubmitAccept.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
35 | }
36 | static fromCborObj( cbor: CborObj ): LocalTxSubmitAccept
37 | {
38 | if(!(
39 | cbor instanceof CborArray &&
40 | cbor.array[0] instanceof CborUInt &&
41 | cbor.array[0].num === BigInt(1)
42 | )) throw new Error("invalid CBOR for 'LocalTxSubmitAccept");
43 |
44 | return new LocalTxSubmitAccept();
45 | }
46 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-submit/messages/LocalTxSubmitDone.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface ILocalTxSubmitDone {}
5 |
6 | export function isILocalTxSubmitDone( stuff: any ): stuff is ILocalTxSubmitDone
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class LocalTxSubmitDone
12 | implements ToCborString, ToCborObj, ILocalTxSubmitDone
13 | {
14 | constructor() {};
15 |
16 | toJSON() { return this.toJson(); }
17 | toJson() { return {}; }
18 |
19 | toCborBytes(): Uint8Array
20 | {
21 | return this.toCbor().toBuffer();
22 | }
23 | toCbor(): CborString
24 | {
25 | return Cbor.encode( this.toCborObj() );
26 | }
27 | toCborObj(): CborArray
28 | {
29 | return new CborArray([ new CborUInt(3) ]);
30 | }
31 |
32 | static fromCbor( cbor: CanBeCborString ): LocalTxSubmitDone
33 | {
34 | return LocalTxSubmitDone.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
35 | }
36 | static fromCborObj( cbor: CborObj ): LocalTxSubmitDone
37 | {
38 | if(!(
39 | cbor instanceof CborArray &&
40 | cbor.array[0] instanceof CborUInt &&
41 | cbor.array[0].num === BigInt(3)
42 | )) throw new Error("invalid CBOR for 'LocalTxSubmitDone");
43 |
44 | return new LocalTxSubmitDone();
45 | }
46 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-submit/messages/LocalTxSubmitReject.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborNegInt, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { canBeInteger, forceBigUInt } from "../../types/ints";
4 |
5 | export interface ILocalTxSubmitReject {
6 | reason: number | bigint
7 | }
8 |
9 | export function isILocalTxSubmitReject( stuff: any ): stuff is ILocalTxSubmitReject
10 | {
11 | return isObject( stuff ) && canBeInteger( stuff.reason );
12 | }
13 |
14 | export class LocalTxSubmitReject
15 | implements ToCborString, ToCborObj, ILocalTxSubmitReject
16 | {
17 | readonly reason: bigint;
18 |
19 | constructor({ reason }: ILocalTxSubmitReject)
20 | {
21 | if(!isILocalTxSubmitReject({ reason }))
22 | throw new Error("invalid interface for 'LocalTxSubmitReject'");
23 |
24 | this.reason = forceBigUInt( reason );
25 | };
26 |
27 | toCborBytes(): Uint8Array
28 | {
29 | return this.toCbor().toBuffer();
30 | }
31 | toCbor(): CborString
32 | {
33 | return Cbor.encode( this.toCborObj() );
34 | }
35 | toCborObj(): CborArray
36 | {
37 | return new CborArray([
38 | new CborUInt(2),
39 | this.reason >= 0 ? new CborUInt( this.reason ) : new CborNegInt( this.reason )
40 | ]);
41 | }
42 |
43 | static fromCbor( cbor: CanBeCborString ): LocalTxSubmitReject
44 | {
45 | return LocalTxSubmitReject.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
46 | }
47 | static fromCborObj( cbor: CborObj ): LocalTxSubmitReject
48 | {
49 | if(!(
50 | cbor instanceof CborArray &&
51 | cbor.array.length >= 2 &&
52 | cbor.array[0] instanceof CborUInt &&
53 | cbor.array[0].num === BigInt(2) &&
54 | (cbor.array[1] instanceof CborUInt || cbor.array[1] instanceof CborNegInt)
55 | )) throw new Error("invalid CBOR for 'LocalTxSubmitReject");
56 |
57 | return new LocalTxSubmitReject({
58 | reason: cbor.array[1].num
59 | });
60 | }
61 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-submit/messages/LocalTxSubmitSubmit.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborBytes, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { getSubCborRef, subCborRefOrUndef } from "../../utils/getSubCborRef";
4 |
5 | export interface ILocalTxSubmitSubmit {
6 | tx: Uint8Array
7 | }
8 |
9 | export function isILocalTxSubmitSubmit( stuff: any ): stuff is ILocalTxSubmitSubmit
10 | {
11 | return isObject( stuff ) && isObject( stuff.tx ) && (stuff.tx instanceof Uint8Array);
12 | }
13 |
14 | export class LocalTxSubmitSubmit
15 | implements ToCbor, ToCborObj, ILocalTxSubmitSubmit
16 | {
17 | readonly tx: Uint8Array;
18 |
19 | constructor(
20 | msg: ILocalTxSubmitSubmit,
21 | readonly cborRef: SubCborRef | undefined = undefined
22 | )
23 | {
24 | const tx = msg.tx;
25 | if(!(tx instanceof Uint8Array))
26 | throw new Error("invalid interface for 'LocalTxSubmitSubmit'");
27 |
28 | this.tx = tx;
29 | this.cborRef = cborRef ?? subCborRefOrUndef( msg );
30 | };
31 |
32 | toCborBytes(): Uint8Array
33 | {
34 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
35 | return this.toCbor().toBuffer();
36 | }
37 | toCbor(): CborString
38 | {
39 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
40 | return Cbor.encode( this.toCborObj() );
41 | }
42 | toCborObj(): CborArray
43 | {
44 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
45 | return new CborArray([
46 | new CborUInt(0),
47 | new CborBytes( this.tx )
48 | ]);
49 | }
50 |
51 | static fromCbor( cbor: CanBeCborString ): LocalTxSubmitSubmit
52 | {
53 | const bytes = cbor instanceof Uint8Array ? cbor : forceCborString( cbor ).toBuffer();
54 | return LocalTxSubmitSubmit.fromCborObj( Cbor.parse( bytes, { keepRef: true } ), bytes );
55 | }
56 | static fromCborObj(
57 | cbor: CborObj,
58 | originalBytes: Uint8Array | undefined = undefined
59 | ): LocalTxSubmitSubmit
60 | {
61 | if(!(
62 | cbor instanceof CborArray &&
63 | cbor.array.length >= 2 &&
64 | cbor.array[0] instanceof CborUInt &&
65 | cbor.array[0].num === BigInt(0) &&
66 | cbor.array[1] instanceof CborBytes
67 | )) throw new Error("invalid CBOR for 'LocalTxSubmitSubmit");
68 |
69 | return new LocalTxSubmitSubmit({
70 | tx: cbor.array[1].bytes
71 | }, getSubCborRef( cbor, originalBytes ));
72 | }
73 | }
--------------------------------------------------------------------------------
/src/protocols/local-tx-submit/messages/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./LocalTxSubmitAccept";
2 | export * from "./LocalTxSubmitDone";
3 | export * from "./LocalTxSubmitReject";
4 | export * from "./LocalTxSubmitSubmit";
--------------------------------------------------------------------------------
/src/protocols/peer-sharing/PeerAddress/PeerAddress.ts:
--------------------------------------------------------------------------------
1 | import { CborArray, CborObj, CborUInt } from "@harmoniclabs/cbor";
2 | import { isIPeerAddressIPv4, PeerAddressIPv4 } from "./PeerAddressIPv4";
3 | import { isIPeerAddressIPv6, PeerAddressIPv6 } from "./PeerAddressIPv6";
4 |
5 | export type PeerAddress = PeerAddressIPv4 | PeerAddressIPv6;
6 |
7 | export function isValidPeerAddress( stuff: any ): stuff is PeerAddress
8 | {
9 | if( stuff instanceof PeerAddressIPv4 ) return isIPeerAddressIPv4( stuff );
10 | if( stuff instanceof PeerAddressIPv6 ) return isIPeerAddressIPv6( stuff );
11 |
12 | return false;
13 | }
14 |
15 | export function peerAddressFromCborObj( cbor: CborObj ): PeerAddress
16 | {
17 | if(!(
18 | cbor instanceof CborArray &&
19 | cbor.array[0] instanceof CborUInt &&
20 | cbor.array[0].num >= 0 &&
21 | cbor.array[0].num <= 1
22 | )) throw new Error("invalid CBOR for `IPeerAddress`");
23 |
24 | const idx = Number( cbor.array[0].num );
25 |
26 | if( idx === 0 ) return PeerAddressIPv4.fromCborObj( cbor );
27 | if( idx === 1 ) return PeerAddressIPv6.fromCborObj( cbor );
28 |
29 | throw new Error("invalid CBOR for `IPeerAddress`");
30 | }
--------------------------------------------------------------------------------
/src/protocols/peer-sharing/PeerAddress/PeerAddressIPv4.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, forceCborString, ToCbor, ToCborObj, ToCborString } from "@harmoniclabs/cbor";
2 | import { isWord16 } from "../../utils/isWord16";
3 | import { isWord32 } from "../../utils/isWord32";
4 |
5 | export interface IPeerAddressIPv4 {
6 | address: number | bigint;
7 | portNumber: number | bigint;
8 | }
9 |
10 | export function isIPeerAddressIPv4( peerAddress: any ): peerAddress is IPeerAddressIPv4
11 | {
12 | return (
13 | isWord32( peerAddress.address ) &&
14 | isWord16( peerAddress.portNumber )
15 | );
16 | }
17 |
18 | export class PeerAddressIPv4 implements IPeerAddressIPv4, ToCborString, ToCborObj
19 | {
20 | readonly address: number;
21 | readonly portNumber: number | bigint;
22 |
23 | constructor( newPeerAddress: IPeerAddressIPv4 )
24 | {
25 | if(!( isIPeerAddressIPv4( newPeerAddress ) ))
26 | throw new Error( "invalid new `IPeerAddressIPv4` data provided" );
27 |
28 | this.address = Number( newPeerAddress.address ) >>> 0;
29 | this.portNumber = newPeerAddress.portNumber;
30 | }
31 |
32 | toCborBytes(): Uint8Array
33 | {
34 | return this.toCbor().toBuffer();
35 | }
36 | toCbor(): CborString
37 | {
38 | return Cbor.encode( this.toCborObj() );
39 | }
40 | toCborObj(): CborArray
41 | {
42 | return new CborArray([
43 | new CborUInt( 0 ),
44 | new CborUInt( this.address ),
45 | new CborUInt( this.portNumber )
46 | ]);
47 | }
48 |
49 | static fromCbor( cbor: CanBeCborString ): PeerAddressIPv4
50 | {
51 | return PeerAddressIPv4.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
52 | }
53 | static fromCborObj( cbor: CborObj ): PeerAddressIPv4
54 | {
55 | if(!(
56 | cbor instanceof CborArray &&
57 | cbor.array.length >= 3 &&
58 | cbor.array[0] instanceof CborUInt &&
59 | cbor.array[1] instanceof CborUInt &&
60 | cbor.array[2] instanceof CborUInt
61 | )) throw new Error( "invalid CBOR for `PeerAddressIPv4`" );
62 |
63 | return new PeerAddressIPv4({
64 | address: cbor.array[1].num,
65 | portNumber: cbor.array[2].num
66 | });
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/src/protocols/peer-sharing/PeerAddress/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarmonicLabs/ouroboros-miniprotocols-ts/0dcb53190b5084be448d4501ccdfee3ecca8fe10/src/protocols/peer-sharing/PeerAddress/index.ts
--------------------------------------------------------------------------------
/src/protocols/peer-sharing/PeerSharingMessage.ts:
--------------------------------------------------------------------------------
1 | import { IPeerSharingRequest, PeerSharingRequest } from "./messages/PeerSharingRequest";
2 | import { IPeerSharingResponse, PeerSharingResponse } from "./messages/PeerSharingResponse";
3 | import { IPeerSharingDone, PeerSharingDone } from "./messages/PeerSharingDone";
4 | import { isObject } from "@harmoniclabs/obj-utils";
5 | import { CanBeCborString, Cbor, CborArray, CborObj, CborUInt, forceCborString } from "@harmoniclabs/cbor";
6 |
7 | export type PeerSharingMessage
8 | = PeerSharingRequest
9 | | PeerSharingResponse
10 | | PeerSharingDone
11 |
12 | export type IPeerSharingMessage
13 | = IPeerSharingRequest
14 | | IPeerSharingResponse
15 | | IPeerSharingDone
16 |
17 | export function isPeerSharingMessage( stuff: any ): stuff is PeerSharingMessage {
18 | return isObject( stuff ) && (
19 | stuff instanceof PeerSharingRequest ||
20 | stuff instanceof PeerSharingResponse ||
21 | stuff instanceof PeerSharingDone
22 | );
23 | }
24 |
25 | export function isIPeerSharingMessage( stuff: any ): stuff is IPeerSharingMessage {
26 | return isObject( stuff );
27 | }
28 |
29 | export function peerSharingMessageFromCbor( cbor: CanBeCborString ): PeerSharingMessage {
30 | const buff = cbor instanceof Uint8Array ?
31 | cbor :
32 | forceCborString( cbor ).toBuffer();
33 |
34 | const msg = peerSharingMessageFromCborObj( Cbor.parse( buff ) );
35 |
36 | // @ts-ignore Cannot assign to 'cborBytes' because it is a read-only property.ts(2540)
37 | msg.cborBytes = buff;
38 |
39 | return msg;
40 | }
41 |
42 | export function peerSharingMessageFromCborObj( cbor: CborObj ): PeerSharingMessage {
43 | if(!(
44 | cbor instanceof CborArray &&
45 | cbor.array.length >= 1 &&
46 | cbor.array[0] instanceof CborUInt
47 | )) throw new Error( "invalid CBOR for `PeerSharingMessage`" );
48 |
49 | const idx = Number( cbor.array[0].num );
50 |
51 | if( idx === 0 ) return PeerSharingRequest.fromCborObj( cbor );
52 | if( idx === 1 ) return PeerSharingResponse.fromCborObj( cbor );
53 | if( idx === 2 ) return new PeerSharingDone();
54 |
55 | throw new Error( "invalid CBOR for `PeerSharingMessage`; unknown index: " + idx );
56 | }
--------------------------------------------------------------------------------
/src/protocols/peer-sharing/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./PeerSharingMessage";
2 | export * from "./PeerSharingClient";
3 | export * from "./messages";
--------------------------------------------------------------------------------
/src/protocols/peer-sharing/messages/PeerSharingDone.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface IPeerSharingDone {}
5 |
6 | export function isIPeerSharingDone( stuff: any ): stuff is IPeerSharingDone {
7 | return isObject( stuff );
8 | }
9 |
10 | export class PeerSharingDone implements ToCborString, ToCborObj, IPeerSharingDone {
11 | constructor() {};
12 |
13 | toCborBytes(): Uint8Array
14 | {
15 | return this.toCbor().toBuffer();
16 | }
17 | toCbor(): CborString {
18 | return Cbor.encode( this.toCborObj() );
19 | }
20 | toCborObj() {
21 | return new CborArray([ new CborUInt(2) ]);
22 | }
23 |
24 | static fromCbor( cbor: CanBeCborString ): PeerSharingDone
25 | {
26 | const buff = cbor instanceof Uint8Array ? cbor: forceCborString( cbor ).toBuffer();
27 | return PeerSharingDone.fromCborObj( Cbor.parse( buff ) );
28 | }
29 |
30 | static fromCborObj( cbor: CborObj ): PeerSharingDone {
31 | if(!(
32 | cbor instanceof CborArray &&
33 | cbor.array[0] instanceof CborUInt &&
34 | cbor.array[0].num === BigInt(2)
35 | )) throw new Error("invalid CBOR for 'PeerSharingDone");
36 |
37 | return new PeerSharingDone();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/protocols/peer-sharing/messages/PeerSharingRequest.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { isByte } from "../../utils/isByte";
4 |
5 | export interface IPeerSharingRequest {
6 | amount: number | bigint;
7 | }
8 |
9 | export function isIPeerSharingRequest( stuff: any ): stuff is IPeerSharingRequest {
10 | return isObject( stuff );
11 | }
12 |
13 | export class PeerSharingRequest implements ToCborString, ToCborObj, IPeerSharingRequest
14 | {
15 | readonly amount: number;
16 |
17 | constructor( { amount } : IPeerSharingRequest ) {
18 | if( !isByte( amount ) ) throw new Error("peer sharing amount is not a number within a byte");
19 |
20 | this.amount = Number( amount ) & 0xff;
21 | }
22 |
23 |
24 | toCborBytes(): Uint8Array
25 | {
26 | return this.toCbor().toBuffer();
27 | }
28 | toCbor(): CborString {
29 | return Cbor.encode( this.toCborObj() );
30 | }
31 | toCborObj(): CborArray {
32 | return new CborArray([
33 | new CborUInt( 0 ),
34 | new CborUInt( this.amount )
35 | ]);
36 | }
37 |
38 | static fromCborObj( cbor: CborObj ): PeerSharingRequest {
39 | if(!(
40 | cbor instanceof CborArray &&
41 | cbor.array[0] instanceof CborUInt &&
42 | cbor.array[1] instanceof CborUInt &&
43 | cbor.array[0].num === BigInt( 0 )
44 | )) throw new Error("invalid CBOR for 'PeerSharingRequest'");
45 |
46 | return new PeerSharingRequest({
47 | amount: cbor.array[1].num
48 | });
49 | }
50 |
51 | static fromCbor( cbor: CanBeCborString ): PeerSharingRequest {
52 | const buff = cbor instanceof Uint8Array ? cbor : forceCborString( cbor ).toBuffer();
53 |
54 | const msg = PeerSharingRequest.fromCborObj(Cbor.parse( buff ));
55 |
56 | // @ts-ignore Cannot assign to 'cborBytes' because it is a read-only property.ts(2540)
57 | msg.cborBytes = buff;
58 |
59 | return msg;
60 | }
61 |
62 | }
--------------------------------------------------------------------------------
/src/protocols/peer-sharing/messages/PeerSharingResponse.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { PeerAddress, isValidPeerAddress, peerAddressFromCborObj } from "../PeerAddress/PeerAddress";
3 |
4 | export interface IPeerSharingResponse {
5 | peerAddresses: PeerAddress[];
6 | }
7 |
8 | export class PeerSharingResponse implements ToCborString, ToCborObj, IPeerSharingResponse
9 | {
10 | readonly peerAddresses: PeerAddress[];
11 |
12 | constructor( { peerAddresses } : IPeerSharingResponse ) {
13 | if( !(
14 | Array.isArray( peerAddresses ) &&
15 | peerAddresses.every( isValidPeerAddress )
16 | ) ) throw new Error( "invalid `IPeerSharingResponse`" );
17 |
18 | this.peerAddresses = peerAddresses;
19 | }
20 |
21 | toCborBytes(): Uint8Array
22 | {
23 | return this.toCbor().toBuffer();
24 | }
25 | toCbor(): CborString {
26 | return Cbor.encode( this.toCborObj() );
27 | }
28 | toCborObj(): CborArray {
29 | return new CborArray([
30 | new CborUInt(1),
31 | new CborArray( this.peerAddresses.map( peer => peer.toCborObj() ) )
32 | ]);
33 | }
34 |
35 | static fromCbor( cbor: CanBeCborString ): PeerSharingResponse {
36 | const buff = cbor instanceof Uint8Array ? cbor: forceCborString( cbor ).toBuffer();
37 | return PeerSharingResponse.fromCborObj( Cbor.parse( buff ) );
38 | }
39 | static fromCborObj( cbor: CborObj ): PeerSharingResponse {
40 | if(!(
41 | cbor instanceof CborArray &&
42 | cbor.array.length === 2 &&
43 | cbor.array[0] instanceof CborUInt &&
44 | cbor.array[0].num === BigInt(1) &&
45 | cbor.array[1] instanceof CborArray
46 | )) throw new Error("invalid CBOR for `PeerSharingResponse`");
47 |
48 | return new PeerSharingResponse({
49 | peerAddresses: cbor.array[1].array.map( peerAddressFromCborObj )
50 | });
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/protocols/peer-sharing/messages/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./PeerSharingDone";
2 | export * from "./PeerSharingRequest";
3 | export * from "./PeerSharingResponse";
--------------------------------------------------------------------------------
/src/protocols/peer-sharing/peer-sharing-v11-12.cddl:
--------------------------------------------------------------------------------
1 | peerSharingMessage = msgShareRequest
2 | / msgSharePeers
3 | / msgDone
4 |
5 | msgShareRequest = [0 , maxShareAmount ]
6 | msgSharePeers = [1 , peerAddresses ]
7 | msgDone = [ 2 ]
8 |
9 | peerAddresses = [ * peerAddress ]
10 |
11 | maxShareAmount = byte
12 | byte = 0..255
13 |
14 | peerAddress = [0 , word32 , portNumber ] ; IPv4 + portNumber
15 | / [1 , word32 , word32 , word32 , word32 , flowInfo , scopeId , portNumber ] ; IPv6 + portNumber
16 |
17 | portNumber = word16
18 | flowInfo = word32
19 | scopeId = word32
20 | word16 = 0..65535
21 | word32 = 0..4294967295
--------------------------------------------------------------------------------
/src/protocols/peer-sharing/peer-sharing-v13.cddl:
--------------------------------------------------------------------------------
1 | ;
2 | ; Peer Sharing MiniProtocol
3 | ;
4 |
5 | peerSharingMessage = msgShareRequest
6 | / msgSharePeers
7 | / msgDone
8 |
9 | msgShareRequest = [0, byte]
10 | msgSharePeers = [1, peerAddresses]
11 | msgDone = [2]
12 |
13 | peerAddresses = [* peerAddress]
14 |
15 | byte = 0..255
16 |
17 | peerAddress = [0, word32, portNumber] ; ipv4 + portNumber
18 | / [1, word32, word32, word32, word32, portNumber] ; ipv6 + portNumber
19 |
20 | portNumber = word16
--------------------------------------------------------------------------------
/src/protocols/tx-submission/TxSubmitMessage.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "@harmoniclabs/obj-utils";
2 | import { TxSubmitDone, ITxSubmitDone } from "./messages/TxSubmitDone";
3 | import { TxSubmitInit, ITxSubmitInit } from "./messages/TxSubmitInit";
4 | import { TxSubmitReplyIds, ITxSubmitReplyIds } from "./messages/TxSubmitReplyIds";
5 | import { TxSubmitReplyTxs, ITxSubmitReplyTxs } from "./messages/TxSubmitReplyTxs";
6 | import { TxSubmitRequestIds, ITxSubmitRequestIds } from "./messages/TxSubmitRequestIds";
7 | import { TxSubmitRequestTxs, ITxSubmitRequestTxs } from "./messages/TxSubmitRequestTxs";
8 | import { CanBeCborString, Cbor, CborArray, CborObj, CborUInt, forceCborString } from "@harmoniclabs/cbor";
9 |
10 | export type TxSubmitMessage
11 | = TxSubmitInit
12 | | TxSubmitRequestIds
13 | | TxSubmitReplyIds
14 | | TxSubmitRequestTxs
15 | | TxSubmitReplyTxs
16 | | TxSubmitDone;
17 |
18 | export type ITxSubmitMessage
19 | = ITxSubmitInit
20 | | ITxSubmitRequestIds
21 | | ITxSubmitReplyIds
22 | | ITxSubmitRequestTxs
23 | | ITxSubmitReplyTxs
24 | | ITxSubmitDone;
25 |
26 | export function isTxSubmitMessage( stuff: any ): stuff is TxSubmitMessage {
27 | return isObject( stuff ) && (
28 | stuff instanceof TxSubmitInit ||
29 | stuff instanceof TxSubmitRequestIds ||
30 | stuff instanceof TxSubmitReplyIds ||
31 | stuff instanceof TxSubmitRequestTxs ||
32 | stuff instanceof TxSubmitReplyTxs ||
33 | stuff instanceof TxSubmitDone
34 | );
35 | }
36 |
37 | export function txSubmitSubmitMessageFromCbor( cbor: CanBeCborString ): TxSubmitMessage {
38 | const buff = cbor instanceof Uint8Array ?
39 | cbor :
40 | forceCborString( cbor ).toBuffer();
41 |
42 | const msg = txSubmitMessageFromCborObj( Cbor.parse( buff ) );
43 |
44 | // @ts-ignore Cannot assign to 'cborBytes' because it is a read-only property.ts(2540)
45 | msg.cborBytes = buff;
46 |
47 | return msg;
48 | }
49 |
50 | export function txSubmitMessageFromCborObj( cbor: CborObj ): TxSubmitMessage {
51 | if(!(
52 | cbor instanceof CborArray &&
53 | cbor.array.length >= 1 &&
54 | cbor.array[0] instanceof CborUInt
55 | )) throw new Error("invalid CBOR for `TxSubmitMessage`");
56 |
57 | const idx = Number( cbor.array[0].num );
58 |
59 | if( idx === 6 ) return TxSubmitInit.fromCborObj( cbor );
60 | if( idx === 0 ) return TxSubmitRequestIds.fromCborObj( cbor );
61 | if( idx === 1 ) return TxSubmitReplyIds.fromCborObj( cbor );
62 | if( idx === 2 ) return TxSubmitRequestTxs.fromCborObj( cbor );
63 | if( idx === 3 ) return TxSubmitReplyTxs.fromCborObj( cbor );
64 | if( idx === 4 ) return TxSubmitDone.fromCborObj( cbor );
65 |
66 | throw new Error("invalid CBOR for `TxSubmitMessage`; unknown index: " + idx);
67 | }
--------------------------------------------------------------------------------
/src/protocols/tx-submission/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./TxSubmitMessage";
2 | export * from "./TxSubmitServer";
3 | export * from "./TxSubmitClient";
4 | export * from "./messages";
--------------------------------------------------------------------------------
/src/protocols/tx-submission/interfaces/IMempool.ts:
--------------------------------------------------------------------------------
1 | import { MempoolTxHash, MempoolTxHashLike, MempoolTx, MempoolAppendResult, SupportedMempoolSize } from "./types";
2 |
3 | export interface SharedMempoolArgs {}
4 |
5 | export const defaultConfig: SharedMempoolArgs = {}
6 |
7 | export interface SharedMempoolConfig extends SharedMempoolArgs
8 | {
9 | readonly size: SupportedMempoolSize,
10 | readonly maxTxs: number,
11 | readonly allHashesSize: number
12 | readonly startHashesU8: number,
13 | readonly startTxsU8: number,
14 | }
15 |
16 |
17 | export interface TxHashAndSize
18 | {
19 | hash: MempoolTxHash;
20 | size: number;
21 | }
22 |
23 | export interface IMempool
24 | {
25 | readonly config: SharedMempoolConfig;
26 | getTxCount(): Promise;
27 | getAviableSpace(): Promise;
28 | getTxHashes(): Promise;
29 | getTxHashesAndSizes(): Promise;
30 | getTxs( hashes: MempoolTxHashLike[] ): Promise;
31 | append( hash: MempoolTxHashLike, tx: Uint8Array ): Promise;
32 | drop( hashes: MempoolTxHashLike[] ): Promise;
33 | }
34 |
--------------------------------------------------------------------------------
/src/protocols/tx-submission/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./IMempool";
--------------------------------------------------------------------------------
/src/protocols/tx-submission/interfaces/types/IndexedHash.ts:
--------------------------------------------------------------------------------
1 | import { MempoolTxHash } from "./MempoolTxHash";
2 |
3 | export type IndexedHash = [ hash: MempoolTxHash, idx: number ];
4 |
5 | export function findSortedIndex( hashes: IndexedHash[], index: number ): number
6 | {
7 | let low = 0;
8 | let high = hashes.length;
9 |
10 | while( low < high )
11 | {
12 | const mid = (low + high) >>> 1;
13 | if( hashes[mid][1] < index ) low = mid + 1;
14 | else high = mid;
15 | }
16 | return low;
17 | }
18 |
19 | export function insertSortedHash( hashes: IndexedHash[], indexedHash: IndexedHash ): void
20 | {
21 | void hashes.splice( findSortedIndex( hashes, indexedHash[1] ), 0, indexedHash );
22 | }
23 |
--------------------------------------------------------------------------------
/src/protocols/tx-submission/interfaces/types/MempoolAppendResult.ts:
--------------------------------------------------------------------------------
1 | export enum MempoolAppendStatus {
2 | Ok = 0,
3 | AlreadyPresent = 1,
4 | InsufficientSpace = 2,
5 | MaxTxsReached = 3,
6 | }
7 |
8 | Object.freeze( MempoolAppendStatus );
9 |
10 | export interface MempoolAppendResult {
11 | status: MempoolAppendStatus;
12 | nTxs: number;
13 | aviableSpace: number;
14 | }
15 |
16 | export function mempoolAppendResultToJson( res: MempoolAppendResult )
17 | {
18 | return {
19 | staus: MempoolAppendStatus[ res.status ],
20 | nTxs: res.nTxs,
21 | aviableSpace: res.aviableSpace,
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/src/protocols/tx-submission/interfaces/types/MempoolIndex.ts:
--------------------------------------------------------------------------------
1 | export interface MempoolIndex {
2 | start: number;
3 | size: number;
4 | }
5 |
--------------------------------------------------------------------------------
/src/protocols/tx-submission/interfaces/types/MempoolTx.ts:
--------------------------------------------------------------------------------
1 | import { toHex } from "@harmoniclabs/uint8array-utils";
2 | import { mempoolTxHashToString, U8Arr32 } from "./MempoolTxHash";
3 |
4 | export interface MempoolTx {
5 | hash: U8Arr32;
6 | bytes: Uint8Array;
7 | }
8 |
9 | export function mempoolTxToJson( memTx: MempoolTx )
10 | {
11 | return {
12 | hash: mempoolTxHashToString( memTx.hash ),
13 | bytes: toHex( memTx.bytes )
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/src/protocols/tx-submission/interfaces/types/MempoolTxHash.ts:
--------------------------------------------------------------------------------
1 | import { fromHex, toHex } from "@harmoniclabs/uint8array-utils";
2 |
3 | export type U8Arr = Uint8Array & { readonly length: Len };
4 |
5 | export type U8Arr32 = U8Arr<32>;
6 |
7 | export type MempoolTxHashBI = BigUint64Array & { length: 4 }; // 32 bytes
8 | export type MempoolTxHash = Int32Array & { length: 8 }; // 32 bytes
9 |
10 | export type MempoolTxHashLike = Uint8Array | Int32Array | BigUint64Array;
11 |
12 | export function mempoolTxHashToString( hash: MempoolTxHashLike ): string
13 | {
14 | return toHex( forceMempoolTxHashU8( hash ) );
15 | }
16 |
17 | export function mempoolTxHashFromString( hash: string ): MempoolTxHash
18 | {
19 | const u8 = fromHex( hash );
20 | return new Int32Array( u8.buffer ) as MempoolTxHash;
21 | }
22 |
23 | export function isMempoolTxHashLike( hashLike: any ): hashLike is MempoolTxHashLike
24 | {
25 | return (
26 | (
27 | hashLike instanceof Uint8Array &&
28 | hashLike.length === 32
29 | ) ||
30 | (
31 | hashLike instanceof Int32Array &&
32 | hashLike.length === 8
33 | ) ||
34 | (
35 | hashLike instanceof BigUint64Array &&
36 | hashLike.length === 4
37 | )
38 | );
39 | }
40 |
41 | export function forceMempoolTxHash( hashLike: MempoolTxHashLike ): MempoolTxHash
42 | {
43 | return new Int32Array( hashLike.buffer, 0, 8 ) as MempoolTxHash;
44 | }
45 |
46 | export function forceMempoolTxHashU8( hashLike: MempoolTxHashLike ): U8Arr32
47 | {
48 | const buff = new ArrayBuffer( 32 );
49 | const u8 = new Uint8Array( buff );
50 | const i32 = new Int32Array( buff );
51 | i32.set( forceMempoolTxHash( hashLike ) );
52 | return u8 as U8Arr32;
53 | }
54 |
55 | export function isMempoolTxHash( hash : any ): hash is MempoolTxHash
56 | {
57 | return hash instanceof Int32Array && hash.length === 8;
58 | }
59 |
60 | export function eqMempoolTxHash(a: MempoolTxHash, b: MempoolTxHash): boolean
61 | {
62 | return (
63 | a[0] === b[0] &&
64 | a[1] === b[1] &&
65 | a[2] === b[2] &&
66 | a[3] === b[3] &&
67 | a[4] === b[4] &&
68 | a[5] === b[5] &&
69 | a[6] === b[6] &&
70 | a[7] === b[7]
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/src/protocols/tx-submission/interfaces/types/SupportedMempoolSize.ts:
--------------------------------------------------------------------------------
1 | export enum MempoolSize {
2 | kb32 = 32768,
3 | kb64 = 65536,
4 | kb128 = 131072,
5 | kb256 = 262144
6 | }
7 |
8 | Object.freeze( MempoolSize );
9 |
10 | export type SupportedMempoolSize
11 | = 32768 // 32KB
12 | | 65536 // 64KB
13 | | 131072 // 128KB
14 | | 262144 // 256KB
15 |
16 |
17 | export function isSupportedMempoolSize(value: any): value is SupportedMempoolSize
18 | {
19 | return (
20 | value === 32768 ||
21 | value === 65536 ||
22 | value === 131072 ||
23 | value === 262144
24 | );
25 | }
26 |
27 | export function getMaxTxAllowed( size: SupportedMempoolSize ): number
28 | {
29 | // only odd max txs
30 | // to always allign memory as multiple of 8 ( 64 bit reads )
31 | // the first index is awlays omitted ( implicit )
32 | // so odd max txs => even n of indexes
33 | switch( size )
34 | {
35 | case 32768: return 63;
36 | case 65536: return 127;
37 |
38 | case 131072:
39 | case 262144: return 255;
40 | default: throw new Error(`Invalid SupportedMempoolSize: ${size}`);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/protocols/tx-submission/interfaces/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./IndexedHash";
2 | export * from "./MempoolTxHash";
3 | export * from "./MempoolAppendResult";
4 | export * from "./MempoolIndex";
5 | export * from "./MempoolTx";
6 | export * from "./SupportedMempoolSize";
--------------------------------------------------------------------------------
/src/protocols/tx-submission/messages/TxSubmitDone.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface ITxSubmitDone {}
5 |
6 | export function isITxSubmitDone( stuff: any ): stuff is ITxSubmitDone
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class TxSubmitDone
12 | implements ToCborString, ToCborObj, ITxSubmitDone
13 | {
14 | constructor() {};
15 |
16 | toJSON() { return this.toJson(); }
17 | toJson() { return {}; }
18 |
19 | toCborBytes(): Uint8Array
20 | {
21 | return this.toCbor().toBuffer();
22 | }
23 | toCbor(): CborString
24 | {
25 | return Cbor.encode( this.toCborObj() );
26 | }
27 | toCborObj(): CborArray
28 | {
29 | return new CborArray([ new CborUInt(4) ]);
30 | }
31 |
32 | static fromCbor( cbor: CanBeCborString ): TxSubmitDone
33 | {
34 | return TxSubmitDone.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
35 | }
36 | static fromCborObj( cbor: CborObj ): TxSubmitDone
37 | {
38 | if(!(
39 | cbor instanceof CborArray &&
40 | cbor.array[0] instanceof CborUInt &&
41 | cbor.array[0].num === BigInt(4)
42 | )) throw new Error("invalid CBOR for 'TxSubmitDone");
43 |
44 | return new TxSubmitDone();
45 | }
46 | }
--------------------------------------------------------------------------------
/src/protocols/tx-submission/messages/TxSubmitInit.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 |
4 | export interface ITxSubmitInit {}
5 |
6 | export function isITxSubmitInit( stuff: any ): stuff is ITxSubmitInit
7 | {
8 | return isObject( stuff );
9 | }
10 |
11 | export class TxSubmitInit
12 | implements ToCborString, ToCborObj, ITxSubmitInit
13 | {
14 | constructor() {};
15 |
16 | toJSON() { return this.toJson(); }
17 | toJson() { return {}; }
18 |
19 | toCborBytes(): Uint8Array
20 | {
21 | return this.toCbor().toBuffer();
22 | }
23 | toCbor(): CborString
24 | {
25 | return Cbor.encode( this.toCborObj() );
26 | }
27 | toCborObj(): CborArray
28 | {
29 | return new CborArray([ new CborUInt(6) ]);
30 | }
31 |
32 | static fromCbor( cbor: CanBeCborString ): TxSubmitInit
33 | {
34 | return TxSubmitInit.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
35 | }
36 | static fromCborObj( cbor: CborObj ): TxSubmitInit
37 | {
38 | if(!(
39 | cbor instanceof CborArray &&
40 | cbor.array[0] instanceof CborUInt &&
41 | cbor.array[0].num === BigInt(6)
42 | )) throw new Error("invalid CBOR for 'TxSubmitInit");
43 |
44 | return new TxSubmitInit();
45 | }
46 | }
--------------------------------------------------------------------------------
/src/protocols/tx-submission/messages/TxSubmitReplyIds.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborBytes, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { canBeUInteger, forceBigUInt, forceUInteger } from "../../types/ints";
4 |
5 | export interface ITxIdAndSize {
6 | txId: Uint8Array,
7 | txSize: number
8 | }
9 |
10 | export function isITxIdAndSize( stuff: any ): stuff is ITxIdAndSize
11 | {
12 | return isObject( stuff ) && (
13 | stuff.txId instanceof Uint8Array &&
14 | canBeUInteger( stuff.txSize )
15 | );
16 | }
17 |
18 | export function txIdAndSizeToCborObj({ txId, txSize }: ITxIdAndSize ): CborArray
19 | {
20 | return new CborArray([
21 | new CborBytes( txId ),
22 | new CborUInt( txSize )
23 | ]);
24 | }
25 |
26 | export function txIdAndSizeFromCborObj( cbor: CborObj ): ITxIdAndSize
27 | {
28 | if(!(
29 | cbor instanceof CborArray &&
30 | cbor.array.length >= 2 &&
31 | cbor.array[0] instanceof CborBytes &&
32 | cbor.array[1] instanceof CborUInt
33 | ))
34 | throw new Error("invalid CBOR for 'ITxIdAndSize'");
35 |
36 | return {
37 | txId: cbor.array[0].bytes,
38 | txSize: Number( cbor.array[1].num )
39 | };
40 | }
41 |
42 | export interface ITxSubmitReplyIds {
43 | response: ITxIdAndSize[]
44 | }
45 |
46 | export function isITxSubmitReplyIds( stuff: any ): stuff is TxSubmitReplyIds
47 | {
48 | return isObject( stuff ) && (
49 | typeof stuff.blocking === "boolean" &&
50 | Array.isArray( stuff.response ) && stuff.response.every( isITxIdAndSize )
51 | );
52 | }
53 |
54 | /**
55 | * The server requests aviable transactions ids
56 | **/
57 | export class TxSubmitReplyIds
58 | implements ToCborString, ToCborObj, TxSubmitReplyIds
59 | {
60 | readonly response: readonly Readonly[]
61 |
62 | constructor({ response }: ITxSubmitReplyIds)
63 | {
64 | if(!(
65 | Array.isArray( response ) &&
66 | response.every( isITxIdAndSize )
67 | )) throw new Error("invalid interface for 'TxSubmitReplyIds'")
68 |
69 | Object.defineProperties(
70 | this, {
71 | response: {
72 | value: Object.freeze( response.map( Object.freeze ) ),
73 | writable: false,
74 | enumerable: true,
75 | configurable: false
76 | }
77 | }
78 | )
79 | }
80 |
81 | toCborBytes(): Uint8Array
82 | {
83 | return this.toCbor().toBuffer();
84 | }
85 | toCbor(): CborString
86 | {
87 | return Cbor.encode( this.toCborObj() );
88 | }
89 | toCborObj(): CborArray
90 | {
91 | return new CborArray([
92 | new CborUInt(1),
93 | new CborArray( this.response.map( txIdAndSizeToCborObj ) )
94 | ]);
95 | }
96 |
97 | static fromCbor( cbor: CanBeCborString ): TxSubmitReplyIds
98 | {
99 | return TxSubmitReplyIds.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
100 | }
101 | static fromCborObj( cbor: CborObj ): TxSubmitReplyIds
102 | {
103 | if(!(
104 | cbor instanceof CborArray &&
105 | cbor.array.length >= 2 &&
106 | cbor.array[0] instanceof CborUInt &&
107 | cbor.array[0].num === BigInt(1) &&
108 | cbor.array[1] instanceof CborArray
109 | )) throw new Error("invalid CBOR for 'TxSubmitReplyIds");
110 |
111 | return new TxSubmitReplyIds({
112 | response: cbor.array[1].array.map( txIdAndSizeFromCborObj )
113 | });
114 | }
115 | }
--------------------------------------------------------------------------------
/src/protocols/tx-submission/messages/TxSubmitReplyTxs.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborBytes, CborObj, CborString, CborUInt, SubCborRef, ToCbor, ToCborObj, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils"
3 | import { getSubCborRef, subCborRefOrUndef } from "../../utils/getSubCborRef";
4 |
5 | export interface ITxSubmitReplyTxs {
6 | txs: Uint8Array[] | readonly Uint8Array[]
7 | }
8 |
9 | export function isITxSubmitReplyTx( stuff: any ): stuff is ITxSubmitReplyTxs
10 | {
11 | return isObject( stuff ) && (
12 | Array.isArray( stuff.txs ) && stuff.txs.every( (thing: any) => thing instanceof Uint8Array )
13 | );
14 | }
15 |
16 | export class TxSubmitReplyTxs
17 | implements ToCbor, ToCborObj, ITxSubmitReplyTxs
18 | {
19 | readonly txs: readonly Uint8Array[];
20 |
21 | constructor(
22 | msg: ITxSubmitReplyTxs,
23 | readonly cborRef: SubCborRef | undefined = undefined
24 | )
25 | {
26 | const txs = msg.txs;
27 | if(!isITxSubmitReplyTx({ txs })) throw new Error("invalid interface for 'TxSubmitReplyTx'");
28 |
29 | this.txs = txs;
30 | this.cborRef = cborRef ?? subCborRefOrUndef( msg );
31 | }
32 |
33 | toCborBytes(): Uint8Array
34 | {
35 | if( this.cborRef instanceof SubCborRef ) return this.cborRef.toBuffer();
36 | return this.toCbor().toBuffer();
37 | }
38 | toCbor(): CborString
39 | {
40 | if( this.cborRef instanceof SubCborRef ) return new CborString( this.cborRef.toBuffer() );
41 | return Cbor.encode( this.toCborObj() );
42 | }
43 | toCborObj(): CborArray
44 | {
45 | if( this.cborRef instanceof SubCborRef ) return Cbor.parse( this.cborRef.toBuffer() ) as CborArray;
46 | return new CborArray([
47 | new CborUInt(3),
48 | new CborArray(
49 | this.txs.map( id => new CborBytes( id ) ),
50 | {
51 | // CDDL specification comment
52 | // ; The codec only accepts infinit-length list encoding for tsIdList!
53 | indefinite: true
54 | }
55 | )
56 | ]);
57 | }
58 |
59 | static fromCbor( cbor: CanBeCborString ): TxSubmitReplyTxs
60 | {
61 | const bytes = cbor instanceof Uint8Array ? cbor : forceCborString( cbor ).toBuffer();
62 | return TxSubmitReplyTxs.fromCborObj( Cbor.parse( bytes, { keepRef: true } ), bytes );
63 | }
64 | static fromCborObj(
65 | cbor: CborObj,
66 | originalBytes: Uint8Array | undefined = undefined
67 | ): TxSubmitReplyTxs
68 | {
69 | if(!(
70 | cbor instanceof CborArray &&
71 | cbor.array.length >= 2 &&
72 | cbor.array[0] instanceof CborUInt &&
73 | cbor.array[0].num === BigInt(3) &&
74 | cbor.array[1] instanceof CborArray &&
75 | cbor.array[1].array.every( thing => thing instanceof CborBytes )
76 | )) throw new Error("invalid CBOR for 'TxSubmitReplyTx");
77 |
78 | return new TxSubmitReplyTxs({
79 | txs: cbor.array[1].array.map( id => (id as CborBytes).bytes )
80 | }, getSubCborRef( cbor, originalBytes ));
81 | }
82 | }
--------------------------------------------------------------------------------
/src/protocols/tx-submission/messages/TxSubmitRequestIds.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborSimple, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { canBeUInteger, forceUInteger } from "../../types/ints";
3 | import { isObject } from "@harmoniclabs/obj-utils";
4 | import { assert } from "../../utils/assert";
5 |
6 | export interface ITxSubmitRequestIds
7 | {
8 | blocking: boolean,
9 | knownTxCount: number | bigint
10 | requestedTxCount: number | bigint
11 | }
12 |
13 | export function isITxSubmitRequestIds( stuff: any ): stuff is TxSubmitRequestIds
14 | {
15 | return(
16 | isObject( stuff ) &&
17 | typeof stuff.blocking === "boolean" &&
18 | canBeUInteger( stuff.knownTxCount ) &&
19 | canBeUInteger( stuff.requestedTxCount )
20 | );
21 | }
22 |
23 | /**
24 | * Server request of available transactions ids
25 | **/
26 | export class TxSubmitRequestIds
27 | implements ToCborString, ToCborObj, ITxSubmitRequestIds
28 | {
29 | readonly blocking: boolean;
30 | readonly knownTxCount: number;
31 | readonly requestedTxCount: number;
32 |
33 | constructor({
34 | blocking,
35 | knownTxCount,
36 | requestedTxCount
37 | }: ITxSubmitRequestIds)
38 | {
39 | if(
40 | !isITxSubmitRequestIds({
41 | blocking,
42 | knownTxCount,
43 | requestedTxCount
44 | })
45 | ) throw new Error( "invalid TxSubmitRequestIds" );
46 |
47 | this.blocking = Boolean( blocking );
48 | this.knownTxCount = forceUInteger( knownTxCount );
49 | this.requestedTxCount = forceUInteger( requestedTxCount );
50 | }
51 |
52 | toCborBytes(): Uint8Array
53 | {
54 | return this.toCbor().toBuffer();
55 | }
56 | toCbor(): CborString
57 | {
58 | return Cbor.encode( this.toCborObj() );
59 | }
60 | toCborObj(): CborArray
61 | {
62 | return new CborArray([
63 | new CborUInt( 0 ),
64 | new CborSimple( this.blocking ? 1 : 0 ),
65 | new CborUInt( this.knownTxCount ),
66 | new CborUInt( this.requestedTxCount )
67 | ]);
68 | }
69 |
70 | static fromCbor( cbor: CanBeCborString ): TxSubmitRequestIds
71 | {
72 | return TxSubmitRequestIds.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
73 | }
74 | static fromCborObj( cbor: CborObj ): TxSubmitRequestIds
75 | {
76 | if(!(
77 | cbor instanceof CborArray &&
78 | cbor.array.length >= 4
79 | )) throw new Error( "invalid CBOR for 'TxSubmitRequestIds" );
80 |
81 | const [
82 | cborMsgTag,
83 | cborBlocking,
84 | cborKnownTxCount,
85 | cborRequestedTxCount
86 | ] = cbor.array;
87 |
88 | if(!(
89 | cborMsgTag instanceof CborUInt &&
90 | Number( cborMsgTag.num ) === 0 &&
91 | cborBlocking instanceof CborSimple &&
92 | cborKnownTxCount instanceof CborUInt &&
93 | cborRequestedTxCount instanceof CborUInt
94 | )) throw new Error( "invalid CBOR for 'TxSubmitRequestIds" );
95 |
96 | return new TxSubmitRequestIds({
97 | blocking: cborBlocking.simple === 1 ? true : false,
98 | knownTxCount: cborKnownTxCount.num,
99 | requestedTxCount: cborRequestedTxCount.num,
100 | });
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/protocols/tx-submission/messages/TxSubmitRequestTxs.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborBytes, CborObj, CborString, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils"
3 | import { assert } from "../../utils/assert";
4 |
5 | export interface ITxSubmitRequestTxs
6 | {
7 | ids: Uint8Array[]
8 | }
9 |
10 | export function isITxSubmitRequestTxs( stuff: any ): stuff is ITxSubmitRequestTxs
11 | {
12 | return(
13 | isObject( stuff ) &&
14 | Array.isArray( stuff.ids ) &&
15 | stuff.ids.every( ( thing: any ) => thing instanceof Uint8Array )
16 | );
17 | }
18 |
19 | /**
20 | * Server request of available transactions
21 | **/
22 | export class TxSubmitRequestTxs
23 | implements ToCborString, ToCborObj, ITxSubmitRequestTxs
24 | {
25 | readonly ids: Uint8Array[];
26 |
27 | constructor({ ids }: ITxSubmitRequestTxs)
28 | {
29 | assert(!isITxSubmitRequestTxs({ ids }), "invalid interface for 'TxSubmitRequestTxs'" );
30 |
31 | this.ids = ids;
32 | }
33 |
34 | toCborBytes(): Uint8Array
35 | {
36 | return this.toCbor().toBuffer();
37 | }
38 | toCbor(): CborString
39 | {
40 | return Cbor.encode( this.toCborObj() );
41 | }
42 | toCborObj(): CborArray
43 | {
44 | return new CborArray([
45 | new CborUInt( 2 ),
46 | new CborArray(
47 | this.ids.map(( id ) => new CborBytes( id )),
48 | {
49 | // CDDL specification comment
50 | // ; The codec only accepts infinit-length list encoding for tsIdList!
51 | indefinite: true
52 | }
53 | )
54 | ]);
55 | }
56 |
57 | static fromCbor( cbor: CanBeCborString ): TxSubmitRequestTxs
58 | {
59 | return TxSubmitRequestTxs.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
60 | }
61 | static fromCborObj( cbor: CborObj ): TxSubmitRequestTxs
62 | {
63 | if(!(
64 | cbor instanceof CborArray &&
65 | cbor.array.length >= 2
66 | )) throw new Error("invalid CBOR for 'TxSubmitRequestTxs");
67 |
68 | const [
69 | cborMsgTag,
70 | cborIds
71 | ] = cbor.array;
72 |
73 | if(!(
74 | cborMsgTag instanceof CborUInt &&
75 | Number( cborMsgTag.num ) === 2 &&
76 | cborIds instanceof CborArray
77 | )) throw new Error("invalid CBOR for 'TxSubmitRequestTxs");
78 |
79 | return new TxSubmitRequestTxs({
80 | ids: cborIds.array.map( ( id ) => ( id as CborBytes ).bytes )
81 | });
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/protocols/tx-submission/messages/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./TxSubmitDone";
2 | export * from "./TxSubmitInit";
3 | export * from "./TxSubmitReplyIds";
4 | export * from "./TxSubmitReplyTxs";
5 | export * from "./TxSubmitRequestIds";
6 | export * from "./TxSubmitRequestTxs";
--------------------------------------------------------------------------------
/src/protocols/tx-submission/tx-submission2.cddl:
--------------------------------------------------------------------------------
1 | ;
2 | ; TxSubmission mini-protocol v2
3 | ;
4 |
5 | ; reference implementation of the codec in :
6 | ; ouroboros-network/src/Ouroboros/Network/Protocol/TxSubmission2/Codec.hs
7 |
8 | txSubmission2Message
9 | = msgInit
10 | / msgRequestTxIds
11 | / msgReplyTxIds
12 | / msgRequestTxs
13 | / msgReplyTxs
14 | / tsMsgDone
15 |
16 |
17 | msgInit = [6]
18 | msgRequestTxIds = [0, tsBlocking, txCount, txCount]
19 | msgReplyTxIds = [1, [ *txIdAndSize] ]
20 | msgRequestTxs = [2, txIdList ]
21 | msgReplyTxs = [3, txList ]
22 | tsMsgDone = [4]
23 |
24 | tsBlocking = false / true
25 | txCount = word16
26 | ; The codec only accepts infinite-length list encoding for txIdList !
27 | txIdList = [ *txId ]
28 | txList = [ *tx ]
29 | txIdAndSize = [txId, txSizeInBytes]
30 | txSizeInBytes = word32
--------------------------------------------------------------------------------
/src/protocols/types/AsOptions.ts:
--------------------------------------------------------------------------------
1 |
2 | export type AsOptions = {
3 | [P in keyof T]?: boolean
4 | };
--------------------------------------------------------------------------------
/src/protocols/types/ChainPoint.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborBytes, CborObj, CborUInt, ToCbor, ToCborBytes, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { isObject } from "@harmoniclabs/obj-utils";
3 | import { canBeUInteger } from "./ints";
4 | import { toHex, uint8ArrayEq } from "@harmoniclabs/uint8array-utils";
5 |
6 | export interface IBlockHeaderHash {
7 | readonly slotNumber: number | bigint,
8 | readonly hash: Uint8Array
9 | }
10 |
11 | export function isIBlockHeaderHash( stuff: any ): stuff is IBlockHeaderHash
12 | {
13 | return (
14 | isObject( stuff ) &&
15 | canBeUInteger( stuff.slotNumber ) &&
16 | ( stuff.hash instanceof Uint8Array )
17 | );
18 | }
19 |
20 | export interface IChainPoint {
21 | blockHeader?: IBlockHeaderHash
22 | }
23 |
24 | export interface IOriginPoint extends IChainPoint {
25 | blockHeader: undefined
26 | }
27 |
28 | export interface IRealPoint extends IChainPoint {
29 | blockHeader: IBlockHeaderHash
30 | }
31 |
32 | export function isIChainPoint( stuff: any ): stuff is IChainPoint
33 | {
34 | return (
35 | isObject( stuff ) &&
36 | (
37 | typeof stuff.blockHeader === "undefined" ||
38 | isIBlockHeaderHash( stuff.blockHeader )
39 | )
40 | );
41 | }
42 |
43 | export function isOriginPoint( point: IChainPoint ): point is IOriginPoint
44 | {
45 | return typeof point.blockHeader === "undefined" || !isIBlockHeaderHash( point.blockHeader );
46 | }
47 |
48 | export function isRealPoint( point: IChainPoint ): point is IRealPoint
49 | {
50 | return isIBlockHeaderHash( point.blockHeader );
51 | }
52 |
53 | export class ChainPoint
54 | implements ToCborObj, ToCborString, ToCborBytes, IChainPoint
55 | {
56 | constructor( chainPoint: IChainPoint )
57 | {
58 | if( !isIChainPoint( chainPoint ) )
59 | throw new Error("invalid IChainPoint interface");
60 |
61 | this.blockHeader = chainPoint.blockHeader ? { ...chainPoint.blockHeader } : undefined;
62 | }
63 |
64 | readonly blockHeader?: IBlockHeaderHash;
65 |
66 | isOrigin(): boolean { return isOriginPoint( this ) }
67 |
68 | static get origin(): ChainPoint
69 | {
70 | return new ChainPoint({});
71 | }
72 |
73 | toJSON() { return this.toJson(); }
74 | toJson()
75 | {
76 | if( this.isOrigin() ) return {};
77 | return {
78 | blockHeader: {
79 | hash: toHex( this.blockHeader!.hash ),
80 | slot: Number( this.blockHeader!.slotNumber )
81 | }
82 | };
83 | }
84 | toString(): string
85 | {
86 | if( this.isOrigin() ) return "(point: origin)";
87 |
88 | return `(point: ( hash: ${toHex( this.blockHeader!.hash )}, slot: ${this.blockHeader!.slotNumber} ))`
89 | }
90 |
91 | toCborBytes(): Uint8Array
92 | {
93 | return this.toCbor().toBuffer();
94 | }
95 | toCbor()
96 | {
97 | return Cbor.encode( this.toCborObj() )
98 | }
99 | toCborObj(): CborArray
100 | {
101 | if( this.isOrigin() || this.blockHeader === undefined ) return new CborArray([]);
102 |
103 | return new CborArray([
104 | new CborUInt( this.blockHeader.slotNumber ),
105 | new CborBytes( this.blockHeader.hash )
106 | ]);
107 | }
108 |
109 | static fromCbor( cbor: CanBeCborString ): ChainPoint
110 | {
111 | return ChainPoint.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
112 | }
113 | static fromCborObj( cbor: CborObj ): ChainPoint
114 | {
115 | if(!(cbor instanceof CborArray)) throw new Error("invalid CBOR for 'ChainPoint'");
116 |
117 | if( cbor.array.length === 0 ) return new ChainPoint({}); // origin
118 |
119 | const [ slot, hash ] = cbor.array;
120 |
121 | if(!(
122 | slot instanceof CborUInt &&
123 | hash instanceof CborBytes
124 | )) throw new Error("invalid CBOR for 'ChainPoint'");
125 |
126 | return new ChainPoint({
127 | blockHeader: {
128 | slotNumber: slot.num,
129 | hash: hash.bytes
130 | }
131 | });
132 | }
133 |
134 | static eq( a: IChainPoint, b: IChainPoint ): boolean
135 | {
136 | return (
137 | ( a.blockHeader === undefined && b.blockHeader === undefined ) ||
138 | (
139 | isIBlockHeaderHash( a.blockHeader ) &&
140 | isIBlockHeaderHash( b.blockHeader ) &&
141 | BigInt(a.blockHeader.slotNumber) === BigInt(b.blockHeader.slotNumber) &&
142 | uint8ArrayEq( a.blockHeader.hash, b.blockHeader.hash )
143 | )
144 | );
145 | }
146 | }
--------------------------------------------------------------------------------
/src/protocols/types/ChainTip.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, CborArray, CborObj, CborUInt, ToCbor, ToCborObj, ToCborString, forceCborString } from "@harmoniclabs/cbor";
2 | import { ChainPoint, IChainPoint, isIChainPoint } from "./ChainPoint";
3 | import { isObject } from "@harmoniclabs/obj-utils";
4 | import { canBeUInteger, forceBigUInt } from "./ints";
5 |
6 | export interface IChainTip {
7 | point: IChainPoint,
8 | blockNo: number | bigint
9 | }
10 |
11 | export function isIChainTip( stuff: any ): stuff is IChainTip
12 | {
13 | return (
14 | isObject( stuff ) &&
15 | isIChainPoint( stuff.point ) &&
16 | canBeUInteger( stuff.blockNo )
17 | )
18 | }
19 |
20 | export class ChainTip
21 | implements ToCborString, ToCborObj, IChainTip
22 | {
23 | readonly point: ChainPoint;
24 | readonly blockNo: bigint;
25 |
26 | constructor({ point, blockNo }: IChainTip)
27 | {
28 | if(!(
29 | isIChainPoint( point ) &&
30 | canBeUInteger( blockNo )
31 | )) throw new Error("invalid IChainTip interface");
32 |
33 | this.point = point instanceof ChainPoint ? point : new ChainPoint( point );
34 | this.blockNo = forceBigUInt( blockNo );
35 | }
36 |
37 | toJSON() { return this.toJson(); }
38 | toJson()
39 | {
40 | return {
41 | point: this.point.toJson(),
42 | blockNo: Number( this.blockNo )
43 | };
44 | }
45 |
46 | toString(): string
47 | {
48 | return `(tip: ${this.point.toString()} (${this.blockNo}))`;
49 | }
50 |
51 | toCbor()
52 | {
53 | return Cbor.encode( this.toCborObj() )
54 | }
55 | toCborObj(): CborArray
56 | {
57 | return new CborArray([
58 | this.point.toCborObj(),
59 | new CborUInt( this.blockNo )
60 | ]);
61 | }
62 |
63 | static fromCbor( cbor: CanBeCborString ): ChainTip
64 | {
65 | return ChainTip.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
66 | }
67 | static fromCborObj( cbor: CborObj ): ChainTip
68 | {
69 | if(!(cbor instanceof CborArray)) throw new Error("invalid CBOR for 'ChainTip'");
70 |
71 | const [ _point, _blockNo ] = cbor.array;
72 |
73 | if(!(
74 | _blockNo instanceof CborUInt
75 | )) throw new Error("invalid CBOR for 'ChainTip'");
76 |
77 | return new ChainTip({
78 | point: ChainPoint.fromCborObj( _point ),
79 | blockNo: _blockNo.num
80 | });
81 | }
82 |
83 | static eq( a: IChainTip, b: IChainTip ): boolean
84 | {
85 | return (
86 | ChainPoint.eq( a.point, b.point ) &&
87 | a.blockNo === b.blockNo
88 | );
89 | }
90 | }
--------------------------------------------------------------------------------
/src/protocols/types/Definitely.ts:
--------------------------------------------------------------------------------
1 |
2 | export type Definitely = {
3 | [P in keyof T]-?: (
4 | T[P] extends ((infer Something) | undefined) ?
5 | Something :
6 | T[P]
7 | )
8 | };
9 |
10 | /*
11 | interface Something {
12 | a: string;
13 | b: number | undefined;
14 | c?: boolean;
15 | d: boolean | never;
16 | f?: number | undefined;
17 | }
18 |
19 | // type DefinitelySomething = {
20 | // a: string;
21 | // b: number;
22 | // c: boolean;
23 | // d: boolean | never;
24 | // f: number;
25 | // }
26 | type DefinitelySomething = Definitely;
27 | */
--------------------------------------------------------------------------------
/src/protocols/types/OptField.ts:
--------------------------------------------------------------------------------
1 |
2 | export type OptField = Omit & Partial>;
--------------------------------------------------------------------------------
/src/protocols/types/RealPoint.ts:
--------------------------------------------------------------------------------
1 | import { CanBeCborString, Cbor, forceCborString, CborObj, CborArray, CborUInt, CborBytes } from "@harmoniclabs/cbor";
2 | import { ChainPoint, IBlockHeaderHash, IChainPoint, IRealPoint, isOriginPoint } from "./ChainPoint";
3 |
4 | export class RealPoint extends ChainPoint
5 | implements IRealPoint
6 | {
7 | readonly blockHeader!: IBlockHeaderHash;
8 |
9 | constructor( point: IRealPoint )
10 | {
11 | if( isOriginPoint( point ) )
12 | throw new Error("'RealPoint' cannot be origin");
13 | super( point );
14 | }
15 |
16 | static fromCbor( cbor: CanBeCborString ): RealPoint
17 | {
18 | return RealPoint.fromCborObj( Cbor.parse( forceCborString( cbor ) ) );
19 | }
20 | static fromCborObj( cbor: CborObj ): RealPoint
21 | {
22 | if(!(cbor instanceof CborArray)) throw new Error("invalid CBOR for 'ChainPoint'");
23 |
24 | if( cbor.array.length < 2 )
25 | throw new Error("'RealPoint' cannot be origin; while parsing cbor");
26 |
27 | const [ slot, hash ] = cbor.array;
28 |
29 | if(!(
30 | slot instanceof CborUInt &&
31 | hash instanceof CborBytes
32 | )) throw new Error("invalid CBOR for 'ChainPoint'");
33 |
34 | return new RealPoint({
35 | blockHeader: {
36 | slotNumber: slot.num,
37 | hash: hash.bytes
38 | }
39 | });
40 | }
41 | }
--------------------------------------------------------------------------------
/src/protocols/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ChainPoint";
2 | export * from "./ChainTip";
3 | export * from "./RealPoint";
--------------------------------------------------------------------------------
/src/protocols/types/ints.ts:
--------------------------------------------------------------------------------
1 |
2 | export function canBeInteger( stuff: any ): stuff is (number | bigint)
3 | {
4 | return ( typeof stuff === "number" || typeof stuff === "bigint" );
5 | }
6 |
7 | export function forceInteger( stuff: number | bigint ): number
8 | {
9 | return Math.round( Number( stuff ) );
10 | }
11 |
12 | export function canBeUInteger( stuff: any ): stuff is (number | bigint)
13 | {
14 | return canBeInteger( stuff ) && stuff >= 0;
15 | }
16 |
17 | export function isInteger( stuff: number | bigint ): boolean
18 | {
19 | return Number.isSafeInteger( stuff ) || typeof stuff === "bigint";
20 | }
21 |
22 | export function isUInteger( stuff: number | bigint ): boolean
23 | {
24 | return isInteger( stuff ) && stuff >= 0;
25 | }
26 |
27 | export function forceUInteger( stuff: number | bigint ): number
28 | {
29 | return Math.round( Math.abs( Number( stuff ) ) );
30 | }
31 |
32 | export function forceBigUInt( stuff: number | bigint ): bigint
33 | {
34 | if( typeof stuff === "number" ) return BigInt( forceUInteger( stuff ) );
35 | return stuff < 0 ? -stuff : stuff;
36 | }
--------------------------------------------------------------------------------
/src/protocols/utils/assert.ts:
--------------------------------------------------------------------------------
1 | export function assert( condition: boolean, errorMessage: string | Error , addInfos?: any ,...args: any[])
2 | {
3 | if( condition ) return;
4 |
5 | args.length > 0 && console.error(...args);
6 | addInfos && console.error(addInfos);
7 |
8 | if( errorMessage instanceof Error )
9 | {
10 | throw errorMessage
11 | };
12 |
13 | throw new Error( errorMessage );
14 | }
15 |
--------------------------------------------------------------------------------
/src/protocols/utils/bool.ts:
--------------------------------------------------------------------------------
1 | export function bool( stuff: any, defaultValue: boolean = false ): boolean
2 | {
3 | return typeof stuff === 'boolean' ? stuff : Boolean(
4 | typeof stuff === 'undefined' ? defaultValue : stuff
5 | );
6 | }
7 |
8 | export function isMaybeBool( stuff: any ): stuff is boolean | undefined
9 | {
10 | return typeof stuff === 'undefined' || typeof stuff === 'boolean';
11 | }
--------------------------------------------------------------------------------
/src/protocols/utils/getSubCborRef.ts:
--------------------------------------------------------------------------------
1 | import { Cbor, CborObj, SubCborRef } from "@harmoniclabs/cbor";
2 |
3 | /**
4 | * given a @param {CborObj} cObj instance of `CborObj`
5 | * @returns {SubCborRef} a `SubCborRef` corresponding to the encoded object
6 | *
7 | * if `cObj` has a `cborRef` propery instance of `SubCborRef` it will return that property;
8 | *
9 | * otherwise it will encode the object and return a new `SubCborRef`
10 | */
11 | export function getSubCborRef(
12 | cObj: CborObj,
13 | originalBytes: Uint8Array | undefined = undefined
14 | ): SubCborRef
15 | {
16 | if( cObj.subCborRef instanceof SubCborRef )
17 | {
18 | return cObj.subCborRef.clone(); // does not clone bytes, only the object
19 | }
20 |
21 | const bytes = originalBytes instanceof Uint8Array ? originalBytes : Cbor.encode( cObj ).toBuffer();
22 |
23 | // encoding might have created a new SubCborRef
24 | if( (cObj as CborObj).subCborRef instanceof SubCborRef )
25 | {
26 | return (cObj.subCborRef as unknown as SubCborRef).clone();
27 | }
28 |
29 | return new SubCborRef({
30 | _bytes: bytes,
31 | start: 0,
32 | end: bytes.length
33 | });
34 | }
35 |
36 | export function subCborRefOrUndef( thing: any ): SubCborRef | undefined
37 | {
38 | if( thing instanceof SubCborRef )
39 | {
40 | return thing;
41 | }
42 | if( thing.cborRef instanceof SubCborRef )
43 | {
44 | return thing.cborRef.clone();
45 | }
46 | return undefined;
47 | }
--------------------------------------------------------------------------------
/src/protocols/utils/isByte.ts:
--------------------------------------------------------------------------------
1 | export function isByte( n: number | bigint ): boolean {
2 | if( typeof n === "bigint" ) {
3 | return( n >= 0 && n <= 255 );
4 | }
5 |
6 | return( Number.isSafeInteger( n ) && ( n >= 0 && n <= 255 ) );
7 | }
--------------------------------------------------------------------------------
/src/protocols/utils/isWord16.ts:
--------------------------------------------------------------------------------
1 |
2 | export function isWord16( n: number | bigint ): boolean
3 | {
4 | if( typeof n === "bigint" )
5 | {
6 | return n >= 0 && n <= 65535;
7 | }
8 |
9 | return Number.isSafeInteger( n ) && (
10 | n >= 0 && n <= 65535
11 | );
12 | }
--------------------------------------------------------------------------------
/src/protocols/utils/isWord32.ts:
--------------------------------------------------------------------------------
1 | export function isWord32( n: number | bigint ): boolean {
2 | if( typeof n === "bigint" ) {
3 | return( n >= 0 && n <= 4294967295 );
4 | }
5 |
6 | return( Number.isSafeInteger( n ) && ( n >= 0 && n <= 4294967295 ) );
7 | }
--------------------------------------------------------------------------------
/src/protocols/utils/safeParseInt.ts:
--------------------------------------------------------------------------------
1 | export function safeParseInt( stuff: any ): number | undefined
2 | {
3 | try {
4 | return parseInt( stuff );
5 | } catch { return undefined; }
6 | }
--------------------------------------------------------------------------------