mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 17:15:03 -07:00
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b971e9ce4c | ||
|
|
e1b88207d3 | ||
|
|
e7f7cb52af | ||
|
|
6cf1c225a4 | ||
|
|
fd775965e5 | ||
|
|
554a8916a7 | ||
|
|
fda3f21b71 | ||
|
|
0201703e35 | ||
|
|
ddb7a89615 | ||
|
|
f9e49a8955 | ||
|
|
2912dd5071 | ||
|
|
4282233d2f | ||
|
|
8ce057b3b5 | ||
|
|
7bb436bf1f | ||
|
|
0917911eb0 | ||
|
|
de2b41a3c1 | ||
|
|
1cd5bcc97d | ||
|
|
277aa10144 | ||
|
|
f08103bd8c | ||
|
|
811e7972f9 | ||
|
|
c654a706ae | ||
|
|
e0239d2805 | ||
|
|
fc96b9ea45 | ||
|
|
ad1f32cc2c | ||
|
|
917d312211 | ||
|
|
76e2426c30 | ||
|
|
098fe2867a | ||
|
|
6e3902b732 | ||
|
|
8c62817f73 | ||
|
|
9f723782ab | ||
|
|
2e296c00d5 | ||
|
|
437b0c9bc4 | ||
|
|
deed775d55 | ||
|
|
ed537658ac | ||
|
|
29e0d884e3 | ||
|
|
e35bacc648 |
+5
-7
@@ -10,6 +10,9 @@ BITCOIN_RPC_PASSWORD=
|
|||||||
BITCOIN_RPC_PORT=8332
|
BITCOIN_RPC_PORT=8332
|
||||||
BITCOIN_RPC_TIMEOUT=10000
|
BITCOIN_RPC_TIMEOUT=10000
|
||||||
|
|
||||||
|
# You can use this instead of BITCOIN_RPC_USER and BITCOIN_RPC_PASSWORD
|
||||||
|
BITCOIN_RPC_COOKIEFILE=
|
||||||
|
|
||||||
# Enable in bitcoin.conf with
|
# Enable in bitcoin.conf with
|
||||||
# zmqpubrawblock=tcp://*:3000
|
# zmqpubrawblock=tcp://*:3000
|
||||||
# BITCOIN_ZMQ_HOST="tcp://192.168.1.100:3000"
|
# BITCOIN_ZMQ_HOST="tcp://192.168.1.100:3000"
|
||||||
@@ -31,10 +34,5 @@ DEV_FEE_ADDRESS=
|
|||||||
NETWORK=mainnet
|
NETWORK=mainnet
|
||||||
|
|
||||||
API_SECURE=false
|
API_SECURE=false
|
||||||
|
# Default is "Public-Pool", you can change it to any string it will be removed if it will make the block or coinbase script too big
|
||||||
ENABLE_SOLO=false
|
POOL_IDENTIFIER="Public-Pool"
|
||||||
ENABLE_PROXY=true
|
|
||||||
|
|
||||||
BRAIINS_ACCESS_TOKEN=
|
|
||||||
|
|
||||||
PROXY_PORT=3333
|
|
||||||
|
|||||||
+4
-2
@@ -2,7 +2,7 @@
|
|||||||
# Docker build environment #
|
# Docker build environment #
|
||||||
############################
|
############################
|
||||||
|
|
||||||
FROM node:18.16.1-bookworm-slim AS build
|
FROM node:22.11.0-bookworm-slim AS build
|
||||||
|
|
||||||
# Upgrade all packages and install dependencies
|
# Upgrade all packages and install dependencies
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
@@ -11,6 +11,8 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|||||||
python3 \
|
python3 \
|
||||||
build-essential \
|
build-essential \
|
||||||
cmake \
|
cmake \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
&& apt clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
&& apt clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
@@ -24,7 +26,7 @@ RUN npm i && npm run build
|
|||||||
# Docker final environment #
|
# Docker final environment #
|
||||||
############################
|
############################
|
||||||
|
|
||||||
FROM node:18.16.1-bookworm-slim
|
FROM node:22.11.0-bookworm-slim
|
||||||
|
|
||||||
# Expose ports for Stratum and Bitcoin RPC
|
# Expose ports for Stratum and Bitcoin RPC
|
||||||
EXPOSE 3333 3334 8332
|
EXPOSE 3333 3334 8332
|
||||||
|
|||||||
@@ -25,10 +25,3 @@ DEV_FEE_ADDRESS=
|
|||||||
NETWORK=mainnet
|
NETWORK=mainnet
|
||||||
|
|
||||||
API_SECURE=false
|
API_SECURE=false
|
||||||
|
|
||||||
ENABLE_SOLO=true
|
|
||||||
ENABLE_PROXY=false
|
|
||||||
|
|
||||||
BRAIINS_ACCESS_TOKEN=
|
|
||||||
|
|
||||||
PROXY_PORT=3333
|
|
||||||
|
|||||||
@@ -25,10 +25,3 @@ DEV_FEE_ADDRESS=
|
|||||||
NETWORK=regtest
|
NETWORK=regtest
|
||||||
|
|
||||||
API_SECURE=false
|
API_SECURE=false
|
||||||
|
|
||||||
ENABLE_SOLO=true
|
|
||||||
ENABLE_PROXY=false
|
|
||||||
|
|
||||||
BRAIINS_ACCESS_TOKEN=
|
|
||||||
|
|
||||||
PROXY_PORT=3333
|
|
||||||
|
|||||||
@@ -25,10 +25,3 @@ DEV_FEE_ADDRESS=
|
|||||||
NETWORK=testnet
|
NETWORK=testnet
|
||||||
|
|
||||||
API_SECURE=false
|
API_SECURE=false
|
||||||
|
|
||||||
ENABLE_SOLO=true
|
|
||||||
ENABLE_PROXY=false
|
|
||||||
|
|
||||||
BRAIINS_ACCESS_TOKEN=
|
|
||||||
|
|
||||||
PROXY_PORT=3333
|
|
||||||
|
|||||||
Generated
+531
-48
@@ -7,6 +7,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "public-pool",
|
"name": "public-pool",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
|
"hasInstallScript": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/axios": "^3.0.0",
|
"@nestjs/axios": "^3.0.0",
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"jest": "29.5.0",
|
"jest": "29.5.0",
|
||||||
|
"patch-package": "8.0.0",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"source-map-support": "^0.5.20",
|
"source-map-support": "^0.5.20",
|
||||||
"supertest": "^6.1.3",
|
"supertest": "^6.1.3",
|
||||||
@@ -2875,6 +2877,12 @@
|
|||||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@yarnpkg/lockfile": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/abbrev": {
|
"node_modules/abbrev": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
@@ -3211,6 +3219,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/at-least-node": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/atomic-sleep": {
|
"node_modules/atomic-sleep": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||||
@@ -3850,12 +3867,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/call-bind": {
|
"node_modules/call-bind": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1",
|
"es-define-property": "^1.0.0",
|
||||||
"get-intrinsic": "^1.0.2"
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-intrinsic": "^1.2.4",
|
||||||
|
"set-function-length": "^1.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -4423,6 +4446,22 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/define-data-property": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||||
|
"dependencies": {
|
||||||
|
"es-define-property": "^1.0.0",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/define-properties": {
|
"node_modules/define-properties": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
|
||||||
@@ -4746,6 +4785,25 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-define-property": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"get-intrinsic": "^1.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-module-lexer": {
|
"node_modules/es-module-lexer": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz",
|
||||||
@@ -5397,6 +5455,15 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/find-yarn-workspace-root": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"micromatch": "^4.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/flat-cache": {
|
"node_modules/flat-cache": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
|
||||||
@@ -5627,9 +5694,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/function.prototype.name": {
|
"node_modules/function.prototype.name": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
@@ -5693,14 +5763,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||||
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1",
|
"es-errors": "^1.3.0",
|
||||||
"has": "^1.0.3",
|
"function-bind": "^1.1.2",
|
||||||
"has-proto": "^1.0.1",
|
"has-proto": "^1.0.1",
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3",
|
||||||
|
"hasown": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -5952,11 +6026,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/has-property-descriptors": {
|
"node_modules/has-property-descriptors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||||
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
|
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"get-intrinsic": "^1.1.1"
|
"es-define-property": "^1.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -6038,6 +6112,17 @@
|
|||||||
"minimalistic-assert": "^1.0.1"
|
"minimalistic-assert": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hexoid": {
|
"node_modules/hexoid": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||||
@@ -6402,6 +6487,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-docker": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"is-docker": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@@ -6610,6 +6710,18 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-wsl": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"is-docker": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/isarray": {
|
"node_modules/isarray": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
@@ -7497,12 +7609,36 @@
|
|||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/json-stable-stringify": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.5",
|
||||||
|
"isarray": "^2.0.5",
|
||||||
|
"jsonify": "^0.0.1",
|
||||||
|
"object-keys": "^1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/json-stable-stringify-without-jsonify": {
|
"node_modules/json-stable-stringify-without-jsonify": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
||||||
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/json-stable-stringify/node_modules/isarray": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/json-stringify-safe": {
|
"node_modules/json-stringify-safe": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||||
@@ -7538,6 +7674,15 @@
|
|||||||
"graceful-fs": "^4.1.6"
|
"graceful-fs": "^4.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonify": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jsprim": {
|
"node_modules/jsprim": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
|
||||||
@@ -7552,6 +7697,15 @@
|
|||||||
"node": ">=0.6.0"
|
"node": ">=0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/klaw-sync": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.1.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/kleur": {
|
"node_modules/kleur": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||||
@@ -7675,6 +7829,7 @@
|
|||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"yallist": "^4.0.0"
|
"yallist": "^4.0.0"
|
||||||
},
|
},
|
||||||
@@ -8537,6 +8692,22 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/open": {
|
||||||
|
"version": "7.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
|
||||||
|
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"is-docker": "^2.0.0",
|
||||||
|
"is-wsl": "^2.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.1",
|
"version": "0.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
|
||||||
@@ -8704,6 +8875,106 @@
|
|||||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
|
||||||
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
|
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/patch-package": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@yarnpkg/lockfile": "^1.1.0",
|
||||||
|
"chalk": "^4.1.2",
|
||||||
|
"ci-info": "^3.7.0",
|
||||||
|
"cross-spawn": "^7.0.3",
|
||||||
|
"find-yarn-workspace-root": "^2.0.0",
|
||||||
|
"fs-extra": "^9.0.0",
|
||||||
|
"json-stable-stringify": "^1.0.2",
|
||||||
|
"klaw-sync": "^6.0.0",
|
||||||
|
"minimist": "^1.2.6",
|
||||||
|
"open": "^7.4.2",
|
||||||
|
"rimraf": "^2.6.3",
|
||||||
|
"semver": "^7.5.3",
|
||||||
|
"slash": "^2.0.0",
|
||||||
|
"tmp": "^0.0.33",
|
||||||
|
"yaml": "^2.2.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"patch-package": "index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14",
|
||||||
|
"npm": ">5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/patch-package/node_modules/fs-extra": {
|
||||||
|
"version": "9.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
|
||||||
|
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"at-least-node": "^1.0.0",
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^6.0.1",
|
||||||
|
"universalify": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/patch-package/node_modules/glob": {
|
||||||
|
"version": "7.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.1.1",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/patch-package/node_modules/rimraf": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||||
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"glob": "^7.1.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rimraf": "bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/patch-package/node_modules/slash": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/patch-package/node_modules/yaml": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-exists": {
|
"node_modules/path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
@@ -9652,12 +9923,9 @@
|
|||||||
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
|
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.5.1",
|
"version": "7.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
"integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||||
"dependencies": {
|
|
||||||
"lru-cache": "^6.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
},
|
},
|
||||||
@@ -9684,6 +9952,22 @@
|
|||||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
|
||||||
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ=="
|
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/set-function-length": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||||
|
"dependencies": {
|
||||||
|
"define-data-property": "^1.1.4",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-intrinsic": "^1.2.4",
|
||||||
|
"gopd": "^1.0.1",
|
||||||
|
"has-property-descriptors": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sha.js": {
|
"node_modules/sha.js": {
|
||||||
"version": "2.4.11",
|
"version": "2.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
||||||
@@ -13599,6 +13883,12 @@
|
|||||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@yarnpkg/lockfile": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"abbrev": {
|
"abbrev": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
@@ -13857,6 +14147,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
},
|
},
|
||||||
|
"at-least-node": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"atomic-sleep": {
|
"atomic-sleep": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||||
@@ -14354,12 +14650,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"call-bind": {
|
"call-bind": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"function-bind": "^1.1.1",
|
"es-define-property": "^1.0.0",
|
||||||
"get-intrinsic": "^1.0.2"
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-intrinsic": "^1.2.4",
|
||||||
|
"set-function-length": "^1.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"callsites": {
|
"callsites": {
|
||||||
@@ -14774,6 +15073,16 @@
|
|||||||
"clone": "^1.0.2"
|
"clone": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"define-data-property": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||||
|
"requires": {
|
||||||
|
"es-define-property": "^1.0.0",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"define-properties": {
|
"define-properties": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
|
||||||
@@ -15036,6 +15345,19 @@
|
|||||||
"which-typed-array": "^1.1.9"
|
"which-typed-array": "^1.1.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es-define-property": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||||
|
"requires": {
|
||||||
|
"get-intrinsic": "^1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
|
||||||
|
},
|
||||||
"es-module-lexer": {
|
"es-module-lexer": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz",
|
||||||
@@ -15538,6 +15860,15 @@
|
|||||||
"path-exists": "^4.0.0"
|
"path-exists": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"find-yarn-workspace-root": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"micromatch": "^4.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flat-cache": {
|
"flat-cache": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
|
||||||
@@ -15703,9 +16034,9 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"function-bind": {
|
"function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
|
||||||
},
|
},
|
||||||
"function.prototype.name": {
|
"function.prototype.name": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
@@ -15751,14 +16082,15 @@
|
|||||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
||||||
},
|
},
|
||||||
"get-intrinsic": {
|
"get-intrinsic": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||||
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"function-bind": "^1.1.1",
|
"es-errors": "^1.3.0",
|
||||||
"has": "^1.0.3",
|
"function-bind": "^1.1.2",
|
||||||
"has-proto": "^1.0.1",
|
"has-proto": "^1.0.1",
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3",
|
||||||
|
"hasown": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"get-package-type": {
|
"get-package-type": {
|
||||||
@@ -15938,11 +16270,11 @@
|
|||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||||
},
|
},
|
||||||
"has-property-descriptors": {
|
"has-property-descriptors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||||
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
|
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"get-intrinsic": "^1.1.1"
|
"es-define-property": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"has-proto": {
|
"has-proto": {
|
||||||
@@ -15999,6 +16331,14 @@
|
|||||||
"minimalistic-assert": "^1.0.1"
|
"minimalistic-assert": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"requires": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"hexoid": {
|
"hexoid": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||||
@@ -16261,6 +16601,12 @@
|
|||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"is-docker": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"is-extglob": {
|
"is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@@ -16394,6 +16740,15 @@
|
|||||||
"call-bind": "^1.0.2"
|
"call-bind": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"is-wsl": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-docker": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"isarray": {
|
"isarray": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
@@ -17084,6 +17439,26 @@
|
|||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||||
},
|
},
|
||||||
|
"json-stable-stringify": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"call-bind": "^1.0.5",
|
||||||
|
"isarray": "^2.0.5",
|
||||||
|
"jsonify": "^0.0.1",
|
||||||
|
"object-keys": "^1.1.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"isarray": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"json-stable-stringify-without-jsonify": {
|
"json-stable-stringify-without-jsonify": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
||||||
@@ -17117,6 +17492,12 @@
|
|||||||
"universalify": "^2.0.0"
|
"universalify": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jsonify": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"jsprim": {
|
"jsprim": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
|
||||||
@@ -17128,6 +17509,15 @@
|
|||||||
"verror": "1.10.0"
|
"verror": "1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"klaw-sync": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"graceful-fs": "^4.1.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"kleur": {
|
"kleur": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||||
@@ -17227,6 +17617,7 @@
|
|||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"yallist": "^4.0.0"
|
"yallist": "^4.0.0"
|
||||||
}
|
}
|
||||||
@@ -17907,6 +18298,16 @@
|
|||||||
"mimic-fn": "^2.1.0"
|
"mimic-fn": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"open": {
|
||||||
|
"version": "7.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
|
||||||
|
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-docker": "^2.0.0",
|
||||||
|
"is-wsl": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"optionator": {
|
"optionator": {
|
||||||
"version": "0.9.1",
|
"version": "0.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
|
||||||
@@ -18028,6 +18429,78 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"patch-package": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@yarnpkg/lockfile": "^1.1.0",
|
||||||
|
"chalk": "^4.1.2",
|
||||||
|
"ci-info": "^3.7.0",
|
||||||
|
"cross-spawn": "^7.0.3",
|
||||||
|
"find-yarn-workspace-root": "^2.0.0",
|
||||||
|
"fs-extra": "^9.0.0",
|
||||||
|
"json-stable-stringify": "^1.0.2",
|
||||||
|
"klaw-sync": "^6.0.0",
|
||||||
|
"minimist": "^1.2.6",
|
||||||
|
"open": "^7.4.2",
|
||||||
|
"rimraf": "^2.6.3",
|
||||||
|
"semver": "^7.5.3",
|
||||||
|
"slash": "^2.0.0",
|
||||||
|
"tmp": "^0.0.33",
|
||||||
|
"yaml": "^2.2.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"fs-extra": {
|
||||||
|
"version": "9.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
|
||||||
|
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"at-least-node": "^1.0.0",
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^6.0.1",
|
||||||
|
"universalify": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"glob": {
|
||||||
|
"version": "7.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.1.1",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rimraf": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"glob": "^7.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"slash": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"yaml": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"path-exists": {
|
"path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
@@ -18695,12 +19168,9 @@
|
|||||||
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
|
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "7.5.1",
|
"version": "7.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
"integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
|
||||||
"requires": {
|
|
||||||
"lru-cache": "^6.0.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"serialize-javascript": {
|
"serialize-javascript": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
@@ -18721,6 +19191,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
|
||||||
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ=="
|
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ=="
|
||||||
},
|
},
|
||||||
|
"set-function-length": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||||
|
"requires": {
|
||||||
|
"define-data-property": "^1.1.4",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-intrinsic": "^1.2.4",
|
||||||
|
"gopd": "^1.0.1",
|
||||||
|
"has-property-descriptors": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sha.js": {
|
"sha.js": {
|
||||||
"version": "2.4.11",
|
"version": "2.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
|
"postinstall": "patch-package",
|
||||||
"start": "nest start",
|
"start": "nest start",
|
||||||
"start:dev": "nest start --watch",
|
"start:dev": "nest start --watch",
|
||||||
"start:debug": "nest start --debug --watch",
|
"start:debug": "nest start --debug --watch",
|
||||||
@@ -65,6 +66,7 @@
|
|||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"jest": "29.5.0",
|
"jest": "29.5.0",
|
||||||
|
"patch-package": "8.0.0",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"source-map-support": "^0.5.20",
|
"source-map-support": "^0.5.20",
|
||||||
"supertest": "^6.1.3",
|
"supertest": "^6.1.3",
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
diff --git a/node_modules/rpc-bitcoin/build/src/rpc.d.ts b/node_modules/rpc-bitcoin/build/src/rpc.d.ts
|
||||||
|
index d25d732..ce4eb22 100644
|
||||||
|
--- a/node_modules/rpc-bitcoin/build/src/rpc.d.ts
|
||||||
|
+++ b/node_modules/rpc-bitcoin/build/src/rpc.d.ts
|
||||||
|
@@ -6,7 +6,7 @@ export declare type RPCIniOptions = RESTIniOptions & {
|
||||||
|
fullResponse?: boolean;
|
||||||
|
};
|
||||||
|
export declare type JSONRPC = {
|
||||||
|
- jsonrpc?: string | number;
|
||||||
|
+ jsonrpc?: string;
|
||||||
|
id?: string | number;
|
||||||
|
method: string;
|
||||||
|
params?: object;
|
||||||
|
diff --git a/node_modules/rpc-bitcoin/build/src/rpc.js b/node_modules/rpc-bitcoin/build/src/rpc.js
|
||||||
|
index 7fec7aa..3bee5fc 100644
|
||||||
|
--- a/node_modules/rpc-bitcoin/build/src/rpc.js
|
||||||
|
+++ b/node_modules/rpc-bitcoin/build/src/rpc.js
|
||||||
|
@@ -12,7 +12,7 @@ class RPCClient extends rest_1.RESTClient {
|
||||||
|
}
|
||||||
|
async rpc(method, params = {}, wallet) {
|
||||||
|
const uri = typeof wallet === "undefined" ? "/" : "wallet/" + wallet;
|
||||||
|
- const body = { method, params, jsonrpc: 1.0, id: "rpc-bitcoin" };
|
||||||
|
+ const body = { method, params, jsonrpc: "1.0", id: "rpc-bitcoin" };
|
||||||
|
try {
|
||||||
|
const response = await this.batch(body, uri);
|
||||||
|
return this.fullResponse ? response : response.result;
|
||||||
@@ -17,5 +17,8 @@ export class AddressSettingsEntity extends TrackedEntity {
|
|||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
miscCoinbaseScriptData: string;
|
miscCoinbaseScriptData: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
bestDifficultyUserAgent: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,26 @@ export class AddressSettingsService {
|
|||||||
public async getSettings(address: string, createIfNotFound: boolean) {
|
public async getSettings(address: string, createIfNotFound: boolean) {
|
||||||
const settings = await this.addressSettingsRepository.findOne({ where: { address } });
|
const settings = await this.addressSettingsRepository.findOne({ where: { address } });
|
||||||
if (createIfNotFound == true && settings == null) {
|
if (createIfNotFound == true && settings == null) {
|
||||||
return await this.createNew(address);
|
// It's possible to have a race condition here so if we get a PK violation, fetch it
|
||||||
|
try {
|
||||||
|
return await this.createNew(address);
|
||||||
|
} catch (e) {
|
||||||
|
return await this.addressSettingsRepository.findOne({ where: { address } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateBestDifficulty(address: string, bestDifficulty: number) {
|
public async updateBestDifficulty(address: string, bestDifficulty: number, bestDifficultyUserAgent: string) {
|
||||||
return await this.addressSettingsRepository.update({ address }, { bestDifficulty });
|
return await this.addressSettingsRepository.update({ address }, { bestDifficulty, bestDifficultyUserAgent });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getHighScores() {
|
||||||
|
return await this.addressSettingsRepository.createQueryBuilder()
|
||||||
|
.select('"updatedAt", "bestDifficulty", "bestDifficultyUserAgent"')
|
||||||
|
.orderBy('"bestDifficulty"', 'DESC')
|
||||||
|
.limit(10)
|
||||||
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createNew(address: string) {
|
public async createNew(address: string) {
|
||||||
|
|||||||
@@ -17,29 +17,24 @@ export class ClientStatisticsService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async save(clientStatistic: Partial<ClientStatisticsEntity>) {
|
public async update(clientStatistic: Partial<ClientStatisticsEntity>) {
|
||||||
// Attempt to update the existing record
|
|
||||||
const updateResult = await this.clientStatisticsRepository
|
|
||||||
.createQueryBuilder()
|
|
||||||
.update(ClientStatisticsEntity)
|
|
||||||
.set({
|
|
||||||
shares: () => `"shares" + :sharesIncrement`,
|
|
||||||
acceptedCount: () => `"acceptedCount" + 1`
|
|
||||||
})
|
|
||||||
.where('address = :address AND clientName = :clientName AND sessionId = :sessionId AND time = :time', {
|
|
||||||
address: clientStatistic.address,
|
|
||||||
clientName: clientStatistic.clientName,
|
|
||||||
sessionId: clientStatistic.sessionId,
|
|
||||||
time: clientStatistic.time,
|
|
||||||
sharesIncrement: clientStatistic.shares
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
// Check if the update affected any rows
|
await this.clientStatisticsRepository.update({
|
||||||
if (updateResult.affected === 0) {
|
address: clientStatistic.address,
|
||||||
// If no rows were updated, insert a new record
|
clientName: clientStatistic.clientName,
|
||||||
await this.clientStatisticsRepository.insert(clientStatistic);
|
sessionId: clientStatistic.sessionId,
|
||||||
}
|
time: clientStatistic.time
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shares: clientStatistic.shares,
|
||||||
|
acceptedCount: clientStatistic.acceptedCount,
|
||||||
|
updatedAt: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
public async insert(clientStatistic: Partial<ClientStatisticsEntity>) {
|
||||||
|
// If no rows were updated, insert a new record
|
||||||
|
await this.clientStatisticsRepository.insert(clientStatistic);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteOldStatistics() {
|
public async deleteOldStatistics() {
|
||||||
@@ -212,14 +207,16 @@ export class ClientStatisticsService {
|
|||||||
|
|
||||||
if (result.length < 2) {
|
if (result.length < 2) {
|
||||||
const time = new Date(latestStat.updatedAt).getTime() - new Date(latestStat.createdAt).getTime();
|
const time = new Date(latestStat.updatedAt).getTime() - new Date(latestStat.createdAt).getTime();
|
||||||
if (time < 1) {
|
// 1min
|
||||||
|
if (time < 1000 * 60) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return (latestStat.shares * 4294967296) / (time / 1000);
|
return (latestStat.shares * 4294967296) / (time / 1000);
|
||||||
} else {
|
} else {
|
||||||
const secondLatestStat = result[1];
|
const secondLatestStat = result[1];
|
||||||
const time = new Date(latestStat.updatedAt).getTime() - new Date(secondLatestStat.createdAt).getTime();
|
const time = new Date(latestStat.updatedAt).getTime() - new Date(secondLatestStat.createdAt).getTime();
|
||||||
if (time < 1) {
|
// 1min
|
||||||
|
if (time < 1000 * 60) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return ((latestStat.shares + secondLatestStat.shares) * 4294967296) / (time / 1000);
|
return ((latestStat.shares + secondLatestStat.shares) * 4294967296) / (time / 1000);
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
@Index(['address', 'time'])
|
||||||
|
export class ExternalSharesEntity extends TrackedEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column({ length: 62, type: 'varchar' })
|
||||||
|
address: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
clientName: string;
|
||||||
|
|
||||||
|
@Column({ type: 'integer' })
|
||||||
|
time: number;
|
||||||
|
|
||||||
|
@Column({ type: 'real' })
|
||||||
|
difficulty: number;
|
||||||
|
|
||||||
|
@Column({ length: 128, type: 'varchar', nullable: true })
|
||||||
|
userAgent: string;
|
||||||
|
|
||||||
|
@Column({ length: 128, type: 'varchar', nullable: true })
|
||||||
|
externalPoolName: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
header: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { ExternalSharesEntity } from './external-shares.entity';
|
||||||
|
import { ExternalSharesService } from './external-shares.service';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([ExternalSharesEntity])],
|
||||||
|
providers: [ExternalSharesService],
|
||||||
|
exports: [TypeOrmModule, ExternalSharesService],
|
||||||
|
})
|
||||||
|
export class ExternalSharesModule { }
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { ExternalSharesEntity } from './external-shares.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ExternalSharesService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(ExternalSharesEntity)
|
||||||
|
private externalSharesRepository: Repository<ExternalSharesEntity>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async insert(externalShare: Partial<ExternalSharesEntity>) {
|
||||||
|
return await this.externalSharesRepository.insert(externalShare);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getTopDifficulties(): Promise<Array<{userAgent: string, time: number, externalPoolName: string, difficulty: number}>> {
|
||||||
|
return await this.externalSharesRepository
|
||||||
|
.createQueryBuilder('share')
|
||||||
|
.select('share.userAgent', 'userAgent')
|
||||||
|
.addSelect('share.time', 'time')
|
||||||
|
.addSelect('share.externalPoolName', 'externalPoolName')
|
||||||
|
.addSelect('MAX(share.difficulty)', 'difficulty')
|
||||||
|
.groupBy('share.address')
|
||||||
|
.orderBy('MAX(share.difficulty)', 'DESC')
|
||||||
|
.limit(10)
|
||||||
|
.getRawMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getAddressBestDifficulty(address: string): Promise<number> {
|
||||||
|
const result = await this.externalSharesRepository
|
||||||
|
.createQueryBuilder()
|
||||||
|
.select('MAX(difficulty)', 'maxDifficulty')
|
||||||
|
.where('address = :address', { address })
|
||||||
|
.getRawOne();
|
||||||
|
return result?.maxDifficulty || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteOldShares() {
|
||||||
|
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||||
|
return await this.externalSharesRepository
|
||||||
|
.createQueryBuilder()
|
||||||
|
.delete()
|
||||||
|
.from(ExternalSharesEntity)
|
||||||
|
.where('time < :time', { time: oneDayAgo.getTime() })
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteAll() {
|
||||||
|
return await this.externalSharesRepository.delete({});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,4 +25,19 @@ export class RpcBlockService {
|
|||||||
public saveBlock(blockHeight: number, data: string) {
|
public saveBlock(blockHeight: number, data: string) {
|
||||||
return this.rpcBlockRepository.update(blockHeight, { data })
|
return this.rpcBlockRepository.update(blockHeight, { data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async deleteOldBlocks() {
|
||||||
|
const result = await this.rpcBlockRepository.createQueryBuilder('entity')
|
||||||
|
.select('MAX(entity.blockHeight)', 'maxNumber')
|
||||||
|
.getRawOne();
|
||||||
|
|
||||||
|
const newestBlock = result ? result.maxNumber : null;
|
||||||
|
|
||||||
|
await this.rpcBlockRepository.createQueryBuilder()
|
||||||
|
.delete()
|
||||||
|
.where('"blockHeight" < :newestBlock', { newestBlock })
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ import { Controller, Get, Inject } from '@nestjs/common';
|
|||||||
import { Cache } from 'cache-manager';
|
import { Cache } from 'cache-manager';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
|
||||||
|
import { AddressSettingsService } from './ORM/address-settings/address-settings.service';
|
||||||
import { BlocksService } from './ORM/blocks/blocks.service';
|
import { BlocksService } from './ORM/blocks/blocks.service';
|
||||||
import { ClientStatisticsService } from './ORM/client-statistics/client-statistics.service';
|
import { ClientStatisticsService } from './ORM/client-statistics/client-statistics.service';
|
||||||
import { ClientService } from './ORM/client/client.service';
|
import { ClientService } from './ORM/client/client.service';
|
||||||
@@ -18,7 +19,8 @@ export class AppController {
|
|||||||
private readonly clientService: ClientService,
|
private readonly clientService: ClientService,
|
||||||
private readonly clientStatisticsService: ClientStatisticsService,
|
private readonly clientStatisticsService: ClientStatisticsService,
|
||||||
private readonly blocksService: BlocksService,
|
private readonly blocksService: BlocksService,
|
||||||
private readonly bitcoinRpcService: BitcoinRpcService
|
private readonly bitcoinRpcService: BitcoinRpcService,
|
||||||
|
private readonly addressSettingsService: AddressSettingsService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@Get('info')
|
@Get('info')
|
||||||
@@ -35,10 +37,12 @@ export class AppController {
|
|||||||
|
|
||||||
const blockData = await this.blocksService.getFoundBlocks();
|
const blockData = await this.blocksService.getFoundBlocks();
|
||||||
const userAgents = await this.clientService.getUserAgents();
|
const userAgents = await this.clientService.getUserAgents();
|
||||||
|
const highScores = await this.addressSettingsService.getHighScores();
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
blockData,
|
blockData,
|
||||||
userAgents,
|
userAgents,
|
||||||
|
highScores,
|
||||||
uptime: this.uptime
|
uptime: this.uptime
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+9
-6
@@ -21,11 +21,12 @@ import { BraiinsService } from './services/braiins.service';
|
|||||||
import { BTCPayService } from './services/btc-pay.service';
|
import { BTCPayService } from './services/btc-pay.service';
|
||||||
import { DiscordService } from './services/discord.service';
|
import { DiscordService } from './services/discord.service';
|
||||||
import { NotificationService } from './services/notification.service';
|
import { NotificationService } from './services/notification.service';
|
||||||
import { ProxyService } from './services/proxy.service';
|
|
||||||
import { StratumV1JobsService } from './services/stratum-v1-jobs.service';
|
import { StratumV1JobsService } from './services/stratum-v1-jobs.service';
|
||||||
import { StratumV1Service } from './services/stratum-v1.service';
|
import { StratumV1Service } from './services/stratum-v1.service';
|
||||||
import { TelegramService } from './services/telegram.service';
|
import { TelegramService } from './services/telegram.service';
|
||||||
|
import { ExternalSharesService } from './services/external-shares.service';
|
||||||
|
import { ExternalShareController } from './controllers/external-share/external-share.controller';
|
||||||
|
import { ExternalSharesModule } from './ORM/external-shares/external-shares.module';
|
||||||
|
|
||||||
const ORMModules = [
|
const ORMModules = [
|
||||||
ClientStatisticsModule,
|
ClientStatisticsModule,
|
||||||
@@ -33,7 +34,8 @@ const ORMModules = [
|
|||||||
AddressSettingsModule,
|
AddressSettingsModule,
|
||||||
TelegramSubscriptionsModule,
|
TelegramSubscriptionsModule,
|
||||||
BlocksModule,
|
BlocksModule,
|
||||||
RpcBlocksModule
|
RpcBlocksModule,
|
||||||
|
ExternalSharesModule
|
||||||
]
|
]
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -57,7 +59,8 @@ const ORMModules = [
|
|||||||
controllers: [
|
controllers: [
|
||||||
AppController,
|
AppController,
|
||||||
ClientController,
|
ClientController,
|
||||||
AddressController
|
AddressController,
|
||||||
|
ExternalShareController
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DiscordService,
|
DiscordService,
|
||||||
@@ -68,9 +71,9 @@ const ORMModules = [
|
|||||||
NotificationService,
|
NotificationService,
|
||||||
BitcoinAddressValidator,
|
BitcoinAddressValidator,
|
||||||
StratumV1JobsService,
|
StratumV1JobsService,
|
||||||
ProxyService,
|
|
||||||
BTCPayService,
|
BTCPayService,
|
||||||
BraiinsService
|
BraiinsService,
|
||||||
|
ExternalSharesService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { Body, Controller, Post, Get, UnauthorizedException, Headers } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { ExternalPoolShare } from '../../models/ExternalPoolShare';
|
||||||
|
import { ExternalSharesService } from '../../ORM/external-shares/external-shares.service';
|
||||||
|
import { DifficultyUtils } from '../../utils/difficulty.utils';
|
||||||
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
|
|
||||||
|
@Controller('share')
|
||||||
|
export class ExternalShareController {
|
||||||
|
private readonly apiKey: string;
|
||||||
|
private readonly minimumDifficulty: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly externalSharesService: ExternalSharesService,
|
||||||
|
) {
|
||||||
|
this.apiKey = this.configService.get('SHARE_SUBMISSION_API_KEY');
|
||||||
|
this.minimumDifficulty = this.configService.get('MINIMUM_DIFFICULTY') || 1000000000000; // 1T
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('top-difficulties')
|
||||||
|
async getTopDifficulties() {
|
||||||
|
const topDifficulties = await this.externalSharesService.getTopDifficulties();
|
||||||
|
return topDifficulties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async submitExternalShare(
|
||||||
|
@Body() externalShare: ExternalPoolShare,
|
||||||
|
@Headers('x-api-key') apiKey: string,
|
||||||
|
) {
|
||||||
|
// Only validate API key if one is configured
|
||||||
|
if (this.apiKey && apiKey !== this.apiKey) {
|
||||||
|
throw new UnauthorizedException('Invalid API key');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the header hash matches claimed difficulty
|
||||||
|
const headerBuffer = Buffer.from(externalShare.header, 'hex');
|
||||||
|
const { submissionDifficulty: difficulty } = DifficultyUtils.calculateDifficulty(headerBuffer);
|
||||||
|
|
||||||
|
// Verify the calculated difficulty matches or exceeds minimum difficulty
|
||||||
|
if (difficulty < this.minimumDifficulty) {
|
||||||
|
throw new UnauthorizedException('Share difficulty too low');
|
||||||
|
}
|
||||||
|
|
||||||
|
const block = bitcoinjs.Block.fromBuffer(headerBuffer);
|
||||||
|
|
||||||
|
const tenMinutesAgo = Math.floor(Date.now() / 1000) - (10 * 60);
|
||||||
|
|
||||||
|
if (block.timestamp < tenMinutesAgo) {
|
||||||
|
throw new UnauthorizedException('Share timestamp too old - must be within last 10 minutes');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store share submission
|
||||||
|
await this.externalSharesService.insert({
|
||||||
|
address: externalShare.address,
|
||||||
|
clientName: externalShare.worker,
|
||||||
|
time: new Date().getTime(),
|
||||||
|
difficulty: difficulty,
|
||||||
|
userAgent: externalShare.userAgent,
|
||||||
|
externalPoolName: externalShare.externalPoolName,
|
||||||
|
header: externalShare.header
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Accepted external share. ${difficulty}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
calculatedDifficulty: difficulty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -33,8 +33,8 @@ async function bootstrap() {
|
|||||||
new ValidationPipe({
|
new ValidationPipe({
|
||||||
transform: true,
|
transform: true,
|
||||||
whitelist: true,
|
whitelist: true,
|
||||||
forbidNonWhitelisted: true,
|
//forbidNonWhitelisted: true,
|
||||||
forbidUnknownValues: true
|
//forbidUnknownValues: true
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { IsString, Matches, MaxLength } from 'class-validator';
|
||||||
|
import { Transform } from 'class-transformer';
|
||||||
|
import { IsBitcoinAddress } from './validators/bitcoin-address.validator';
|
||||||
|
|
||||||
|
export class ExternalPoolShare {
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(64)
|
||||||
|
worker: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsBitcoinAddress()
|
||||||
|
address: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(128)
|
||||||
|
userAgent: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(128)
|
||||||
|
externalPoolName: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@Matches(/^[0-9a-fA-F]+$/, {
|
||||||
|
message: 'Header must be a valid hex string'
|
||||||
|
})
|
||||||
|
header: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
|
import { MiningJob } from "./MiningJob";
|
||||||
|
import { IJobTemplate } from "../services/stratum-v1-jobs.service";
|
||||||
|
|
||||||
|
|
||||||
|
function hexToAscii(hex: string): string {
|
||||||
|
let ascii = '';
|
||||||
|
for (let i = 0; i < hex.length; i += 2) {
|
||||||
|
const char = String.fromCharCode(parseInt(hex.substr(i, 2), 16));
|
||||||
|
if (char !== '\0') { // Ignore null characters to make sure string matching works
|
||||||
|
ascii += char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ascii;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPoolIdentifierFromScript(coinbaseHex: string) {
|
||||||
|
let offset = 8 + 2 + 64 + 8; // Skip Version, Input Count, Previous Transaction Hash, Previous Output Index
|
||||||
|
|
||||||
|
// Coinbase Data Length (1 byte / 2 hex characters)
|
||||||
|
const coinbaseDataLengthHex = coinbaseHex.substr(offset, 2);
|
||||||
|
const coinbaseDataLength = parseInt(coinbaseDataLengthHex, 16);
|
||||||
|
offset += 2;
|
||||||
|
|
||||||
|
// Coinbase Data (coinbaseDataLength * 2 hex characters)
|
||||||
|
const coinbaseDataHex = coinbaseHex.substr(offset, coinbaseDataLength * 2);
|
||||||
|
|
||||||
|
const coinbaseDataAscii = hexToAscii(coinbaseDataHex);
|
||||||
|
|
||||||
|
return coinbaseDataAscii;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('MiningJob', () => {
|
||||||
|
let moduleRef: TestingModule;
|
||||||
|
let configService: ConfigService;
|
||||||
|
let block: bitcoinjs.Block;
|
||||||
|
let jobTemplate: IJobTemplate;
|
||||||
|
let payoutInformation: any[];
|
||||||
|
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
moduleRef = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: ConfigService,
|
||||||
|
useValue: {
|
||||||
|
get: jest.fn((key: string) => {
|
||||||
|
switch (key) {
|
||||||
|
// Configure mock responses for ConfigService
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
configService = moduleRef.get<ConfigService>(ConfigService);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
|
||||||
|
console.warn = jest.fn((message: string) => console.log('WARN:', message));
|
||||||
|
payoutInformation = [
|
||||||
|
{
|
||||||
|
address: 'tb1qr2ylpdgp9ejpt6v2uxlqrn9penp82rzz2grnns',
|
||||||
|
percent: 100,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
block = new bitcoinjs.Block();
|
||||||
|
block.witnessCommit = Buffer.from('000');
|
||||||
|
block.prevHash = Buffer.from('000');
|
||||||
|
block.merkleRoot = Buffer.from('000');
|
||||||
|
block.transactions = [];
|
||||||
|
|
||||||
|
const maxTransactions = 7545; // this is only for block weight calculation, other metrics like operations are not considered because they are not affected by the size of the pool identifier
|
||||||
|
for (let i = 0; i < maxTransactions; i++) {
|
||||||
|
block.transactions.push(bitcoinjs.Transaction.fromHex("010000000001017405e391018c5e9dc79f324f9607c9c46d21b02f66dabaa870b4add871d6379f01000000171600148d7a0a3461e3891723e5fdf8129caa0075060cffffffffff01fcf60200000000001600148d7a0a3461e3891723e5fdf8129caa0075060cff0248304502210088025cffdaf69d310c6fed11832edd9c19b6a912c132262701ad0e6133227d9202207d73bbf777abd2aeae995d684e6bb1a048c5ac722e16de48bdd35643df7decf001210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f44800000000"));
|
||||||
|
}
|
||||||
|
|
||||||
|
jobTemplate = {
|
||||||
|
block: block,
|
||||||
|
blockData: {
|
||||||
|
id: '1',
|
||||||
|
coinbasevalue: 0,
|
||||||
|
networkDifficulty: 0,
|
||||||
|
height: 0,
|
||||||
|
clearJobs: false,
|
||||||
|
}
|
||||||
|
} as IJobTemplate;
|
||||||
|
});
|
||||||
|
it('should create a new MiningJob if POOL_IDENTIFIER is not set and use the default', () => {
|
||||||
|
const expectedMiningIdentifier = 'Public-Pool';
|
||||||
|
expect(jobTemplate.block).toBeDefined();
|
||||||
|
const miningJob = new MiningJob(configService, bitcoinjs.networks.testnet, '1', payoutInformation, jobTemplate);
|
||||||
|
|
||||||
|
const response = JSON.parse(miningJob.response(jobTemplate));
|
||||||
|
|
||||||
|
const miningIdentifier = extractPoolIdentifierFromScript(response.params[2]);
|
||||||
|
expect(miningIdentifier).toBe(expectedMiningIdentifier);
|
||||||
|
expect(console.warn).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use the POOL_IDENTIFIER if it doesn\'t make the script size too big', () => {
|
||||||
|
const expectedMiningIdentifier = 'My Mining Pool';
|
||||||
|
|
||||||
|
configService.get = jest.fn((key: string) => {
|
||||||
|
switch (key) {
|
||||||
|
case 'POOL_IDENTIFIER': return expectedMiningIdentifier;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const miningJob = new MiningJob(configService, bitcoinjs.networks.testnet, '1', payoutInformation, jobTemplate);
|
||||||
|
const response = JSON.parse(miningJob.response(jobTemplate));
|
||||||
|
|
||||||
|
const miningIdentifier = extractPoolIdentifierFromScript(response.params[2]);
|
||||||
|
expect(miningIdentifier).toBe(expectedMiningIdentifier);
|
||||||
|
expect(console.warn).not.toBeCalled();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove pool identifier if block is too big with identifier', () => {
|
||||||
|
const expectedMiningIdentifier = '';
|
||||||
|
|
||||||
|
configService.get = jest.fn((key: string) => {
|
||||||
|
switch (key) {
|
||||||
|
case 'POOL_IDENTIFIER': return 'A'.repeat(84);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const miningJob = new MiningJob(configService, bitcoinjs.networks.testnet, '1', payoutInformation, jobTemplate);
|
||||||
|
const response = JSON.parse(miningJob.response(jobTemplate));
|
||||||
|
|
||||||
|
const miningIdentifier = extractPoolIdentifierFromScript(response.params[2]);
|
||||||
|
expect(console.warn).toBeCalledWith('Block weight exceeds the maximum allowed weight, removing the pool identifier');
|
||||||
|
expect(miningIdentifier).toBe(expectedMiningIdentifier);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use the POOL_IDENTIFIER if it doesn\'t make the script size too big with identifier abcabc', () => {
|
||||||
|
|
||||||
|
jobTemplate.block.transactions = []; // remove transactions because we only want to test the script size
|
||||||
|
const expectedMiningIdentifier = 'A'.repeat(88); // 88 chars is the maximum size validated against bitcoin core in regtest
|
||||||
|
configService.get = jest.fn((key: string) => {
|
||||||
|
switch (key) {
|
||||||
|
case 'POOL_IDENTIFIER': return expectedMiningIdentifier;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const miningJob = new MiningJob(configService, bitcoinjs.networks.testnet, '1', payoutInformation, jobTemplate);
|
||||||
|
const response = JSON.parse(miningJob.response(jobTemplate));
|
||||||
|
|
||||||
|
const miningIdentifier = extractPoolIdentifierFromScript(response.params[2]);
|
||||||
|
expect(miningIdentifier).toBe(expectedMiningIdentifier);
|
||||||
|
expect(console.warn).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove pool identifier if script is too big with identifier', () => {
|
||||||
|
const expectedMiningIdentifier = '';
|
||||||
|
jobTemplate.block.transactions = []; // remove transactions because we only want to test the script size
|
||||||
|
configService.get = jest.fn((key: string) => {
|
||||||
|
switch (key) {
|
||||||
|
case 'POOL_IDENTIFIER': return 'A'.repeat(89); // 88 chars is the maximum size validated against bitcoin core in regtest
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const miningJob = new MiningJob(configService, bitcoinjs.networks.testnet, '1', payoutInformation, jobTemplate);
|
||||||
|
const response = JSON.parse(miningJob.response(jobTemplate));
|
||||||
|
|
||||||
|
const miningIdentifier = extractPoolIdentifierFromScript(response.params[2]);
|
||||||
|
expect(console.warn).toBeCalledWith('Pool identifier is too long, removing the pool identifier');
|
||||||
|
expect(miningIdentifier).toBe(expectedMiningIdentifier);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+25
-6
@@ -4,8 +4,10 @@ import * as bitcoinjs from 'bitcoinjs-lib';
|
|||||||
import { IJobTemplate } from '../services/stratum-v1-jobs.service';
|
import { IJobTemplate } from '../services/stratum-v1-jobs.service';
|
||||||
import { eResponseMethod } from './enums/eResponseMethod';
|
import { eResponseMethod } from './enums/eResponseMethod';
|
||||||
import { IMiningNotify } from './stratum-messages/IMiningNotify';
|
import { IMiningNotify } from './stratum-messages/IMiningNotify';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
const MAX_BLOCK_WEIGHT = 4000000;
|
||||||
|
const MAX_SCRIPT_SIZE = 100; // https://github.com/bitcoin/bitcoin/blob/ffdc3d6060f6e65e69cf115a13b83e6eb4a0a0a8/src/consensus/tx_check.cpp#L49
|
||||||
interface AddressObject {
|
interface AddressObject {
|
||||||
address: string;
|
address: string;
|
||||||
percent: number;
|
percent: number;
|
||||||
@@ -18,15 +20,17 @@ export class MiningJob {
|
|||||||
|
|
||||||
public jobTemplateId: string;
|
public jobTemplateId: string;
|
||||||
public networkDifficulty: number;
|
public networkDifficulty: number;
|
||||||
|
public creation: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
configService: ConfigService,
|
||||||
private network: bitcoinjs.networks.Network,
|
private network: bitcoinjs.networks.Network,
|
||||||
public jobId: string,
|
public jobId: string,
|
||||||
payoutInformation: AddressObject[],
|
payoutInformation: AddressObject[],
|
||||||
jobTemplate: IJobTemplate
|
jobTemplate: IJobTemplate
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
this.creation = new Date().getTime();
|
||||||
this.jobTemplateId = jobTemplate.blockData.id;
|
this.jobTemplateId = jobTemplate.blockData.id;
|
||||||
|
|
||||||
this.coinbaseTransaction = this.createCoinbaseTransaction(payoutInformation, jobTemplate.blockData.coinbasevalue);
|
this.coinbaseTransaction = this.createCoinbaseTransaction(payoutInformation, jobTemplate.blockData.coinbasevalue);
|
||||||
@@ -39,7 +43,9 @@ export class MiningJob {
|
|||||||
// 32-byte - Commitment hash: Double-SHA256(witness root hash|witness reserved value)
|
// 32-byte - Commitment hash: Double-SHA256(witness root hash|witness reserved value)
|
||||||
|
|
||||||
// 39th byte onwards: Optional data with no consensus meaning
|
// 39th byte onwards: Optional data with no consensus meaning
|
||||||
const extra = Buffer.from('\\public-pool\\');
|
// Initial pool identifier
|
||||||
|
let poolIdentifier = configService.get('POOL_IDENTIFIER') || 'Public-Pool';
|
||||||
|
let extra = Buffer.from(poolIdentifier);
|
||||||
|
|
||||||
// Encode the block height
|
// Encode the block height
|
||||||
// https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki
|
// https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki
|
||||||
@@ -48,14 +54,27 @@ export class MiningJob {
|
|||||||
// Get the length of the block height encoding
|
// Get the length of the block height encoding
|
||||||
const blockHeightLengthByte = Buffer.from([blockHeightEncoded.length]);
|
const blockHeightLengthByte = Buffer.from([blockHeightEncoded.length]);
|
||||||
|
|
||||||
// generate padding and take length of encode blockHeight into account
|
// Generate padding and take length of encode blockHeight into account
|
||||||
const padding = Buffer.alloc(8 + (3 - blockHeightEncoded.length), 0)
|
const padding = Buffer.alloc(8 + (3 - blockHeightEncoded.length), 0)
|
||||||
|
|
||||||
// build the script
|
// Build the script
|
||||||
this.coinbaseTransaction.ins[0].script = Buffer.concat([blockHeightLengthByte, blockHeightEncoded, extra, padding])
|
let script = Buffer.concat([blockHeightLengthByte, blockHeightEncoded, extra, padding]);
|
||||||
|
// Check if the pool identifier is too long
|
||||||
|
if (script.length > MAX_SCRIPT_SIZE) {
|
||||||
|
console.warn('Pool identifier is too long, removing the pool identifier');
|
||||||
|
script = Buffer.concat([blockHeightLengthByte, blockHeightEncoded, padding]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.coinbaseTransaction.ins[0].script = script;
|
||||||
this.coinbaseTransaction.addOutput(bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.concat([segwitMagicBits, jobTemplate.block.witnessCommit])]), 0);
|
this.coinbaseTransaction.addOutput(bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.concat([segwitMagicBits, jobTemplate.block.witnessCommit])]), 0);
|
||||||
|
|
||||||
|
// Check if the pool identifier is too long
|
||||||
|
if ((this.coinbaseTransaction.weight() + jobTemplate.block.weight()) > MAX_BLOCK_WEIGHT) {
|
||||||
|
console.warn('Block weight exceeds the maximum allowed weight, removing the pool identifier');
|
||||||
|
let script = Buffer.concat([blockHeightLengthByte, blockHeightEncoded, padding]);
|
||||||
|
this.coinbaseTransaction.ins[0].script = script;
|
||||||
|
}
|
||||||
|
|
||||||
// get the non-witness coinbase tx
|
// get the non-witness coinbase tx
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const serializedCoinbaseTx = this.coinbaseTransaction.__toBuffer().toString('hex');
|
const serializedCoinbaseTx = this.coinbaseTransaction.__toBuffer().toString('hex');
|
||||||
|
|||||||
@@ -1,235 +0,0 @@
|
|||||||
import Big from 'big.js';
|
|
||||||
import { Block } from 'bitcoinjs-lib';
|
|
||||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
|
||||||
import { plainToInstance } from 'class-transformer';
|
|
||||||
import { validate, ValidatorOptions } from 'class-validator';
|
|
||||||
import { Socket } from 'net';
|
|
||||||
|
|
||||||
import { eRequestMethod } from './enums/eRequestMethod';
|
|
||||||
import { eResponseMethod } from './enums/eResponseMethod';
|
|
||||||
import { IMiningNotify } from './stratum-messages/IMiningNotify';
|
|
||||||
import { MiningSubmitMessage } from './stratum-messages/MiningSubmitMessage';
|
|
||||||
|
|
||||||
export class ProxyClient {
|
|
||||||
|
|
||||||
|
|
||||||
public braiinsSocket: Socket;
|
|
||||||
public serverDifficulty: number;
|
|
||||||
public jobs: { [jobId: string]: IMiningNotify } = {};
|
|
||||||
private extraNonce: string;
|
|
||||||
|
|
||||||
constructor(public socket: Socket) {
|
|
||||||
this.braiinsSocket = new Socket();
|
|
||||||
this.braiinsSocket.connect(3333, 'us-east.stratum.braiins.com', () => {
|
|
||||||
console.log('Connected to braiins');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.braiinsSocket.on('data', (data) => {
|
|
||||||
data.toString()
|
|
||||||
.split('\n')
|
|
||||||
.filter(m => m.length > 0)
|
|
||||||
.forEach(async (m) => {
|
|
||||||
try {
|
|
||||||
await this.toClient(m);
|
|
||||||
} catch (e) {
|
|
||||||
await socket.end();
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
this.braiinsSocket.on('close', () => {
|
|
||||||
console.log('Braiins closed connection');
|
|
||||||
socket.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
socket.on('error', async (error: Error) => { });
|
|
||||||
|
|
||||||
socket.on('data', (data: Buffer) => {
|
|
||||||
data.toString()
|
|
||||||
.split('\n')
|
|
||||||
.filter(m => m.length > 0)
|
|
||||||
.forEach(async (m) => {
|
|
||||||
try {
|
|
||||||
await this.toServer(m);
|
|
||||||
} catch (e) {
|
|
||||||
await socket.end();
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
socket.on('close', () => {
|
|
||||||
console.log('Client closed connection');
|
|
||||||
this.braiinsSocket.end();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private async toClient(message: string) {
|
|
||||||
let parsedMessage: IMiningNotify | any;
|
|
||||||
try {
|
|
||||||
parsedMessage = JSON.parse(message);
|
|
||||||
} catch (e) {
|
|
||||||
//console.log("Invalid JSON");
|
|
||||||
await this.socket.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsedMessage.method == null && Array.isArray(parsedMessage.result)) {
|
|
||||||
this.extraNonce = parsedMessage.result[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (parsedMessage.method) {
|
|
||||||
case eResponseMethod.SET_DIFFICULTY: {
|
|
||||||
this.serverDifficulty = parsedMessage.params[0];
|
|
||||||
parsedMessage.params[0] = 512;
|
|
||||||
message = JSON.stringify(parsedMessage);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eResponseMethod.MINING_NOTIFY: {
|
|
||||||
// clear jobs
|
|
||||||
if ((parsedMessage as IMiningNotify).params[8]) {
|
|
||||||
this.jobs = {};
|
|
||||||
}
|
|
||||||
this.jobs[parsedMessage.params[0]] = parsedMessage;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('Server:');
|
|
||||||
console.log(message);
|
|
||||||
this.socket.write(message + `\n`);
|
|
||||||
}
|
|
||||||
private async toServer(message: string) {
|
|
||||||
|
|
||||||
let parsedMessage = null;
|
|
||||||
try {
|
|
||||||
parsedMessage = JSON.parse(message);
|
|
||||||
} catch (e) {
|
|
||||||
//console.log("Invalid JSON");
|
|
||||||
await this.socket.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (parsedMessage.method) {
|
|
||||||
|
|
||||||
case eRequestMethod.AUTHORIZE: {
|
|
||||||
parsedMessage.params[0] = 'battlechicken';
|
|
||||||
message = JSON.stringify(parsedMessage);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eRequestMethod.SUBMIT: {
|
|
||||||
parsedMessage.params[0] = 'battlechicken';
|
|
||||||
|
|
||||||
message = JSON.stringify(parsedMessage);
|
|
||||||
|
|
||||||
const miningSubmitMessage = plainToInstance(
|
|
||||||
MiningSubmitMessage,
|
|
||||||
parsedMessage,
|
|
||||||
);
|
|
||||||
|
|
||||||
const validatorOptions: ValidatorOptions = {
|
|
||||||
whitelist: true,
|
|
||||||
forbidNonWhitelisted: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const errors = await validate(miningSubmitMessage, validatorOptions);
|
|
||||||
|
|
||||||
if (errors.length === 0) {
|
|
||||||
this.checkSubmission(miningSubmitMessage);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log('parsing error')
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('Client:');
|
|
||||||
console.log(message);
|
|
||||||
this.braiinsSocket.write(message + '\n');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkSubmission(submission: MiningSubmitMessage) {
|
|
||||||
console.log('Submission:');
|
|
||||||
console.log(submission);
|
|
||||||
|
|
||||||
const job = this.jobs[submission.jobId];
|
|
||||||
if (job == null) {
|
|
||||||
console.log('Job not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const versionMask = parseInt(submission.versionMask, 16);
|
|
||||||
|
|
||||||
const block = new Block();
|
|
||||||
|
|
||||||
block.version = parseInt(job.params[5], 16);
|
|
||||||
if (submission.versionMask !== undefined && versionMask != 0) {
|
|
||||||
block.version = (block.version ^ versionMask);
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevHash = this.swapEndianWords(job.params[1]);
|
|
||||||
block.prevHash = Buffer.from(prevHash, 'hex');
|
|
||||||
const coinbase = Buffer.from(`${job.params[2]}${this.extraNonce}${submission.extraNonce2}${job.params[3]}`, 'hex');
|
|
||||||
const coinbaseHash = bitcoinjs.crypto.hash256(coinbase);
|
|
||||||
block.merkleRoot = this.calculateMerkleRootHash(coinbaseHash, job.params[4]);
|
|
||||||
block.timestamp = parseInt(submission.ntime, 16);
|
|
||||||
block.bits = parseInt(job.params[6], 16);
|
|
||||||
block.nonce = parseInt(submission.nonce, 16);
|
|
||||||
|
|
||||||
const header = block.toBuffer(true);
|
|
||||||
|
|
||||||
const diff = this.calculateDifficulty(header);
|
|
||||||
|
|
||||||
console.log(`DIFFICULTY: ${diff.submissionDifficulty}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private calculateMerkleRootHash(newRoot: Buffer, merkleBranches: string[]): Buffer {
|
|
||||||
|
|
||||||
const bothMerkles = Buffer.alloc(64);
|
|
||||||
|
|
||||||
bothMerkles.set(newRoot);
|
|
||||||
|
|
||||||
for (let i = 0; i < merkleBranches.length; i++) {
|
|
||||||
bothMerkles.set(Buffer.from(merkleBranches[i], 'hex'), 32);
|
|
||||||
newRoot = bitcoinjs.crypto.hash256(bothMerkles);
|
|
||||||
bothMerkles.set(newRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bothMerkles.subarray(0, 32)
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculateDifficulty(header: Buffer): { submissionDifficulty: number, submissionHash: string } {
|
|
||||||
|
|
||||||
const hashResult = bitcoinjs.crypto.hash256(header);
|
|
||||||
|
|
||||||
let s64 = this.le256todouble(hashResult);
|
|
||||||
|
|
||||||
const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960');
|
|
||||||
const difficulty = truediffone.div(s64.toString());
|
|
||||||
return { submissionDifficulty: difficulty.toNumber(), submissionHash: hashResult.toString('hex') };
|
|
||||||
}
|
|
||||||
|
|
||||||
private le256todouble(target: Buffer): bigint {
|
|
||||||
|
|
||||||
const number = target.reduceRight((acc, byte) => {
|
|
||||||
// Shift the number 8 bits to the left and OR with the current byte
|
|
||||||
return (acc << BigInt(8)) | BigInt(byte);
|
|
||||||
}, BigInt(0));
|
|
||||||
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
private swapEndianWords(str: string) {
|
|
||||||
const hexGroups = str.match(/.{1,8}/g);
|
|
||||||
// Reverse each group and concatenate them
|
|
||||||
const reversedHexString = hexGroups.reduce((pre, cur, indx, arr) => {
|
|
||||||
const reversed = cur.match(/.{2}/g).reverse();
|
|
||||||
return `${pre}${reversed.join('')}`;
|
|
||||||
}, '');
|
|
||||||
return reversedHexString;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -108,7 +108,8 @@ describe('StratumV1Client', () => {
|
|||||||
|
|
||||||
configService = moduleRef.get<ConfigService>(ConfigService);
|
configService = moduleRef.get<ConfigService>(ConfigService);
|
||||||
|
|
||||||
bitcoinRpcService = new MockBitcoinRpcService(null);
|
|
||||||
|
bitcoinRpcService = new MockBitcoinRpcService(configService,null);
|
||||||
jest.spyOn(bitcoinRpcService, 'getBlockTemplate').mockReturnValue(Promise.resolve(MockRecording1.BLOCK_TEMPLATE));
|
jest.spyOn(bitcoinRpcService, 'getBlockTemplate').mockReturnValue(Promise.resolve(MockRecording1.BLOCK_TEMPLATE));
|
||||||
bitcoinRpcService.newBlock$ = newBlockEmitter.asObservable();
|
bitcoinRpcService.newBlock$ = newBlockEmitter.asObservable();
|
||||||
|
|
||||||
@@ -202,7 +203,7 @@ describe('StratumV1Client', () => {
|
|||||||
expect(socket.on).toHaveBeenCalled();
|
expect(socket.on).toHaveBeenCalled();
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUGGEST_DIFFICULTY));
|
socketEmitter(Buffer.from(MockRecording1.MINING_SUGGEST_DIFFICULTY));
|
||||||
await new Promise((r) => setTimeout(r, 1));
|
await new Promise((r) => setTimeout(r, 1));
|
||||||
expect(socket.write).toHaveBeenCalledWith(`{"id":4,"method":"mining.set_difficulty","params":[512]}\n`, expect.any(Function));
|
expect(socket.write).toHaveBeenCalledWith(`{"id":null,"method":"mining.set_difficulty","params":[512]}\n`, expect.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set difficulty', async () => {
|
it('should set difficulty', async () => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import Big from 'big.js';
|
|
||||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
import { plainToInstance } from 'class-transformer';
|
import { plainToInstance } from 'class-transformer';
|
||||||
import { validate, ValidatorOptions } from 'class-validator';
|
import { validate, ValidatorOptions } from 'class-validator';
|
||||||
@@ -27,6 +26,8 @@ import { StratumErrorMessage } from './stratum-messages/StratumErrorMessage';
|
|||||||
import { SubscriptionMessage } from './stratum-messages/SubscriptionMessage';
|
import { SubscriptionMessage } from './stratum-messages/SubscriptionMessage';
|
||||||
import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
|
import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
|
||||||
import { StratumV1ClientStatistics } from './StratumV1ClientStatistics';
|
import { StratumV1ClientStatistics } from './StratumV1ClientStatistics';
|
||||||
|
import { ExternalSharesService } from '../services/external-shares.service';
|
||||||
|
import { DifficultyUtils } from '../utils/difficulty.utils';
|
||||||
|
|
||||||
|
|
||||||
export class StratumV1Client {
|
export class StratumV1Client {
|
||||||
@@ -49,8 +50,11 @@ export class StratumV1Client {
|
|||||||
public extraNonceAndSessionId: string;
|
public extraNonceAndSessionId: string;
|
||||||
public sessionStart: Date;
|
public sessionStart: Date;
|
||||||
public noFee: boolean;
|
public noFee: boolean;
|
||||||
public hashRate: number;
|
public hashRate: number = 0;
|
||||||
|
|
||||||
|
private buffer: string = '';
|
||||||
|
|
||||||
|
private miningSubmissionHashes = new Set<string>()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly socket: Socket,
|
public readonly socket: Socket,
|
||||||
@@ -61,12 +65,16 @@ export class StratumV1Client {
|
|||||||
private readonly notificationService: NotificationService,
|
private readonly notificationService: NotificationService,
|
||||||
private readonly blocksService: BlocksService,
|
private readonly blocksService: BlocksService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly addressSettingsService: AddressSettingsService
|
private readonly addressSettingsService: AddressSettingsService,
|
||||||
|
private readonly externalSharesService: ExternalSharesService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
this.socket.on('data', (data: Buffer) => {
|
this.socket.on('data', (data: Buffer) => {
|
||||||
data.toString()
|
this.buffer += data.toString();
|
||||||
.split('\n')
|
let lines = this.buffer.split('\n');
|
||||||
|
this.buffer = lines.pop() || ''; // Save the last part of the data (incomplete line) to the buffer
|
||||||
|
|
||||||
|
lines
|
||||||
.filter(m => m.length > 0)
|
.filter(m => m.length > 0)
|
||||||
.forEach(async (m) => {
|
.forEach(async (m) => {
|
||||||
try {
|
try {
|
||||||
@@ -75,7 +83,7 @@ export class StratumV1Client {
|
|||||||
await this.socket.end();
|
await this.socket.end();
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -83,7 +91,9 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
public async destroy() {
|
public async destroy() {
|
||||||
|
|
||||||
await this.clientService.delete(this.extraNonceAndSessionId);
|
if (this.extraNonceAndSessionId) {
|
||||||
|
await this.clientService.delete(this.extraNonceAndSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.stratumSubscription != null) {
|
if (this.stratumSubscription != null) {
|
||||||
this.stratumSubscription.unsubscribe();
|
this.stratumSubscription.unsubscribe();
|
||||||
@@ -126,7 +136,7 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
const validatorOptions: ValidatorOptions = {
|
const validatorOptions: ValidatorOptions = {
|
||||||
whitelist: true,
|
whitelist: true,
|
||||||
forbidNonWhitelisted: true,
|
//forbidNonWhitelisted: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const errors = await validate(subscriptionMessage, validatorOptions);
|
const errors = await validate(subscriptionMessage, validatorOptions);
|
||||||
@@ -170,7 +180,7 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
const validatorOptions: ValidatorOptions = {
|
const validatorOptions: ValidatorOptions = {
|
||||||
whitelist: true,
|
whitelist: true,
|
||||||
forbidNonWhitelisted: true,
|
//forbidNonWhitelisted: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const errors = await validate(configurationMessage, validatorOptions);
|
const errors = await validate(configurationMessage, validatorOptions);
|
||||||
@@ -208,7 +218,7 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
const validatorOptions: ValidatorOptions = {
|
const validatorOptions: ValidatorOptions = {
|
||||||
whitelist: true,
|
whitelist: true,
|
||||||
forbidNonWhitelisted: true,
|
//forbidNonWhitelisted: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const errors = await validate(authorizationMessage, validatorOptions);
|
const errors = await validate(authorizationMessage, validatorOptions);
|
||||||
@@ -247,7 +257,7 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
const validatorOptions: ValidatorOptions = {
|
const validatorOptions: ValidatorOptions = {
|
||||||
whitelist: true,
|
whitelist: true,
|
||||||
forbidNonWhitelisted: true,
|
//forbidNonWhitelisted: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const errors = await validate(suggestDifficultyMessage, validatorOptions);
|
const errors = await validate(suggestDifficultyMessage, validatorOptions);
|
||||||
@@ -292,7 +302,7 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
const validatorOptions: ValidatorOptions = {
|
const validatorOptions: ValidatorOptions = {
|
||||||
whitelist: true,
|
whitelist: true,
|
||||||
forbidNonWhitelisted: true,
|
//forbidNonWhitelisted: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const errors = await validate(miningSubmitMessage, validatorOptions);
|
const errors = await validate(miningSubmitMessage, validatorOptions);
|
||||||
@@ -308,7 +318,7 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.error('Mining Submit validation error');
|
console.log('Mining Submit validation error');
|
||||||
const err = new StratumErrorMessage(
|
const err = new StratumErrorMessage(
|
||||||
miningSubmitMessage.id,
|
miningSubmitMessage.id,
|
||||||
eStratumErrorCode.OtherUnknown,
|
eStratumErrorCode.OtherUnknown,
|
||||||
@@ -360,6 +370,9 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
this.stratumSubscription = this.stratumV1JobsService.newMiningJob$.subscribe(async (jobTemplate) => {
|
this.stratumSubscription = this.stratumV1JobsService.newMiningJob$.subscribe(async (jobTemplate) => {
|
||||||
try {
|
try {
|
||||||
|
if(jobTemplate.blockData.clearJobs){
|
||||||
|
this.miningSubmissionHashes.clear();
|
||||||
|
}
|
||||||
await this.sendNewMiningJob(jobTemplate);
|
await this.sendNewMiningJob(jobTemplate);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.socket.end();
|
await this.socket.end();
|
||||||
@@ -373,11 +386,6 @@ export class StratumV1Client {
|
|||||||
}, 60 * 1000)
|
}, 60 * 1000)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.backgroundWork.push(
|
|
||||||
setInterval(async () => {
|
|
||||||
await this.statistics.saveShares(this.entity);
|
|
||||||
}, 60 * 1000)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendNewMiningJob(jobTemplate: IJobTemplate) {
|
private async sendNewMiningJob(jobTemplate: IJobTemplate) {
|
||||||
@@ -387,7 +395,7 @@ export class StratumV1Client {
|
|||||||
//50Th/s
|
//50Th/s
|
||||||
this.noFee = false;
|
this.noFee = false;
|
||||||
if (this.entity) {
|
if (this.entity) {
|
||||||
this.hashRate = await this.clientStatisticsService.getHashRateForSession(this.clientAuthorization.address, this.clientAuthorization.worker, this.extraNonceAndSessionId);
|
this.hashRate = this.statistics.hashRate;
|
||||||
this.noFee = this.hashRate != 0 && this.hashRate < 50000000000000;
|
this.noFee = this.hashRate != 0 && this.hashRate < 50000000000000;
|
||||||
}
|
}
|
||||||
if (this.noFee || devFeeAddress == null || devFeeAddress.length < 1) {
|
if (this.noFee || devFeeAddress == null || devFeeAddress.length < 1) {
|
||||||
@@ -416,6 +424,7 @@ export class StratumV1Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const job = new MiningJob(
|
const job = new MiningJob(
|
||||||
|
this.configService,
|
||||||
network,
|
network,
|
||||||
this.stratumV1JobsService.getNextId(),
|
this.stratumV1JobsService.getNextId(),
|
||||||
payoutInformation,
|
payoutInformation,
|
||||||
@@ -462,10 +471,24 @@ export class StratumV1Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const submissionHash = submission.hash();
|
||||||
|
if(this.miningSubmissionHashes.has(submissionHash)){
|
||||||
|
const err = new StratumErrorMessage(
|
||||||
|
submission.id,
|
||||||
|
eStratumErrorCode.DuplicateShare,
|
||||||
|
'Duplicate share').response();
|
||||||
|
const success = await this.write(err);
|
||||||
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}else{
|
||||||
|
this.miningSubmissionHashes.add(submissionHash);
|
||||||
|
}
|
||||||
|
|
||||||
const job = this.stratumV1JobsService.getJobById(submission.jobId);
|
const job = this.stratumV1JobsService.getJobById(submission.jobId);
|
||||||
|
|
||||||
// a miner may submit a job that doesn't exist anymore if it was removed by a new block notification
|
// a miner may submit a job that doesn't exist anymore if it was removed by a new block notification (or expired, 5 min)
|
||||||
if (job == null) {
|
if (job == null) {
|
||||||
const err = new StratumErrorMessage(
|
const err = new StratumErrorMessage(
|
||||||
submission.id,
|
submission.id,
|
||||||
@@ -489,7 +512,7 @@ export class StratumV1Client {
|
|||||||
parseInt(submission.ntime, 16)
|
parseInt(submission.ntime, 16)
|
||||||
);
|
);
|
||||||
const header = updatedJobBlock.toBuffer(true);
|
const header = updatedJobBlock.toBuffer(true);
|
||||||
const { submissionDifficulty } = this.calculateDifficulty(header);
|
const { submissionDifficulty } = DifficultyUtils.calculateDifficulty(header);
|
||||||
|
|
||||||
//console.log(`DIFF: ${submissionDifficulty} of ${this.sessionDifficulty} from ${this.clientAuthorization.worker + '.' + this.extraNonceAndSessionId}`);
|
//console.log(`DIFF: ${submissionDifficulty} of ${this.sessionDifficulty} from ${this.clientAuthorization.worker + '.' + this.extraNonceAndSessionId}`);
|
||||||
|
|
||||||
@@ -515,7 +538,7 @@ export class StratumV1Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.statistics.addShares(this.sessionDifficulty);
|
await this.statistics.addShares(this.entity, this.sessionDifficulty);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
// only update every minute
|
// only update every minute
|
||||||
if (this.entity.updatedAt == null || now.getTime() - this.entity.updatedAt.getTime() > 1000 * 60) {
|
if (this.entity.updatedAt == null || now.getTime() - this.entity.updatedAt.getTime() > 1000 * 60) {
|
||||||
@@ -525,27 +548,29 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
const err = new StratumErrorMessage(
|
|
||||||
submission.id,
|
|
||||||
eStratumErrorCode.DuplicateShare,
|
|
||||||
'Duplicate share').response();
|
|
||||||
console.error(err);
|
|
||||||
const success = await this.write(err);
|
|
||||||
if (!success) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (submissionDifficulty > this.entity.bestDifficulty) {
|
if (submissionDifficulty > this.entity.bestDifficulty) {
|
||||||
await this.clientService.updateBestDifficulty(this.extraNonceAndSessionId, submissionDifficulty);
|
await this.clientService.updateBestDifficulty(this.extraNonceAndSessionId, submissionDifficulty);
|
||||||
this.entity.bestDifficulty = submissionDifficulty;
|
this.entity.bestDifficulty = submissionDifficulty;
|
||||||
if (submissionDifficulty > (await this.addressSettingsService.getSettings(this.clientAuthorization.address, true)).bestDifficulty) {
|
if (submissionDifficulty > (await this.addressSettingsService.getSettings(this.clientAuthorization.address, true)).bestDifficulty) {
|
||||||
await this.addressSettingsService.updateBestDifficulty(this.clientAuthorization.address, submissionDifficulty);
|
await this.addressSettingsService.updateBestDifficulty(this.clientAuthorization.address, submissionDifficulty, this.entity.userAgent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const externalShareSubmissionEnabled: boolean = this.configService.get('EXTERNAL_SHARE_SUBMISSION_ENABLED')?.toLowerCase() == 'true';
|
||||||
|
const minimumDifficulty: number = parseFloat(this.configService.get('MINIMUM_DIFFICULTY')) || 1000000000000.0; // 1T
|
||||||
|
if (externalShareSubmissionEnabled && submissionDifficulty >= minimumDifficulty) {
|
||||||
|
// Submit share to API if enabled
|
||||||
|
this.externalSharesService.submitShare({
|
||||||
|
worker: this.clientAuthorization.worker,
|
||||||
|
address: this.clientAuthorization.address,
|
||||||
|
userAgent: this.clientSubscription.userAgent,
|
||||||
|
header: header.toString('hex'),
|
||||||
|
externalPoolName: this.configService.get('POOL_IDENTIFIER') || 'Public-Pool'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const err = new StratumErrorMessage(
|
const err = new StratumErrorMessage(
|
||||||
@@ -585,36 +610,14 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
await this.socket.write(data);
|
await this.socket.write(data);
|
||||||
|
|
||||||
|
|
||||||
// we need to clear the jobs so that the difficulty set takes effect. Otherwise the different miner implementations can cause issues
|
|
||||||
const jobTemplate = await firstValueFrom(this.stratumV1JobsService.newMiningJob$);
|
const jobTemplate = await firstValueFrom(this.stratumV1JobsService.newMiningJob$);
|
||||||
|
// we need to clear the jobs so that the difficulty set takes effect. Otherwise the different miner implementations can cause issues
|
||||||
|
jobTemplate.blockData.clearJobs = true;
|
||||||
await this.sendNewMiningJob(jobTemplate);
|
await this.sendNewMiningJob(jobTemplate);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateDifficulty(header: Buffer): { submissionDifficulty: number, submissionHash: string } {
|
|
||||||
|
|
||||||
const hashResult = bitcoinjs.crypto.hash256(header);
|
|
||||||
|
|
||||||
let s64 = this.le256todouble(hashResult);
|
|
||||||
|
|
||||||
const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960');
|
|
||||||
const difficulty = truediffone.div(s64.toString());
|
|
||||||
return { submissionDifficulty: difficulty.toNumber(), submissionHash: hashResult.toString('hex') };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private le256todouble(target: Buffer): bigint {
|
|
||||||
|
|
||||||
const number = target.reduceRight((acc, byte) => {
|
|
||||||
// Shift the number 8 bits to the left and OR with the current byte
|
|
||||||
return (acc << BigInt(8)) | BigInt(byte);
|
|
||||||
}, BigInt(0));
|
|
||||||
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async write(message: string): Promise<boolean> {
|
private async write(message: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
if (!this.socket.destroyed && !this.socket.writableEnded) {
|
if (!this.socket.destroyed && !this.socket.writableEnded) {
|
||||||
|
|||||||
@@ -6,10 +6,21 @@ const TARGET_SUBMISSION_PER_SECOND = 10;
|
|||||||
const MIN_DIFF = 0.00001;
|
const MIN_DIFF = 0.00001;
|
||||||
export class StratumV1ClientStatistics {
|
export class StratumV1ClientStatistics {
|
||||||
|
|
||||||
private shareBacklog: number = 0;
|
private shares: number = 0;
|
||||||
|
private acceptedCount: number = 0;
|
||||||
|
|
||||||
private submissionCacheStart: Date;
|
private submissionCacheStart: Date;
|
||||||
private submissionCache = [];
|
private submissionCache: { time: Date, difficulty: number }[] = [];
|
||||||
|
|
||||||
|
private currentTimeSlot: number = null;
|
||||||
|
private lastSave: number = null;
|
||||||
|
|
||||||
|
public hashRate = 0;
|
||||||
|
|
||||||
|
private previousTimeSlotTime: Date;
|
||||||
|
private currentTimeSlotTime: Date;
|
||||||
|
|
||||||
|
private previousShares: number = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly clientStatisticsService: ClientStatisticsService
|
private readonly clientStatisticsService: ClientStatisticsService
|
||||||
@@ -18,42 +29,90 @@ export class StratumV1ClientStatistics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async saveShares(client: ClientEntity) {
|
// We don't want to save them here because it can be DB intensive, instead do it every once in
|
||||||
|
// awhile with saveShares()
|
||||||
if (client == null || client.address == null || client.clientName == null || client.sessionId == null) {
|
public async addShares(client: ClientEntity, targetDifficulty: number) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 10 min
|
// 10 min
|
||||||
var coeff = 1000 * 60 * 10;
|
var coeff = 1000 * 60 * 10;
|
||||||
var date = new Date();
|
var date = new Date();
|
||||||
var rounded = new Date(Math.floor(date.getTime() / coeff) * coeff);
|
var timeSlot = new Date(Math.floor(date.getTime() / coeff) * coeff).getTime();
|
||||||
|
|
||||||
await this.clientStatisticsService.save({
|
|
||||||
time: rounded.getTime(),
|
|
||||||
shares: this.shareBacklog,
|
|
||||||
address: client.address,
|
|
||||||
clientName: client.clientName,
|
|
||||||
sessionId: client.sessionId
|
|
||||||
});
|
|
||||||
|
|
||||||
this.shareBacklog = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't want to save them here because it can be DB intensive, stead do it every once in
|
|
||||||
// awhile with saveShares()
|
|
||||||
public async addShares(targetDifficulty: number) {
|
|
||||||
|
|
||||||
if (this.submissionCache.length > CACHE_SIZE) {
|
if (this.submissionCache.length > CACHE_SIZE) {
|
||||||
this.submissionCache.shift();
|
this.submissionCache.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.submissionCache.push({
|
this.submissionCache.push({
|
||||||
time: new Date(),
|
time: date,
|
||||||
difficulty: targetDifficulty,
|
difficulty: targetDifficulty,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.shareBacklog += targetDifficulty;
|
|
||||||
|
if (this.currentTimeSlot == null) {
|
||||||
|
// First record, insert it
|
||||||
|
this.previousTimeSlotTime = new Date();
|
||||||
|
this.currentTimeSlotTime = new Date();
|
||||||
|
this.currentTimeSlot = timeSlot;
|
||||||
|
this.shares += targetDifficulty;
|
||||||
|
this.acceptedCount++;
|
||||||
|
await this.clientStatisticsService.insert({
|
||||||
|
time: this.currentTimeSlot,
|
||||||
|
shares: this.shares,
|
||||||
|
acceptedCount: this.acceptedCount,
|
||||||
|
address: client.address,
|
||||||
|
clientName: client.clientName,
|
||||||
|
sessionId: client.sessionId
|
||||||
|
});
|
||||||
|
this.lastSave = new Date().getTime();
|
||||||
|
} else if (this.currentTimeSlot != timeSlot) {
|
||||||
|
// Transitioning to a new time slot,
|
||||||
|
// First update the old time slot with the latest data
|
||||||
|
await this.clientStatisticsService.update({
|
||||||
|
time: this.currentTimeSlot,
|
||||||
|
shares: this.shares,
|
||||||
|
acceptedCount: this.acceptedCount,
|
||||||
|
address: client.address,
|
||||||
|
clientName: client.clientName,
|
||||||
|
sessionId: client.sessionId
|
||||||
|
});
|
||||||
|
this.previousShares = this.shares;
|
||||||
|
this.previousTimeSlotTime = this.currentTimeSlotTime;
|
||||||
|
this.currentTimeSlotTime = new Date();
|
||||||
|
// Set the new time slot and add incoming shares then insert it
|
||||||
|
this.currentTimeSlot = timeSlot;
|
||||||
|
this.shares = targetDifficulty;
|
||||||
|
this.acceptedCount = 1
|
||||||
|
await this.clientStatisticsService.insert({
|
||||||
|
time: this.currentTimeSlot,
|
||||||
|
shares: this.shares,
|
||||||
|
acceptedCount: this.acceptedCount,
|
||||||
|
address: client.address,
|
||||||
|
clientName: client.clientName,
|
||||||
|
sessionId: client.sessionId
|
||||||
|
});
|
||||||
|
this.lastSave = new Date().getTime();
|
||||||
|
} else if ((date.getTime() - 60 * 1000) > this.lastSave) {
|
||||||
|
// If we haven't saved for a minute, update the table
|
||||||
|
this.shares += targetDifficulty;
|
||||||
|
this.acceptedCount++;
|
||||||
|
await this.clientStatisticsService.update({
|
||||||
|
time: this.currentTimeSlot,
|
||||||
|
shares: this.shares,
|
||||||
|
acceptedCount: this.acceptedCount,
|
||||||
|
address: client.address,
|
||||||
|
clientName: client.clientName,
|
||||||
|
sessionId: client.sessionId
|
||||||
|
});
|
||||||
|
this.lastSave = new Date().getTime();
|
||||||
|
} else {
|
||||||
|
// Accept the shares if none of the prior conditions are met,
|
||||||
|
// saving to memory for storing later
|
||||||
|
this.shares += targetDifficulty;
|
||||||
|
this.acceptedCount++;
|
||||||
|
if(this.shares > 0) {
|
||||||
|
const time = new Date().getTime() - this.previousTimeSlotTime.getTime();
|
||||||
|
this.hashRate = ((this.previousShares + this.shares) * 4294967296) / (time / 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Expose, Transform } from 'class-transformer';
|
import { Expose, Transform } from 'class-transformer';
|
||||||
import { ArrayMaxSize, ArrayMinSize, IsArray, IsString, MaxLength } from 'class-validator';
|
import { ArrayMaxSize, ArrayMinSize, IsArray, IsOptional, IsString, MaxLength } from 'class-validator';
|
||||||
|
|
||||||
import { eRequestMethod } from '../enums/eRequestMethod';
|
import { eRequestMethod } from '../enums/eRequestMethod';
|
||||||
import { IsBitcoinAddress } from '../validators/bitcoin-address.validator';
|
import { IsBitcoinAddress } from '../validators/bitcoin-address.validator';
|
||||||
@@ -35,7 +35,8 @@ export class AuthorizationMessage extends StratumBaseMessage {
|
|||||||
return obj.params[1];
|
return obj.params[1];
|
||||||
})
|
})
|
||||||
@MaxLength(64)
|
@MaxLength(64)
|
||||||
public password: string;
|
@IsOptional()
|
||||||
|
public password?: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { ArrayMaxSize, ArrayMinSize, IsArray, IsString } from 'class-validator';
|
|||||||
|
|
||||||
import { eRequestMethod } from '../enums/eRequestMethod';
|
import { eRequestMethod } from '../enums/eRequestMethod';
|
||||||
import { StratumBaseMessage } from './StratumBaseMessage';
|
import { StratumBaseMessage } from './StratumBaseMessage';
|
||||||
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
|
|
||||||
|
|
||||||
export class MiningSubmitMessage extends StratumBaseMessage {
|
export class MiningSubmitMessage extends StratumBaseMessage {
|
||||||
|
|
||||||
@@ -63,6 +65,11 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hash(): string{
|
||||||
|
const buffer = Buffer.from(this.versionMask + this.nonce + this.extraNonce2 + this.ntime + this.jobId);
|
||||||
|
return bitcoinjs.crypto.hash256(buffer).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { IsEnum, IsNumber } from 'class-validator';
|
import { IsDefined, IsEnum, IsNumber, IsNumberString, isNumberString, IsOptional } from 'class-validator';
|
||||||
|
|
||||||
import { eRequestMethod } from '../enums/eRequestMethod';
|
import { eRequestMethod } from '../enums/eRequestMethod';
|
||||||
|
|
||||||
export class StratumBaseMessage {
|
export class StratumBaseMessage {
|
||||||
@IsNumber()
|
@IsDefined()
|
||||||
id?: number = null;
|
id?: number | string = null;
|
||||||
@IsEnum(eRequestMethod)
|
@IsEnum(eRequestMethod)
|
||||||
method: eRequestMethod;
|
method: eRequestMethod;
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ import { eStratumErrorCode } from '../enums/eStratumErrorCode';
|
|||||||
export class StratumErrorMessage {
|
export class StratumErrorMessage {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private id: number = null,
|
private id: number | string = null,
|
||||||
private errorCode: eStratumErrorCode,
|
private errorCode: eStratumErrorCode,
|
||||||
private errorMessage: string,
|
private errorMessage: string,
|
||||||
private validationErrors: ValidationError[] = []
|
private validationErrors: ValidationError[] = []
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export class SubscriptionMessage extends StratumBaseMessage {
|
|||||||
@IsString()
|
@IsString()
|
||||||
@MaxLength(128)
|
@MaxLength(128)
|
||||||
@Transform(({ value, key, obj, type }) => {
|
@Transform(({ value, key, obj, type }) => {
|
||||||
return obj.params[0] == null ? 'unknown' : SubscriptionMessage.refineUserAgent(obj.params[0]);
|
return obj?.params?.[0] == null ? 'unknown' : SubscriptionMessage.refineUserAgent(obj.params[0]);
|
||||||
})
|
})
|
||||||
public userAgent: string;
|
public userAgent: string;
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export class SubscriptionMessage extends StratumBaseMessage {
|
|||||||
|
|
||||||
public static refineUserAgent(userAgent: string): string {
|
public static refineUserAgent(userAgent: string): string {
|
||||||
// return userAgent;
|
// return userAgent;
|
||||||
userAgent = userAgent.split(' ')[0].split('/')[0].split('V')[0];
|
userAgent = userAgent.split(' ')[0].split('/')[0].split('V')[0].split('-')[0];
|
||||||
|
|
||||||
if (userAgent.includes('bosminer') || userAgent.includes('bOS')) {
|
if (userAgent.includes('bosminer') || userAgent.includes('bOS')) {
|
||||||
userAgent = 'Braiins OS';
|
userAgent = 'Braiins OS';
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export class SuggestDifficulty extends StratumBaseMessage {
|
|||||||
|
|
||||||
public response(difficulty: number) {
|
public response(difficulty: number) {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: null,
|
||||||
method: eResponseMethod.SET_DIFFICULTY,
|
method: eResponseMethod.SET_DIFFICULTY,
|
||||||
params: [difficulty]
|
params: [difficulty]
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-16
@@ -1,9 +1,9 @@
|
|||||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
import { Interval } from '@nestjs/schedule';
|
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
|
|
||||||
import { ClientStatisticsService } from '../ORM/client-statistics/client-statistics.service';
|
import { ClientStatisticsService } from '../ORM/client-statistics/client-statistics.service';
|
||||||
import { ClientService } from '../ORM/client/client.service';
|
import { ClientService } from '../ORM/client/client.service';
|
||||||
|
import { RpcBlockService } from '../ORM/rpc-block/rpc-block.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService implements OnModuleInit {
|
export class AppService implements OnModuleInit {
|
||||||
@@ -11,7 +11,8 @@ export class AppService implements OnModuleInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly clientStatisticsService: ClientStatisticsService,
|
private readonly clientStatisticsService: ClientStatisticsService,
|
||||||
private readonly clientService: ClientService,
|
private readonly clientService: ClientService,
|
||||||
private readonly dataSource: DataSource
|
private readonly dataSource: DataSource,
|
||||||
|
private readonly rpcBlockService: RpcBlockService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -28,25 +29,38 @@ export class AppService implements OnModuleInit {
|
|||||||
await this.dataSource.query(`PRAGMA synchronous = off;`);
|
await this.dataSource.query(`PRAGMA synchronous = off;`);
|
||||||
// //6Gb
|
// //6Gb
|
||||||
// await this.dataSource.query(`PRAGMA mmap_size = 6000000000;`);
|
// await this.dataSource.query(`PRAGMA mmap_size = 6000000000;`);
|
||||||
|
|
||||||
|
if (process.env.NODE_APP_INSTANCE == null || process.env.NODE_APP_INSTANCE == '0') {
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
await this.deleteOldStatistics();
|
||||||
|
}, 1000 * 60 * 60);
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
console.log('Killing dead clients');
|
||||||
|
await this.clientService.killDeadClients();
|
||||||
|
}, 1000 * 60 * 5);
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
console.log('Deleting Old Blocks');
|
||||||
|
await this.rpcBlockService.deleteOldBlocks();
|
||||||
|
}, 1000 * 60 * 60 * 24);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Interval(1000 * 60 * 60)
|
|
||||||
private async deleteOldStatistics() {
|
private async deleteOldStatistics() {
|
||||||
console.log('Deleting statistics');
|
console.log('Deleting statistics');
|
||||||
if (process.env.ENABLE_SOLO == 'true' && (process.env.NODE_APP_INSTANCE == null || process.env.NODE_APP_INSTANCE == '0')) {
|
|
||||||
const deletedStatistics = await this.clientStatisticsService.deleteOldStatistics();
|
const deletedStatistics = await this.clientStatisticsService.deleteOldStatistics();
|
||||||
console.log(`Deleted ${deletedStatistics.affected} old statistics`);
|
console.log(`Deleted ${deletedStatistics.affected} old statistics`);
|
||||||
const deletedClients = await this.clientService.deleteOldClients();
|
const deletedClients = await this.clientService.deleteOldClients();
|
||||||
console.log(`Deleted ${deletedClients.affected} old clients`);
|
console.log(`Deleted ${deletedClients.affected} old clients`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Interval(1000 * 60 * 5)
|
|
||||||
private async killDeadClients() {
|
|
||||||
console.log('Killing dead clients');
|
|
||||||
if (process.env.ENABLE_SOLO == 'true' && (process.env.NODE_APP_INSTANCE == null || process.env.NODE_APP_INSTANCE == '0')) {
|
|
||||||
await this.clientService.killDeadClients();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { RPCClient } from 'rpc-bitcoin';
|
import { RPCClient } from 'rpc-bitcoin';
|
||||||
import { BehaviorSubject, filter, shareReplay } from 'rxjs';
|
import { BehaviorSubject, filter, shareReplay } from 'rxjs';
|
||||||
import { RpcBlockService } from 'src/ORM/rpc-block/rpc-block.service';
|
import { RpcBlockService } from '../ORM/rpc-block/rpc-block.service';
|
||||||
import * as zmq from 'zeromq/v5-compat';
|
import * as zmq from 'zeromq';
|
||||||
|
|
||||||
import { IBlockTemplate } from '../models/bitcoin-rpc/IBlockTemplate';
|
import { IBlockTemplate } from '../models/bitcoin-rpc/IBlockTemplate';
|
||||||
import { IMiningInfo } from '../models/bitcoin-rpc/IMiningInfo';
|
import { IMiningInfo } from '../models/bitcoin-rpc/IMiningInfo';
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BitcoinRpcService {
|
export class BitcoinRpcService implements OnModuleInit {
|
||||||
|
|
||||||
private blockHeight = 0;
|
private blockHeight = 0;
|
||||||
private client: RPCClient;
|
private client: RPCClient;
|
||||||
@@ -20,13 +21,24 @@ export class BitcoinRpcService {
|
|||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private rpcBlockService: RpcBlockService
|
private rpcBlockService: RpcBlockService
|
||||||
) {
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
const url = this.configService.get('BITCOIN_RPC_URL');
|
const url = this.configService.get('BITCOIN_RPC_URL');
|
||||||
const user = this.configService.get('BITCOIN_RPC_USER');
|
let user = this.configService.get('BITCOIN_RPC_USER');
|
||||||
const pass = this.configService.get('BITCOIN_RPC_PASSWORD');
|
let pass = this.configService.get('BITCOIN_RPC_PASSWORD');
|
||||||
const port = parseInt(this.configService.get('BITCOIN_RPC_PORT'));
|
const port = parseInt(this.configService.get('BITCOIN_RPC_PORT'));
|
||||||
const timeout = parseInt(this.configService.get('BITCOIN_RPC_TIMEOUT'));
|
const timeout = parseInt(this.configService.get('BITCOIN_RPC_TIMEOUT'));
|
||||||
|
|
||||||
|
const cookiefile = this.configService.get('BITCOIN_RPC_COOKIEFILE')
|
||||||
|
|
||||||
|
if (cookiefile != undefined && cookiefile != '') {
|
||||||
|
const cookie = fs.readFileSync(cookiefile).toString().split(':')
|
||||||
|
|
||||||
|
user = cookie[0]
|
||||||
|
pass = cookie[1]
|
||||||
|
}
|
||||||
|
|
||||||
this.client = new RPCClient({ url, port, timeout, user, pass });
|
this.client = new RPCClient({ url, port, timeout, user, pass });
|
||||||
|
|
||||||
this.client.getrpcinfo().then((res) => {
|
this.client.getrpcinfo().then((res) => {
|
||||||
@@ -36,19 +48,36 @@ export class BitcoinRpcService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (this.configService.get('BITCOIN_ZMQ_HOST')) {
|
if (this.configService.get('BITCOIN_ZMQ_HOST')) {
|
||||||
const sock = zmq.socket("sub");
|
console.log('Using ZMQ');
|
||||||
sock.connect(this.configService.get('BITCOIN_ZMQ_HOST'));
|
const sock = new zmq.Subscriber;
|
||||||
sock.subscribe("rawblock");
|
|
||||||
sock.on("message", async (topic: Buffer, message: Buffer) => {
|
|
||||||
console.log("new block zmq");
|
sock.connectTimeout = 1000;
|
||||||
await this.pollMiningInfo();
|
sock.events.on('connect', () => {
|
||||||
|
console.log('ZMQ Connected');
|
||||||
});
|
});
|
||||||
this.pollMiningInfo().then(() => { });
|
sock.events.on('connect:retry', () => {
|
||||||
|
console.log('ZMQ Unable to connect, Retrying');
|
||||||
|
});
|
||||||
|
|
||||||
|
sock.connect(this.configService.get('BITCOIN_ZMQ_HOST'));
|
||||||
|
sock.subscribe('rawblock');
|
||||||
|
// Don't await this, otherwise it will block the rest of the program
|
||||||
|
this.listenForNewBlocks(sock);
|
||||||
|
await this.pollMiningInfo();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
setInterval(this.pollMiningInfo.bind(this), 500);
|
setInterval(this.pollMiningInfo.bind(this), 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async listenForNewBlocks(sock: zmq.Subscriber) {
|
||||||
|
for await (const [topic, msg] of sock) {
|
||||||
|
console.log("New Block");
|
||||||
|
await this.pollMiningInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async pollMiningInfo() {
|
public async pollMiningInfo() {
|
||||||
const miningInfo = await this.getMiningInfo();
|
const miningInfo = await this.getMiningInfo();
|
||||||
if (miningInfo != null && miningInfo.blocks > this.blockHeight) {
|
if (miningInfo != null && miningInfo.blocks > this.blockHeight) {
|
||||||
@@ -64,23 +93,26 @@ export class BitcoinRpcService {
|
|||||||
|
|
||||||
const block = await this.rpcBlockService.getBlock(blockHeight);
|
const block = await this.rpcBlockService.getBlock(blockHeight);
|
||||||
if (block != null && block.data != null) {
|
if (block != null && block.data != null) {
|
||||||
console.log('promise loop resolved');
|
console.log(`promise loop resolved, block height ${blockHeight}`);
|
||||||
return Promise.resolve(JSON.parse(block.data));
|
return Promise.resolve(JSON.parse(block.data));
|
||||||
}
|
}
|
||||||
console.log('promise loop');
|
console.log(`promise loop, block height ${blockHeight}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getBlockTemplate(blockHeight: number): Promise<IBlockTemplate> {
|
public async getBlockTemplate(blockHeight: number): Promise<IBlockTemplate> {
|
||||||
let result: IBlockTemplate;
|
let result: IBlockTemplate;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const block = await this.rpcBlockService.getBlock(blockHeight);
|
const block = await this.rpcBlockService.getBlock(blockHeight);
|
||||||
|
const completeBlock = block?.data != null;
|
||||||
|
|
||||||
if (block != null && block.data != null) {
|
// If the block has already been loaded, and the same instance is fetching the template again, we just need to refresh it.
|
||||||
|
if (completeBlock && block.lockedBy == process.env.NODE_APP_INSTANCE) {
|
||||||
|
result = await this.loadBlockTemplate(blockHeight);
|
||||||
|
}
|
||||||
|
else if (completeBlock) {
|
||||||
return Promise.resolve(JSON.parse(block.data));
|
return Promise.resolve(JSON.parse(block.data));
|
||||||
} else if (block == null) {
|
} else if (!completeBlock) {
|
||||||
|
|
||||||
if (process.env.NODE_APP_INSTANCE != null) {
|
if (process.env.NODE_APP_INSTANCE != null) {
|
||||||
// There is a unique constraint on the block height so if another process tries to lock, it'll throw
|
// There is a unique constraint on the block height so if another process tries to lock, it'll throw
|
||||||
try {
|
try {
|
||||||
@@ -89,24 +121,11 @@ export class BitcoinRpcService {
|
|||||||
result = await this.waitForBlock(blockHeight);
|
result = await this.waitForBlock(blockHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result = await this.loadBlockTemplate(blockHeight);
|
||||||
result = await this.client.getblocktemplate({
|
|
||||||
template_request: {
|
|
||||||
rules: ['segwit'],
|
|
||||||
mode: 'template',
|
|
||||||
capabilities: ['serverlist', 'proposal']
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await this.rpcBlockService.saveBlock(blockHeight, JSON.stringify(result));
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//wait for block
|
//wait for block
|
||||||
result = await this.waitForBlock(blockHeight);
|
result = await this.waitForBlock(blockHeight);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error getblocktemplate:', e.message);
|
console.error('Error getblocktemplate:', e.message);
|
||||||
throw new Error('Error getblocktemplate');
|
throw new Error('Error getblocktemplate');
|
||||||
@@ -115,6 +134,25 @@ export class BitcoinRpcService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async loadBlockTemplate(blockHeight: number) {
|
||||||
|
|
||||||
|
let blockTemplate: IBlockTemplate;
|
||||||
|
while (blockTemplate == null) {
|
||||||
|
blockTemplate = await this.client.getblocktemplate({
|
||||||
|
template_request: {
|
||||||
|
rules: ['segwit'],
|
||||||
|
mode: 'template',
|
||||||
|
capabilities: ['serverlist', 'proposal']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await this.rpcBlockService.saveBlock(blockHeight, JSON.stringify(blockTemplate));
|
||||||
|
|
||||||
|
return blockTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
public async getMiningInfo(): Promise<IMiningInfo> {
|
public async getMiningInfo(): Promise<IMiningInfo> {
|
||||||
try {
|
try {
|
||||||
return await this.client.getmininginfo();
|
return await this.client.getmininginfo();
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { HttpService } from '@nestjs/axios';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { ExternalPoolShare } from '../models/ExternalPoolShare';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ExternalSharesService {
|
||||||
|
private readonly shareApiUrl: string;
|
||||||
|
private readonly shareApiKey: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly httpService: HttpService
|
||||||
|
) {
|
||||||
|
this.shareApiUrl = this.configService.get('SHARE_SUBMISSION_URL') || 'https://web.public-pool.io';
|
||||||
|
this.shareApiKey = this.configService.get('SHARE_SUBMISSION_API_KEY');
|
||||||
|
}
|
||||||
|
|
||||||
|
public submitShare(share: ExternalPoolShare): void {
|
||||||
|
this.httpService.post(`${this.shareApiUrl}/api/share`, share, {
|
||||||
|
headers: {
|
||||||
|
'x-api-key': this.shareApiKey
|
||||||
|
}
|
||||||
|
}).subscribe({
|
||||||
|
next: () =>{
|
||||||
|
console.log('External share accepted');
|
||||||
|
},
|
||||||
|
error: (error) => console.error('Failed to submit share to API:', error.message)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
|
||||||
import { Server, Socket } from 'net';
|
|
||||||
|
|
||||||
import { ProxyClient } from '../models/ProxyClient';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ProxyService implements OnModuleInit {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async onModuleInit(): Promise<void> {
|
|
||||||
if (process.env.ENABLE_PROXY == 'true') {
|
|
||||||
console.log('Connecting to braiins');
|
|
||||||
|
|
||||||
|
|
||||||
const proxyServer = new Server(async (socket: Socket) => {
|
|
||||||
const proxyClient = new ProxyClient(socket);
|
|
||||||
});
|
|
||||||
|
|
||||||
proxyServer.listen(process.env.PROXY_PORT, () => {
|
|
||||||
console.log(`Proxy server is listening on port ${process.env.PROXY_PORT}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ export interface IJobTemplate {
|
|||||||
merkle_branch: string[];
|
merkle_branch: string[];
|
||||||
blockData: {
|
blockData: {
|
||||||
id: string,
|
id: string,
|
||||||
|
creation: number,
|
||||||
coinbasevalue: number;
|
coinbasevalue: number;
|
||||||
networkDifficulty: number;
|
networkDifficulty: number;
|
||||||
height: number;
|
height: number;
|
||||||
@@ -66,13 +67,14 @@ export class StratumV1JobsService {
|
|||||||
|
|
||||||
this.lastIntervalCount = interval;
|
this.lastIntervalCount = interval;
|
||||||
|
|
||||||
|
const currentTime = Math.floor(new Date().getTime() / 1000);
|
||||||
return {
|
return {
|
||||||
version: blockTemplate.version,
|
version: blockTemplate.version,
|
||||||
bits: parseInt(blockTemplate.bits, 16),
|
bits: parseInt(blockTemplate.bits, 16),
|
||||||
prevHash: this.convertToLittleEndian(blockTemplate.previousblockhash),
|
prevHash: this.convertToLittleEndian(blockTemplate.previousblockhash),
|
||||||
transactions: blockTemplate.transactions.map(t => bitcoinjs.Transaction.fromHex(t.data)),
|
transactions: blockTemplate.transactions.map(t => bitcoinjs.Transaction.fromHex(t.data)),
|
||||||
coinbasevalue: blockTemplate.coinbasevalue,
|
coinbasevalue: blockTemplate.coinbasevalue,
|
||||||
timestamp: Math.floor(new Date().getTime() / 1000),
|
timestamp: blockTemplate.mintime > currentTime ? blockTemplate.mintime : currentTime,
|
||||||
networkDifficulty: this.calculateNetworkDifficulty(parseInt(blockTemplate.bits, 16)),
|
networkDifficulty: this.calculateNetworkDifficulty(parseInt(blockTemplate.bits, 16)),
|
||||||
clearJobs,
|
clearJobs,
|
||||||
height: blockTemplate.height
|
height: blockTemplate.height
|
||||||
@@ -113,6 +115,7 @@ export class StratumV1JobsService {
|
|||||||
merkle_branch,
|
merkle_branch,
|
||||||
blockData: {
|
blockData: {
|
||||||
id,
|
id,
|
||||||
|
creation: new Date().getTime(),
|
||||||
coinbasevalue,
|
coinbasevalue,
|
||||||
networkDifficulty,
|
networkDifficulty,
|
||||||
height,
|
height,
|
||||||
@@ -124,6 +127,20 @@ export class StratumV1JobsService {
|
|||||||
if (data.blockData.clearJobs) {
|
if (data.blockData.clearJobs) {
|
||||||
this.blocks = {};
|
this.blocks = {};
|
||||||
this.jobs = {};
|
this.jobs = {};
|
||||||
|
}else{
|
||||||
|
const now = new Date().getTime();
|
||||||
|
// Delete old templates (5 minutes)
|
||||||
|
for(const templateId in this.blocks){
|
||||||
|
if(now - this.blocks[templateId].blockData.creation > (1000 * 60 * 5)){
|
||||||
|
delete this.blocks[templateId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Delete old jobs (5 minutes)
|
||||||
|
for (const jobId in this.jobs) {
|
||||||
|
if(now - this.jobs[jobId].creation > (1000 * 60 * 5)){
|
||||||
|
delete this.jobs[jobId];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.blocks[data.blockData.id] = data;
|
this.blocks[data.blockData.id] = data;
|
||||||
}),
|
}),
|
||||||
@@ -137,7 +154,8 @@ export class StratumV1JobsService {
|
|||||||
|
|
||||||
const target: number = mantissa * Math.pow(256, (exponent - 3)); // Calculate the target value
|
const target: number = mantissa * Math.pow(256, (exponent - 3)); // Calculate the target value
|
||||||
|
|
||||||
const difficulty: number = (Math.pow(2, 208) * 65535) / target; // Calculate the difficulty
|
const maxTarget = Math.pow(2, 208) * 65535; // Easiest target (max_target)
|
||||||
|
const difficulty: number = maxTarget / target; // Calculate the difficulty
|
||||||
|
|
||||||
return difficulty;
|
return difficulty;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ClientService } from '../ORM/client/client.service';
|
|||||||
import { BitcoinRpcService } from './bitcoin-rpc.service';
|
import { BitcoinRpcService } from './bitcoin-rpc.service';
|
||||||
import { NotificationService } from './notification.service';
|
import { NotificationService } from './notification.service';
|
||||||
import { StratumV1JobsService } from './stratum-v1-jobs.service';
|
import { StratumV1JobsService } from './stratum-v1-jobs.service';
|
||||||
|
import { ExternalSharesService } from './external-shares.service';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -23,15 +24,14 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
private readonly blocksService: BlocksService,
|
private readonly blocksService: BlocksService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly stratumV1JobsService: StratumV1JobsService,
|
private readonly stratumV1JobsService: StratumV1JobsService,
|
||||||
private readonly addressSettingsService: AddressSettingsService
|
private readonly addressSettingsService: AddressSettingsService,
|
||||||
|
private readonly externalSharesService: ExternalSharesService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onModuleInit(): Promise<void> {
|
async onModuleInit(): Promise<void> {
|
||||||
|
|
||||||
if (process.env.ENABLE_SOLO == 'true') {
|
|
||||||
//await this.clientStatisticsService.deleteAll();
|
|
||||||
if (process.env.NODE_APP_INSTANCE == '0') {
|
if (process.env.NODE_APP_INSTANCE == '0') {
|
||||||
await this.clientService.deleteAll();
|
await this.clientService.deleteAll();
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,6 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
this.startSocketServer();
|
this.startSocketServer();
|
||||||
}, 1000 * 10)
|
}, 1000 * 10)
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private startSocketServer() {
|
private startSocketServer() {
|
||||||
@@ -57,7 +56,8 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
this.notificationService,
|
this.notificationService,
|
||||||
this.blocksService,
|
this.blocksService,
|
||||||
this.configService,
|
this.configService,
|
||||||
this.addressSettingsService
|
this.addressSettingsService,
|
||||||
|
this.externalSharesService
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import Big from 'big.js';
|
||||||
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
|
|
||||||
|
export class DifficultyUtils {
|
||||||
|
static calculateDifficulty(header: Buffer): { submissionDifficulty: number; submissionHash: string } {
|
||||||
|
const hashResult = bitcoinjs.crypto.hash256(Buffer.isBuffer(header) ? header : Buffer.from(header, 'hex'));
|
||||||
|
const s64 = DifficultyUtils.le256todouble(hashResult);
|
||||||
|
const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960');
|
||||||
|
const difficulty = truediffone.div(s64.toString());
|
||||||
|
|
||||||
|
return {
|
||||||
|
submissionDifficulty: difficulty.toNumber(),
|
||||||
|
submissionHash: hashResult.toString('hex')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static le256todouble(target: Buffer): bigint {
|
||||||
|
const number = target.reduceRight((acc, byte) => {
|
||||||
|
return (acc << BigInt(8)) | BigInt(byte);
|
||||||
|
}, BigInt(0));
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,11 @@ import { IBlockTemplate } from '../../src/models/bitcoin-rpc/IBlockTemplate';
|
|||||||
|
|
||||||
export class MockRecording1 {
|
export class MockRecording1 {
|
||||||
public static EXTRA_NONCE = `57a6f098`;
|
public static EXTRA_NONCE = `57a6f098`;
|
||||||
public static MINING_SUBSCRIBE = `{"id": 1, "method": "mining.subscribe", "params": ["bitaxe v2.2"]}`;
|
public static MINING_SUBSCRIBE = `{"id": 1, "method": "mining.subscribe", "params": ["bitaxe v2.2"]}\n`;
|
||||||
public static MINING_CONFIGURE = `{"id": 2, "method": "mining.configure", "params": [["version-rolling"], {"version-rolling.mask": "ffffffff"}]}`;
|
public static MINING_CONFIGURE = `{"id": 2, "method": "mining.configure", "params": [["version-rolling"], {"version-rolling.mask": "ffffffff"}]}\n`;
|
||||||
public static MINING_AUTHORIZE = `{"id": 3, "method": "mining.authorize", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "x"]}`;
|
public static MINING_AUTHORIZE = `{"id": 3, "method": "mining.authorize", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "x"]}\n`;
|
||||||
public static MINING_SUGGEST_DIFFICULTY = `{"id": 4, "method": "mining.suggest_difficulty", "params": [512]}`;
|
public static MINING_SUGGEST_DIFFICULTY = `{"id": 4, "method": "mining.suggest_difficulty", "params": [512]}\n`;
|
||||||
public static BLOCK_TEMPLATE: IBlockTemplate = { "capabilities": ["proposal"], "version": 536870912, "rules": ["csv", "!segwit", "taproot"], "vbavailable": {}, "vbrequired": 0, "previousblockhash": "00000000000000022246451e9af7ac4f8bff2527d223f6e623740e92171592f2", "transactions": [{ "data": "02000000000101c3efa41c94ee24dd6c6aaccab0f27e9d2baf92e5112bff15cc463b600e4e3a3c0100000000fdffffff024ea319000000000017a914a5b8d32a6388f02204a1445bd623150ee3b851d18765d67b000000000017a914a316e3a4eaeee9cbee9c254c3292830281a8c9c38702473044022065fbcd2918e8b52c3af8c4ae21b3646d7a12f66ddcaa73a68273f5806527c0da0220291f555dd120bb9eadb144ccf4203db659b78d79bffcd37a29016da068f3abb9012102baab4d464b57771f5108f40ffc31b7242c5b430a61d4bfead024d715c441f3f3c7432500", "txid": "a174b99a87c899e66854ba6f101799ac852ef588ec69299846875e9d64355317", "hash": "748f4db5e45dab6a9903f50e7e760470ce80ac219c637d61418667a0ccb6c536", "depends": [], "fee": 14300, "sigops": 1, "weight": 569 }, { "data": "01000000018985719caa78472c8fad7efa88fc109c6faf6df3dc3a690656479e53c6b10fe9030000006b483045022100e7abb2f2f7e6cfad680efe10a7fc218d685a9cce929b1a4e62fcb726228ac9b50220538f3d2eb36d277bcadf00f3ac2abbaf80395c389e1f8048dc95fdf6204cf1840121037435c194e9b01b3d7f7a2802d6684a3af68d05bbf4ec8f17021980d777691f1dfdffffff040000000000000000536a4c5054325b0b15aaff5c59aab47ce39829055c3007922e2934d3b7c9f51238f8d4a98718da4f9c99a6692adacd0e324b7775eebe151c4f3ad37fcc5f9303eed491a02638b8002543c70002002543a7000a4c10270000000000001976a914000000000000000000000000000000000000000088ac10270000000000001976a914000000000000000000000000000000000000000088aca99f6412000000001976a914ba27f99e007c7f605a8305e318c1abde3cd220ac88ac00000000", "txid": "c34ac3f822a0b49f298be1a25fc9e9dd375c23985ed2656f7cf7d87b67fee1c0", "hash": "c34ac3f822a0b49f298be1a25fc9e9dd375c23985ed2656f7cf7d87b67fee1c0", "depends": [], "fee": 9153, "sigops": 12, "weight": 1408 }, { "data": "020000000001013f8e5ebf5620f2c62c9c4e4f77bb4eb903699620da372ad44f666a552e5b9fc801000000171600148442ee1fe3742153a6b40623d363a22ceed677c3ffffffff02bc0200000000000017a91470b93cf7ead31ef24b71159388c2e8e147f755b987aa0415000000000017a9145506e665bcf63e1225100d7c8613a640699fd5d087024730440220215cd74141ebcde3aee383f232dfd2c6b011645012f35714781d28d4a6959829022017ea40a15f4116bbf0a6f86dc0af08758d9a74673498a61164ed13e9d1c712e501210294184e8093958861740c4ce32e724a24b7df7eeb04702cae274652e86b01f83a00000000", "txid": "5aaf2fa707da25c24374b87d89c420c9e4c51022a4bbed4cc91614831f359366", "hash": "2c19268e65cc223c5fac9666883ecc1d10e680c54a4fdbbb3e4775f886b0feec", "depends": [], "fee": 491, "sigops": 1, "weight": 661 }, { "data": "020000000001012c53f5b0a12cadb57602466e703e60272183b75f3366073f4841dac8e616f2740000000000feffffff02e80300000000000016001496e381bd3fe4bae631adc5a5a420fb9124e5480af0a8100000000000160014cd06fc96fea44c6c5e074ba16dd20b222a0a9b32024730440220440ba2252ea6f70f1b8106897984330a997ef504f7f0dbb89679e44309026f2b02207074ad7dbd9a2bd37a41352527b7f3e68612956fe2085a0eb0b41f97f6a3599d012103b3ecf0f44126019a94a818245e35ecff1582c2900a38487a306fcd91bdb28003c8432500", "txid": "69770cf296b64329e784a5666fb0eab034fbb1780bf067f4e1606874c214f01d", "hash": "fadcaa8de64274e63c093592ed6d8c6b7584bb92c0e8d9c49f02a4df11f6f69d", "depends": [], "fee": 141, "sigops": 1, "weight": 561 }, { "data": "01000000019e03cd78895f6876932501b66eab1e44611d48b4c5029d250b7d2d6073b44b68000000006b48304502210088dfbeb842d54893987e5afb87b70fe70e7b91ebad740782515c44464b19a74202205a6bc47ad6c925f85aa29b2be1a174efcc30a4ad8ed6cfe6236b66d7a4f8d96f01210259530ee281106e237ac3aa801d1f7699ada05501186401da78b7d88849bbda8bffffffff0204ce3900000000001976a91419803f0d83a1b58348a4617c743638bbf9ec235588ac44ce5c01000000001976a9149e38aebe02be7cecacdb521f514cffe445c5533788ac00000000", "txid": "2137ca428d6233e680cea25c428b984391314d872a1a0f5f7b8dc8cfc4474c44", "hash": "2137ca428d6233e680cea25c428b984391314d872a1a0f5f7b8dc8cfc4474c44", "depends": [], "fee": 226, "sigops": 8, "weight": 904 }], "coinbaseaux": {}, "coinbasevalue": 2465717, "longpollid": "00000000000000022246451e9af7ac4f8bff2527d223f6e623740e92171592f29370", "target": "000000000000002495f800000000000000000000000000000000000000000000", "mintime": 1689512405, "mutable": ["time", "transactions", "prevblock"], "noncerange": "00000000ffffffff", "sigoplimit": 80000, "sizelimit": 4000000, "weightlimit": 4000000, "curtime": 1689514989, "bits": "192495f8", "height": 2442185, "default_witness_commitment": "6a24aa21a9edbd3d1d916aa0b57326a2d88ebe1b68a1d7c48585f26d8335fe6a94b62755f64c" };
|
public static BLOCK_TEMPLATE: IBlockTemplate = { "capabilities": ["proposal"], "version": 536870912, "rules": ["csv", "!segwit", "taproot"], "vbavailable": {}, "vbrequired": 0, "previousblockhash": "00000000000000022246451e9af7ac4f8bff2527d223f6e623740e92171592f2", "transactions": [{ "data": "02000000000101c3efa41c94ee24dd6c6aaccab0f27e9d2baf92e5112bff15cc463b600e4e3a3c0100000000fdffffff024ea319000000000017a914a5b8d32a6388f02204a1445bd623150ee3b851d18765d67b000000000017a914a316e3a4eaeee9cbee9c254c3292830281a8c9c38702473044022065fbcd2918e8b52c3af8c4ae21b3646d7a12f66ddcaa73a68273f5806527c0da0220291f555dd120bb9eadb144ccf4203db659b78d79bffcd37a29016da068f3abb9012102baab4d464b57771f5108f40ffc31b7242c5b430a61d4bfead024d715c441f3f3c7432500", "txid": "a174b99a87c899e66854ba6f101799ac852ef588ec69299846875e9d64355317", "hash": "748f4db5e45dab6a9903f50e7e760470ce80ac219c637d61418667a0ccb6c536", "depends": [], "fee": 14300, "sigops": 1, "weight": 569 }, { "data": "01000000018985719caa78472c8fad7efa88fc109c6faf6df3dc3a690656479e53c6b10fe9030000006b483045022100e7abb2f2f7e6cfad680efe10a7fc218d685a9cce929b1a4e62fcb726228ac9b50220538f3d2eb36d277bcadf00f3ac2abbaf80395c389e1f8048dc95fdf6204cf1840121037435c194e9b01b3d7f7a2802d6684a3af68d05bbf4ec8f17021980d777691f1dfdffffff040000000000000000536a4c5054325b0b15aaff5c59aab47ce39829055c3007922e2934d3b7c9f51238f8d4a98718da4f9c99a6692adacd0e324b7775eebe151c4f3ad37fcc5f9303eed491a02638b8002543c70002002543a7000a4c10270000000000001976a914000000000000000000000000000000000000000088ac10270000000000001976a914000000000000000000000000000000000000000088aca99f6412000000001976a914ba27f99e007c7f605a8305e318c1abde3cd220ac88ac00000000", "txid": "c34ac3f822a0b49f298be1a25fc9e9dd375c23985ed2656f7cf7d87b67fee1c0", "hash": "c34ac3f822a0b49f298be1a25fc9e9dd375c23985ed2656f7cf7d87b67fee1c0", "depends": [], "fee": 9153, "sigops": 12, "weight": 1408 }, { "data": "020000000001013f8e5ebf5620f2c62c9c4e4f77bb4eb903699620da372ad44f666a552e5b9fc801000000171600148442ee1fe3742153a6b40623d363a22ceed677c3ffffffff02bc0200000000000017a91470b93cf7ead31ef24b71159388c2e8e147f755b987aa0415000000000017a9145506e665bcf63e1225100d7c8613a640699fd5d087024730440220215cd74141ebcde3aee383f232dfd2c6b011645012f35714781d28d4a6959829022017ea40a15f4116bbf0a6f86dc0af08758d9a74673498a61164ed13e9d1c712e501210294184e8093958861740c4ce32e724a24b7df7eeb04702cae274652e86b01f83a00000000", "txid": "5aaf2fa707da25c24374b87d89c420c9e4c51022a4bbed4cc91614831f359366", "hash": "2c19268e65cc223c5fac9666883ecc1d10e680c54a4fdbbb3e4775f886b0feec", "depends": [], "fee": 491, "sigops": 1, "weight": 661 }, { "data": "020000000001012c53f5b0a12cadb57602466e703e60272183b75f3366073f4841dac8e616f2740000000000feffffff02e80300000000000016001496e381bd3fe4bae631adc5a5a420fb9124e5480af0a8100000000000160014cd06fc96fea44c6c5e074ba16dd20b222a0a9b32024730440220440ba2252ea6f70f1b8106897984330a997ef504f7f0dbb89679e44309026f2b02207074ad7dbd9a2bd37a41352527b7f3e68612956fe2085a0eb0b41f97f6a3599d012103b3ecf0f44126019a94a818245e35ecff1582c2900a38487a306fcd91bdb28003c8432500", "txid": "69770cf296b64329e784a5666fb0eab034fbb1780bf067f4e1606874c214f01d", "hash": "fadcaa8de64274e63c093592ed6d8c6b7584bb92c0e8d9c49f02a4df11f6f69d", "depends": [], "fee": 141, "sigops": 1, "weight": 561 }, { "data": "01000000019e03cd78895f6876932501b66eab1e44611d48b4c5029d250b7d2d6073b44b68000000006b48304502210088dfbeb842d54893987e5afb87b70fe70e7b91ebad740782515c44464b19a74202205a6bc47ad6c925f85aa29b2be1a174efcc30a4ad8ed6cfe6236b66d7a4f8d96f01210259530ee281106e237ac3aa801d1f7699ada05501186401da78b7d88849bbda8bffffffff0204ce3900000000001976a91419803f0d83a1b58348a4617c743638bbf9ec235588ac44ce5c01000000001976a9149e38aebe02be7cecacdb521f514cffe445c5533788ac00000000", "txid": "2137ca428d6233e680cea25c428b984391314d872a1a0f5f7b8dc8cfc4474c44", "hash": "2137ca428d6233e680cea25c428b984391314d872a1a0f5f7b8dc8cfc4474c44", "depends": [], "fee": 226, "sigops": 8, "weight": 904 }], "coinbaseaux": {}, "coinbasevalue": 2465717, "longpollid": "00000000000000022246451e9af7ac4f8bff2527d223f6e623740e92171592f29370", "target": "000000000000002495f800000000000000000000000000000000000000000000", "mintime": 1689512405, "mutable": ["time", "transactions", "prevblock"], "noncerange": "00000000ffffffff", "sigoplimit": 80000, "sizelimit": 4000000, "weightlimit": 4000000, "curtime": 1689514989, "bits": "192495f8", "height": 2442185, "default_witness_commitment": "6a24aa21a9edbd3d1d916aa0b57326a2d88ebe1b68a1d7c48585f26d8335fe6a94b62755f64c" };
|
||||||
public static MINING_SUBMIT = `{"id": 5, "method": "mining.submit", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "1", "c7080000", "64b3f3ec", "ed460d91", "00002000"]}`;
|
public static MINING_SUBMIT = `{"id": 5, "method": "mining.submit", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "1", "c7080000", "64b3f3ec", "ed460d91", "00002000"]}\n`;
|
||||||
public static TIME = '64b3f3ec';
|
public static TIME = '64b3f3ec';
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user