Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f72c33b02 | |||
| 3759b59e6b | |||
| 3e88a90d61 | |||
| 3eae86d1eb | |||
| e04a5500aa | |||
| ba570f2de9 | |||
| f29afbb2a5 | |||
| 3b30129a4e | |||
| 5733c4eae9 | |||
| a4134bde72 | |||
| 77ad87cee5 |
@@ -117,6 +117,7 @@ set(HEADERS
|
||||
src/merge_mining_client.h
|
||||
src/merge_mining_client_json_rpc.h
|
||||
src/merkle.h
|
||||
src/nodejs_pool_rpc.h
|
||||
src/p2p_server.h
|
||||
src/p2pool.h
|
||||
src/p2pool_api.h
|
||||
@@ -153,6 +154,7 @@ set(SOURCES
|
||||
src/merge_mining_client.cpp
|
||||
src/merge_mining_client_json_rpc.cpp
|
||||
src/merkle.cpp
|
||||
src/nodejs_pool_rpc.cpp
|
||||
src/p2p_server.cpp
|
||||
src/p2pool.cpp
|
||||
src/p2pool_api.cpp
|
||||
|
||||
@@ -35,25 +35,25 @@ These are 3rd-party pages. If they are down, it doesn't mean there is a problem
|
||||
- [General Considerations](#general-considerations)
|
||||
- [GUI for P2Pool](#gui-for-p2pool)
|
||||
- [Merge mining](#merge-mining)
|
||||
- [GNU/Linux](#gnulinux)
|
||||
- [Windows](#windows)
|
||||
- [Mining on GNU/Linux](#gnulinux)
|
||||
- [Mining on Windows](#windows)
|
||||
- [Build instructions](#build-instructions)
|
||||
- [Reproducible builds](#reproducible-builds)
|
||||
- [Donations](#donations)
|
||||
|
||||
## Pool mining vs Solo mining vs P2Pool mining
|
||||
|
||||
Here's the comparison table of the different ways of mining. While pool mining is the easiest to setup, it centralizes Monero network and pool admin gets full power over your hashrate and your unpaid funds. Solo mining is 100% independent and the best for the network. P2Pool mining has all the advantages of solo mining, but also makes regular payouts possible.
|
||||
Here's the comparison table of the different ways of mining. While pool mining is the easiest to set up, it centralizes the Monero network and the pool admin gets full power over your hashrate and your unpaid funds. Solo mining is 100% independent and the best for the network. P2Pool mining has all the advantages of solo mining, but also makes regular payouts possible.
|
||||
|
||||
|Pool type|Payouts|Fee|Min. payout|Centralized?|Stability|Control|Setup
|
||||
|-|-|-|-|-|-|-|-|
|
||||
|Centralized pool|Regular|0-3%|0.001-0.01 XMR|Yes|Less stable due to pool server outages|Pool admin controls your mined funds, what you mine and can execute network attacks|Only miner software is required
|
||||
|Centralized pool|Regular|0-3%|0.001-0.01 XMR|Yes|Less stable due to pool server outages|The pool admin controls your mined funds and what you mine, and can execute attacks against the network|Only miner software is required
|
||||
|Solo|Rare|0%|0.6 XMR or more|No|As stable as your Monero node|100% under your control|Monero node + optional miner
|
||||
|**P2Pool**|Regular|0%|~0.00027 XMR|No|Very stable: node failover and multiple Monero nodes are supported|100% under your control|Monero node(s) + P2Pool node(s) + optional miner(s)
|
||||
|
||||
## Features
|
||||
|
||||
* Decentralized: no central server that can be shutdown/blocked. P2Pool uses a separate blockchain to merge mine with Monero. Pool admin can't go rogue or be pressured to do an attack on the network because there is no pool admin!
|
||||
* Decentralized: no central server that can be shut down/blocked. P2Pool uses a separate blockchain to merge mine with Monero. Pool admin can't go rogue or be pressured to do an attack on the network because there is no pool admin!
|
||||
* Permissionless: there is no one to decide who can mine on the pool and who can't.
|
||||
* Trustless: there is no pool wallet, funds are never in custody. All pool blocks pay out to miners directly.
|
||||
* PPLNS payout scheme
|
||||
@@ -63,14 +63,14 @@ Here's the comparison table of the different ways of mining. While pool mining i
|
||||
* Fast block times, down to 1 second
|
||||
* Uncle blocks are supported to avoid orphans - all your shares will be accounted for!
|
||||
* Configurable PPLNS window size and block time
|
||||
* Advanced mempool picking algorithm, it creates blocks with better reward than what monerod solo mining does
|
||||
* Advanced mempool picking algorithm: it creates blocks with better rewards than what monerod solo mining does
|
||||
* Password protected private pools
|
||||
* Highly reliable configurations are supported (multiple P2Pool nodes mining to the same wallet, each P2Pool node can use multiple Monero nodes and switch on the fly if an issue is detected)
|
||||
* Mining through [TOR](docs/TOR.MD) and [I2P](docs/I2P.MD) are fully supported
|
||||
* Mining through [TOR](docs/TOR.MD) and [I2P](docs/I2P.MD) is fully supported
|
||||
|
||||
## How payouts work in P2Pool
|
||||
|
||||
First you need to find a pool share. This share will stay in [PPLNS](https://en.wikipedia.org/wiki/Mining_pool#Pay-per-last-N-shares) window for up to 2160 pool blocks (6 hours, auto adjustable to balance payout sizes and frequency). The moment P2Pool finds a Monero block and you have at least 1 pool share in PPLNS window, you'll get a payout! Monero block reward is split between all miner wallets in PPLNS window. Each miner gets a part of block reward proportional to the total difficulty of his/her shares in PPLNS window.
|
||||
First you need to find a pool share. This share will stay in [PPLNS](https://en.wikipedia.org/wiki/Mining_pool#Pay-per-last-N-shares) window for up to 2160 pool blocks (6 hours, auto adjustable to balance payout sizes and frequency). The moment P2Pool finds a Monero block and you have at least 1 pool share in PPLNS window, you'll get a payout! Monero block reward is split between all miner wallets in PPLNS window. Each miner gets a part of the block reward proportional to the total difficulty of his/her shares in PPLNS window.
|
||||
|
||||
**NOTE** If P2Pool doesn't have enough hashrate to find Monero blocks faster than every 6 hours on average (~15 MH/s), **not all your pool shares will result in a payout**. Even if pool hashrate is higher, bad luck can sometimes result in a share going through PPLNS window without a payout. But in the long run it will be compensated by other shares receiving multiple payouts - your payouts will average out to what you'd get with regular pool mining.
|
||||
|
||||
@@ -89,7 +89,7 @@ In order to continue mining on P2Pool, you must update both Monero and P2Pool so
|
||||
|
||||
|Monero protocol version|Required Monero software version|Required P2Pool version
|
||||
|-|-|-|
|
||||
|v15, v16 (active after August 13th, 2022)|v0.18.0.0 or newer, v0.18.4.2 is recommended|v4.0 or newer
|
||||
|v15, v16 (active after August 13th, 2022)|v0.18.0.0 or newer, v0.18.4.6 or newer is recommended|v4.0 or newer
|
||||
|
||||
## How to mine on P2Pool
|
||||
|
||||
@@ -97,7 +97,7 @@ In order to continue mining on P2Pool, you must update both Monero and P2Pool so
|
||||
|
||||
- In order to mine on P2Pool, a synced Monero node using monerod v0.18.0.0 or newer is required. If you don't currently have one, you can download the [official Monero binaries](https://www.getmonero.org/downloads/), start `monerod` on your PC and wait until it's fully synced. Advanced Monero node setup instructions are [here](https://sethforprivacy.com/guides/run-a-monero-node-advanced/).
|
||||
- It is highly recommended that you create a separate restricted user account (in your OS) for mining. While P2Pool has been battle-tested for a long time now, any software may have unknown bugs/vulnerabilities.
|
||||
- You can mine to a primary wallet address (the one starting with `4`) for mining. If you want to mine to a subaddress, you will need to provide both the main address (starting with 4) and the subaddress (starting with 8) using `--wallet` and `--subaddress` command line parameters.
|
||||
- You can mine to a primary wallet address (the one starting with `4`). If you want to mine to a subaddress, you will need to provide both the main address (starting with 4) and the subaddress (starting with 8) using `--wallet` and `--subaddress` command line parameters.
|
||||
- You can add the `--mini` or `--nano` parameter to your P2Pool command to connect to the respective sidechain.
|
||||
> [!NOTE]
|
||||
> You cannot use both `--mini` and `--nano` together<br>
|
||||
@@ -124,7 +124,7 @@ In order to continue mining on P2Pool, you must update both Monero and P2Pool so
|
||||
Merge mining is available in P2Pool since the fork that happened on October 12th, 2024. Version 4.0 or newer is required to use it.
|
||||
|
||||
Blockchains that will support [Merge mining RPC API](https://github.com/SChernykh/p2pool/blob/master/docs/MERGE_MINING.MD#proposed-rpc-api):
|
||||
- [Townforge](https://townforge.net/) supports it in their [tmp-mm branch](https://git.townforge.net/townforge/townforge/src/branch/tmp-mm) (not released yet)
|
||||
- [Townforge](https://townforge.net/) supports merge mining (but Townforge is still in testnet phase)
|
||||
- [DarkFi](https://dark.fi/) supports it in their [testnet](https://dark.fi/book/testnet/merge-mining.html)
|
||||
```
|
||||
p2pool.exe --wallet YOUR_MONERO_WALLET_ADDRESS --merge-mine IP:port YOUR_WALLET_ADDRESS_ON_ANOTHER_BLOCKCHAIN
|
||||
@@ -144,7 +144,7 @@ TARI_NODE_IP is 127.0.0.1 if you run both on the same machine (recommended).
|
||||
|
||||
Merge mining is available on Tari's mainnet:
|
||||
- Download [Tari suite](https://github.com/tari-project/tari/releases/latest) for your OS
|
||||
- Run Minotari node and wait until in synchronizes
|
||||
- Run Minotari node and wait until it synchronizes
|
||||
- Run Minotari console wallet to create a Tari wallet
|
||||
- Copy the "Tari Address one-sided" from the "Receive" tab of the wallet
|
||||
- Paste it into the P2Pool command line above
|
||||
@@ -152,7 +152,7 @@ Merge mining is available on Tari's mainnet:
|
||||
|
||||
Note that Tari will be mined in solo mode (only full Tari blocks can be mined for now). This doesn't affect P2Pool payouts - they will stay the same.
|
||||
|
||||
### GNU/Linux
|
||||
### Mining on GNU/Linux
|
||||
|
||||
1. Download the latest P2Pool binaries [here](https://github.com/SChernykh/p2pool/releases/latest).
|
||||
- Alternatively, grab the latest source code for P2Pool and [build it](#build-instructions).
|
||||
@@ -180,7 +180,7 @@ The `--zmq-pub` option is required for P2Pool to work properly.
|
||||
|
||||
6. Start P2Pool with the following command/options:
|
||||
```
|
||||
./p2pool --host 127.0.0.1 --wallet YOUR_WALLET_ADDRESS
|
||||
./p2pool --host 127.0.0.1 --wallet YOUR_WALLET_ADDRESS --mini
|
||||
```
|
||||
7. Wait until the initial P2Pool sync is finished (shouldn't take more than 5-10 minutes).
|
||||
8. Start XMRig with the following command/options:
|
||||
@@ -195,7 +195,7 @@ The `--zmq-pub` option is required for P2Pool to work properly.
|
||||
9. XMRig should connect and start mining!
|
||||
|
||||
**Additional Information:**
|
||||
- For a more in-depth beginner friendly walk-through with the option of using Docker, please see SethForPrivacy's guide at: https://sethforprivacy.com/guides/run-a-p2pool-node/
|
||||
- For a more in-depth beginner-friendly walk-through with the option of using Docker, please see SethForPrivacy's guide at: https://sethforprivacy.com/guides/run-a-p2pool-node/
|
||||
- You can check the p2pool.log for any warnings or errors using the following command:
|
||||
```
|
||||
grep -E 'WARNING|ERROR' p2pool.log
|
||||
@@ -213,9 +213,9 @@ nocreate
|
||||
}
|
||||
```
|
||||
|
||||
### Windows
|
||||
### Mining on Windows
|
||||
|
||||
**Note:** *Windows SmartScreen may block incoming connections by files that are "Downloaded from the Internet". You can allow 'p2pool.exe' and 'monerod.exe' by double-clicking them, clicking "More Info", then click "Run Anyway" and then closing them immediately so you can run them from the command line. Advanced users can use the PowerShell cmdlet `Unblock-File` to remove this flag.*
|
||||
**Note:** *Windows SmartScreen may block incoming connections by files that are "Downloaded from the Internet". You can allow 'p2pool.exe' and 'monerod.exe' by double-clicking them, clicking "More Info", then clicking "Run Anyway" and then closing them immediately so you can run them from the command line. Advanced users can use the PowerShell cmdlet `Unblock-File` to remove this flag.*
|
||||
|
||||
1. Download the latest P2Pool binaries [here](https://github.com/SChernykh/p2pool/releases/latest).
|
||||
- Alternatively, grab the latest source code for P2Pool and [build it](#build-instructions).
|
||||
@@ -223,7 +223,7 @@ nocreate
|
||||
3. Expand the P2Pool binaries into an appropriate location (`%USERPROFILE%/bin` or `C:/bin/` are good options)
|
||||
4. Expand XMRig binary into an appropriate location (the same folder as P2Pool is fine).
|
||||
5. Prepare huge pages to work properly (each of monerod/P2Pool/XMRig needs them):
|
||||
- On Windows 10 or above, run XMRig at least once as Administrator (right-click Run As Administrator)
|
||||
- On Windows 10 or above, run XMRig at least once as Administrator (right-click, "Run As Administrator")
|
||||
- On earlier versions of Windows, you'll need to run XMRig as Administrator at least once per login.
|
||||
6. Create "Monero" folder inside extracted P2Pool folder and copy Monero binaries there.
|
||||
7. Open a command prompt and navigate to the folder where you extracted P2Pool.
|
||||
@@ -273,11 +273,11 @@ PAUSE
|
||||
```
|
||||
|
||||
## Build instructions
|
||||
Only 64-bit builds are supported, in particular ARMv7 or older CPUs are not supported. The reason is that RandomX hashing algorithm is too slow in 32-bit mode, and P2Pool needs to check new blocks very fast to keep up with other nodes.
|
||||
Only 64-bit builds are supported. In particular, ARMv7 and older CPUs are not supported. The reason is that RandomX hashing algorithm is too slow in 32-bit mode, and P2Pool needs to check new blocks very fast to keep up with other nodes.
|
||||
|
||||
### Prerequisites
|
||||
- cmake >= 3.10
|
||||
- C++ compiler with C++17 support. GCC-8, Clang-13 and MSVC-2019 have been tested and confirmed to work, older compilers may fail to build P2Pool.
|
||||
- C++ compiler with C++17 support. GCC-8, Clang-13 and MSVC-2019 have been tested and confirmed to work. Older compilers may fail to build P2Pool.
|
||||
|
||||
### Ubuntu 20.04
|
||||
|
||||
@@ -300,7 +300,7 @@ pacman -S p2pool
|
||||
### [Nix/NixOS](https://nixos.org)
|
||||
|
||||
This is a flake only project. So you have to use [nixUnstable with nix flakes](https://nixos.wiki/wiki/Flakes) to build or install P2Pool.
|
||||
The commands below use the new flake specific reference-format, so be sure to also set `ca-references` in `--experimental-features`.
|
||||
The commands below use the new flake-specific reference-format, so be sure to also set `ca-references` in `--experimental-features`.
|
||||
|
||||
Because this project has submodules which are not fixed in _nixUnstable_ yet you have to use the `nix/master` branch:
|
||||
```
|
||||
@@ -378,19 +378,21 @@ Anyone can run the same build script on the same source code, and get the same r
|
||||
|
||||
Prerequisites: Ubuntu with Docker and Git installed.
|
||||
|
||||
Example of building v4.9 release binaries:
|
||||
Example of building v4.15 release binaries:
|
||||
|
||||
```
|
||||
git clone --branch v4.9 --recursive https://github.com/SChernykh/p2pool
|
||||
p2pool/scripts/release/build_all.sh v4.9
|
||||
git clone --branch v4.15 --recursive https://github.com/SChernykh/p2pool
|
||||
p2pool/scripts/release/build_all.sh v4.15
|
||||
```
|
||||
The binaries will be created in `p2pool/scripts/release` folder. First time you run it, it will take up to several hours to first build the Docker image with all the necessary tools in it. Wait until all builds are finished and then run
|
||||
The binaries will be created in `p2pool/scripts/release` folder. The first time you run it, it will take up to several hours to first build the Docker image with all the necessary tools in it. Wait until all builds are finished and then run
|
||||
```
|
||||
cd p2pool/scripts/release
|
||||
./gen_sums.sh
|
||||
```
|
||||
to generate the hash sums and compare them with the official release's hash sums.
|
||||
|
||||
Reproducible builds of (almost) all commits are available [here](https://p2pool.io/builds/). These builds use the release build scripts, so they can act as a "latest release" for all means and purposes, but they might be less stable than the official releases.
|
||||
|
||||
## Donations
|
||||
|
||||
If you'd like to support further development of Monero P2Pool, you're welcome to send any amount of XMR to the following address:
|
||||
@@ -399,7 +401,7 @@ If you'd like to support further development of Monero P2Pool, you're welcome to
|
||||
44MnN1f3Eto8DZYUWuE5XZNUtE3vcRzt2j6PzqWpPau34e6Cf4fAxt6X2MBmrm6F9YMEiMNjN6W4Shn4pLcfNAja621jwyg
|
||||
```
|
||||
|
||||
Starting from v4.6, P2Pool will also have an ability to merge mine donations for the author.
|
||||
Starting from v4.6, P2Pool also has an ability to merge mine donations for the author.
|
||||
|
||||
Note that this will not affect your hashrate or payouts in any way. P2Pool will always (100% of the time) keep mining to your configured Monero wallet address, but it might also use the available merge mining capacity to donate to the author.
|
||||
|
||||
@@ -407,4 +409,4 @@ If you merge mine yourself, your settings will take priority.
|
||||
|
||||
The merge mining donation runs without a donation server - there is no "phoning home" to a server, everything is done through the P2Pool's network in a decentralized way.
|
||||
|
||||
To opt out of this, build P2Pool with `-DWITH_MERGE_MINING_DONATION=OFF` in cmake command line. Due to the architecture of the decentralized P2Pool network, your node will still relay donation messages to other nodes even if you opted out (but it will not process them).
|
||||
To opt out of this, build P2Pool with `-DWITH_MERGE_MINING_DONATION=OFF` in the cmake command line. Due to the architecture of the decentralized P2Pool network, your node will still relay donation messages to other nodes even if you opted out (but it will not process them).
|
||||
|
||||
+5
-4
@@ -23,10 +23,6 @@ if (CMAKE_CXX_COMPILER_ID MATCHES GNU)
|
||||
set(WARNING_FLAGS "${WARNING_FLAGS} -Wstrict-overflow=2")
|
||||
endif()
|
||||
|
||||
if (NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 15))
|
||||
set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-cpp")
|
||||
endif()
|
||||
|
||||
if (DISABLE_WARNINGS)
|
||||
set(WARNING_FLAGS "-w")
|
||||
endif()
|
||||
@@ -40,6 +36,11 @@ if (CMAKE_CXX_COMPILER_ID MATCHES GNU)
|
||||
endif()
|
||||
set(OPTIMIZATION_FLAGS "${OPTIMIZATION_FLAGS} -g3 -ftrapv")
|
||||
else()
|
||||
# Looks like this warning is a false positive on GCC 16.1 (mingw) at -O2/-O3 optimization level
|
||||
if (MINGW AND (NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16)))
|
||||
set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-array-bounds")
|
||||
endif()
|
||||
|
||||
set(OPTIMIZATION_FLAGS "-O3 -ffast-math -s")
|
||||
endif()
|
||||
|
||||
|
||||
+3
-3
@@ -16,12 +16,12 @@ Follow [these instructions](https://docs.i2pd.website/en/latest/user-guide/insta
|
||||
First, ensure the SOCKS proxy tunnel is enabled (as it should be by default). See [here](https://docs.i2pd.website/en/latest/user-guide/configuration/) for more information.
|
||||
|
||||
You will need to modify some lines in the `tunnels.conf` file, usually located in `/var/lib/i2pd/tunnels.conf` on Linux.
|
||||
Add a server tunnel to allow inbound connections from peers:
|
||||
Add a server tunnel to allow inbound connections from peers and use port number 37888 for p2pool-mini, 37890 for p2pool-nano:
|
||||
```ini
|
||||
[p2pool]
|
||||
type = server
|
||||
host = 127.0.0.1
|
||||
port = 28723
|
||||
port = 37889
|
||||
keys = p2pool.dat
|
||||
```
|
||||
This will create a hidden service key in `/var/lib/i2pd/p2pool.dat`; you may want to back this up.
|
||||
@@ -34,7 +34,7 @@ sudo systemctl restart i2pd.service
|
||||
|
||||
Get the hostname of your hidden service:
|
||||
```bash
|
||||
curl -s http://127.0.0.1:7070/?page=i2p_tunnels | grep -Eo "[a-zA-Z0-9./?=_%:-]*" | grep "28723"
|
||||
curl -s http://127.0.0.1:7070/?page=i2p_tunnels | grep -Eo "[a-zA-Z0-9./?=_%:-]*" | grep "37889"
|
||||
```
|
||||
|
||||
The resulting `.b32.i2p` address should then be used for the `--i2p-address` command-line parameter.
|
||||
|
||||
@@ -11,7 +11,7 @@ if [ $? -ne 0 ]; then
|
||||
images/ubuntu/build.sh
|
||||
fi
|
||||
|
||||
for i in 8 15;
|
||||
for i in 8 16;
|
||||
do
|
||||
gcc/build.sh $1 $i
|
||||
done
|
||||
|
||||
@@ -120,6 +120,20 @@ mkdir build && cd build
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
echo "Installing GCC 16.1.0"
|
||||
|
||||
cd /root
|
||||
|
||||
git clone --depth 1 --branch releases/gcc-16.1.0 --jobs $(nproc) git://gcc.gnu.org/git/gcc.git gcc-16
|
||||
|
||||
cd gcc-16
|
||||
contrib/download_prerequisites
|
||||
|
||||
mkdir build && cd build
|
||||
../configure --enable-languages=c,c++ --disable-multilib --disable-bootstrap --prefix=/usr/local/gcc-16
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
echo "Installing clang"
|
||||
|
||||
cd /root
|
||||
|
||||
@@ -8,7 +8,7 @@ BINUTILS_VERSION=2_46
|
||||
CLANG_VERSION=21.1.8
|
||||
CMAKE_VERSION=4.3.2
|
||||
FREEBSD_VERSION=12.4
|
||||
GCC_VERSION=15.2.0
|
||||
GCC_VERSION=16.1.0
|
||||
GLIBC_VERSION=2.43
|
||||
LINUX_HEADERS_VERSION=6.19.14
|
||||
MACOSX_SDK_VERSION=26.1
|
||||
@@ -269,7 +269,7 @@ echo "Install GCC for aarch64-linux-gnu"
|
||||
cd /root
|
||||
|
||||
mkdir gcc_build_aarch64 && cd gcc_build_aarch64
|
||||
../gcc/configure --target=aarch64-linux-gnu --enable-languages=c,c++ --disable-multilib
|
||||
../gcc/configure --target=aarch64-linux-gnu --enable-languages=c,c++ --disable-multilib --disable-libatomic
|
||||
make -j$(nproc) all-gcc
|
||||
make install-gcc
|
||||
|
||||
@@ -321,7 +321,7 @@ echo "Install GCC for riscv64-linux-gnu"
|
||||
cd /root
|
||||
|
||||
mkdir gcc_build_riscv64 && cd gcc_build_riscv64
|
||||
../gcc/configure --target=riscv64-linux-gnu --enable-languages=c,c++ --disable-multilib
|
||||
../gcc/configure --target=riscv64-linux-gnu --enable-languages=c,c++ --disable-multilib --disable-libatomic
|
||||
make -j$(nproc) all-gcc
|
||||
make install-gcc
|
||||
|
||||
@@ -380,7 +380,7 @@ echo "Install GCC for x86_64-w64-mingw32"
|
||||
cd /root
|
||||
|
||||
mkdir gcc_build_w64 && cd gcc_build_w64
|
||||
../gcc/configure --target=x86_64-w64-mingw32 --enable-languages=c,c++ --disable-multilib
|
||||
../gcc/configure --target=x86_64-w64-mingw32 --enable-languages=c,c++ --disable-multilib --disable-libatomic
|
||||
make -j$(nproc) all-gcc
|
||||
make install-gcc
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ flags_datetime="-D__DATE__=\"\\\"$CURRENT_DATE\\\"\" -D__TIME__=\"\\\"$CURRENT_T
|
||||
flags_libs="--target=x86_64-pc-windows-gnu -Os -flto -Wl,/timestamp:$BUILD_TIMESTAMP -fuse-ld=lld -w $flags_size $flags_datetime"
|
||||
|
||||
flags_p2pool="--target=x86_64-pc-windows-gnu -Wl,/timestamp:$BUILD_TIMESTAMP -fuse-ld=lld -femulated-tls -Wno-unused-command-line-argument -Wno-unknown-attributes $flags_size $flags_datetime"
|
||||
flags_cxx_headers="-isystem /usr/local/x86_64-w64-mingw32/include/c++ -isystem /usr/local/x86_64-w64-mingw32/include/c++/15.2.0 -isystem /usr/local/x86_64-w64-mingw32/include"
|
||||
flags_cxx_headers="-isystem /usr/local/x86_64-w64-mingw32/include/c++ -isystem /usr/local/x86_64-w64-mingw32/include/c++/16.1.0 -isystem /usr/local/x86_64-w64-mingw32/include"
|
||||
|
||||
cd /p2pool
|
||||
git apply --verbose --ignore-whitespace --directory=external/src/grpc/third_party/boringssl-with-bazel patches/boringssl/win7.patch
|
||||
|
||||
@@ -111,7 +111,10 @@ private:
|
||||
#ifdef P2POOL_UNIT_TESTS
|
||||
BlockTemplate* m_oldTemplates[1] = {};
|
||||
#else
|
||||
BlockTemplate* m_oldTemplates[4] = {};
|
||||
// Nodejs-pool submits can arrive noticeably later than native stratum
|
||||
// submits. Keep enough old templates to rebuild aux proofs for delayed
|
||||
// shares instead of failing with stale_template after a few rotations.
|
||||
BlockTemplate* m_oldTemplates[512] = {};
|
||||
#endif
|
||||
|
||||
std::atomic<uint64_t> m_finalReward;
|
||||
|
||||
+1
-1
@@ -236,7 +236,7 @@ struct alignas(uint64_t) hash
|
||||
uint64_t k = 0;
|
||||
|
||||
for (size_t i = 0; i < sizeof(uint64_t); ++i) {
|
||||
k |= static_cast<uint64_t>(h[index * sizeof(uint64_t) + i]) << (i * sizeof(uint64_t));
|
||||
k |= static_cast<uint64_t>(h[index * sizeof(uint64_t) + i]) << (i * 8);
|
||||
}
|
||||
|
||||
return k;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace JSONRPCRequest {
|
||||
|
||||
struct CurlContext : public nocopy_nomove
|
||||
{
|
||||
CurlContext(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop);
|
||||
CurlContext(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop, int timeout_seconds);
|
||||
~CurlContext();
|
||||
|
||||
static int socket_func(CURL* easy, curl_socket_t s, int action, void* userp, void* socketp)
|
||||
@@ -81,9 +81,10 @@ struct CurlContext : public nocopy_nomove
|
||||
uint64_t m_connectedTime;
|
||||
|
||||
std::string m_proxy;
|
||||
int m_timeoutSeconds;
|
||||
};
|
||||
|
||||
CurlContext::CurlContext(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop)
|
||||
CurlContext::CurlContext(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop, int timeout_seconds)
|
||||
: m_callback(cb)
|
||||
, m_closeCallback(close_cb)
|
||||
, m_loop(loop)
|
||||
@@ -95,6 +96,7 @@ CurlContext::CurlContext(const std::string& address, int port, const std::string
|
||||
, m_startTime(0)
|
||||
, m_connectedTime(0)
|
||||
, m_proxy(proxy)
|
||||
, m_timeoutSeconds(timeout_seconds)
|
||||
{
|
||||
m_pollHandles.reserve(2);
|
||||
|
||||
@@ -179,11 +181,14 @@ CurlContext::CurlContext(const std::string& address, int port, const std::string
|
||||
curl_easy_setopt_checked(m_handle, CURLOPT_WRITEFUNCTION, write_func);
|
||||
curl_easy_setopt_checked(m_handle, CURLOPT_WRITEDATA, this);
|
||||
|
||||
int timeout = m_timeoutSeconds;
|
||||
if (timeout <= 0) {
|
||||
#ifdef DEV_TEST_SYNC
|
||||
const int timeout = 10;
|
||||
timeout = 10;
|
||||
#else
|
||||
const int timeout = proxy.empty() ? 1 : 5;
|
||||
timeout = proxy.empty() ? 1 : 5;
|
||||
#endif
|
||||
}
|
||||
|
||||
curl_easy_setopt_checked(m_handle, CURLOPT_URL, m_url.c_str());
|
||||
|
||||
@@ -518,7 +523,7 @@ void CurlContext::shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
void Call(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop)
|
||||
void Call(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop, int timeout_seconds)
|
||||
{
|
||||
if (!loop) {
|
||||
loop = uv_default_loop_checked();
|
||||
@@ -528,7 +533,7 @@ void Call(const std::string& address, int port, const std::string& req, const st
|
||||
[=]()
|
||||
{
|
||||
try {
|
||||
new CurlContext(address, port, req, auth, proxy, ssl, ssl_fingerprint, cb, close_cb, loop);
|
||||
new CurlContext(address, port, req, auth, proxy, ssl, ssl_fingerprint, cb, close_cb, loop, timeout_seconds);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
const char* msg = e.what();
|
||||
|
||||
@@ -24,15 +24,15 @@ typedef Callback<void, const char* /*msg*/, size_t /*msg_size*/, double /*tcp_pi
|
||||
|
||||
FORCEINLINE static constexpr void dummy_callback(const char* /*msg*/, size_t /*msg_size*/, double /*tcp_ping*/) {}
|
||||
|
||||
void Call(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop);
|
||||
void Call(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop, int timeout_seconds = 0);
|
||||
|
||||
template<typename T, typename U>
|
||||
FORCEINLINE void call(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, T&& cb, U&& close_cb, uv_loop_t* loop = nullptr)
|
||||
FORCEINLINE void call(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, T&& cb, U&& close_cb, uv_loop_t* loop = nullptr, int timeout_seconds = 0)
|
||||
{
|
||||
typedef Callback<void, const char*, size_t, double>::Derived<T> CallbackT;
|
||||
typedef Callback<void, const char*, size_t, double>::Derived<U> CallbackU;
|
||||
|
||||
Call(address, port, req, auth, proxy, ssl, ssl_fingerprint, new CallbackT(std::forward<T>(cb)), new CallbackU(std::forward<U>(close_cb)), loop);
|
||||
Call(address, port, req, auth, proxy, ssl, ssl_fingerprint, new CallbackT(std::forward<T>(cb)), new CallbackU(std::forward<U>(close_cb)), loop, timeout_seconds);
|
||||
}
|
||||
|
||||
} // namespace JSONRPCRequest
|
||||
|
||||
@@ -65,6 +65,7 @@ void p2pool_usage()
|
||||
"--rpc-port monerod RPC API port number, default is 18081\n"
|
||||
"--zmq-port monerod ZMQ pub port number, default is 18083 (same port as in monerod's \"--zmq-pub\" command line parameter)\n"
|
||||
"--stratum Comma-separated list of IP:port for stratum server to listen on\n"
|
||||
"--nodejs-pool-rpc Comma-separated list of IP:port for daemon-compatible RPC facade for nodejs pools\n"
|
||||
"--p2p Comma-separated list of IP:port for p2p server to listen on\n"
|
||||
"--addpeers Comma-separated list of IP:port of other p2pool nodes to connect to\n"
|
||||
"--stratum-ban-time N Number of seconds to ban misbehaving stratum client, default is %u\n"
|
||||
|
||||
@@ -50,6 +50,11 @@ public:
|
||||
[[nodiscard]] virtual bool get_params(ChainParameters& out_params) const = 0;
|
||||
virtual void on_external_block(const PoolBlock& block) = 0;
|
||||
virtual void submit_solution(const std::vector<uint8_t>& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path) = 0;
|
||||
virtual void submit_solution(const ChainParameters& chain_params, const std::vector<uint8_t>& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path)
|
||||
{
|
||||
(void)chain_params;
|
||||
submit_solution(coinbase_merkle_proof, hashing_blob, nonce_offset, seed_hash, blob, merkle_proof, merkle_proof_path);
|
||||
}
|
||||
|
||||
virtual void print_status() const = 0;
|
||||
virtual void api_status(log::Stream&) const = 0;
|
||||
|
||||
@@ -28,6 +28,10 @@ LOG_CATEGORY(MergeMiningClientJSON_RPC)
|
||||
|
||||
namespace p2pool {
|
||||
|
||||
namespace {
|
||||
static constexpr int MERGE_MINING_RPC_TIMEOUT_SECONDS = 30;
|
||||
}
|
||||
|
||||
MergeMiningClientJSON_RPC::MergeMiningClientJSON_RPC(p2pool* pool, const std::string& host, const std::string& wallet)
|
||||
: MergeMiningClientShared(pool, wallet)
|
||||
, m_host(host)
|
||||
@@ -135,7 +139,7 @@ void MergeMiningClientJSON_RPC::merge_mining_get_chain_id()
|
||||
if (size > 0) {
|
||||
LOGERR(1, "couldn't get merge mining id from " << m_host << ':' << m_port << ", error " << log::const_buf(data, size));
|
||||
}
|
||||
}, &m_loop);
|
||||
}, &m_loop, MERGE_MINING_RPC_TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
bool MergeMiningClientJSON_RPC::parse_merge_mining_get_chain_id(const char* data, size_t size)
|
||||
@@ -237,7 +241,7 @@ void MergeMiningClientJSON_RPC::merge_mining_get_aux_block(uint64_t height, cons
|
||||
LOGERR(3, "couldn't get merge mining job from " << m_host << ':' << m_port << ", error " << log::const_buf(data, size));
|
||||
}
|
||||
m_getJobRunning = false;
|
||||
}, &m_loop);
|
||||
}, &m_loop, MERGE_MINING_RPC_TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
bool MergeMiningClientJSON_RPC::parse_merge_mining_get_aux_block(const char* data, size_t size, bool& changed)
|
||||
@@ -313,7 +317,18 @@ void MergeMiningClientJSON_RPC::submit_solution(const std::vector<uint8_t>& /*co
|
||||
|
||||
const std::vector<uint8_t>& aux_blob = (index < NUM_PREVIOUS_HASHES) ? m_previousAuxBlobs[index] : m_chainParams.aux_blob;
|
||||
const hash& aux_hash = (index < NUM_PREVIOUS_HASHES) ? m_previousAuxHashes[index] : m_chainParams.aux_hash;
|
||||
const ChainParameters chain_params = m_chainParams;
|
||||
|
||||
submit_solution_with_params(chain_params, aux_hash, aux_blob, seed_hash, blob, merkle_proof, merkle_proof_path);
|
||||
}
|
||||
|
||||
void MergeMiningClientJSON_RPC::submit_solution(const ChainParameters& chain_params, const std::vector<uint8_t>& /*coinbase_merkle_proof*/, const uint8_t (&/*hashing_blob*/)[128], size_t /*nonce_offset*/, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path)
|
||||
{
|
||||
submit_solution_with_params(chain_params, chain_params.aux_hash, chain_params.aux_blob, seed_hash, blob, merkle_proof, merkle_proof_path);
|
||||
}
|
||||
|
||||
void MergeMiningClientJSON_RPC::submit_solution_with_params(const ChainParameters& /*chain_params*/, const hash& aux_hash, const std::vector<uint8_t>& aux_blob, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path)
|
||||
{
|
||||
std::vector<char> buf((aux_blob.size() + HASH_SIZE + blob.size()) * 2 + merkle_proof.size() * (HASH_SIZE * 2 + 3) + 256);
|
||||
log::Stream s(buf.data(), buf.size());
|
||||
|
||||
@@ -343,7 +358,7 @@ void MergeMiningClientJSON_RPC::submit_solution(const std::vector<uint8_t>& /*co
|
||||
}
|
||||
// Get new mining job
|
||||
on_timer();
|
||||
}, &m_loop);
|
||||
}, &m_loop, MERGE_MINING_RPC_TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
void MergeMiningClientJSON_RPC::print_status() const
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
|
||||
bool get_params(ChainParameters& out_params) const override;
|
||||
void submit_solution(const std::vector<uint8_t>& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path) override;
|
||||
void submit_solution(const ChainParameters& chain_params, const std::vector<uint8_t>& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path) override;
|
||||
|
||||
void print_status() const override;
|
||||
void api_status(log::Stream&) const override;
|
||||
@@ -51,6 +52,7 @@ private:
|
||||
bool parse_merge_mining_get_aux_block(const char* data, size_t size, bool& changed);
|
||||
|
||||
bool parse_merge_mining_submit_solution(const char* data, size_t size) const;
|
||||
void submit_solution_with_params(const ChainParameters& chain_params, const hash& aux_hash, const std::vector<uint8_t>& aux_blob, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path);
|
||||
|
||||
std::vector<uint8_t> m_previousAuxBlobs[NUM_PREVIOUS_HASHES];
|
||||
|
||||
|
||||
+1
-1
@@ -256,7 +256,7 @@ void Miner::run(WorkerData* data)
|
||||
for (const AuxChainData& aux_data : j.m_auxChains) {
|
||||
if (aux_data.difficulty.check_pow(h)) {
|
||||
LOGINFO(0, log::Green() << "AUX BLOCK FOUND: chain_id " << aux_data.unique_id << ", diff " << aux_data.difficulty << ", worker thread " << data->m_index << '/' << data->m_count);
|
||||
aux_blocks.emplace_back(p2pool::SubmitAuxBlockData{ aux_data.unique_id, j.m_templateId, j.m_nonce, j.m_extraNonce });
|
||||
aux_blocks.emplace_back(p2pool::SubmitAuxBlockData{ aux_data.unique_id, j.m_templateId, j.m_nonce, j.m_extraNonce, {} });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,794 @@
|
||||
/*
|
||||
* This file is part of the Monero P2Pool <https://github.com/SChernykh/p2pool>
|
||||
* Copyright (c) 2021-2026 SChernykh <https://github.com/SChernykh>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "nodejs_pool_rpc.h"
|
||||
#include "block_template.h"
|
||||
#include "keccak.h"
|
||||
#include "p2pool.h"
|
||||
#include "rapidjson_wrapper.h"
|
||||
|
||||
LOG_CATEGORY(NodejsPoolRpc)
|
||||
|
||||
namespace p2pool {
|
||||
|
||||
static constexpr int DEFAULT_BACKLOG = 64;
|
||||
static constexpr size_t MAX_CACHED_WORK_ITEMS = 512;
|
||||
static constexpr size_t NODEJS_POOL_RPC_CALLBACK_BUF_SIZE = 65536;
|
||||
static constexpr size_t EXTERNAL_EXTRA_NONCE_SIZE = 4;
|
||||
static constexpr uint32_t MIN_HASHING_BLOB_BATCH_COUNT = 128;
|
||||
static thread_local const char* log_category_prefix = "NodejsPoolRpc ";
|
||||
|
||||
namespace {
|
||||
|
||||
FORCEINLINE void append_u64(std::vector<uint8_t>& buf, uint64_t value)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(value); ++i) {
|
||||
buf.push_back(static_cast<uint8_t>(value >> (i * 8)));
|
||||
}
|
||||
}
|
||||
|
||||
FORCEINLINE void append_u32(std::vector<uint8_t>& buf, uint32_t value)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(value); ++i) {
|
||||
buf.push_back(static_cast<uint8_t>(value >> (i * 8)));
|
||||
}
|
||||
}
|
||||
|
||||
FORCEINLINE uint32_t read_u32_le(const uint8_t* p)
|
||||
{
|
||||
return static_cast<uint32_t>(p[0])
|
||||
| (static_cast<uint32_t>(p[1]) << 8)
|
||||
| (static_cast<uint32_t>(p[2]) << 16)
|
||||
| (static_cast<uint32_t>(p[3]) << 24);
|
||||
}
|
||||
|
||||
bool compare_ignoring_ranges(const uint8_t* a, const uint8_t* b, size_t size, const std::initializer_list<std::pair<size_t, size_t>>& ignored_ranges)
|
||||
{
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
bool ignored = false;
|
||||
|
||||
for (const auto& range : ignored_ranges) {
|
||||
const size_t offset = range.first;
|
||||
const size_t range_size = range.second;
|
||||
if ((range_size > 0) && (offset <= i) && (i < offset + range_size)) {
|
||||
ignored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignored && (a[i] != b[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string http_response(const std::string& body, const char* status = "200 OK", const char* content_type = "application/json")
|
||||
{
|
||||
std::string s;
|
||||
s.reserve(body.size() + 128);
|
||||
s += "HTTP/1.1 ";
|
||||
s += status;
|
||||
s += "\r\nContent-Type: ";
|
||||
s += content_type;
|
||||
s += "\r\nContent-Length: ";
|
||||
s += std::to_string(body.size());
|
||||
s += "\r\nConnection: close\r\n\r\n";
|
||||
s += body;
|
||||
return s;
|
||||
}
|
||||
|
||||
bool parse_content_length(const std::string& headers, size_t& out)
|
||||
{
|
||||
const std::string lower = tolower(headers);
|
||||
size_t k = lower.find("\r\ncontent-length:");
|
||||
if (k == std::string::npos) {
|
||||
if (lower.rfind("content-length:", 0) == 0) {
|
||||
k = 0;
|
||||
}
|
||||
else {
|
||||
out = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
k = lower.find(':', k);
|
||||
if (k == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
++k;
|
||||
|
||||
while ((k < headers.size()) && ((headers[k] == ' ') || (headers[k] == '\t'))) {
|
||||
++k;
|
||||
}
|
||||
|
||||
char* end = nullptr;
|
||||
const unsigned long long value = strtoull(headers.c_str() + k, &end, 10);
|
||||
if ((end == headers.c_str() + k) || (value > std::numeric_limits<size_t>::max())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out = static_cast<size_t>(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* jsonrpc_id_to_cstr(const rapidjson::Value& id, std::string& tmp)
|
||||
{
|
||||
if (id.IsString()) {
|
||||
return id.GetString();
|
||||
}
|
||||
|
||||
if (id.IsInt64()) {
|
||||
tmp = std::to_string(id.GetInt64());
|
||||
return tmp.c_str();
|
||||
}
|
||||
|
||||
if (id.IsUint64()) {
|
||||
tmp = std::to_string(id.GetUint64());
|
||||
return tmp.c_str();
|
||||
}
|
||||
|
||||
return "0";
|
||||
}
|
||||
|
||||
bool extract_submitblock_request(const rapidjson::Value& params, std::vector<uint8_t>& blob, uint32_t& template_id, bool& has_template_id)
|
||||
{
|
||||
if (params.IsArray() && !params.Empty() && params[0].IsString()) {
|
||||
has_template_id = false;
|
||||
return from_hex(params[0].GetString(), params[0].GetStringLength(), blob);
|
||||
}
|
||||
|
||||
if (params.IsArray() && !params.Empty() && params[0].IsObject()) {
|
||||
const rapidjson::Value& req = params[0];
|
||||
auto blob_it = req.FindMember("blob");
|
||||
if ((blob_it == req.MemberEnd()) || !blob_it->value.IsString()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
has_template_id = false;
|
||||
auto template_it = req.FindMember("template_id");
|
||||
if (template_it != req.MemberEnd()) {
|
||||
if (template_it->value.IsUint()) {
|
||||
template_id = template_it->value.GetUint();
|
||||
has_template_id = true;
|
||||
}
|
||||
else if (template_it->value.IsUint64() && (template_it->value.GetUint64() <= std::numeric_limits<uint32_t>::max())) {
|
||||
template_id = static_cast<uint32_t>(template_it->value.GetUint64());
|
||||
has_template_id = true;
|
||||
}
|
||||
else if (template_it->value.IsInt64() && (template_it->value.GetInt64() >= 0) && (template_it->value.GetInt64() <= std::numeric_limits<uint32_t>::max())) {
|
||||
template_id = static_cast<uint32_t>(template_it->value.GetInt64());
|
||||
has_template_id = true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return from_hex(blob_it->value.GetString(), blob_it->value.GetStringLength(), blob);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t get_requested_hashing_blob_batch_count(const rapidjson::Value& params)
|
||||
{
|
||||
if (!params.IsObject()) {
|
||||
return MIN_HASHING_BLOB_BATCH_COUNT;
|
||||
}
|
||||
|
||||
auto it = params.FindMember("expected_workers");
|
||||
if (it == params.MemberEnd()) {
|
||||
return MIN_HASHING_BLOB_BATCH_COUNT;
|
||||
}
|
||||
|
||||
uint64_t expected_workers = 0;
|
||||
if (it->value.IsUint64()) {
|
||||
expected_workers = it->value.GetUint64();
|
||||
}
|
||||
else if (it->value.IsInt64() && (it->value.GetInt64() > 0)) {
|
||||
expected_workers = static_cast<uint64_t>(it->value.GetInt64());
|
||||
}
|
||||
else {
|
||||
return MIN_HASHING_BLOB_BATCH_COUNT;
|
||||
}
|
||||
|
||||
const uint64_t scaled = (expected_workers * 13ULL + 9ULL) / 10ULL;
|
||||
const uint64_t batch_count = std::max<uint64_t>(MIN_HASHING_BLOB_BATCH_COUNT, scaled);
|
||||
return (batch_count > std::numeric_limits<uint32_t>::max()) ? std::numeric_limits<uint32_t>::max() : static_cast<uint32_t>(batch_count);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NodejsPoolRpc::NodejsPoolRpc(p2pool* pool, const std::string& listen_addresses)
|
||||
: TCPServer(DEFAULT_BACKLOG, RpcClient::allocate, std::string(), Params::ProxyType::INVALID)
|
||||
, m_pool(pool)
|
||||
{
|
||||
m_callbackBuf.resize(NODEJS_POOL_RPC_CALLBACK_BUF_SIZE);
|
||||
uv_mutex_init_checked(&m_recentWorkLock);
|
||||
start_listening(listen_addresses, false);
|
||||
|
||||
if (m_listenPort < 0) {
|
||||
LOGERR(1, "failed to listen on nodejs pool rpc facade");
|
||||
throw std::exception();
|
||||
}
|
||||
}
|
||||
|
||||
NodejsPoolRpc::~NodejsPoolRpc()
|
||||
{
|
||||
shutdown_tcp();
|
||||
uv_mutex_destroy(&m_recentWorkLock);
|
||||
}
|
||||
|
||||
void NodejsPoolRpc::on_shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
const char* NodejsPoolRpc::get_log_category() const
|
||||
{
|
||||
return log_category_prefix;
|
||||
}
|
||||
|
||||
void NodejsPoolRpc::remember_work_snapshot(const WorkSnapshot& work) const
|
||||
{
|
||||
if (!work.ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
WorkSnapshot cached = work;
|
||||
|
||||
MutexLock lock(m_recentWorkLock);
|
||||
|
||||
auto same_template = [&cached](const WorkSnapshot& item) {
|
||||
return (item.template_id == cached.template_id)
|
||||
&& (item.work_hash == cached.work_hash)
|
||||
&& (item.sidechain_height == cached.sidechain_height);
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < m_recentWork.size(); ++i) {
|
||||
if (same_template(m_recentWork[i])) {
|
||||
m_recentWork.erase(m_recentWork.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_recentWork.insert(m_recentWork.begin(), std::move(cached));
|
||||
if (m_recentWork.size() > MAX_CACHED_WORK_ITEMS) {
|
||||
m_recentWork.resize(MAX_CACHED_WORK_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
bool NodejsPoolRpc::find_cached_work_by_block_blob(const std::vector<uint8_t>& blob, WorkSnapshot& out) const
|
||||
{
|
||||
MutexLock lock(m_recentWorkLock);
|
||||
|
||||
for (const WorkSnapshot& work : m_recentWork) {
|
||||
if (!work.ready || (work.block_template_blob.size() != blob.size())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((work.nonce_offset + NONCE_SIZE > blob.size()) || (work.extra_nonce_offset + work.extra_nonce_size > blob.size())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!compare_ignoring_ranges(
|
||||
blob.data(),
|
||||
work.block_template_blob.data(),
|
||||
blob.size(),
|
||||
{
|
||||
{ work.nonce_offset, NONCE_SIZE },
|
||||
{ work.extra_nonce_offset, work.extra_nonce_size }
|
||||
}))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
out = work;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NodejsPoolRpc::find_cached_work_by_template_id(uint32_t template_id, WorkSnapshot& out) const
|
||||
{
|
||||
MutexLock lock(m_recentWorkLock);
|
||||
|
||||
for (const WorkSnapshot& work : m_recentWork) {
|
||||
if (!work.ready) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (work.template_id == template_id) {
|
||||
out = work;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t NodejsPoolRpc::copy_cached_hashing_blob(const WorkSnapshot& work, uint32_t extra_nonce, uint8_t (&blob)[128], size_t& nonce_offset) const
|
||||
{
|
||||
if (!work.hashing_blob_size || !work.hashing_blob_count || work.hashing_blobs.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (extra_nonce < work.hashing_blob_extra_nonce_start) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint32_t index = extra_nonce - work.hashing_blob_extra_nonce_start;
|
||||
if (index >= work.hashing_blob_count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const size_t blob_size = work.hashing_blob_size;
|
||||
const size_t offset = static_cast<size_t>(index) * blob_size;
|
||||
if ((blob_size > sizeof(blob)) || (offset + blob_size > work.hashing_blobs.size())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(blob, work.hashing_blobs.data() + offset, blob_size);
|
||||
nonce_offset = work.hashing_nonce_offset;
|
||||
return static_cast<uint32_t>(blob_size);
|
||||
}
|
||||
|
||||
bool NodejsPoolRpc::make_work_snapshot(WorkSnapshot& out, uint32_t hashing_blob_batch_count) const
|
||||
{
|
||||
const MinerData miner_data = m_pool->miner_data();
|
||||
if (!miner_data.height || miner_data.prev_id.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t hashing_blob[128] = {};
|
||||
uint64_t mining_height = 0;
|
||||
uint64_t sidechain_height = 0;
|
||||
difficulty_type mainchain_diff, aux_diff, sidechain_diff;
|
||||
hash seed_hash;
|
||||
size_t nonce_offset = 0;
|
||||
uint32_t template_id = 0;
|
||||
|
||||
const uint32_t hashing_blob_size = m_pool->block_template().get_hashing_blob(0, hashing_blob, mining_height, sidechain_height, mainchain_diff, aux_diff, sidechain_diff, seed_hash, nonce_offset, template_id);
|
||||
if (!hashing_blob_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t full_nonce_offset = 0;
|
||||
size_t extra_nonce_offset = 0;
|
||||
size_t merkle_root_offset = 0;
|
||||
hash merge_mining_root;
|
||||
const BlockTemplate* block_tpl = nullptr;
|
||||
|
||||
std::vector<uint8_t> block_template_blob = m_pool->block_template().get_block_template_blob(template_id, 0, full_nonce_offset, extra_nonce_offset, merkle_root_offset, merge_mining_root, &block_tpl);
|
||||
if (block_template_blob.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (merkle_root_offset && (merkle_root_offset + HASH_SIZE <= block_template_blob.size())) {
|
||||
memcpy(block_template_blob.data() + merkle_root_offset, merge_mining_root.h, HASH_SIZE);
|
||||
}
|
||||
|
||||
out.ready = true;
|
||||
out.mainchain_height = miner_data.height - 1;
|
||||
out.mining_height = mining_height;
|
||||
out.sidechain_height = sidechain_height;
|
||||
out.parent_prev_id = miner_data.prev_id;
|
||||
out.seed_hash = seed_hash;
|
||||
out.work_hash = {};
|
||||
out.template_id = template_id;
|
||||
out.nonce_offset = full_nonce_offset;
|
||||
out.hashing_nonce_offset = nonce_offset;
|
||||
out.extra_nonce_offset = extra_nonce_offset;
|
||||
out.extra_nonce_size = EXTERNAL_EXTRA_NONCE_SIZE;
|
||||
out.hashing_blob_extra_nonce_start = 1;
|
||||
out.mainchain_difficulty = mainchain_diff;
|
||||
out.sidechain_difficulty = sidechain_diff;
|
||||
out.aux_difficulty = aux_diff;
|
||||
out.submit_difficulty = aux_diff.empty() ? sidechain_diff : aux_diff;
|
||||
out.block_template_blob = std::move(block_template_blob);
|
||||
out.hashing_blob.assign(hashing_blob, hashing_blob + hashing_blob_size);
|
||||
out.aux_chains = m_pool->block_template().get_aux_chains(template_id);
|
||||
out.aux_chain_params.clear();
|
||||
out.aux_chain_params.reserve(out.aux_chains.size());
|
||||
for (const AuxChainData& aux_data : out.aux_chains) {
|
||||
IMergeMiningClient::ChainParameters chain_params;
|
||||
if (m_pool->get_merge_mining_chain_params(aux_data.unique_id, chain_params) && (chain_params.aux_hash == aux_data.data) && !chain_params.aux_blob.empty()) {
|
||||
out.aux_chain_params.push_back(std::move(chain_params));
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t batch_height = 0;
|
||||
difficulty_type batch_mainchain_diff, batch_aux_diff, batch_sidechain_diff;
|
||||
hash batch_seed_hash;
|
||||
size_t batch_nonce_offset = 0;
|
||||
uint8_t batch_blob[128] = {};
|
||||
out.hashing_blob_size = 0;
|
||||
out.hashing_blobs.clear();
|
||||
out.hashing_blobs.reserve(static_cast<size_t>(hashing_blob_batch_count) * sizeof(batch_blob));
|
||||
|
||||
for (uint32_t i = 0; i < hashing_blob_batch_count; ++i) {
|
||||
const uint32_t current_extra_nonce = out.hashing_blob_extra_nonce_start + i;
|
||||
const uint32_t current_blob_size = m_pool->block_template().get_hashing_blob(
|
||||
template_id,
|
||||
current_extra_nonce,
|
||||
batch_blob,
|
||||
batch_height,
|
||||
batch_mainchain_diff,
|
||||
batch_aux_diff,
|
||||
batch_sidechain_diff,
|
||||
batch_seed_hash,
|
||||
batch_nonce_offset);
|
||||
|
||||
if (!current_blob_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
out.hashing_blob_size = current_blob_size;
|
||||
}
|
||||
else if (current_blob_size != out.hashing_blob_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((batch_nonce_offset != nonce_offset) || (batch_seed_hash != seed_hash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out.hashing_blobs.insert(out.hashing_blobs.end(), batch_blob, batch_blob + current_blob_size);
|
||||
++out.hashing_blob_count;
|
||||
}
|
||||
|
||||
if (!out.hashing_blob_size || !out.hashing_blob_count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> work_key;
|
||||
work_key.reserve(HASH_SIZE * 2 + sizeof(uint64_t) * 4 + sizeof(uint32_t) + out.hashing_blob.size());
|
||||
work_key.insert(work_key.end(), miner_data.prev_id.h, miner_data.prev_id.h + HASH_SIZE);
|
||||
work_key.insert(work_key.end(), seed_hash.h, seed_hash.h + HASH_SIZE);
|
||||
append_u64(work_key, mining_height);
|
||||
append_u64(work_key, sidechain_height);
|
||||
append_u64(work_key, out.submit_difficulty.lo);
|
||||
append_u64(work_key, out.submit_difficulty.hi);
|
||||
append_u32(work_key, template_id);
|
||||
work_key.insert(work_key.end(), out.hashing_blob.begin(), out.hashing_blob.end());
|
||||
keccak(work_key.data(), static_cast<int>(work_key.size()), out.work_hash.h);
|
||||
|
||||
remember_work_snapshot(out);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NodejsPoolRpc::RpcClient::on_read(const char* data, uint32_t size)
|
||||
{
|
||||
m_request.append(data, size);
|
||||
return static_cast<NodejsPoolRpc*>(m_owner)->handle_http_request(this);
|
||||
}
|
||||
|
||||
bool NodejsPoolRpc::handle_http_request(RpcClient* client)
|
||||
{
|
||||
std::string& request = client->m_request;
|
||||
const size_t headers_end = request.find("\r\n\r\n");
|
||||
if (headers_end == std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t content_length = 0;
|
||||
if (!parse_content_length(request.substr(0, headers_end), content_length)) {
|
||||
const std::string response = http_response("{\"error\":\"invalid content-length\"}", "400 Bad Request");
|
||||
const bool sent = send(client, [&response](uint8_t* buf, size_t buf_size) {
|
||||
const size_t n = std::min(buf_size, response.size());
|
||||
memcpy(buf, response.data(), n);
|
||||
return n;
|
||||
}, true);
|
||||
(void)sent;
|
||||
client->close();
|
||||
return true;
|
||||
}
|
||||
|
||||
const size_t request_size = headers_end + 4 + content_length;
|
||||
if (request.size() < request_size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const size_t line_end = request.find("\r\n");
|
||||
if (line_end == std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string first_line = request.substr(0, line_end);
|
||||
const size_t s1 = first_line.find(' ');
|
||||
const size_t s2 = (s1 == std::string::npos) ? std::string::npos : first_line.find(' ', s1 + 1);
|
||||
if ((s1 == std::string::npos) || (s2 == std::string::npos)) {
|
||||
const std::string response = http_response("{\"error\":\"invalid request line\"}", "400 Bad Request");
|
||||
const bool sent = send(client, [&response](uint8_t* buf, size_t buf_size) {
|
||||
const size_t n = std::min(buf_size, response.size());
|
||||
memcpy(buf, response.data(), n);
|
||||
return n;
|
||||
}, true);
|
||||
(void)sent;
|
||||
client->close();
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string method = first_line.substr(0, s1);
|
||||
const std::string path = first_line.substr(s1 + 1, s2 - s1 - 1);
|
||||
const std::string body = request.substr(headers_end + 4, content_length);
|
||||
request.clear();
|
||||
LOGINFO(4, "http request: method=" << method << ", path=" << path << ", content_length=" << content_length);
|
||||
|
||||
std::string response;
|
||||
if ((method == "GET") && (path == "/get_height")) {
|
||||
response = http_response(build_get_height_response());
|
||||
}
|
||||
else if ((method == "POST") && (path == "/json_rpc")) {
|
||||
response = http_response(build_json_rpc_response(body));
|
||||
}
|
||||
else {
|
||||
response = http_response("{\"error\":\"not found\"}", "404 Not Found");
|
||||
}
|
||||
|
||||
const bool sent = send(client, [&response](uint8_t* buf, size_t buf_size) {
|
||||
const size_t n = std::min(buf_size, response.size());
|
||||
memcpy(buf, response.data(), n);
|
||||
return n;
|
||||
}, true);
|
||||
(void)sent;
|
||||
client->close();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string NodejsPoolRpc::build_get_height_response() const
|
||||
{
|
||||
WorkSnapshot work;
|
||||
if (!make_work_snapshot(work, MIN_HASHING_BLOB_BATCH_COUNT)) {
|
||||
return "{\"status\":\"BUSY\"}";
|
||||
}
|
||||
|
||||
char body[512] = {};
|
||||
log::Stream s(body);
|
||||
s << "{\"height\":" << work.mainchain_height
|
||||
<< ",\"hash\":\"" << work.work_hash << '"'
|
||||
<< ",\"status\":\"OK\""
|
||||
<< ",\"untrusted\":false}";
|
||||
return std::string(body, s.m_pos);
|
||||
}
|
||||
|
||||
std::string NodejsPoolRpc::json_error_response(const char* id, int code, const std::string& message) const
|
||||
{
|
||||
char body[1024] = {};
|
||||
log::Stream s(body);
|
||||
s << "{\"id\":\"" << log::EscapedString(id)
|
||||
<< "\",\"jsonrpc\":\"2.0\",\"error\":{\"code\":" << code
|
||||
<< ",\"message\":\"" << log::EscapedString(message) << "\"}}";
|
||||
return std::string(body, s.m_pos);
|
||||
}
|
||||
|
||||
std::string NodejsPoolRpc::build_json_rpc_response(const std::string& body) const
|
||||
{
|
||||
rapidjson::Document doc;
|
||||
doc.Parse(body.c_str(), body.size());
|
||||
|
||||
if (doc.HasParseError() || !doc.IsObject()) {
|
||||
return json_error_response("0", -32700, "parse error");
|
||||
}
|
||||
|
||||
std::string id_buf;
|
||||
const char* id = "0";
|
||||
if (doc.HasMember("id")) {
|
||||
id = jsonrpc_id_to_cstr(doc["id"], id_buf);
|
||||
}
|
||||
|
||||
auto method_it = doc.FindMember("method");
|
||||
if ((method_it == doc.MemberEnd()) || !method_it->value.IsString()) {
|
||||
return json_error_response(id, -32600, "invalid request");
|
||||
}
|
||||
|
||||
const std::string method = method_it->value.GetString();
|
||||
LOGINFO(4, "json_rpc method=" << method);
|
||||
|
||||
if (method == "getlastblockheader") {
|
||||
WorkSnapshot work;
|
||||
if (!make_work_snapshot(work, MIN_HASHING_BLOB_BATCH_COUNT)) {
|
||||
return json_error_response(id, 0, "not_ready");
|
||||
}
|
||||
|
||||
char response[1024] = {};
|
||||
log::Stream s(response);
|
||||
s << "{\"id\":\"" << log::EscapedString(id)
|
||||
<< "\",\"jsonrpc\":\"2.0\",\"result\":{\"status\":\"OK\",\"block_header\":{\"hash\":\""
|
||||
<< work.work_hash << "\",\"height\":" << work.mainchain_height << "}}}";
|
||||
return std::string(response, s.m_pos);
|
||||
}
|
||||
|
||||
if (method == "getblocktemplate") {
|
||||
WorkSnapshot work;
|
||||
const uint32_t hashing_blob_batch_count = (doc.HasMember("params") ? get_requested_hashing_blob_batch_count(doc["params"]) : MIN_HASHING_BLOB_BATCH_COUNT);
|
||||
if (!make_work_snapshot(work, hashing_blob_batch_count)) {
|
||||
return json_error_response(id, 0, "not_ready");
|
||||
}
|
||||
|
||||
const size_t response_capacity =
|
||||
2048 +
|
||||
(work.block_template_blob.size() * 2) +
|
||||
(work.hashing_blob.size() * 2) +
|
||||
(work.hashing_blobs.size() * 2) +
|
||||
256;
|
||||
|
||||
std::vector<char> response(response_capacity, '\0');
|
||||
log::Stream s(response.data(), response.size());
|
||||
s << "{\"id\":\"" << log::EscapedString(id) << "\",\"jsonrpc\":\"2.0\",\"result\":{"
|
||||
<< "\"blocktemplate_blob\":\"" << log::hex_buf(work.block_template_blob.data(), work.block_template_blob.size()) << '"'
|
||||
<< ",\"blockhashing_blob\":\"" << log::hex_buf(work.hashing_blob.data(), work.hashing_blob.size()) << '"'
|
||||
<< ",\"p2pool_hashing_blob_batch_start\":" << work.hashing_blob_extra_nonce_start
|
||||
<< ",\"p2pool_hashing_blob_batch_size\":" << work.hashing_blob_size
|
||||
<< ",\"p2pool_hashing_blob_batch_count\":" << work.hashing_blob_count
|
||||
<< ",\"p2pool_hashing_blob_batch\":\"" << log::hex_buf(work.hashing_blobs.data(), work.hashing_blobs.size()) << '"'
|
||||
<< ",\"difficulty\":" << work.submit_difficulty.lo
|
||||
<< ",\"height\":" << work.mining_height
|
||||
<< ",\"nonce_offset\":" << work.nonce_offset
|
||||
<< ",\"hashing_nonce_offset\":" << work.hashing_nonce_offset
|
||||
<< ",\"reserved_offset\":" << work.extra_nonce_offset
|
||||
<< ",\"seed_hash\":\"" << work.seed_hash << '"'
|
||||
<< ",\"status\":\"OK\""
|
||||
<< ",\"template_id\":" << work.template_id
|
||||
<< ",\"sidechain_height\":" << work.sidechain_height
|
||||
<< ",\"aux_difficulty\":\"" << work.aux_difficulty << '"'
|
||||
<< ",\"sidechain_difficulty\":\"" << work.sidechain_difficulty << '"'
|
||||
<< ",\"mainchain_difficulty\":\"" << work.mainchain_difficulty << '"'
|
||||
<< ",\"backend_submit_difficulty\":\"" << work.submit_difficulty << '"'
|
||||
<< ",\"backend_mode\":\"aux_only\""
|
||||
<< ",\"p2pool_external_extra_nonce\":true"
|
||||
<< ",\"p2pool_external_extra_nonce_size\":" << work.extra_nonce_size
|
||||
<< ",\"p2pool_external_extra_nonce_endianness\":\"LE\""
|
||||
<< "}}";
|
||||
return std::string(response.data(), s.m_pos);
|
||||
}
|
||||
|
||||
if ((method == "submitblock") || (method == "submit_block")) {
|
||||
auto params_it = doc.FindMember("params");
|
||||
if (params_it == doc.MemberEnd()) {
|
||||
return json_error_response(id, -32602, "missing params");
|
||||
}
|
||||
|
||||
std::vector<uint8_t> blob;
|
||||
uint32_t requested_template_id = 0;
|
||||
bool has_requested_template_id = false;
|
||||
if (!extract_submitblock_request(params_it->value, blob, requested_template_id, has_requested_template_id) || blob.empty()) {
|
||||
return json_error_response(id, -32602, "invalid blob");
|
||||
}
|
||||
|
||||
WorkSnapshot work;
|
||||
const bool found_work = has_requested_template_id
|
||||
? find_cached_work_by_template_id(requested_template_id, work)
|
||||
: find_cached_work_by_block_blob(blob, work);
|
||||
if (!found_work) {
|
||||
return json_error_response(id, 0, "unknown_or_stale_template");
|
||||
}
|
||||
|
||||
if (work.nonce_offset + NONCE_SIZE > blob.size()) {
|
||||
return json_error_response(id, 0, "invalid_nonce_offset");
|
||||
}
|
||||
|
||||
const uint32_t nonce = read_u32_le(blob.data() + work.nonce_offset);
|
||||
if (work.extra_nonce_offset + work.extra_nonce_size > blob.size()) {
|
||||
return json_error_response(id, 0, "invalid_extra_nonce_offset");
|
||||
}
|
||||
const uint32_t extra_nonce = read_u32_le(blob.data() + work.extra_nonce_offset);
|
||||
|
||||
uint8_t hashing_blob[128] = {};
|
||||
uint64_t height = work.mining_height;
|
||||
difficulty_type mainchain_diff = work.mainchain_difficulty;
|
||||
difficulty_type aux_diff = work.aux_difficulty;
|
||||
difficulty_type sidechain_diff = work.sidechain_difficulty;
|
||||
hash seed_hash = work.seed_hash;
|
||||
size_t nonce_offset = 0;
|
||||
|
||||
uint32_t hashing_blob_size = copy_cached_hashing_blob(work, extra_nonce, hashing_blob, nonce_offset);
|
||||
if (!hashing_blob_size) {
|
||||
hashing_blob_size = m_pool->block_template().get_hashing_blob(work.template_id, extra_nonce, hashing_blob, height, mainchain_diff, aux_diff, sidechain_diff, seed_hash, nonce_offset);
|
||||
}
|
||||
if (!hashing_blob_size || (nonce_offset + NONCE_SIZE > hashing_blob_size)) {
|
||||
return json_error_response(id, 0, "stale_template");
|
||||
}
|
||||
|
||||
memcpy(hashing_blob + nonce_offset, blob.data() + work.nonce_offset, NONCE_SIZE);
|
||||
|
||||
hash result_hash;
|
||||
if (!m_pool->calculate_hash(hashing_blob, hashing_blob_size, height, seed_hash, result_hash, false)) {
|
||||
return json_error_response(id, 0, "pow_check_failed");
|
||||
}
|
||||
|
||||
if (!work.submit_difficulty.check_pow(result_hash)) {
|
||||
return json_error_response(id, 0, "low_diff");
|
||||
}
|
||||
|
||||
const bool meets_mainchain = mainchain_diff.check_pow(result_hash);
|
||||
const bool meets_aux = aux_diff.check_pow(result_hash);
|
||||
const bool meets_sidechain = sidechain_diff.check_pow(result_hash);
|
||||
|
||||
LOGINFO(4, "NodejsPoolRpc submitblock: template id = " << work.template_id
|
||||
<< ", nonce = " << nonce
|
||||
<< ", extra_nonce = " << extra_nonce
|
||||
<< ", backend_submit_diff = " << work.submit_difficulty
|
||||
<< ", aux_diff = " << aux_diff
|
||||
<< ", sidechain_diff = " << sidechain_diff
|
||||
<< ", mainchain_diff = " << mainchain_diff
|
||||
<< ", meets_aux = " << (meets_aux ? "true" : "false")
|
||||
<< ", meets_sidechain = " << (meets_sidechain ? "true" : "false")
|
||||
<< ", meets_mainchain = " << (meets_mainchain ? "true" : "false"));
|
||||
|
||||
bool parent_submitted = false;
|
||||
bool sidechain_submitted = false;
|
||||
size_t aux_submitted = 0;
|
||||
|
||||
if (meets_mainchain) {
|
||||
m_pool->submit_block_async(std::move(blob));
|
||||
parent_submitted = true;
|
||||
}
|
||||
|
||||
if (meets_aux) {
|
||||
std::vector<p2pool::SubmitAuxBlockData> aux_blocks;
|
||||
aux_blocks.reserve(work.aux_chains.size());
|
||||
|
||||
for (const AuxChainData& aux_data : work.aux_chains) {
|
||||
if (aux_data.difficulty.check_pow(result_hash)) {
|
||||
auto params_it = std::find_if(work.aux_chain_params.begin(), work.aux_chain_params.end(),
|
||||
[&aux_data](const IMergeMiningClient::ChainParameters& params) {
|
||||
return (params.aux_id == aux_data.unique_id) && (params.aux_hash == aux_data.data);
|
||||
});
|
||||
if (params_it == work.aux_chain_params.end()) {
|
||||
LOGWARN(3, "NodejsPoolRpc submitblock: missing cached aux params for chain_id = " << aux_data.unique_id
|
||||
<< ", aux_hash = " << aux_data.data);
|
||||
continue;
|
||||
}
|
||||
aux_blocks.emplace_back(p2pool::SubmitAuxBlockData{ aux_data.unique_id, work.template_id, nonce, extra_nonce, *params_it });
|
||||
}
|
||||
}
|
||||
|
||||
aux_submitted = aux_blocks.size();
|
||||
if (!aux_blocks.empty()) {
|
||||
m_pool->submit_aux_block_async(aux_blocks);
|
||||
}
|
||||
}
|
||||
|
||||
if (meets_sidechain) {
|
||||
sidechain_submitted = m_pool->submit_sidechain_block(work.template_id, nonce, extra_nonce);
|
||||
}
|
||||
|
||||
char response[2048] = {};
|
||||
log::Stream s(response, sizeof(response));
|
||||
s << "{\"id\":\"" << log::EscapedString(id) << "\",\"jsonrpc\":\"2.0\",\"result\":{"
|
||||
<< "\"status\":\"OK\""
|
||||
<< ",\"accepted\":true"
|
||||
<< ",\"backend_mode\":\"aux_only\""
|
||||
<< ",\"aux_accepted\":" << ((aux_submitted > 0) ? "true" : "false")
|
||||
<< ",\"aux_submitted\":" << aux_submitted
|
||||
<< ",\"sidechain_accepted\":" << (sidechain_submitted ? "true" : "false")
|
||||
<< ",\"parent_submitted\":" << (parent_submitted ? "true" : "false")
|
||||
<< ",\"template_id\":" << work.template_id
|
||||
<< ",\"submit_difficulty\":\"" << work.submit_difficulty << '"'
|
||||
<< "}}";
|
||||
return std::string(response, s.m_pos);
|
||||
}
|
||||
|
||||
return json_error_response(id, -32601, "method not found");
|
||||
}
|
||||
|
||||
} // namespace p2pool
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* This file is part of the Monero P2Pool <https://github.com/SChernykh/p2pool>
|
||||
* Copyright (c) 2021-2026 SChernykh <https://github.com/SChernykh>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "tcp_server.h"
|
||||
#include "merge_mining_client.h"
|
||||
|
||||
namespace p2pool {
|
||||
|
||||
class p2pool;
|
||||
|
||||
class NodejsPoolRpc : public TCPServer
|
||||
{
|
||||
public:
|
||||
NodejsPoolRpc(p2pool* pool, const std::string& listen_addresses);
|
||||
~NodejsPoolRpc() override;
|
||||
|
||||
void on_shutdown() override;
|
||||
|
||||
private:
|
||||
struct WorkSnapshot
|
||||
{
|
||||
bool ready = false;
|
||||
uint64_t mainchain_height = 0;
|
||||
uint64_t mining_height = 0;
|
||||
uint64_t sidechain_height = 0;
|
||||
hash parent_prev_id;
|
||||
hash seed_hash;
|
||||
hash work_hash;
|
||||
uint32_t template_id = 0;
|
||||
size_t nonce_offset = 0;
|
||||
size_t hashing_nonce_offset = 0;
|
||||
size_t extra_nonce_offset = 0;
|
||||
size_t extra_nonce_size = 4;
|
||||
uint32_t hashing_blob_extra_nonce_start = 0;
|
||||
uint32_t hashing_blob_count = 0;
|
||||
uint32_t hashing_blob_size = 0;
|
||||
difficulty_type mainchain_difficulty;
|
||||
difficulty_type sidechain_difficulty;
|
||||
difficulty_type aux_difficulty;
|
||||
difficulty_type submit_difficulty;
|
||||
std::vector<uint8_t> block_template_blob;
|
||||
std::vector<uint8_t> hashing_blob;
|
||||
std::vector<uint8_t> hashing_blobs;
|
||||
std::vector<AuxChainData> aux_chains;
|
||||
std::vector<IMergeMiningClient::ChainParameters> aux_chain_params;
|
||||
};
|
||||
|
||||
struct RpcClient : public Client
|
||||
{
|
||||
RpcClient() : Client(m_readBuf, sizeof(m_readBuf)) {}
|
||||
|
||||
static Client* allocate() { return new RpcClient(); }
|
||||
|
||||
size_t size() const override { return sizeof(RpcClient); }
|
||||
void reset() override
|
||||
{
|
||||
Client::reset();
|
||||
m_request.clear();
|
||||
m_request.shrink_to_fit();
|
||||
}
|
||||
|
||||
bool on_connect() override { return true; }
|
||||
bool on_read(const char* data, uint32_t size) override;
|
||||
|
||||
alignas(8) char m_readBuf[16384] = {};
|
||||
std::string m_request;
|
||||
};
|
||||
|
||||
p2pool* m_pool;
|
||||
mutable uv_mutex_t m_recentWorkLock;
|
||||
mutable std::vector<WorkSnapshot> m_recentWork;
|
||||
|
||||
const char* get_log_category() const override;
|
||||
|
||||
bool make_work_snapshot(WorkSnapshot& out, uint32_t hashing_blob_batch_count) const;
|
||||
void remember_work_snapshot(const WorkSnapshot& work) const;
|
||||
bool find_cached_work_by_template_id(uint32_t template_id, WorkSnapshot& out) const;
|
||||
bool find_cached_work_by_block_blob(const std::vector<uint8_t>& blob, WorkSnapshot& out) const;
|
||||
uint32_t copy_cached_hashing_blob(const WorkSnapshot& work, uint32_t extra_nonce, uint8_t (&blob)[128], size_t& nonce_offset) const;
|
||||
bool handle_http_request(RpcClient* client);
|
||||
std::string build_get_height_response() const;
|
||||
std::string build_json_rpc_response(const std::string& body) const;
|
||||
std::string json_error_response(const char* id, int code, const std::string& message) const;
|
||||
};
|
||||
|
||||
} // namespace p2pool
|
||||
+47
-3
@@ -2216,6 +2216,14 @@ bool P2PServer::P2PClient::on_read(const char* data, uint32_t size)
|
||||
|
||||
if (bytes_left >= 1 + sizeof(uint32_t)) {
|
||||
const uint32_t block_size = read_unaligned(reinterpret_cast<uint32_t*>(buf + 1));
|
||||
|
||||
if (block_size > MAX_BLOCK_SIZE) {
|
||||
LOGWARN(4, "peer " << static_cast<char*>(m_addrString) << " sent too big BLOCK_RESPONSE");
|
||||
ban(DEFAULT_BAN_TIME);
|
||||
server->remove_peer_from_list(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes_left >= 1 + sizeof(uint32_t) + block_size) {
|
||||
bytes_read = 1 + sizeof(uint32_t) + block_size;
|
||||
|
||||
@@ -2239,6 +2247,14 @@ bool P2PServer::P2PClient::on_read(const char* data, uint32_t size)
|
||||
|
||||
if (bytes_left >= 1 + sizeof(uint32_t)) {
|
||||
const uint32_t block_size = read_unaligned(reinterpret_cast<uint32_t*>(buf + 1));
|
||||
|
||||
if (block_size > MAX_BLOCK_SIZE) {
|
||||
LOGWARN(4, "peer " << static_cast<char*>(m_addrString) << " sent too big " << (compact ? "BLOCK_BROADCAST_COMPACT" : "BLOCK_BROADCAST"));
|
||||
ban(DEFAULT_BAN_TIME);
|
||||
server->remove_peer_from_list(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes_left >= 1 + sizeof(uint32_t) + block_size) {
|
||||
bytes_read = 1 + sizeof(uint32_t) + block_size;
|
||||
if (!on_block_broadcast(buf + 1 + sizeof(uint32_t), block_size, compact)) {
|
||||
@@ -2309,6 +2325,14 @@ bool P2PServer::P2PClient::on_read(const char* data, uint32_t size)
|
||||
|
||||
if (bytes_left >= 1 + sizeof(uint32_t)) {
|
||||
const uint32_t msg_size = read_unaligned(reinterpret_cast<uint32_t*>(buf + 1));
|
||||
|
||||
if (msg_size > MAX_BLOCK_SIZE) {
|
||||
LOGWARN(4, "peer " << static_cast<char*>(m_addrString) << " sent too big AUX_JOB_DONATION");
|
||||
ban(DEFAULT_BAN_TIME);
|
||||
server->remove_peer_from_list(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes_left >= 1 + sizeof(uint32_t) + msg_size) {
|
||||
bytes_read = 1 + sizeof(uint32_t) + msg_size;
|
||||
|
||||
@@ -2326,6 +2350,14 @@ bool P2PServer::P2PClient::on_read(const char* data, uint32_t size)
|
||||
|
||||
if (bytes_left >= 1 + sizeof(uint32_t)) {
|
||||
const uint32_t msg_size = read_unaligned(reinterpret_cast<uint32_t*>(buf + 1));
|
||||
|
||||
if (msg_size > MAX_BLOCK_SIZE) {
|
||||
LOGWARN(4, "peer " << static_cast<char*>(m_addrString) << " sent too big MONERO_BLOCK_BROADCAST");
|
||||
ban(DEFAULT_BAN_TIME);
|
||||
server->remove_peer_from_list(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes_left >= 1 + sizeof(uint32_t) + msg_size) {
|
||||
bytes_read = 1 + sizeof(uint32_t) + msg_size;
|
||||
|
||||
@@ -3037,7 +3069,8 @@ void P2PServer::P2PClient::on_peer_list_response(const uint8_t* buf)
|
||||
m_protocolVersion = PROTOCOL_VERSION_1_0;
|
||||
}
|
||||
else {
|
||||
m_protocolVersion = std::min(version, SUPPORTED_PROTOCOL_VERSION);
|
||||
// Don't allow version downgrades
|
||||
m_protocolVersion = std::max(m_protocolVersion, std::min(version, SUPPORTED_PROTOCOL_VERSION));
|
||||
}
|
||||
|
||||
m_SoftwareVersion = *reinterpret_cast<uint32_t*>(ip.data + 4);
|
||||
@@ -3109,11 +3142,19 @@ void P2PServer::P2PClient::on_block_notify(const uint8_t* buf)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!server->m_blockNotifyRequests.insert(*id.u64()).second || !server->m_missingBlockRequests.emplace(m_peerId, *id.u64()).second) {
|
||||
const uint64_t id64 = *id.u64();
|
||||
|
||||
// First check these two, then update them only after a successful send
|
||||
if (server->m_blockNotifyRequests.find(id64) != server->m_blockNotifyRequests.end()) {
|
||||
LOGINFO(6, "BLOCK_REQUEST for id = " << id << " was already sent");
|
||||
return;
|
||||
}
|
||||
|
||||
if (server->m_missingBlockRequests.find(std::make_pair(m_peerId, id64)) != server->m_missingBlockRequests.end()) {
|
||||
LOGINFO(6, "BLOCK_REQUEST for id = " << id << " was already sent to this peer");
|
||||
return;
|
||||
}
|
||||
|
||||
const bool result = server->send(this,
|
||||
[&id, this](uint8_t* buf, size_t buf_size) -> size_t
|
||||
{
|
||||
@@ -3134,7 +3175,10 @@ void P2PServer::P2PClient::on_block_notify(const uint8_t* buf)
|
||||
});
|
||||
|
||||
if (result) {
|
||||
m_blockPendingRequests.push_back(*id.u64());
|
||||
server->m_blockNotifyRequests.insert(id64);
|
||||
server->m_missingBlockRequests.emplace(m_peerId, id64);
|
||||
|
||||
m_blockPendingRequests.push_back(id64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+84
-5
@@ -29,6 +29,7 @@
|
||||
#include "side_chain.h"
|
||||
#include "stratum_server.h"
|
||||
#include "p2p_server.h"
|
||||
#include "nodejs_pool_rpc.h"
|
||||
#if defined(WITH_RANDOMX) && !defined(P2POOL_UNIT_TESTS)
|
||||
#include "miner.h"
|
||||
#endif
|
||||
@@ -171,6 +172,7 @@ p2pool::p2pool(const Params& params)
|
||||
|
||||
uv_mutex_init_checked(&m_submitBlockDataLock);
|
||||
uv_mutex_init_checked(&m_submitAuxBlockDataLock);
|
||||
uv_mutex_init_checked(&m_recentAuxSubmitsLock);
|
||||
|
||||
uv_mutex_init_checked(&m_missingHeightsLock);
|
||||
|
||||
@@ -255,6 +257,7 @@ p2pool::~p2pool()
|
||||
|
||||
uv_mutex_destroy(&m_submitBlockDataLock);
|
||||
uv_mutex_destroy(&m_submitAuxBlockDataLock);
|
||||
uv_mutex_destroy(&m_recentAuxSubmitsLock);
|
||||
|
||||
uv_mutex_destroy(&m_missingHeightsLock);
|
||||
|
||||
@@ -274,6 +277,8 @@ p2pool::~p2pool()
|
||||
PoolBlock::s_precalculatedSharesLock = nullptr;
|
||||
delete p;
|
||||
}
|
||||
|
||||
delete m_nodejsPoolRpc;
|
||||
}
|
||||
|
||||
void p2pool::update_host_ping(const std::string& display_name, double ping)
|
||||
@@ -653,6 +658,21 @@ void p2pool::set_aux_job_donation(const std::vector<IMergeMiningClient::ChainPar
|
||||
}
|
||||
#endif
|
||||
|
||||
bool p2pool::get_merge_mining_chain_params(const hash& chain_id, IMergeMiningClient::ChainParameters& out_params) const
|
||||
{
|
||||
ReadLock lock(m_mergeMiningClientsLock);
|
||||
|
||||
IMergeMiningClient::ChainParameters chain_params;
|
||||
for (const IMergeMiningClient* c : m_mergeMiningClients) {
|
||||
if (c->get_params(chain_params) && (chain_params.aux_id == chain_id)) {
|
||||
out_params = chain_params;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void p2pool::update_aux_data(const hash& chain_id)
|
||||
{
|
||||
if (m_stopped) {
|
||||
@@ -903,9 +923,28 @@ void p2pool::submit_block_async(std::vector<uint8_t>&& blob)
|
||||
|
||||
void p2pool::submit_aux_block_async(const std::vector<SubmitAuxBlockData>& aux_blocks)
|
||||
{
|
||||
std::vector<SubmitAuxBlockData> filtered_aux_blocks;
|
||||
filtered_aux_blocks.reserve(aux_blocks.size());
|
||||
|
||||
for (const SubmitAuxBlockData& aux_block : aux_blocks) {
|
||||
if (remember_recent_aux_submit(aux_block)) {
|
||||
filtered_aux_blocks.push_back(aux_block);
|
||||
}
|
||||
else {
|
||||
LOGINFO(4, "submit_aux_block_async: skipping duplicate aux submit for template id = " << aux_block.template_id
|
||||
<< ", chain_id = " << aux_block.chain_id
|
||||
<< ", nonce = " << aux_block.nonce
|
||||
<< ", extra_nonce = " << aux_block.extra_nonce);
|
||||
}
|
||||
}
|
||||
|
||||
if (filtered_aux_blocks.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(m_submitAuxBlockDataLock);
|
||||
m_submitAuxBlockData.insert(m_submitAuxBlockData.end(), aux_blocks.begin(), aux_blocks.end());
|
||||
m_submitAuxBlockData.insert(m_submitAuxBlockData.end(), filtered_aux_blocks.begin(), filtered_aux_blocks.end());
|
||||
}
|
||||
|
||||
// If p2pool is stopped, m_submitAuxBlockAsync is most likely already closed
|
||||
@@ -921,6 +960,36 @@ void p2pool::submit_aux_block_async(const std::vector<SubmitAuxBlockData>& aux_b
|
||||
}
|
||||
}
|
||||
|
||||
bool p2pool::remember_recent_aux_submit(const SubmitAuxBlockData& data) const
|
||||
{
|
||||
// Collapse burst duplicates from overlapping workers, but keep the window short
|
||||
// so a later retry after a genuine transport failure can still go through.
|
||||
constexpr uint64_t DUPLICATE_WINDOW = 5;
|
||||
const uint64_t now = seconds_since_epoch();
|
||||
|
||||
MutexLock lock(m_recentAuxSubmitsLock);
|
||||
|
||||
m_recentAuxSubmits.erase(
|
||||
std::remove_if(m_recentAuxSubmits.begin(), m_recentAuxSubmits.end(),
|
||||
[now](const RecentAuxSubmit& item) {
|
||||
return (now > item.timestamp) && (now - item.timestamp > DUPLICATE_WINDOW);
|
||||
}),
|
||||
m_recentAuxSubmits.end());
|
||||
|
||||
for (const RecentAuxSubmit& item : m_recentAuxSubmits) {
|
||||
if ((item.chain_id == data.chain_id)
|
||||
&& (item.template_id == data.template_id)
|
||||
&& (item.nonce == data.nonce)
|
||||
&& (item.extra_nonce == data.extra_nonce)
|
||||
&& (data.chain_params.aux_hash.empty() || item.aux_hash.empty() || (item.aux_hash == data.chain_params.aux_hash))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_recentAuxSubmits.push_back(RecentAuxSubmit{ data.chain_id, data.chain_params.aux_hash, data.template_id, data.nonce, data.extra_nonce, now });
|
||||
return true;
|
||||
}
|
||||
|
||||
void p2pool::submit_aux_block() const
|
||||
{
|
||||
std::vector<SubmitAuxBlockData> submit_data;
|
||||
@@ -972,24 +1041,29 @@ void p2pool::submit_aux_block() const
|
||||
}
|
||||
|
||||
if (chain_id == chain_params.aux_id) {
|
||||
IMergeMiningClient::ChainParameters submit_chain_params = submit_data[i].chain_params;
|
||||
if (submit_chain_params.aux_id.empty() || submit_chain_params.aux_hash.empty() || submit_chain_params.aux_blob.empty()) {
|
||||
submit_chain_params = chain_params;
|
||||
}
|
||||
|
||||
std::vector<hash> proof;
|
||||
uint32_t path;
|
||||
|
||||
if (m_blockTemplate->get_aux_proof(template_id, extra_nonce, chain_params.aux_hash, proof, path)) {
|
||||
if (m_blockTemplate->get_aux_proof(template_id, extra_nonce, submit_chain_params.aux_hash, proof, path)) {
|
||||
if (pool_block_debug()) {
|
||||
const MinerData data = miner_data();
|
||||
const uint32_t n_aux_chains = static_cast<uint32_t>(data.aux_chains.size() + 1);
|
||||
const uint32_t index = get_aux_slot(chain_params.aux_id, data.aux_nonce, n_aux_chains);
|
||||
|
||||
if (!verify_merkle_proof(chain_params.aux_hash, proof, index, n_aux_chains, merge_mining_root)) {
|
||||
if (!verify_merkle_proof(submit_chain_params.aux_hash, proof, index, n_aux_chains, merge_mining_root)) {
|
||||
LOGERR(0, "submit_aux_block: verify_merkle_proof (1) failed for chain_id " << chain_id);
|
||||
}
|
||||
if (!verify_merkle_proof(chain_params.aux_hash, proof, path, merge_mining_root)) {
|
||||
if (!verify_merkle_proof(submit_chain_params.aux_hash, proof, path, merge_mining_root)) {
|
||||
LOGERR(0, "submit_aux_block: verify_merkle_proof (2) failed for chain_id " << chain_id);
|
||||
}
|
||||
}
|
||||
|
||||
c->submit_solution(block_tpl->get_coinbase_merkle_proof(), hashing_blob, nonce_offset, seed_hash, blob, proof, path);
|
||||
c->submit_solution(submit_chain_params, block_tpl->get_coinbase_merkle_proof(), hashing_blob, nonce_offset, seed_hash, blob, proof, path);
|
||||
}
|
||||
else {
|
||||
LOGWARN(3, "submit_aux_block: failed to get merkle proof for chain_id " << chain_id);
|
||||
@@ -1358,6 +1432,9 @@ void p2pool::download_block_headers4(uint64_t start_height, uint64_t current_hei
|
||||
if (m_serversStarted.exchange(1) == 0) {
|
||||
m_p2pServer = new P2PServer(this);
|
||||
m_stratumServer = new StratumServer(this);
|
||||
if (!m_params.m_nodejsPoolRpc.empty()) {
|
||||
m_nodejsPoolRpc = new NodejsPoolRpc(this, m_params.m_nodejsPoolRpc);
|
||||
}
|
||||
#if defined(WITH_RANDOMX) && !defined(P2POOL_UNIT_TESTS)
|
||||
if (m_params.m_minerThreads) {
|
||||
start_mining(m_params.m_minerThreads);
|
||||
@@ -2355,6 +2432,8 @@ int p2pool::run()
|
||||
#endif
|
||||
delete m_stratumServer;
|
||||
delete m_p2pServer;
|
||||
delete m_nodejsPoolRpc;
|
||||
m_nodejsPoolRpc = nullptr;
|
||||
|
||||
LOGINFO(1, "stopped");
|
||||
return 0;
|
||||
|
||||
@@ -30,6 +30,7 @@ class Mempool;
|
||||
class SideChain;
|
||||
class StratumServer;
|
||||
class P2PServer;
|
||||
class NodejsPoolRpc;
|
||||
class Miner;
|
||||
class ConsoleCommands;
|
||||
class p2pool_api;
|
||||
@@ -76,6 +77,7 @@ public:
|
||||
|
||||
StratumServer* stratum_server() const { return m_stratumServer; }
|
||||
P2PServer* p2p_server() const { return m_p2pServer; }
|
||||
NodejsPoolRpc* nodejs_pool_rpc() const { return m_nodejsPoolRpc; }
|
||||
|
||||
#if defined(WITH_RANDOMX) && !defined(P2POOL_UNIT_TESTS)
|
||||
void print_miner_status();
|
||||
@@ -94,6 +96,7 @@ public:
|
||||
#endif
|
||||
|
||||
void update_aux_data(const hash& chain_id);
|
||||
bool get_merge_mining_chain_params(const hash& chain_id, IMergeMiningClient::ChainParameters& out_params) const;
|
||||
|
||||
void submit_block_async(uint32_t template_id, uint32_t nonce, uint32_t extra_nonce);
|
||||
void submit_block_async(std::vector<uint8_t>&& blob);
|
||||
@@ -104,6 +107,7 @@ public:
|
||||
uint32_t template_id = 0;
|
||||
uint32_t nonce = 0;
|
||||
uint32_t extra_nonce = 0;
|
||||
IMergeMiningClient::ChainParameters chain_params;
|
||||
};
|
||||
|
||||
void submit_aux_block_async(const std::vector<SubmitAuxBlockData>& aux_blocks);
|
||||
@@ -155,6 +159,7 @@ private:
|
||||
|
||||
void submit_block() const;
|
||||
void submit_aux_block() const;
|
||||
bool remember_recent_aux_submit(const SubmitAuxBlockData& data) const;
|
||||
|
||||
std::atomic<bool> m_stopped;
|
||||
|
||||
@@ -225,6 +230,7 @@ private:
|
||||
std::atomic<uint32_t> m_serversStarted{ 0 };
|
||||
StratumServer* m_stratumServer = nullptr;
|
||||
P2PServer* m_p2pServer = nullptr;
|
||||
NodejsPoolRpc* m_nodejsPoolRpc = nullptr;
|
||||
|
||||
std::atomic<bool> m_startupFinished{ false };
|
||||
|
||||
@@ -251,6 +257,19 @@ private:
|
||||
mutable uv_mutex_t m_submitAuxBlockDataLock;
|
||||
mutable std::vector<SubmitAuxBlockData> m_submitAuxBlockData;
|
||||
|
||||
struct RecentAuxSubmit
|
||||
{
|
||||
hash chain_id;
|
||||
hash aux_hash;
|
||||
uint32_t template_id = 0;
|
||||
uint32_t nonce = 0;
|
||||
uint32_t extra_nonce = 0;
|
||||
uint64_t timestamp = 0;
|
||||
};
|
||||
|
||||
mutable uv_mutex_t m_recentAuxSubmitsLock;
|
||||
mutable std::vector<RecentAuxSubmit> m_recentAuxSubmits;
|
||||
|
||||
uv_async_t m_submitBlockAsync;
|
||||
uv_async_t m_submitAuxBlockAsync;
|
||||
uv_async_t m_blockTemplateAsync;
|
||||
|
||||
@@ -273,6 +273,11 @@ bool Params::process_arg(const std::vector<std::string>& arg)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((arg[0] == "nodejs-pool-rpc") && has1(arg)) {
|
||||
m_nodejsPoolRpc = arg[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((arg[0] == "stratum-ban-time") && has1(arg)) {
|
||||
m_stratumBanTime = strtoull(arg[1].c_str(), nullptr, 10);
|
||||
return true;
|
||||
|
||||
@@ -89,6 +89,7 @@ struct Params
|
||||
|
||||
std::string m_stratumAddresses;
|
||||
std::string m_p2pAddresses;
|
||||
std::string m_nodejsPoolRpc;
|
||||
std::string m_p2pPeerList;
|
||||
std::string m_dataDir;
|
||||
std::string m_logFilePath;
|
||||
|
||||
@@ -56,6 +56,9 @@ static constexpr uint64_t MAX_BLOCK_SIZE = 128 * 1024 - 5;
|
||||
// 0.6 XMR
|
||||
static constexpr uint64_t BASE_BLOCK_REWARD = 600000000000ULL;
|
||||
|
||||
// Because these values are stored in a 56 bit field
|
||||
static constexpr uint64_t MAX_OUTPUT_VALUE = (1ULL << 56) - 1;
|
||||
|
||||
// 1000 years at 1 TH/s. It should be enough for any normal use.
|
||||
static constexpr difficulty_type MAX_CUMULATIVE_DIFFICULTY{ 13019633956666736640ULL, 1710ULL };
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
|
||||
READ_VARINT(reward);
|
||||
|
||||
// TxOutput max value check
|
||||
if (reward >= (1ULL << 56)) {
|
||||
if (reward > MAX_OUTPUT_VALUE) {
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -1247,8 +1247,15 @@ bool SideChain::split_reward(uint64_t reward, const std::vector<MinerShare>& sha
|
||||
w += shares[i].m_weight;
|
||||
|
||||
const difficulty_type next_value = w * reward / total_weight;
|
||||
rewards.emplace_back(next_value.lo - reward_given);
|
||||
const uint64_t r = next_value.lo - reward_given;
|
||||
reward_given = next_value.lo;
|
||||
|
||||
if (r > MAX_OUTPUT_VALUE) {
|
||||
LOGERR(1, "Reward of " << log::XMRAmount(reward) << " is too big for the current split.");
|
||||
return false;
|
||||
}
|
||||
|
||||
rewards.emplace_back(r);
|
||||
}
|
||||
|
||||
// Double check that we gave out the exact amount
|
||||
|
||||
@@ -445,7 +445,7 @@ bool StratumServer::on_submit(StratumClient* client, uint32_t id, const char* jo
|
||||
if (aux_data.difficulty.check_pow(resultHash)) {
|
||||
const char* s = client->m_customUser;
|
||||
LOGINFO(0, log::Green() << "client " << static_cast<char*>(client->m_addrString) << (*s ? " user " : "") << s << " found an aux block for chain_id " << aux_data.unique_id << ", diff " << aux_data.difficulty << ", submitting it");
|
||||
aux_blocks.emplace_back(p2pool::SubmitAuxBlockData{ aux_data.unique_id, template_id, nonce, extra_nonce });
|
||||
aux_blocks.emplace_back(p2pool::SubmitAuxBlockData{ aux_data.unique_id, template_id, nonce, extra_nonce, {} });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user