diff --git a/.github/workflows/build-image-on-push.yml b/.github/workflows/build-image-on-push.yml new file mode 100644 index 0000000..27b6e92 --- /dev/null +++ b/.github/workflows/build-image-on-push.yml @@ -0,0 +1,35 @@ +name: "Test build of image when Dockerfile is changed" + +on: + push: + branches-ignore: + - master + pull_request: + workflow_dispatch: + +jobs: + rebuild-container: + name: "Build image with cache" + runs-on: ubuntu-latest + steps: + - + name: Set up QEMU + uses: docker/setup-qemu-action@v3.0.0 + with: + platforms: linux/arm64 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + - + name: Checkout repository + uses: actions/checkout@v4 + - + name: Build image + id: docker_build_amd64 + uses: docker/build-push-action@v5.1.0 + with: + push: false + load: true + platforms: linux/amd64 + tags: public-pool:amd64 + cache-from: type=registry,ref=${{ secrets.DOCKER_USERNAME }}/public-pool:latest diff --git a/.github/workflows/trivy-analysis.yml b/.github/workflows/trivy-analysis.yml new file mode 100644 index 0000000..ae45fe2 --- /dev/null +++ b/.github/workflows/trivy-analysis.yml @@ -0,0 +1,37 @@ +name: Build and scan container for vulnerabilities with Trivy + +on: + push: + pull_request: + schedule: + - cron: '22 14 * * 0' + workflow_dispatch: + +jobs: + build: + name: Build and scan images + runs-on: ubuntu-latest + steps: + - + name: Checkout code + uses: actions/checkout@v4 + - + name: Build image from Dockerfile + uses: docker/build-push-action@v5.1.0 + with: + push: false + load: true + tags: ${{ secrets.DOCKER_USERNAME }}/public-pool:latest + - + name: Run Trivy vulnerability scanner against "latest" image + uses: aquasecurity/trivy-action@master + with: + image-ref: '${{ secrets.DOCKER_USERNAME }}/public-pool:latest' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' + - + name: Upload "latest" Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/update-base-image.yml b/.github/workflows/update-base-image.yml new file mode 100644 index 0000000..365e43a --- /dev/null +++ b/.github/workflows/update-base-image.yml @@ -0,0 +1,61 @@ +name: "Update image and push to Github Packages and Docker Hub weekly" + +on: + schedule: + - cron: "0 12 * * 1" # Run every Monday at noon. + workflow_dispatch: + +jobs: + rebuild-container: + name: "Rebuild Container with the latest base image" + runs-on: ubuntu-latest + steps: + - + name: Prepare outputs + id: prep + run: | + echo "::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + - + name: Set up QEMU + uses: docker/setup-qemu-action@v3.0.0 + with: + platforms: linux/arm64 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + - + name: Login to GitHub Container Registry + uses: docker/login-action@v3.0.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Login to DockerHub + uses: docker/login-action@v3.0.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - + name: Checkout repository + uses: actions/checkout@v4 + - + name: Get short SHA + id: get_short_sha + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + - + name: Build and push to Docker Hub and GitHub Packages Docker Registry + id: docker_build + uses: docker/build-push-action@v5.1.0 + with: + push: true + platforms: linux/amd64,linux/arm64 + tags: | + ghcr.io/${{ github.repository_owner }}/public-pool:latest + ghcr.io/${{ github.repository_owner }}/public-pool:${{ steps.get_short_sha.outputs.sha_short }} + ${{ secrets.DOCKER_USERNAME }}/public-pool:latest + ${{ secrets.DOCKER_USERNAME }}/public-pool:${{ steps.get_short_sha.outputs.sha_short }} + labels: | + org.opencontainers.image.source=${{ github.event.repository.html_url }} + org.opencontainers.image.created=${{ steps.prep.outputs.created }} + org.opencontainers.image.revision=${{ github.sha }} diff --git a/.github/workflows/update-image-on-push.yml b/.github/workflows/update-image-on-push.yml new file mode 100644 index 0000000..732ad40 --- /dev/null +++ b/.github/workflows/update-image-on-push.yml @@ -0,0 +1,64 @@ +name: "Update image when Dockerfile is changed" + +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + rebuild-container: + name: "Rebuild Container with the latest base image" + runs-on: ubuntu-latest + steps: + - + name: Prepare outputs + id: prep + run: | + echo "::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + - + name: Set up QEMU + uses: docker/setup-qemu-action@v3.0.0 + with: + platforms: linux/arm64 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + - + name: Login to GitHub Container Registry + uses: docker/login-action@v3.0.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Login to DockerHub + uses: docker/login-action@v3.0.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - + name: Checkout repository + uses: actions/checkout@v4 + - + name: Get short SHA + id: get_short_sha + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + - + name: Build and push to Docker Hub and GitHub Packages Docker Registry + uses: docker/build-push-action@v5.1.0 + id: docker_build_push + with: + push: true + platforms: linux/amd64,linux/arm64 + tags: | + ghcr.io/${{ github.repository_owner }}/public-pool:latest + ghcr.io/${{ github.repository_owner }}/public-pool:${{ steps.get_short_sha.outputs.sha_short }} + ${{ secrets.DOCKER_USERNAME }}/public-pool:latest + ${{ secrets.DOCKER_USERNAME }}/public-pool:${{ steps.get_short_sha.outputs.sha_short }} + labels: | + org.opencontainers.image.source=${{ github.event.repository.html_url }} + org.opencontainers.image.created=${{ steps.prep.outputs.created }} + org.opencontainers.image.revision=${{ github.sha }} + cache-from: type=registry,ref=${{ secrets.DOCKER_USERNAME }}/public-pool:latest + cache-to: type=inline diff --git a/Dockerfile b/Dockerfile index 894bdbd..e902ca9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,27 +2,36 @@ # Docker build environment # ############################ -FROM node:18.16.1-bookworm AS build +FROM node:18.16.1-bookworm-slim AS build + +# Upgrade all packages and install dependencies +RUN apt-get update \ + && apt-get upgrade -y +RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + python3 \ + build-essential \ + cmake \ + && apt clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* WORKDIR /build COPY . . -RUN npm i -RUN npm run build +# Build Public Pool using NPM +RUN npm i && npm run build ############################ # Docker final environment # ############################ -FROM node:18.16.1-bookworm +FROM node:18.16.1-bookworm-slim -EXPOSE 3333 -EXPOSE 3334 -EXPOSE 8332 +# Expose ports for Stratum and Bitcoin RPC +EXPOSE 3333 3334 8332 WORKDIR /public-pool +# Copy built binaries into the final image COPY --from=build /build . #COPY .env.example .env diff --git a/README.md b/README.md index c083953..2ed731e 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ $ docker build -t public-pool . Run container: ```bash -$ docker container run --name public-pool --rm -p 3333:3333 -p 3334:3334 -p 8332:8332 -v .env:.env public-pool +$ docker container run --name public-pool --rm -p 3333:3333 -p 3334:3334 -p 8332:8332 -v .env:/public-pool/.env public-pool ``` ### Docker Compose diff --git a/package-lock.json b/package-lock.json index e846e8b..53b032b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "sqlite3": "^5.1.6", "tiny-secp256k1": "^2.2.3", "typeorm": "^0.3.17", - "zeromq": "^5.3.1" + "zeromq": "6.0.0-beta.19" }, "devDependencies": { "@nestjs/cli": "^9.0.0", @@ -65,6 +65,16 @@ "typescript": "^5.0.0" } }, + "node_modules/@aminya/node-gyp-build": { + "version": "4.5.0-aminya.5", + "resolved": "https://registry.npmjs.org/@aminya/node-gyp-build/-/node-gyp-build-4.5.0-aminya.5.tgz", + "integrity": "sha512-TO7GldxDfSeSRNZVmhlm0liS2GX2o2Q/qTlcD3iD4ltTM6dir568LTRZ+ZDsDbLfMAkfhrbU+VuzNYImwYfczg==", + "bin": { + "aminya-node-gyp-build": "bin.js", + "aminya-node-gyp-build-optional": "optional.js", + "aminya-node-gyp-build-test": "build-test.js" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -4317,11 +4327,27 @@ "luxon": "^3.2.1" } }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6274,7 +6300,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, "engines": { "node": ">= 0.10" } @@ -6365,7 +6390,6 @@ "version": "2.12.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -6603,8 +6627,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isstream": { "version": "0.1.2", @@ -7905,7 +7928,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8145,6 +8167,14 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "dev": true }, + "node_modules/node-addon-api": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", + "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", + "engines": { + "node": "^16 || ^18 || >= 20" + } + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -8197,16 +8227,6 @@ "node": ">= 10.12.0" } }, - "node_modules/node-gyp-build": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", - "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-gyp/node_modules/are-we-there-yet": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", @@ -8719,7 +8739,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -8727,8 +8746,7 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { "version": "1.9.2", @@ -9330,7 +9348,6 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, "dependencies": { "resolve": "^1.1.6" }, @@ -9477,7 +9494,6 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, "dependencies": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -9821,7 +9837,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -9833,7 +9848,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -9842,7 +9856,6 @@ "version": "0.8.5", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, "dependencies": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -9859,7 +9872,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9875,6 +9887,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -10319,7 +10346,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -11300,7 +11326,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, "dependencies": { "isexe": "^2.0.0" }, @@ -11508,20 +11533,28 @@ } }, "node_modules/zeromq": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/zeromq/-/zeromq-5.3.1.tgz", - "integrity": "sha512-4WDF9bNWWXe8OAI319bVw5dmG4BklEk8wzFGwRQxEzKb+0mgDU5J/jtyZPo0BEusVIU1+3mRQIEdT5LtQn+aAw==", + "version": "6.0.0-beta.19", + "resolved": "https://registry.npmjs.org/zeromq/-/zeromq-6.0.0-beta.19.tgz", + "integrity": "sha512-2eU6H7Z4r9LmTkseGdIfEqp0k3rJr5EpXQw2oC0bUqy3wpoj1M83IU/c3qHPXp0z8IklNMoVmVm130d3g2Xf3g==", "hasInstallScript": true, "dependencies": { - "nan": "2.17.0", - "node-gyp-build": "^4.5.0" + "@aminya/node-gyp-build": "4.5.0-aminya.5", + "cross-env": "^7.0.3", + "node-addon-api": "^7.0.0", + "shelljs": "^0.8.5", + "shx": "^0.3.4" }, "engines": { - "node": ">=6.0" + "node": ">= 10.2" } } }, "dependencies": { + "@aminya/node-gyp-build": { + "version": "4.5.0-aminya.5", + "resolved": "https://registry.npmjs.org/@aminya/node-gyp-build/-/node-gyp-build-4.5.0-aminya.5.tgz", + "integrity": "sha512-TO7GldxDfSeSRNZVmhlm0liS2GX2o2Q/qTlcD3iD4ltTM6dir568LTRZ+ZDsDbLfMAkfhrbU+VuzNYImwYfczg==" + }, "@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -14817,11 +14850,18 @@ "luxon": "^3.2.1" } }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "requires": { + "cross-spawn": "^7.0.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -16290,8 +16330,7 @@ "interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" }, "ip": { "version": "2.0.0", @@ -16355,7 +16394,6 @@ "version": "2.12.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dev": true, "requires": { "has": "^1.0.3" } @@ -16509,8 +16547,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "isstream": { "version": "0.1.2", @@ -17522,8 +17559,7 @@ "minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "minipass": { "version": "4.2.8", @@ -17722,6 +17758,11 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "dev": true }, + "node-addon-api": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", + "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==" + }, "node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -17831,11 +17872,6 @@ } } }, - "node-gyp-build": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", - "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==" - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -18156,14 +18192,12 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-scurry": { "version": "1.9.2", @@ -18600,7 +18634,6 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, "requires": { "resolve": "^1.1.6" } @@ -18707,7 +18740,6 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, "requires": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -18942,7 +18974,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -18950,14 +18981,12 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "shelljs": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, "requires": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -18968,7 +18997,6 @@ "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", @@ -18980,6 +19008,15 @@ } } }, + "shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "requires": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + } + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -19312,8 +19349,7 @@ "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, "symbol-observable": { "version": "4.0.0", @@ -19952,7 +19988,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, "requires": { "isexe": "^2.0.0" } @@ -20089,12 +20124,15 @@ "dev": true }, "zeromq": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/zeromq/-/zeromq-5.3.1.tgz", - "integrity": "sha512-4WDF9bNWWXe8OAI319bVw5dmG4BklEk8wzFGwRQxEzKb+0mgDU5J/jtyZPo0BEusVIU1+3mRQIEdT5LtQn+aAw==", + "version": "6.0.0-beta.19", + "resolved": "https://registry.npmjs.org/zeromq/-/zeromq-6.0.0-beta.19.tgz", + "integrity": "sha512-2eU6H7Z4r9LmTkseGdIfEqp0k3rJr5EpXQw2oC0bUqy3wpoj1M83IU/c3qHPXp0z8IklNMoVmVm130d3g2Xf3g==", "requires": { - "nan": "2.17.0", - "node-gyp-build": "^4.5.0" + "@aminya/node-gyp-build": "4.5.0-aminya.5", + "cross-env": "^7.0.3", + "node-addon-api": "^7.0.0", + "shelljs": "^0.8.5", + "shx": "^0.3.4" } } } diff --git a/package.json b/package.json index f75ea5c..fe01d6c 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "sqlite3": "^5.1.6", "tiny-secp256k1": "^2.2.3", "typeorm": "^0.3.17", - "zeromq": "^5.3.1" + "zeromq": "6.0.0-beta.19" }, "devDependencies": { "@nestjs/cli": "^9.0.0", @@ -92,4 +92,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} +} \ No newline at end of file diff --git a/src/ORM/client-statistics/client-statistics.entity.ts b/src/ORM/client-statistics/client-statistics.entity.ts index 1dd69e7..272fc08 100644 --- a/src/ORM/client-statistics/client-statistics.entity.ts +++ b/src/ORM/client-statistics/client-statistics.entity.ts @@ -32,6 +32,4 @@ export class ClientStatisticsEntity extends TrackedEntity { acceptedCount: number; - - } \ No newline at end of file diff --git a/src/ORM/client-statistics/client-statistics.service.ts b/src/ORM/client-statistics/client-statistics.service.ts index 683785f..efc8b87 100644 --- a/src/ORM/client-statistics/client-statistics.service.ts +++ b/src/ORM/client-statistics/client-statistics.service.ts @@ -19,7 +19,6 @@ export class ClientStatisticsService { } public async save(clientStatistic: Partial) { - // Attempt to update the existing record const updateResult = await this.clientStatisticsRepository .createQueryBuilder() diff --git a/src/app.controller.ts b/src/app.controller.ts index cef29da..d1115a6 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -51,6 +51,37 @@ export class AppController { } + @Get('pool') + public async pool() { + + const CACHE_KEY = 'POOL_INFO'; + const cachedResult = await this.cacheManager.get(CACHE_KEY); + + if (cachedResult != null) { + return cachedResult; + } + + + const userAgents = await this.clientService.getUserAgents(); + const totalHashRate = userAgents.reduce((acc, userAgent) => acc + parseFloat(userAgent.totalHashRate), 0); + const totalMiners = userAgents.reduce((acc, userAgent) => acc + parseInt(userAgent.count), 0); + const blockHeight = (await firstValueFrom(this.bitcoinRpcService.newBlock$)).blocks; + const blocksFound = await this.blocksService.getFoundBlocks(); + + const data = { + totalHashRate, + blockHeight, + totalMiners, + blocksFound, + fee: 0 + } + + //5 min + await this.cacheManager.set(CACHE_KEY, data, 5 * 60 * 1000); + + return data; + } + @Get('network') public async network() { const miningInfo = await firstValueFrom(this.bitcoinRpcService.newBlock$); diff --git a/src/models/MiningJob.ts b/src/models/MiningJob.ts index 02bccf0..775842e 100644 --- a/src/models/MiningJob.ts +++ b/src/models/MiningJob.ts @@ -27,9 +27,9 @@ export class MiningJob { jobTemplate: IJobTemplate ) { - 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); //The commitment is recorded in a scriptPubKey of the coinbase transaction. It must be at least 38 bytes, with the first 6-byte of 0x6a24aa21a9ed, that is: // 1-byte - OP_RETURN (0x6a) diff --git a/src/models/StratumV1Client.ts b/src/models/StratumV1Client.ts index 6bfb53a..68a7ee1 100644 --- a/src/models/StratumV1Client.ts +++ b/src/models/StratumV1Client.ts @@ -6,6 +6,7 @@ import { validate, ValidatorOptions } from 'class-validator'; import * as crypto from 'crypto'; import { Socket } from 'net'; import { firstValueFrom, Subscription } from 'rxjs'; +import { clearInterval } from 'timers'; import { AddressSettingsService } from '../ORM/address-settings/address-settings.service'; import { BlocksService } from '../ORM/blocks/blocks.service'; @@ -35,7 +36,7 @@ export class StratumV1Client { private clientAuthorization: AuthorizationMessage; private clientSuggestedDifficulty: SuggestDifficulty; private stratumSubscription: Subscription; - private backgroundWork: NodeJS.Timer; + private backgroundWork: NodeJS.Timer[] = []; private statistics: StratumV1ClientStatistics; private stratumInitialized = false; @@ -87,9 +88,10 @@ export class StratumV1Client { if (this.stratumSubscription != null) { this.stratumSubscription.unsubscribe(); } - if (this.backgroundWork != null) { - clearInterval(this.backgroundWork); - } + + this.backgroundWork.forEach(work => { + clearInterval(work); + }); } private getRandomHexString() { @@ -333,46 +335,53 @@ export class StratumV1Client { && this.clientAuthorization != null && this.stratumInitialized == false) { - this.stratumInitialized = true; - - switch (this.clientSubscription.userAgent) { - case 'cpuminer': { - this.sessionDifficulty = 0.1; - } - } - - - if (this.clientSuggestedDifficulty == null) { - //console.log(`Setting difficulty to ${this.sessionDifficulty}`) - const setDifficulty = JSON.stringify(new SuggestDifficulty().response(this.sessionDifficulty)); - const success = await this.write(setDifficulty + '\n'); - if (!success) { - return; - } - } - - - this.stratumSubscription = this.stratumV1JobsService.newMiningJob$.subscribe(async (jobTemplate) => { - try { - await this.sendNewMiningJob(jobTemplate); - } catch (e) { - await this.socket.end(); - console.error(e); - } - }); - - this.backgroundWork = setInterval(async () => { - await this.checkDifficulty(); - await this.statistics.saveShares(this.entity); - }, 60 * 1000); + this.initStratum(); } } + private async initStratum() { + this.stratumInitialized = true; + + switch (this.clientSubscription.userAgent) { + case 'cpuminer': { + this.sessionDifficulty = 0.1; + } + } + + if (this.clientSuggestedDifficulty == null) { + //console.log(`Setting difficulty to ${this.sessionDifficulty}`) + const setDifficulty = JSON.stringify(new SuggestDifficulty().response(this.sessionDifficulty)); + const success = await this.write(setDifficulty + '\n'); + if (!success) { + return; + } + } + + this.stratumSubscription = this.stratumV1JobsService.newMiningJob$.subscribe(async (jobTemplate) => { + try { + await this.sendNewMiningJob(jobTemplate); + } catch (e) { + await this.socket.end(); + console.error(e); + } + }); + + this.backgroundWork.push( + setInterval(async () => { + await this.checkDifficulty(); + }, 60 * 1000) + ); + + this.backgroundWork.push( + setInterval(async () => { + await this.statistics.saveShares(this.entity); + }, 60 * 1000) + ); + } + private async sendNewMiningJob(jobTemplate: IJobTemplate) { - - let payoutInformation; const devFeeAddress = this.configService.get('DEV_FEE_ADDRESS'); //50Th/s @@ -480,7 +489,7 @@ export class StratumV1Client { parseInt(submission.ntime, 16) ); const header = updatedJobBlock.toBuffer(true); - const { submissionDifficulty, submissionHash } = this.calculateDifficulty(header); + const { submissionDifficulty } = this.calculateDifficulty(header); //console.log(`DIFF: ${submissionDifficulty} of ${this.sessionDifficulty} from ${this.clientAuthorization.worker + '.' + this.extraNonceAndSessionId}`); diff --git a/src/services/app.service.ts b/src/services/app.service.ts index 43a2365..16825e8 100644 --- a/src/services/app.service.ts +++ b/src/services/app.service.ts @@ -21,7 +21,6 @@ export class AppService implements OnModuleInit { } async onModuleInit() { - } @Interval(1000 * 60 * 60) diff --git a/src/services/bitcoin-rpc.service.ts b/src/services/bitcoin-rpc.service.ts index 38c9574..dff9883 100644 --- a/src/services/bitcoin-rpc.service.ts +++ b/src/services/bitcoin-rpc.service.ts @@ -3,12 +3,11 @@ import { ConfigService } from '@nestjs/config'; import { RPCClient } from 'rpc-bitcoin'; import { BehaviorSubject, filter, shareReplay } from 'rxjs'; import { RpcBlockService } from 'src/ORM/rpc-block/rpc-block.service'; +import * as zmq from 'zeromq/v5-compat'; import { IBlockTemplate } from '../models/bitcoin-rpc/IBlockTemplate'; import { IMiningInfo } from '../models/bitcoin-rpc/IMiningInfo'; -// import * as zmq from 'zeromq'; - @Injectable() export class BitcoinRpcService { @@ -30,17 +29,21 @@ export class BitcoinRpcService { this.client = new RPCClient({ url, port, timeout, user, pass }); - console.log('Bitcoin RPC connected'); + this.client.getrpcinfo().then((res) => { + console.log('Bitcoin RPC connected'); + }, () => { + console.error('Could not reach RPC host'); + }); if (this.configService.get('BITCOIN_ZMQ_HOST')) { - // const sock = zmq.socket("sub"); - // sock.connect(this.configService.get('BITCOIN_ZMQ_HOST')); - // sock.subscribe("rawblock"); - // sock.on("message", async (topic: Buffer, message: Buffer) => { - // console.log("new block zmq"); - // this.pollMiningInfo(); - // }); - this.pollMiningInfo(); + const sock = zmq.socket("sub"); + sock.connect(this.configService.get('BITCOIN_ZMQ_HOST')); + sock.subscribe("rawblock"); + sock.on("message", async (topic: Buffer, message: Buffer) => { + console.log("new block zmq"); + await this.pollMiningInfo(); + }); + this.pollMiningInfo().then(() => { }); } else { setInterval(this.pollMiningInfo.bind(this), 500); } @@ -77,11 +80,14 @@ export class BitcoinRpcService { if (block != null && block.data != null) { return Promise.resolve(JSON.parse(block.data)); } else if (block == null) { - // There is a unique constraint on the block height so if another process tries to lock, it'll throw - try { - await this.rpcBlockService.lockBlock(blockHeight, process.env.NODE_APP_INSTANCE); - } catch (e) { - result = await this.waitForBlock(blockHeight); + + 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 + try { + await this.rpcBlockService.lockBlock(blockHeight, process.env.NODE_APP_INSTANCE); + } catch (e) { + result = await this.waitForBlock(blockHeight); + } } result = await this.client.getblocktemplate({