mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 17:15:03 -07:00
Compare commits
34
Commits
master
...
87508bfb69
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87508bfb69 | ||
|
|
39e537a00c | ||
|
|
9ecfc68155 | ||
|
|
3e2de62259 | ||
|
|
8e977c21a1 | ||
|
|
5fd9f98807 | ||
|
|
6bc42defef | ||
|
|
bbc14e2272 | ||
|
|
fc767fa770 | ||
|
|
40e32d6e1a | ||
|
|
df6ec07c58 | ||
|
|
df245f396e | ||
|
|
42fc863c7c | ||
|
|
e46bb24155 | ||
|
|
cb4a173733 | ||
|
|
83c769849d | ||
|
|
ee5fea1df5 | ||
|
|
06acad6770 | ||
|
|
f7b22bb310 | ||
|
|
6cf6a007d9 | ||
|
|
5b71632f8a | ||
|
|
c5ff8521ac | ||
|
|
53ee510a1e | ||
|
|
7be554a358 | ||
|
|
eb5d434157 | ||
|
|
893c7c2003 | ||
|
|
4736eadf7b | ||
|
|
1089e6210c | ||
|
|
017978d250 | ||
|
|
5a17528b8f | ||
|
|
0b147ea5db | ||
|
|
bffcf6c74c | ||
|
|
96a38f9d9b | ||
|
|
39c4c033e2 |
@@ -38,3 +38,12 @@ ENABLE_PROXY=true
|
|||||||
BRAIINS_ACCESS_TOKEN=
|
BRAIINS_ACCESS_TOKEN=
|
||||||
|
|
||||||
PROXY_PORT=3333
|
PROXY_PORT=3333
|
||||||
|
|
||||||
|
#postgresql
|
||||||
|
DB_HOST=
|
||||||
|
DB_PORT=
|
||||||
|
DB_USERNAME=
|
||||||
|
DB_PASSWORD=
|
||||||
|
DB_DATABASE=
|
||||||
|
|
||||||
|
PRODUCTION=false
|
||||||
Generated
+240
@@ -29,6 +29,7 @@
|
|||||||
"discord.js": "^14.11.0",
|
"discord.js": "^14.11.0",
|
||||||
"merkle-lib": "^2.0.10",
|
"merkle-lib": "^2.0.10",
|
||||||
"node-telegram-bot-api": "^0.61.0",
|
"node-telegram-bot-api": "^0.61.0",
|
||||||
|
"pg": "^8.11.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rpc-bitcoin": "^2.0.0",
|
"rpc-bitcoin": "^2.0.0",
|
||||||
"rxjs": "^7.2.0",
|
"rxjs": "^7.2.0",
|
||||||
@@ -3740,6 +3741,14 @@
|
|||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-writer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/buffer-xor": {
|
"node_modules/buffer-xor": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||||
@@ -8656,6 +8665,11 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/packet-reader": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
||||||
|
},
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
@@ -8799,6 +8813,89 @@
|
|||||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
|
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
|
||||||
},
|
},
|
||||||
|
"node_modules/pg": {
|
||||||
|
"version": "8.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
|
||||||
|
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-writer": "2.0.0",
|
||||||
|
"packet-reader": "1.0.0",
|
||||||
|
"pg-connection-string": "^2.6.2",
|
||||||
|
"pg-pool": "^3.6.1",
|
||||||
|
"pg-protocol": "^1.6.0",
|
||||||
|
"pg-types": "^2.1.0",
|
||||||
|
"pgpass": "1.x"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"pg-cloudflare": "^1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg-native": ">=3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"pg-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-cloudflare": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-connection-string": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA=="
|
||||||
|
},
|
||||||
|
"node_modules/pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-pool": {
|
||||||
|
"version": "3.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
|
||||||
|
"integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-protocol": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
|
||||||
|
},
|
||||||
|
"node_modules/pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"dependencies": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
@@ -8934,6 +9031,41 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-bytea": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@@ -11324,6 +11456,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xtend": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/y18n": {
|
"node_modules/y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
@@ -14264,6 +14404,11 @@
|
|||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"buffer-writer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
|
||||||
|
},
|
||||||
"buffer-xor": {
|
"buffer-xor": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||||
@@ -17987,6 +18132,11 @@
|
|||||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"packet-reader": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
||||||
|
},
|
||||||
"parent-module": {
|
"parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
@@ -18094,6 +18244,68 @@
|
|||||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
|
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
|
||||||
},
|
},
|
||||||
|
"pg": {
|
||||||
|
"version": "8.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
|
||||||
|
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
|
||||||
|
"requires": {
|
||||||
|
"buffer-writer": "2.0.0",
|
||||||
|
"packet-reader": "1.0.0",
|
||||||
|
"pg-cloudflare": "^1.1.1",
|
||||||
|
"pg-connection-string": "^2.6.2",
|
||||||
|
"pg-pool": "^3.6.1",
|
||||||
|
"pg-protocol": "^1.6.0",
|
||||||
|
"pg-types": "^2.1.0",
|
||||||
|
"pgpass": "1.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pg-cloudflare": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"pg-connection-string": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA=="
|
||||||
|
},
|
||||||
|
"pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
|
||||||
|
},
|
||||||
|
"pg-pool": {
|
||||||
|
"version": "3.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
|
||||||
|
"integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"pg-protocol": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
|
||||||
|
},
|
||||||
|
"pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"requires": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"requires": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"picocolors": {
|
"picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
@@ -18198,6 +18410,29 @@
|
|||||||
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
|
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
|
||||||
|
},
|
||||||
|
"postgres-bytea": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="
|
||||||
|
},
|
||||||
|
"postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
|
||||||
|
},
|
||||||
|
"postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"requires": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"prelude-ls": {
|
"prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@@ -19836,6 +20071,11 @@
|
|||||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"xtend": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||||
|
},
|
||||||
"y18n": {
|
"y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"discord.js": "^14.11.0",
|
"discord.js": "^14.11.0",
|
||||||
"merkle-lib": "^2.0.10",
|
"merkle-lib": "^2.0.10",
|
||||||
"node-telegram-bot-api": "^0.61.0",
|
"node-telegram-bot-api": "^0.61.0",
|
||||||
|
"pg": "^8.11.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rpc-bitcoin": "^2.0.0",
|
"rpc-bitcoin": "^2.0.0",
|
||||||
"rxjs": "^7.2.0",
|
"rxjs": "^7.2.0",
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class UniqueNonceIndex implements MigrationInterface {
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE UNIQUE INDEX "IDX_unique_nonce" ON "client_entity" ("sessionId") WHERE "deletedAt" IS NOT NULL`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_unique_nonce"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { UserAgentReportService } from './user-agent-report.service';
|
||||||
|
import { UserAgentReportView } from './user-agent-report.view';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([UserAgentReportView])],
|
||||||
|
providers: [UserAgentReportService],
|
||||||
|
exports: [TypeOrmModule, UserAgentReportService],
|
||||||
|
})
|
||||||
|
export class UserAgentReportModule { }
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import { UserAgentReportView } from './user-agent-report.view';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserAgentReportService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(UserAgentReportView)
|
||||||
|
private userAgentReport: Repository<UserAgentReportView>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getReport() {
|
||||||
|
return await this.userAgentReport.find();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async refreshReport() {
|
||||||
|
try {
|
||||||
|
return await this.userAgentReport.query(`
|
||||||
|
COMMIT;
|
||||||
|
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
|
||||||
|
REFRESH MATERIALIZED VIEW user_agent_report_view;
|
||||||
|
COMMIT;
|
||||||
|
`);
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { DataSource, ViewColumn, ViewEntity } from 'typeorm';
|
||||||
|
|
||||||
|
import { ClientEntity } from '../../client/client.entity';
|
||||||
|
|
||||||
|
@ViewEntity({
|
||||||
|
materialized: true,
|
||||||
|
expression: (dataSource: DataSource) =>
|
||||||
|
dataSource
|
||||||
|
.createQueryBuilder()
|
||||||
|
.select('client.userAgent as "userAgent"')
|
||||||
|
.addSelect('COUNT(client.userAgent)', 'count')
|
||||||
|
.addSelect('MAX(client.bestDifficulty)', 'bestDifficulty')
|
||||||
|
.addSelect('SUM(client.hashRate)', 'totalHashRate')
|
||||||
|
.from(ClientEntity, 'client')
|
||||||
|
.groupBy('client.userAgent')
|
||||||
|
.orderBy('"totalHashRate"', 'DESC')
|
||||||
|
})
|
||||||
|
export class UserAgentReportView {
|
||||||
|
@ViewColumn()
|
||||||
|
userAgent: string;
|
||||||
|
@ViewColumn()
|
||||||
|
count: number;
|
||||||
|
@ViewColumn()
|
||||||
|
bestDifficulty: number;
|
||||||
|
@ViewColumn()
|
||||||
|
totalHashRate: number;
|
||||||
|
}
|
||||||
@@ -11,9 +11,12 @@ export class AddressSettingsEntity extends TrackedEntity {
|
|||||||
@Column({ default: 0 })
|
@Column({ default: 0 })
|
||||||
shares: number;
|
shares: number;
|
||||||
|
|
||||||
@Column({ type: 'real', default: 0 })
|
@Column({ type: 'decimal', default: 0 })
|
||||||
bestDifficulty: number;
|
bestDifficulty: number;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
bestDifficultyUserAgent: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
miscCoinbaseScriptData: string;
|
miscCoinbaseScriptData: string;
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,18 @@ 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) {
|
||||||
|
// It's possible to have a race condition here so if we get a PK violation, fetch it
|
||||||
|
try {
|
||||||
return await this.createNew(address);
|
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 createNew(address: string) {
|
public async createNew(address: string) {
|
||||||
@@ -46,4 +51,12 @@ export class AddressSettingsService {
|
|||||||
bestDifficulty: 0
|
bestDifficulty: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getHighScores() {
|
||||||
|
return await this.addressSettingsRepository.createQueryBuilder()
|
||||||
|
.select('"updatedAt", "bestDifficulty", "bestDifficultyUserAgent"')
|
||||||
|
.orderBy('"bestDifficulty"', 'DESC')
|
||||||
|
.limit(10)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
|
import { ClientEntity } from '../client/client.entity';
|
||||||
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
//Index for getHashRateForSession
|
|
||||||
@Index(["address", "clientName", "sessionId"])
|
|
||||||
//Index for statistics save
|
//Index for statistics save
|
||||||
@Index(["address", "clientName", "sessionId", "time"])
|
@Index(["clientId", "time"])
|
||||||
export class ClientStatisticsEntity extends TrackedEntity {
|
export class ClientStatisticsEntity extends TrackedEntity {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
@@ -22,14 +21,27 @@ export class ClientStatisticsEntity extends TrackedEntity {
|
|||||||
sessionId: string;
|
sessionId: string;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ type: 'integer' })
|
@Column({ type: 'bigint' })
|
||||||
time: number;
|
time: number;
|
||||||
|
|
||||||
@Column({ type: 'real' })
|
@Column({ type: 'decimal' })
|
||||||
shares: number;
|
shares: number;
|
||||||
|
|
||||||
@Column({ default: 0, type: 'integer' })
|
@Column({ default: 0, type: 'bigint' })
|
||||||
acceptedCount: number;
|
acceptedCount: number;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ManyToOne(
|
||||||
|
() => ClientEntity,
|
||||||
|
clientEntity => clientEntity.statistics,
|
||||||
|
{ nullable: false, }
|
||||||
|
)
|
||||||
|
client: ClientEntity;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'clientId' })
|
||||||
|
public clientId: string;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { ClientStatisticsEntity } from './client-statistics.entity';
|
import { ClientStatisticsEntity } from './client-statistics.entity';
|
||||||
|
|
||||||
@@ -10,7 +10,8 @@ export class ClientStatisticsService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|
||||||
|
@InjectDataSource()
|
||||||
|
private dataSource: DataSource,
|
||||||
@InjectRepository(ClientStatisticsEntity)
|
@InjectRepository(ClientStatisticsEntity)
|
||||||
private clientStatisticsRepository: Repository<ClientStatisticsEntity>,
|
private clientStatisticsRepository: Repository<ClientStatisticsEntity>,
|
||||||
) {
|
) {
|
||||||
@@ -18,28 +19,19 @@ export class ClientStatisticsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async save(clientStatistic: Partial<ClientStatisticsEntity>) {
|
public async save(clientStatistic: Partial<ClientStatisticsEntity>) {
|
||||||
// Attempt to update the existing record
|
// // Attempt to update the existing record
|
||||||
const updateResult = await this.clientStatisticsRepository
|
const result = await this.clientStatisticsRepository.update({ clientId: clientStatistic.clientId, time: clientStatistic.time },
|
||||||
.createQueryBuilder()
|
{
|
||||||
.update(ClientStatisticsEntity)
|
shares: clientStatistic.shares,
|
||||||
.set({
|
acceptedCount: clientStatistic.acceptedCount
|
||||||
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
|
// Check if the update affected any rows
|
||||||
if (updateResult.affected === 0) {
|
if (result.affected === 0) {
|
||||||
// If no rows were updated, insert a new record
|
// If no rows were updated, insert a new record
|
||||||
await this.clientStatisticsRepository.insert(clientStatistic);
|
await this.clientStatisticsRepository.insert(clientStatistic);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteOldStatistics() {
|
public async deleteOldStatistics() {
|
||||||
@@ -53,57 +45,7 @@ export class ClientStatisticsService {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChartDataForSite() {
|
|
||||||
|
|
||||||
var yesterday = new Date(new Date().getTime() - (24 * 60 * 60 * 1000));
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
SELECT
|
|
||||||
time AS label,
|
|
||||||
ROUND(((SUM(shares) * 4294967296) / 600)) AS data
|
|
||||||
FROM
|
|
||||||
client_statistics_entity AS entry
|
|
||||||
WHERE
|
|
||||||
entry.time > ${yesterday.getTime()}
|
|
||||||
GROUP BY
|
|
||||||
time
|
|
||||||
ORDER BY
|
|
||||||
time
|
|
||||||
LIMIT 144;
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result: any[] = await this.clientStatisticsRepository.query(query);
|
|
||||||
|
|
||||||
|
|
||||||
return result.map(res => {
|
|
||||||
res.label = new Date(res.label).toISOString();
|
|
||||||
return res;
|
|
||||||
}).slice(0, result.length - 1)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// public async getHashRateForAddress(address: string) {
|
|
||||||
|
|
||||||
// const oneHour = new Date(new Date().getTime() - (60 * 60 * 1000));
|
|
||||||
|
|
||||||
// const query = `
|
|
||||||
// SELECT
|
|
||||||
// SUM(entry.shares) AS difficultySum
|
|
||||||
// FROM
|
|
||||||
// client_statistics_entity AS entry
|
|
||||||
// WHERE
|
|
||||||
// entry.address = ? AND entry.time > ${oneHour}
|
|
||||||
// `;
|
|
||||||
|
|
||||||
// const result = await this.clientStatisticsRepository.query(query, [address]);
|
|
||||||
|
|
||||||
// const difficultySum = result[0].difficultySum;
|
|
||||||
|
|
||||||
// return (difficultySum * 4294967296) / (600);
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
public async getChartDataForAddress(address: string) {
|
public async getChartDataForAddress(address: string) {
|
||||||
|
|
||||||
@@ -111,12 +53,12 @@ export class ClientStatisticsService {
|
|||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
time label,
|
time AS label,
|
||||||
(SUM(shares) * 4294967296) / 600 AS data
|
(SUM(shares) * 4294967296) / 600 AS data
|
||||||
FROM
|
FROM
|
||||||
client_statistics_entity AS entry
|
client_statistics_entity AS entry
|
||||||
WHERE
|
WHERE
|
||||||
entry.address = ? AND entry.time > ${yesterday.getTime()}
|
entry.address = $1 AND entry.time > $2
|
||||||
GROUP BY
|
GROUP BY
|
||||||
time
|
time
|
||||||
ORDER BY
|
ORDER BY
|
||||||
@@ -125,10 +67,10 @@ export class ClientStatisticsService {
|
|||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await this.clientStatisticsRepository.query(query, [address]);
|
const result = await this.clientStatisticsRepository.query(query, [address, yesterday.getTime()]);
|
||||||
|
|
||||||
return result.map(res => {
|
return result.map(res => {
|
||||||
res.label = new Date(res.label).toISOString();
|
res.label = new Date(parseInt(res.label)).toISOString();
|
||||||
return res;
|
return res;
|
||||||
}).slice(0, result.length - 1);
|
}).slice(0, result.length - 1);
|
||||||
|
|
||||||
@@ -146,7 +88,7 @@ export class ClientStatisticsService {
|
|||||||
FROM
|
FROM
|
||||||
client_statistics_entity AS entry
|
client_statistics_entity AS entry
|
||||||
WHERE
|
WHERE
|
||||||
entry.address = ? AND entry.clientName = ? AND entry.time > ${oneHour.getTime()}
|
entry.address = $1 AND entry.clientName = $2 AND entry.time > ${oneHour.getTime()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await this.clientStatisticsRepository.query(query, [address, clientName]);
|
const result = await this.clientStatisticsRepository.query(query, [address, clientName]);
|
||||||
@@ -163,12 +105,12 @@ export class ClientStatisticsService {
|
|||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
time label,
|
time AS label,
|
||||||
(SUM(shares) * 4294967296) / 600 AS data
|
(SUM(shares) * 4294967296) / 600 AS data
|
||||||
FROM
|
FROM
|
||||||
client_statistics_entity AS entry
|
client_statistics_entity AS entry
|
||||||
WHERE
|
WHERE
|
||||||
entry.address = ? AND entry.clientName = ? AND entry.time > ${yesterday.getTime()}
|
entry.address = $1 AND entry."clientName" = $2 AND entry.time > ${yesterday.getTime()}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
time
|
time
|
||||||
ORDER BY
|
ORDER BY
|
||||||
@@ -179,7 +121,7 @@ export class ClientStatisticsService {
|
|||||||
const result = await this.clientStatisticsRepository.query(query, [address, clientName]);
|
const result = await this.clientStatisticsRepository.query(query, [address, clientName]);
|
||||||
|
|
||||||
return result.map(res => {
|
return result.map(res => {
|
||||||
res.label = new Date(res.label).toISOString();
|
res.label = new Date(parseInt(res.label)).toISOString();
|
||||||
return res;
|
return res;
|
||||||
}).slice(0, result.length - 1);
|
}).slice(0, result.length - 1);
|
||||||
|
|
||||||
@@ -187,22 +129,22 @@ export class ClientStatisticsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async getHashRateForSession(address: string, clientName: string, sessionId: string) {
|
public async getHashRateForSession(clientId: string) {
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
createdAt,
|
"createdAt",
|
||||||
updatedAt,
|
"updatedAt",
|
||||||
shares
|
shares
|
||||||
FROM
|
FROM
|
||||||
client_statistics_entity AS entry
|
client_statistics_entity AS entry
|
||||||
WHERE
|
WHERE
|
||||||
entry.address = ? AND entry.clientName = ? AND entry.sessionId = ?
|
entry."clientId" = $1
|
||||||
ORDER BY time DESC
|
ORDER BY time DESC
|
||||||
LIMIT 2;
|
LIMIT 2;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await this.clientStatisticsRepository.query(query, [address, clientName, sessionId]);
|
const result = await this.clientStatisticsRepository.query(query, [clientId]);
|
||||||
|
|
||||||
if (result.length < 1) {
|
if (result.length < 1) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -215,29 +157,29 @@ export class ClientStatisticsService {
|
|||||||
if (time < 1) {
|
if (time < 1) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return (latestStat.shares * 4294967296) / (time / 1000);
|
return (parseFloat(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) {
|
if (time < 1) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return ((latestStat.shares + secondLatestStat.shares) * 4294967296) / (time / 1000);
|
return ((parseFloat(latestStat.shares) + parseFloat(secondLatestStat.shares)) * 4294967296) / (time / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChartDataForSession(address: string, clientName: string, sessionId: string) {
|
public async getChartDataForSession(clientId: string) {
|
||||||
var yesterday = new Date(new Date().getTime() - (24 * 60 * 60 * 1000));
|
var yesterday = new Date(new Date().getTime() - (24 * 60 * 60 * 1000));
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
time label,
|
time AS label,
|
||||||
(SUM(shares) * 4294967296) / 600 AS data
|
(SUM(shares) * 4294967296) / 600 AS data
|
||||||
FROM
|
FROM
|
||||||
client_statistics_entity AS entry
|
client_statistics_entity AS entry
|
||||||
WHERE
|
WHERE
|
||||||
entry.address = ? AND entry.clientName = ? AND entry.sessionId = ? AND entry.time > ${yesterday.getTime()}
|
entry."clientId" = $1 AND entry.time > ${yesterday.getTime()}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
time
|
time
|
||||||
ORDER BY
|
ORDER BY
|
||||||
@@ -245,10 +187,10 @@ export class ClientStatisticsService {
|
|||||||
LIMIT 144;
|
LIMIT 144;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await this.clientStatisticsRepository.query(query, [address, clientName, sessionId]);
|
const result = await this.clientStatisticsRepository.query(query, [clientId]);
|
||||||
|
|
||||||
return result.map(res => {
|
return result.map(res => {
|
||||||
res.label = new Date(res.label).toISOString();
|
res.label = new Date(parseInt(res.label)).toISOString();
|
||||||
return res;
|
return res;
|
||||||
}).slice(0, result.length - 1);
|
}).slice(0, result.length - 1);
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import { Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
import { Column, Entity, Index, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
import { DateTimeTransformer } from '../utils/DateTimeTransformer';
|
import { ClientStatisticsEntity } from '../client-statistics/client-statistics.entity';
|
||||||
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
||||||
|
|
||||||
//https://www.sqlite.org/withoutrowid.html
|
|
||||||
|
|
||||||
//The WITHOUT ROWID optimization is likely to be helpful for tables that have non-integer
|
@Entity()
|
||||||
// or composite (multi-column) PRIMARY KEYs and that do not store large strings or BLOBs.
|
@Index("IDX_unique_nonce", { synchronize: false })
|
||||||
//WITHOUT ROWID tables work best when individual rows are not too large.
|
|
||||||
@Entity({ withoutRowid: true })
|
|
||||||
@Index(['address', 'clientName', 'sessionId'], { unique: true })
|
|
||||||
export class ClientEntity extends TrackedEntity {
|
export class ClientEntity extends TrackedEntity {
|
||||||
|
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
@PrimaryColumn({ length: 62, type: 'varchar' })
|
@Index()
|
||||||
|
@Column({ length: 62, type: 'varchar' })
|
||||||
address: string;
|
address: string;
|
||||||
|
|
||||||
@PrimaryColumn({ length: 64, type: 'varchar' })
|
@Column({ length: 64, type: 'varchar' })
|
||||||
clientName: string;
|
clientName: string;
|
||||||
|
|
||||||
@PrimaryColumn({ length: 8, type: 'varchar' })
|
@Column({ length: 8, type: 'varchar', })
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
|
|
||||||
|
|
||||||
@@ -27,15 +26,20 @@ export class ClientEntity extends TrackedEntity {
|
|||||||
userAgent: string;
|
userAgent: string;
|
||||||
|
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp' })
|
||||||
@Column({ type: 'datetime', transformer: new DateTimeTransformer() })
|
|
||||||
startTime: Date;
|
startTime: Date;
|
||||||
|
|
||||||
@Column({ type: 'real', default: 0 })
|
@Column({ type: 'decimal', default: 0 })
|
||||||
bestDifficulty: number
|
bestDifficulty: number
|
||||||
|
|
||||||
@Column({ default: 0 })
|
@Column({ default: 0, type: 'decimal' })
|
||||||
hashRate: number;
|
hashRate: number;
|
||||||
|
|
||||||
|
@OneToMany(
|
||||||
|
() => ClientStatisticsEntity,
|
||||||
|
clientStatisticsEntity => clientStatisticsEntity.client
|
||||||
|
)
|
||||||
|
statistics: ClientStatisticsEntity[]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Interval } from '@nestjs/schedule';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
import { Repository } from 'typeorm';
|
||||||
import { ObjectLiteral, Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { ClientEntity } from './client.entity';
|
import { ClientEntity } from './client.entity';
|
||||||
|
|
||||||
@@ -12,8 +10,6 @@ import { ClientEntity } from './client.entity';
|
|||||||
export class ClientService {
|
export class ClientService {
|
||||||
|
|
||||||
|
|
||||||
public insertQueue: { result: BehaviorSubject<ObjectLiteral | null>, partialClient: Partial<ClientEntity> }[] = [];
|
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(ClientEntity)
|
@InjectRepository(ClientEntity)
|
||||||
@@ -22,31 +18,18 @@ export class ClientService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Interval(1000 * 5)
|
|
||||||
public async insertClients() {
|
|
||||||
const queueCopy = [...this.insertQueue];
|
|
||||||
this.insertQueue = [];
|
|
||||||
|
|
||||||
const results = await this.clientRepository.insert(queueCopy.map(c => c.partialClient));
|
|
||||||
|
|
||||||
queueCopy.forEach((c, index) => {
|
|
||||||
c.result.next(results.generatedMaps[index]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async killDeadClients() {
|
public async killDeadClients() {
|
||||||
var fiveMinutes = new Date(new Date().getTime() - (5 * 60 * 1000)).toISOString();
|
|
||||||
|
|
||||||
return await this.clientRepository
|
return await this.clientRepository
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(ClientEntity)
|
.update(ClientEntity)
|
||||||
.set({ deletedAt: () => "DATETIME('now')" })
|
.set({ deletedAt: () => "NOW()" })
|
||||||
.where("deletedAt IS NULL AND updatedAt < DATETIME(:fiveMinutes)", { fiveMinutes })
|
.where("deletedAt IS NULL AND updatedAt < NOW() + interval '5 minutes' ")
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async heartbeat(address: string, clientName: string, sessionId: string, hashRate: number, updatedAt: Date) {
|
public async heartbeat(id, hashRate: number, updatedAt: Date) {
|
||||||
return await this.clientRepository.update({ address, clientName, sessionId }, { hashRate, deletedAt: null, updatedAt });
|
return await this.clientRepository.update({ id }, { hashRate, deletedAt: null, updatedAt });
|
||||||
}
|
}
|
||||||
|
|
||||||
// public async save(client: Partial<ClientEntity>) {
|
// public async save(client: Partial<ClientEntity>) {
|
||||||
@@ -55,26 +38,18 @@ export class ClientService {
|
|||||||
|
|
||||||
|
|
||||||
public async insert(partialClient: Partial<ClientEntity>): Promise<ClientEntity> {
|
public async insert(partialClient: Partial<ClientEntity>): Promise<ClientEntity> {
|
||||||
|
const insertResult = await this.clientRepository.insert(partialClient);
|
||||||
const result = new BehaviorSubject(null);
|
|
||||||
|
|
||||||
this.insertQueue.push({ result, partialClient });
|
|
||||||
|
|
||||||
|
|
||||||
// const insertResult = await this.clientRepository.insert(partialClient);
|
|
||||||
|
|
||||||
const generatedMap = await firstValueFrom(result);
|
|
||||||
|
|
||||||
const client = {
|
const client = {
|
||||||
...partialClient,
|
...partialClient,
|
||||||
...generatedMap
|
...insertResult.generatedMaps[0]
|
||||||
};
|
};
|
||||||
|
|
||||||
return client as ClientEntity;
|
return client as ClientEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async delete(sessionId: string) {
|
public async delete(id: string) {
|
||||||
return await this.clientRepository.softDelete({ sessionId });
|
return await this.clientRepository.softDelete({ id });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteOldClients() {
|
public async deleteOldClients() {
|
||||||
@@ -90,8 +65,8 @@ export class ClientService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateBestDifficulty(sessionId: string, bestDifficulty: number) {
|
public async updateBestDifficulty(id: string, bestDifficulty: number) {
|
||||||
return await this.clientRepository.update({ sessionId }, { bestDifficulty });
|
return await this.clientRepository.update({ id }, { bestDifficulty });
|
||||||
}
|
}
|
||||||
public async connectedClientCount(): Promise<number> {
|
public async connectedClientCount(): Promise<number> {
|
||||||
return await this.clientRepository.count();
|
return await this.clientRepository.count();
|
||||||
@@ -129,16 +104,16 @@ export class ClientService {
|
|||||||
return await this.clientRepository.softDelete({})
|
return await this.clientRepository.softDelete({})
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getUserAgents() {
|
// public async getUserAgents() {
|
||||||
const result = await this.clientRepository.createQueryBuilder('client')
|
// const result = await this.clientRepository.createQueryBuilder('client')
|
||||||
.select('client.userAgent as userAgent')
|
// .select('client.userAgent as "userAgent"')
|
||||||
.addSelect('COUNT(client.userAgent)', 'count')
|
// .addSelect('COUNT(client.userAgent)', 'count')
|
||||||
.addSelect('MAX(client.bestDifficulty)', 'bestDifficulty')
|
// .addSelect('MAX(client.bestDifficulty)', 'bestDifficulty')
|
||||||
.addSelect('SUM(client.hashRate)', 'totalHashRate')
|
// .addSelect('SUM(client.hashRate)', 'totalHashRate')
|
||||||
.groupBy('client.userAgent')
|
// .groupBy('client.userAgent')
|
||||||
.orderBy('count', 'DESC')
|
// .orderBy('"totalHashRate"', 'DESC')
|
||||||
.getRawMany();
|
// .getRawMany();
|
||||||
return result;
|
// return result;
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class HomeGraphEntity {
|
||||||
|
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column({ type: 'bigint' })
|
||||||
|
label: number;
|
||||||
|
|
||||||
|
@Column({ type: 'bigint' })
|
||||||
|
data: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { HomeGraphEntity } from './home-graph.entity';
|
||||||
|
import { HomeGraphService } from './home-graph.service';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([HomeGraphEntity])],
|
||||||
|
providers: [HomeGraphService],
|
||||||
|
exports: [TypeOrmModule, HomeGraphService],
|
||||||
|
})
|
||||||
|
export class HomeGraphModule { }
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import { HomeGraphEntity } from './home-graph.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class HomeGraphService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(HomeGraphEntity) private homeGraphRepository: Repository<HomeGraphEntity>
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getLatestTime(): Promise<Date> {
|
||||||
|
const result = await this.homeGraphRepository.createQueryBuilder('entity')
|
||||||
|
.select('MAX(entity.label)', 'maxNumber')
|
||||||
|
.getRawOne();
|
||||||
|
|
||||||
|
if (result.maxNumber == null) {
|
||||||
|
return new Date(new Date().getTime() - (24 * 60 * 60 * 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(parseInt(result.maxNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async save(graph: { label: number, data: number }[]) {
|
||||||
|
await this.homeGraphRepository.insert(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChartDataForSite(limit: number = 144 * 7) {
|
||||||
|
const records = await this.homeGraphRepository
|
||||||
|
.createQueryBuilder('homeGraph')
|
||||||
|
.orderBy('homeGraph.id', 'DESC')
|
||||||
|
.limit(limit)
|
||||||
|
.getRawMany();
|
||||||
|
|
||||||
|
return records.map(res => {
|
||||||
|
return {
|
||||||
|
label: new Date(parseInt(res.homeGraph_label)).toISOString(),
|
||||||
|
data: res.homeGraph_data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
import { CreateDateColumn, DeleteDateColumn, UpdateDateColumn } from 'typeorm';
|
import { CreateDateColumn, DeleteDateColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
import { DateTimeTransformer } from './DateTimeTransformer';
|
|
||||||
|
|
||||||
export abstract class TrackedEntity {
|
export abstract class TrackedEntity {
|
||||||
@DeleteDateColumn({ nullable: true, type: 'datetime', transformer: new DateTimeTransformer() })
|
@DeleteDateColumn({ nullable: true, type: 'timestamp' })
|
||||||
public deletedAt?: Date;
|
public deletedAt?: Date;
|
||||||
|
|
||||||
@CreateDateColumn({ type: 'datetime', transformer: new DateTimeTransformer() })
|
@CreateDateColumn({ type: 'timestamp' })
|
||||||
public createdAt?: Date
|
public createdAt?: Date
|
||||||
|
|
||||||
@UpdateDateColumn({ type: 'datetime', transformer: new DateTimeTransformer() })
|
@UpdateDateColumn({ type: 'timestamp' })
|
||||||
public updatedAt?: Date
|
public updatedAt?: Date
|
||||||
}
|
}
|
||||||
+16
-8
@@ -3,9 +3,12 @@ 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 { UserAgentReportService } from './ORM/_views/user-agent-report/user-agent-report.service';
|
||||||
|
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';
|
||||||
|
import { HomeGraphService } from './ORM/home-graph/home-graph.service';
|
||||||
import { BitcoinRpcService } from './services/bitcoin-rpc.service';
|
import { BitcoinRpcService } from './services/bitcoin-rpc.service';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
@@ -18,7 +21,10 @@ 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 homeGraphService: HomeGraphService,
|
||||||
|
private readonly addressSettingsService: AddressSettingsService,
|
||||||
|
private readonly userAgentReportService: UserAgentReportService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@Get('info')
|
@Get('info')
|
||||||
@@ -34,16 +40,18 @@ 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.userAgentReportService.getReport();
|
||||||
|
const highScores = await this.addressSettingsService.getHighScores();
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
blockData,
|
blockData,
|
||||||
userAgents,
|
userAgents,
|
||||||
|
highScores,
|
||||||
uptime: this.uptime
|
uptime: this.uptime
|
||||||
};
|
};
|
||||||
|
|
||||||
//1 min
|
//5 min
|
||||||
await this.cacheManager.set(CACHE_KEY, data, 1 * 60 * 1000);
|
await this.cacheManager.set(CACHE_KEY, data, 5 * 60 * 1000);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|
||||||
@@ -60,9 +68,9 @@ export class AppController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const userAgents = await this.clientService.getUserAgents();
|
const userAgents = await this.userAgentReportService.getReport();
|
||||||
const totalHashRate = userAgents.reduce((acc, userAgent) => acc + parseFloat(userAgent.totalHashRate), 0);
|
const totalHashRate = userAgents.reduce((acc, userAgent) => acc + userAgent.totalHashRate, 0);
|
||||||
const totalMiners = userAgents.reduce((acc, userAgent) => acc + parseInt(userAgent.count), 0);
|
const totalMiners = userAgents.reduce((acc, userAgent) => acc + userAgent.count, 0);
|
||||||
const blockHeight = (await firstValueFrom(this.bitcoinRpcService.newBlock$)).blocks;
|
const blockHeight = (await firstValueFrom(this.bitcoinRpcService.newBlock$)).blocks;
|
||||||
const blocksFound = await this.blocksService.getFoundBlocks();
|
const blocksFound = await this.blocksService.getFoundBlocks();
|
||||||
|
|
||||||
@@ -97,7 +105,7 @@ export class AppController {
|
|||||||
return cachedResult;
|
return cachedResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chartData = await this.clientStatisticsService.getChartDataForSite();
|
const chartData = await this.homeGraphService.getChartDataForSite();
|
||||||
|
|
||||||
//10 min
|
//10 min
|
||||||
await this.cacheManager.set(CACHE_KEY, chartData, 10 * 60 * 1000);
|
await this.cacheManager.set(CACHE_KEY, chartData, 10 * 60 * 1000);
|
||||||
|
|||||||
+43
-10
@@ -1,7 +1,7 @@
|
|||||||
import { HttpModule } from '@nestjs/axios';
|
import { HttpModule } from '@nestjs/axios';
|
||||||
import { CacheModule } from '@nestjs/cache-manager';
|
import { CacheModule } from '@nestjs/cache-manager';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { ScheduleModule } from '@nestjs/schedule';
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
@@ -9,11 +9,22 @@ import { AppController } from './app.controller';
|
|||||||
import { AddressController } from './controllers/address/address.controller';
|
import { AddressController } from './controllers/address/address.controller';
|
||||||
import { ClientController } from './controllers/client/client.controller';
|
import { ClientController } from './controllers/client/client.controller';
|
||||||
import { BitcoinAddressValidator } from './models/validators/bitcoin-address.validator';
|
import { BitcoinAddressValidator } from './models/validators/bitcoin-address.validator';
|
||||||
|
import { UniqueNonceIndex } from './ORM/_migrations/UniqueNonceIndex';
|
||||||
|
import { UserAgentReportModule } from './ORM/_views/user-agent-report/user-agent-report.module';
|
||||||
|
import { UserAgentReportView } from './ORM/_views/user-agent-report/user-agent-report.view';
|
||||||
|
import { AddressSettingsEntity } from './ORM/address-settings/address-settings.entity';
|
||||||
import { AddressSettingsModule } from './ORM/address-settings/address-settings.module';
|
import { AddressSettingsModule } from './ORM/address-settings/address-settings.module';
|
||||||
|
import { BlocksEntity } from './ORM/blocks/blocks.entity';
|
||||||
import { BlocksModule } from './ORM/blocks/blocks.module';
|
import { BlocksModule } from './ORM/blocks/blocks.module';
|
||||||
|
import { ClientStatisticsEntity } from './ORM/client-statistics/client-statistics.entity';
|
||||||
import { ClientStatisticsModule } from './ORM/client-statistics/client-statistics.module';
|
import { ClientStatisticsModule } from './ORM/client-statistics/client-statistics.module';
|
||||||
|
import { ClientEntity } from './ORM/client/client.entity';
|
||||||
import { ClientModule } from './ORM/client/client.module';
|
import { ClientModule } from './ORM/client/client.module';
|
||||||
|
import { HomeGraphEntity } from './ORM/home-graph/home-graph.entity';
|
||||||
|
import { HomeGraphModule } from './ORM/home-graph/home-graph.module';
|
||||||
|
import { RpcBlockEntity } from './ORM/rpc-block/rpc-block.entity';
|
||||||
import { RpcBlocksModule } from './ORM/rpc-block/rpc-block.module';
|
import { RpcBlocksModule } from './ORM/rpc-block/rpc-block.module';
|
||||||
|
import { TelegramSubscriptionsEntity } from './ORM/telegram-subscriptions/telegram-subscriptions.entity';
|
||||||
import { TelegramSubscriptionsModule } from './ORM/telegram-subscriptions/telegram-subscriptions.module';
|
import { TelegramSubscriptionsModule } from './ORM/telegram-subscriptions/telegram-subscriptions.module';
|
||||||
import { AppService } from './services/app.service';
|
import { AppService } from './services/app.service';
|
||||||
import { BitcoinRpcService } from './services/bitcoin-rpc.service';
|
import { BitcoinRpcService } from './services/bitcoin-rpc.service';
|
||||||
@@ -33,21 +44,43 @@ const ORMModules = [
|
|||||||
AddressSettingsModule,
|
AddressSettingsModule,
|
||||||
TelegramSubscriptionsModule,
|
TelegramSubscriptionsModule,
|
||||||
BlocksModule,
|
BlocksModule,
|
||||||
RpcBlocksModule
|
RpcBlocksModule,
|
||||||
|
HomeGraphModule,
|
||||||
|
UserAgentReportModule
|
||||||
]
|
]
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot(),
|
ConfigModule.forRoot(),
|
||||||
TypeOrmModule.forRoot({
|
TypeOrmModule.forRootAsync({
|
||||||
type: 'sqlite',
|
useFactory: (configService: ConfigService) => {
|
||||||
database: './DB/public-pool.sqlite',
|
return {
|
||||||
synchronize: true,
|
type: 'postgres',
|
||||||
autoLoadEntities: true,
|
host: configService.get('DB_HOST'),
|
||||||
|
port: parseInt(configService.get('DB_PORT')),
|
||||||
|
username: configService.get('DB_USERNAME'),
|
||||||
|
password: configService.get('DB_PASSWORD'),
|
||||||
|
database: configService.get('DB_DATABASE'),
|
||||||
|
entities: [
|
||||||
|
ClientEntity,
|
||||||
|
AddressSettingsEntity,
|
||||||
|
BlocksEntity,
|
||||||
|
ClientStatisticsEntity,
|
||||||
|
RpcBlockEntity,
|
||||||
|
TelegramSubscriptionsEntity,
|
||||||
|
HomeGraphEntity,
|
||||||
|
UserAgentReportView
|
||||||
|
],
|
||||||
|
synchronize: configService.get('PRODUCTION') != 'true',
|
||||||
logging: false,
|
logging: false,
|
||||||
enableWAL: true,
|
poolSize: 30,
|
||||||
busyTimeout: 30 * 1000,
|
migrations: [
|
||||||
|
UniqueNonceIndex
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService]
|
||||||
}),
|
}),
|
||||||
CacheModule.register(),
|
CacheModule.register(),
|
||||||
ScheduleModule.forRoot(),
|
ScheduleModule.forRoot(),
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class ClientController {
|
|||||||
return {
|
return {
|
||||||
sessionId: worker.sessionId,
|
sessionId: worker.sessionId,
|
||||||
name: worker.clientName,
|
name: worker.clientName,
|
||||||
bestDifficulty: worker.bestDifficulty.toFixed(2),
|
bestDifficulty: parseFloat(worker.bestDifficulty as any).toFixed(2),
|
||||||
hashRate: worker.hashRate,
|
hashRate: worker.hashRate,
|
||||||
startTime: worker.startTime,
|
startTime: worker.startTime,
|
||||||
lastSeen: worker.updatedAt
|
lastSeen: worker.updatedAt
|
||||||
@@ -75,7 +75,7 @@ export class ClientController {
|
|||||||
if (worker == null) {
|
if (worker == null) {
|
||||||
return new NotFoundException();
|
return new NotFoundException();
|
||||||
}
|
}
|
||||||
const chartData = await this.clientStatisticsService.getChartDataForSession(worker.address, worker.clientName, worker.sessionId);
|
const chartData = await this.clientStatisticsService.getChartDataForSession(worker.id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionId: worker.sessionId,
|
sessionId: worker.sessionId,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class StratumV1Client {
|
|||||||
private usedSuggestedDifficulty = false;
|
private usedSuggestedDifficulty = false;
|
||||||
private sessionDifficulty: number = 16384;
|
private sessionDifficulty: number = 16384;
|
||||||
|
|
||||||
private entity: ClientEntity;
|
private clientEntity: ClientEntity;
|
||||||
private creatingEntity: Promise<void>;
|
private creatingEntity: Promise<void>;
|
||||||
|
|
||||||
public extraNonceAndSessionId: string;
|
public extraNonceAndSessionId: string;
|
||||||
@@ -83,7 +83,9 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
public async destroy() {
|
public async destroy() {
|
||||||
|
|
||||||
await this.clientService.delete(this.extraNonceAndSessionId);
|
if (this.clientEntity?.id) {
|
||||||
|
await this.clientService.delete(this.clientEntity.id);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.stratumSubscription != null) {
|
if (this.stratumSubscription != null) {
|
||||||
this.stratumSubscription.unsubscribe();
|
this.stratumSubscription.unsubscribe();
|
||||||
@@ -335,7 +337,7 @@ export class StratumV1Client {
|
|||||||
&& this.clientAuthorization != null
|
&& this.clientAuthorization != null
|
||||||
&& this.stratumInitialized == false) {
|
&& this.stratumInitialized == false) {
|
||||||
|
|
||||||
await this.initStratum();
|
this.initStratum();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,11 +375,11 @@ export class StratumV1Client {
|
|||||||
}, 60 * 1000)
|
}, 60 * 1000)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.backgroundWork.push(
|
// this.backgroundWork.push(
|
||||||
setInterval(async () => {
|
// setInterval(async () => {
|
||||||
await this.statistics.saveShares(this.entity);
|
// await this.statistics.saveShares(this.clientEntity);
|
||||||
}, 60 * 1000)
|
// }, 60 * 1000)
|
||||||
);
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendNewMiningJob(jobTemplate: IJobTemplate) {
|
private async sendNewMiningJob(jobTemplate: IJobTemplate) {
|
||||||
@@ -386,8 +388,8 @@ export class StratumV1Client {
|
|||||||
const devFeeAddress = this.configService.get('DEV_FEE_ADDRESS');
|
const devFeeAddress = this.configService.get('DEV_FEE_ADDRESS');
|
||||||
//50Th/s
|
//50Th/s
|
||||||
this.noFee = false;
|
this.noFee = false;
|
||||||
if (this.entity) {
|
if (this.clientEntity) {
|
||||||
this.hashRate = await this.clientStatisticsService.getHashRateForSession(this.clientAuthorization.address, this.clientAuthorization.worker, this.extraNonceAndSessionId);
|
this.hashRate = await this.clientStatisticsService.getHashRateForSession(this.clientEntity.id);
|
||||||
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) {
|
||||||
@@ -438,11 +440,11 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
private async handleMiningSubmission(submission: MiningSubmitMessage) {
|
private async handleMiningSubmission(submission: MiningSubmitMessage) {
|
||||||
|
|
||||||
if (this.entity == null) {
|
if (this.clientEntity == null) {
|
||||||
if (this.creatingEntity == null) {
|
if (this.creatingEntity == null) {
|
||||||
this.creatingEntity = new Promise(async (resolve, reject) => {
|
this.creatingEntity = new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
this.entity = await this.clientService.insert({
|
this.clientEntity = await this.clientService.insert({
|
||||||
sessionId: this.extraNonceAndSessionId,
|
sessionId: this.extraNonceAndSessionId,
|
||||||
address: this.clientAuthorization.address,
|
address: this.clientAuthorization.address,
|
||||||
clientName: this.clientAuthorization.worker,
|
clientName: this.clientAuthorization.worker,
|
||||||
@@ -515,12 +517,12 @@ export class StratumV1Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.statistics.addShares(this.sessionDifficulty);
|
await this.statistics.addShares(this.clientEntity, 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.clientEntity.updatedAt == null || now.getTime() - this.clientEntity.updatedAt.getTime() > 1000 * 60) {
|
||||||
await this.clientService.heartbeat(this.entity.address, this.entity.clientName, this.entity.sessionId, this.hashRate, now);
|
await this.clientService.heartbeat(this.clientEntity.id, this.hashRate, now);
|
||||||
this.entity.updatedAt = now;
|
this.clientEntity.updatedAt = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -537,11 +539,11 @@ export class StratumV1Client {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (submissionDifficulty > this.entity.bestDifficulty) {
|
if (submissionDifficulty > this.clientEntity.bestDifficulty) {
|
||||||
await this.clientService.updateBestDifficulty(this.extraNonceAndSessionId, submissionDifficulty);
|
await this.clientService.updateBestDifficulty(this.clientEntity.id, submissionDifficulty);
|
||||||
this.entity.bestDifficulty = submissionDifficulty;
|
this.clientEntity.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.clientEntity.userAgent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,7 +633,7 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
console.error(`Error: Cannot write to closed or ended socket. ${this.extraNonceAndSessionId} ${message}`);
|
//console.error(`Error: Cannot write to closed or ended socket. ${this.extraNonceAndSessionId} ${message}`);
|
||||||
this.destroy();
|
this.destroy();
|
||||||
if (!this.socket.destroyed) {
|
if (!this.socket.destroyed) {
|
||||||
this.socket.destroy();
|
this.socket.destroy();
|
||||||
@@ -645,7 +647,7 @@ export class StratumV1Client {
|
|||||||
} else if (!this.socket.destroyed) {
|
} else if (!this.socket.destroyed) {
|
||||||
this.socket.destroy();
|
this.socket.destroy();
|
||||||
}
|
}
|
||||||
console.error(`Error occurred while writing to socket: ${this.extraNonceAndSessionId}`, error);
|
//console.error(`Error occurred while writing to socket: ${this.extraNonceAndSessionId}`, error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,14 @@ 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;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly clientStatisticsService: ClientStatisticsService
|
private readonly clientStatisticsService: ClientStatisticsService
|
||||||
@@ -20,40 +24,60 @@ export class StratumV1ClientStatistics {
|
|||||||
|
|
||||||
public async saveShares(client: ClientEntity) {
|
public async saveShares(client: ClientEntity) {
|
||||||
|
|
||||||
if (client == null || client.address == null || client.clientName == null || client.sessionId == null) {
|
// if (client == null || client.address == null || client.clientName == null || client.sessionId == null) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 10 min
|
|
||||||
var coeff = 1000 * 60 * 10;
|
|
||||||
var date = new Date();
|
|
||||||
var rounded = new Date(Math.floor(date.getTime() / coeff) * coeff);
|
|
||||||
|
|
||||||
await this.clientStatisticsService.save({
|
await this.clientStatisticsService.save({
|
||||||
time: rounded.getTime(),
|
time: this.currentTimeSlot,
|
||||||
shares: this.shareBacklog,
|
clientId: client.id,
|
||||||
|
shares: this.shares,
|
||||||
|
acceptedCount: this.acceptedCount,
|
||||||
address: client.address,
|
address: client.address,
|
||||||
clientName: client.clientName,
|
clientName: client.clientName,
|
||||||
sessionId: client.sessionId
|
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
|
// We don't want to save them here because it can be DB intensive, instead do it every once in
|
||||||
// awhile with saveShares()
|
// awhile with saveShares()
|
||||||
public async addShares(targetDifficulty: number) {
|
public async addShares(client: ClientEntity, targetDifficulty: number) {
|
||||||
|
|
||||||
|
// 10 min
|
||||||
|
var coeff = 1000 * 60 * 10;
|
||||||
|
var date = new Date();
|
||||||
|
var timeSlot = new Date(Math.floor(date.getTime() / coeff) * coeff).getTime();
|
||||||
|
|
||||||
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) {
|
||||||
|
this.currentTimeSlot = timeSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentTimeSlot != timeSlot) {
|
||||||
|
await this.saveShares(client);
|
||||||
|
this.shares = 0;
|
||||||
|
this.acceptedCount = 0;
|
||||||
|
this.currentTimeSlot = timeSlot;
|
||||||
|
} else if ((date.getTime() - 60 * 1000) > this.lastSave) {
|
||||||
|
await this.saveShares(client);
|
||||||
|
this.lastSave = new Date().getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.shares += targetDifficulty;
|
||||||
|
this.acceptedCount++;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+71
-24
@@ -1,9 +1,11 @@
|
|||||||
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 { UserAgentReportService } from '../ORM/_views/user-agent-report/user-agent-report.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';
|
||||||
|
import { HomeGraphService } from '../ORM/home-graph/home-graph.service';
|
||||||
|
import { RpcBlockService } from '../ORM/rpc-block/rpc-block.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService implements OnModuleInit {
|
export class AppService implements OnModuleInit {
|
||||||
@@ -11,42 +13,87 @@ 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 rpcBlockService: RpcBlockService,
|
||||||
|
private readonly homeGraphService: HomeGraphService,
|
||||||
|
private readonly dataSource: DataSource,
|
||||||
|
private readonly userAgentReportService: UserAgentReportService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
// if (process.env.NODE_APP_INSTANCE == '0') {
|
if (process.env.ENABLE_SOLO == 'true' && (process.env.NODE_APP_INSTANCE == null || process.env.NODE_APP_INSTANCE == '0')) {
|
||||||
// await this.dataSource.query(`VACUUM;`);
|
|
||||||
// }
|
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);
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
await this.updateChart();
|
||||||
|
}, 1000 * 60 * 10);
|
||||||
|
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
console.log('Refreshing user agent report view')
|
||||||
|
await this.userAgentReportService.refreshReport();
|
||||||
|
console.log('Finished Refreshing user agent report view')
|
||||||
|
}, 1000 * 60 * 5);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
//https://phiresky.github.io/blog/2020/sqlite-performance-tuning/
|
|
||||||
// //500 MB DB cache
|
|
||||||
// await this.dataSource.query(`PRAGMA cache_size = -500000;`);
|
|
||||||
//Normal is still completely corruption safe in WAL mode, and means only WAL checkpoints have to wait for FSYNC.
|
|
||||||
await this.dataSource.query(`PRAGMA synchronous = off;`);
|
|
||||||
// //6Gb
|
|
||||||
// await this.dataSource.query(`PRAGMA mmap_size = 6000000000;`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async updateChart() {
|
||||||
|
console.log('Updating Chart');
|
||||||
|
|
||||||
|
const latestGraphUpdate = await this.homeGraphService.getLatestTime();
|
||||||
|
|
||||||
|
|
||||||
|
const data = await this.dataSource.query(`
|
||||||
|
SELECT
|
||||||
|
time AS label,
|
||||||
|
ROUND(((SUM(shares) * 4294967296) / 600)) AS data
|
||||||
|
FROM
|
||||||
|
client_statistics_entity AS entry
|
||||||
|
WHERE
|
||||||
|
entry.time > ${latestGraphUpdate.getTime()}
|
||||||
|
GROUP BY
|
||||||
|
time
|
||||||
|
ORDER BY
|
||||||
|
time
|
||||||
|
LIMIT 144;
|
||||||
|
`)
|
||||||
|
|
||||||
|
console.log(`Fetched ${data.length} rows`);
|
||||||
|
|
||||||
|
if (data.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = data.slice(0, data.length - 1);
|
||||||
|
|
||||||
|
this.homeGraphService.save(result);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
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 'src/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';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BitcoinRpcService {
|
export class BitcoinRpcService implements OnModuleInit {
|
||||||
|
|
||||||
private blockHeight = 0;
|
private blockHeight = 0;
|
||||||
private client: RPCClient;
|
private client: RPCClient;
|
||||||
@@ -21,6 +21,10 @@ export class BitcoinRpcService {
|
|||||||
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');
|
const user = this.configService.get('BITCOIN_RPC_USER');
|
||||||
const pass = this.configService.get('BITCOIN_RPC_PASSWORD');
|
const pass = this.configService.get('BITCOIN_RPC_PASSWORD');
|
||||||
@@ -36,19 +40,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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user