├── .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 | } --------------------------------------------------------------------------------