diff --git a/.eslintrc.json b/.eslintrc.json index cfdf1ed..bd3056e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -23,7 +23,15 @@ "camelcase": "off", "@typescript-eslint/camelcase": [ "error", - { "properties": "never", "allow": ["hash_or_height", "include_mempool"] } + { + "properties": "never", + "allow": [ + "hash_or_height", + "include_mempool", + "template_request", + "fee_delta" + ] + } ] } } diff --git a/.prettierignore b/.prettierignore index 567609b..f3e9398 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ build/ +package-lock.json diff --git a/README.md b/README.md index e49d1dd..991c089 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,54 @@ const result = await client.generatetoaddress( ); ``` +### Mining + +- [`getblocktemplate`](https://bitcoin.org/en/developer-reference#getblocktemplate) + +```javascript +const rules = ["segwit"]; +const mode = "template"; +const capabilities = ["serverlist", "proposal"]; +const template_request = { rules, mode, capabilities }; +const result = await client.getblocktemplate({ template_request }); +``` + +- [`getmininginfo`](https://bitcoin.org/en/developer-reference#getmininginfo) + +```javascript +const result = await client.getmininginfo(); +``` + +- [`getnetworkhashps`](https://bitcoin.org/en/developer-reference#getnetworkhashps) + +```javascript +const nblocks = 100; +const height = 100; +const result = await client.getnetworkhashps({ nblocks, height }); +``` + +- [`prioritisetransaction`](https://bitcoin.org/en/developer-reference#prioritisetransaction) + +```javascript +const txid = "9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5"; +const fee_delta = 1000; +const result = await client.prioritisetransaction({ txid, fee_delta }); +``` + +- [`submitblock`](https://bitcoin.org/en/developer-reference#submitblock) + +```javascript +const hexdata = "hexEncodedBlock"; +const result = await client.submitblock({ hexdata }); +``` + +- [`submitheader`](https://bitcoin.org/en/developer-reference#submitheader) + +```javascript +const hexdata = "hexEncodedBlockHeaderData"; +const result = await client.submitheader({ hexdata }); +``` + ### ZMQ - [`getzmqnotifications`](https://bitcoincore.org/en/doc/0.17.0/rpc/zmq/getzmqnotifications/) diff --git a/package-lock.json b/package-lock.json index e3e09e9..5c33e3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "rpc-bitcoin", - "version": "1.5.0", + "version": "1.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7f83af4..c5cd1b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rpc-bitcoin", - "version": "1.5.0", + "version": "1.6.0", "description": "A TypeScript library to make RPC and HTTP REST requests to Bitcoin Core", "main": "build/index.js", "type": "module", diff --git a/src/rpc.ts b/src/rpc.ts index b819b8f..3ca1ac8 100644 --- a/src/rpc.ts +++ b/src/rpc.ts @@ -77,6 +77,22 @@ export type GenerateToAddressParams = GenerateParams & { address: string; }; +export type GetBlockTemplateParams = { + template_request: { + mode?: "template" | "proposal"; + capabilities?: string[]; + rules: string[]; + }; +}; + +export type PrioritiseTransactionParams = TxId & { + fee_delta: number; +}; + +export type HexData = { + hexdata: string; +}; + export class RPCClient extends RESTClient { wallet?: string; fullResponse?: boolean; @@ -340,6 +356,51 @@ export class RPCClient extends RESTClient { ); } + /** + * @description It returns data needed to construct a block to work on. + */ + async getblocktemplate({ template_request }: GetBlockTemplateParams) { + return this.rpc("getblocktemplate", { template_request }); + } + + /** + * @description Returns a json object containing mining-related information. + */ + async getmininginfo() { + return this.rpc("getmininginfo"); + } + + /** + * @description Returns the estimated network hashes per second based on the last `n` blocks. + */ + async getnetworkhashps({ nblocks = 120, height = -1 } = {}) { + return this.rpc("getnetworkhashps", { nblocks, height }); + } + + /** + * @description Accepts the transaction into mined blocks at a higher (or lower) priority + */ + async prioritisetransaction({ + txid, + fee_delta + }: PrioritiseTransactionParams) { + return this.rpc("prioritisetransaction", { txid, fee_delta }); + } + + /** + * @description Attempts to submit new block to network. + */ + async submitblock({ hexdata }: HexData) { + return this.rpc("submitblock", { hexdata }); + } + + /** + * @description Decode the given hexdata as a header and submit it as a candidate chain tip if valid. + */ + async submitheader({ hexdata }: HexData) { + return this.rpc("submitheader", { hexdata }); + } + /** * @description Returns information about the active ZeroMQ notifications. */ diff --git a/test/rpc.spec.ts b/test/rpc.spec.ts index 0587993..d04112f 100644 --- a/test/rpc.spec.ts +++ b/test/rpc.spec.ts @@ -1,9 +1,4 @@ -import { - RPCClient, - GetBlockParams, - ScanTxOutSetParams, - Descriptor -} from "../."; +import { RPCClient, ScanTxOutSetParams } from "../."; import * as nock from "nock"; import * as assert from "assert"; @@ -136,8 +131,8 @@ suite("RPCClient", () => { test(".getblock()", async () => { const blockhash = "000000004182034f427d463b92162d35d0accef9ea0c5354a87e870ca1815b4c"; - const verbosity = 2; - const params: GetBlockParams = { blockhash, verbosity }; + const verbosity: 2 = 2; + const params = { blockhash, verbosity }; const request = { params, method: "getblock", id, jsonrpc }; const result = { hash: @@ -804,12 +799,10 @@ suite("RPCClient", () => { test(".scantxoutset()", async () => { const action = "start"; const desc1 = "addr(mxosQ4CvQR8ipfWdRktyB3u16tauEdamGc)"; - const desc2: Descriptor = { - desc: - "wpkh([d34db33f/84'/0'/0']tpubD6NzVbkrYhZ4YTN7usjEzYmfu4JKqnfp9RCbDmdKH78vTyuwgQat8vRw5cX1YaZZvFfQrkHrM2XsyfA8cZE1thA3guTBfTkKqbhCDpcKFLG/0/*)#8gfuh6ex", - range: [1, 20] - }; - const scanobjects = [desc1, desc2]; + const desc = + "wpkh([d34db33f/84'/0'/0']tpubD6NzVbkrYhZ4YTN7usjEzYmfu4JKqnfp9RCbDmdKH78vTyuwgQat8vRw5cX1YaZZvFfQrkHrM2XsyfA8cZE1thA3guTBfTkKqbhCDpcKFLG/0/*)#8gfuh6ex"; + const range: [number, number] = [1, 20]; + const scanobjects = [desc1, { desc, range }]; const params: ScanTxOutSetParams = { action, scanobjects }; const request = { params, method: "scantxoutset", id, jsonrpc }; const result = { @@ -1105,6 +1098,158 @@ suite("RPCClient", () => { }); }); + suite("Mining", () => { + test(".getblocktemplate()", async () => { + const rules = ["segwit"]; + const mode: "template" = "template"; + const capabilities = ["serverlist", "proposal"]; + const template_request = { rules, mode, capabilities }; + const params = { template_request }; + const request = { params, method: "getblocktemplate", id, jsonrpc }; + const result = { + capabilities: ["proposal"], + version: 536870912, + rules: ["csv", "segwit"], + vbavailable: {}, + vbrequired: 0, + previousblockhash: + "00000000001eae7e020859bd4e814194768171fafa32ec0ff29a7f7718c68e3e", + transactions: [ + { + data: + "02000000011dee34a06e97aa79e62a962deaaf36af4ade5cfa9d368e59b5f07d7a95f56a9c000000006a4730440220122a6742b92f4f7028d180a8439cd28923ac786c7014a511ff6a43a0b1f0a19c02201cef3090451ce5c40a11e511e00e906ff44547a21d96ba639b6f56bf43cdf88a012103842711dd54b0e087bc458952e482ef9b7605a74e02267d8e613e90c900b46b2ffeffffff029fe50a000000000017a914c6953707c8fe999f1597445182a316efc8c9a4f087480504000000000017a914895227f5e3b944768038354203245e3c8934acf8870e2b1800", + txid: + "304df7393fd7a5fa6ca3010f52c19a1b11294deb51040645ebc09c76f0c39e16", + hash: + "304df7393fd7a5fa6ca3010f52c19a1b11294deb51040645ebc09c76f0c39e16", + depends: [], + fee: 22361, + sigops: 0, + weight: 884 + }, + { + data: + "02000000000101e451d098b58f159d1879155f3b358ac3f1c9bca1b899c1f11556cfe97dd38109010000001716001490f5c95ca55492835d7b96f205122954496520b2feffffff02eb5c8e210200000017a914dff56b87c21831d6bc8a84407ceedb43ac700b8b87febc37000000000017a914c8847d0cced847080f0bd1811af48f0cde3b8ed38702473044022039395e975051eb8b302fd0bc21b82b336571f6251afe9ecf59da8a5810cfb15802201b005281279f93f3945985c9419cb6d6fb5f2a8e57309f125e71edd6be07383c012102d63dbd2425c008dc5af42f75e35cb14ab12f2d79444dc722aacd7d0b6da8a2260e2b1800", + txid: + "be76e368477a3de6c2f4ee69c578019384dfaf05e69b4481bbc84997691175b5", + hash: + "23f676d0e98ff7c0ab6b118ca2fcf9f073cf7e916a5c61673eb36fb1c4f79221", + depends: [], + fee: 16796, + sigops: 1, + weight: 661 + } + ], + coinbaseaux: { flags: "" }, + coinbasevalue: 39633673, + longpollid: + "00000000001eae7e020859bd4e814194768171fafa32ec0ff29a7f7718c68e3e1596", + target: + "0000000000000175ed0000000000000000000000000000000000000000000000", + mintime: 1571918831, + mutable: ["time", "transactions", "prevblock"], + noncerange: "00000000ffffffff", + sigoplimit: 80000, + sizelimit: 4000000, + weightlimit: 4000000, + curtime: 1571924701, + bits: "1a0175ed", + height: 1583887, + default_witness_commitment: + "6a24aa21a9ed78a54605e10113365da2095badf375ae434b5abcda3d864a73c91477bd480676" + }; + const response = { result, error, id }; + nock(uri) + .post("/", request) + .times(1) + .basicAuth(auth) + .reply(200, response); + const data = await client.getblocktemplate(params); + assert.deepStrictEqual(data, result); + }); + + test(".getmininginfo()", async () => { + const request = { params: {}, method: "getmininginfo", id, jsonrpc }; + const result = { + blocks: 1583887, + currentblockweight: 235257, + currentblocktx: 134, + difficulty: 1, + networkhashps: 26197621661352.18, + pooledtx: 101, + chain: "test", + warnings: "Warning: unknown new rules activated (versionbit 28)" + }; + const response = { result, error, id }; + nock(uri) + .post("/", request) + .times(1) + .basicAuth(auth) + .reply(200, response); + const data = await client.getmininginfo(); + assert.deepStrictEqual(data, result); + }); + + test(".getnetworkhashps()", async () => { + const params = { nblocks: 100, height: 100 }; + const request = { params, method: "getnetworkhashps", id, jsonrpc }; + const result = 40893390.77406456; + const response = { result, error, id }; + nock(uri) + .post("/", request) + .times(1) + .basicAuth(auth) + .reply(200, response); + const data = await client.getnetworkhashps(params); + assert.deepStrictEqual(data, result); + }); + + test(".prioritisetransaction()", async () => { + const txid = + "9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5"; + const fee_delta = 100; + const params = { txid, fee_delta }; + const request = { params, method: "prioritisetransaction", id, jsonrpc }; + const result = true; + const response = { result, error, id }; + nock(uri) + .post("/", request) + .times(1) + .basicAuth(auth) + .reply(200, response); + const data = await client.prioritisetransaction(params); + assert.deepStrictEqual(data, result); + }); + + test(".submitblock()", async () => { + const params = { hexdata: "PreviosBlockHex" }; + const request = { params, method: "submitblock", id, jsonrpc }; + const result = "duplicate"; + const response = { result, error, id }; + nock(uri) + .post("/", request) + .times(1) + .basicAuth(auth) + .reply(200, response); + const data = await client.submitblock(params); + assert.deepStrictEqual(data, result); + }); + + test(".submitheader()", async () => { + const params = { hexdata: "PreviosBlockHeaderHex" }; + const request = { params, method: "submitheader", id, jsonrpc }; + const result = null; + const response = { result, error, id }; + nock(uri) + .post("/", request) + .times(1) + .basicAuth(auth) + .reply(200, response); + const data = await client.submitheader(params); + assert.deepStrictEqual(data, result); + }); + }); + suite("Zmq", () => { test(".getzmqnotifications()", async () => { const params = {};