Skip to content

Commit 565db1a

Browse files
committed
Remove the Test Mode and replace it by a Test Server
Signed-off-by: Pttn <28868425+Pttn@users.noreply.github.com>
1 parent 051b500 commit 565db1a

File tree

7 files changed

+217
-97
lines changed

7 files changed

+217
-97
lines changed

Client.cpp

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -155,59 +155,3 @@ void SearchClient::handleResult(const Job& job) {
155155
else
156156
ERRORMSG("Unable to write tuple to file " << _tuplesFilename);
157157
}
158-
159-
void TestClient::connect() {
160-
if (!_connected) {
161-
_bh = BlockHeader();
162-
_requests = 0;
163-
_timer = std::chrono::steady_clock::now();
164-
_connected = true;
165-
_starting = true;
166-
}
167-
}
168-
169-
void TestClient::process() {
170-
std::lock_guard<std::mutex> lock(_jobMutex);
171-
if (!_starting && timeSince(_timer) >= _timeBeforeNextBlock) {
172-
_height++;
173-
_requests = 0;
174-
_difficulty += 10;
175-
if (_difficulty == 1630) {
176-
_difficulty = 1200;
177-
if (_currentPattern == std::vector<uint64_t>{0, 2, 4, 2, 4})
178-
_currentPattern = {0, 2, 4, 2, 4, 6, 2}; // Fork simulation, triggering the miner restart
179-
}
180-
else if (_difficulty < 1600) {
181-
_difficulty -= 30;
182-
if (_difficulty <= 1040) {
183-
_difficulty = 1600;
184-
_connected = false; // Disconnect simulation
185-
}
186-
}
187-
_timer = std::chrono::steady_clock::now();
188-
}
189-
_bh = BlockHeader();
190-
reinterpret_cast<uint64_t*>(&_bh.previousblockhash[0])[0] = _height - 1;
191-
reinterpret_cast<uint64_t*>(&_bh.merkleRoot[0])[0] = _requests;
192-
_bh.bits = 256*_difficulty;
193-
}
194-
195-
Job TestClient::getJob(const bool dummy) {
196-
std::lock_guard<std::mutex> lock(_jobMutex);
197-
if (_starting && !dummy) {
198-
_timer = std::chrono::steady_clock::now();
199-
_requests = 0;
200-
_starting = false;
201-
}
202-
Job job;
203-
job.clientData.bh = _bh;
204-
job.height = _connected ? _height : 0;
205-
job.powVersion = 1;
206-
job.acceptedPatterns = {_currentPattern};
207-
job.primeCountTarget = _currentPattern.size();
208-
job.primeCountMin = job.primeCountTarget;
209-
job.difficulty = _difficulty;
210-
job.target = job.clientData.bh.target(job.powVersion);
211-
if (!dummy) _requests++;
212-
return job;
213-
}

Client.hpp

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ class BMClient : public Client {
180180
public:
181181
BMClient(const Options &options) : _pattern(options.minerParameters.pattern), _difficulty(options.difficulty), _blockInterval(options.benchmarkBlockInterval), _height(0), _requests(0) {} // The timer is initialized at the first getJob call.
182182
void process();
183-
Job getJob(const bool = false); // Dummy boolean to avoid prevent the block timer of Benchmark and Test Clients from starting when the miner initializes.
183+
Job getJob(const bool = false); // Dummy boolean to prevent the block timer of the Benchmark Client from starting when the miner initializes.
184184
uint32_t currentHeight() const {return _height;}
185185
double currentDifficulty() const {return _difficulty;}
186186
};
@@ -203,22 +203,4 @@ class SearchClient : public Client {
203203
double currentDifficulty() const {return _difficulty;}
204204
};
205205

206-
// Simulates various network situations to test/debug code.
207-
class TestClient : public NetworkedClient { // Actually not networked, but behaves like one
208-
BlockHeader _bh;
209-
uint32_t _height, _difficulty, _requests, _timeBeforeNextBlock;
210-
std::vector<uint64_t> _currentPattern;
211-
bool _starting; // Used to set the timer so the time taken to initialize the miner the first time not counted
212-
std::chrono::time_point<std::chrono::steady_clock> _timer;
213-
214-
bool _fetchWork();
215-
public:
216-
TestClient() : _height(1), _difficulty(1600), _requests(0), _timeBeforeNextBlock(10), _currentPattern{0, 2, 4, 2, 4} {}
217-
void connect();
218-
void process();
219-
Job getJob(const bool = false);
220-
uint32_t currentHeight() const {return _connected ? _height : 0;};
221-
double currentDifficulty() const {return _difficulty;};
222-
};
223-
224206
#endif

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ staticAVX2: LIBS := -Wl,-Bstatic -static-libstdc++ -L libs/ $(LIBS) -Wl,-Bdyna
4747
staticAVX2: rieMinerAVX2
4848
endif
4949

50+
testServer: rieMinerTestServer
51+
52+
rieMinerTestServer: TestServer.cpp
53+
$(CXX) -Wall -Wextra -std=c++20 $^ -o $@
54+
5055
rieMinerAVX2: main.o Miner.o StratumClient.o GBTClient.o Client.o Stats.o tools.o mod_1_4.o mod_1_2_avx.o mod_1_2_avx2.o fermat.o primetest.o primetest512.o
5156
$(CXX) $(CFLAGS) -o rieMiner $^ $(LIBS)
5257

Miner.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ void Miner::init(const MinerParameters &minerParameters) {
216216

217217
uint32_t bitsForOffset;
218218
// The primorial times the maximum factor should be smaller than the allowed limit for the target offset.
219-
if (_mode == "Solo" || _mode == "Pool" || _mode == "Test")
219+
if (_mode == "Solo" || _mode == "Pool")
220220
bitsForOffset = std::floor(static_cast<double>(_difficultyAtInit)/_parameters.restartDifficultyFactor - 265.); // 1 . leading 8 bits . hash (256 bits) . remaining bits for the offset, and some margin to take in account the Difficulty fluctuations
221221
else if (_mode == "Search")
222222
bitsForOffset = std::floor(_difficultyAtInit - 97.); // 1 . leading 16 bits . random 80 bits . remaining bits for the offset

README.md

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -183,20 +183,7 @@ rieMiner proposes the following Modes depending on what you want to do. Use the
183183
* `Solo`: solo mining via GetBlockTemplate;
184184
* `Pool`: pooled mining using Stratum;
185185
* `Benchmark`: test performance with a simulated and deterministic network (use this to compare different settings or share your benchmark results);
186-
* `Search`: pure prime constellation search (useful for record attempts);
187-
* `Test`: simulates various network situations for testing, see below.
188-
189-
#### Test Mode
190-
191-
It does the following:
192-
193-
* Start at Difficulty 1600, the first time with constellation pattern 0, 2, 4, 2, 4;
194-
* Increases Difficulty by 10 every 10 s two times;
195-
* After 10 more seconds, sets Difficulty to 1200 and the constellation pattern to 0, 2, 4, 2, 4, 6, 2;
196-
* Decreases Difficulty by 20 every 10 s (the time taken to restart the miner is counted, so if it takes more than 10 s, it is normal that a new block appears immediately after the reinitialization);
197-
* The miner restarts several times due to the Difficulty variation (this adjusts some parameters if not set);
198-
* When the Difficulty reaches 1040, a disconnect is simulated;
199-
* Repeat (keeping the 7-tuple constellation). The miner will restart twice as when it disconnects, it is not aware that the Difficulty increased a lot.
186+
* `Search`: pure prime constellation search (useful for record attempts).
200187

201188
### Solo and Pooled Mining options
202189

@@ -240,7 +227,7 @@ During mining, rieMiner will regularly print some statistics (use the `RefreshIn
240227

241228
rieMiner will also notify if it found a block or a share, and if the network found a new block. If it finds a block or a share, it will tell if the submission was accepted (solo mining only) or not by the server.
242229

243-
In Benchmark, Search and Test Modes, the behavior is essentially the same as Solo mining. In mining and Test Modes, the statistics are based on the tuples found during the latest five blocks, including the current one. In the other Modes, everything since the beginning is taken in account.
230+
In Benchmark and Search Modes, the behavior is essentially the same as Solo mining. In mining Modes, the statistics are based on the tuples found during the latest five blocks, including the current one, while in the other Modes, everything since the beginning is taken in account.
244231

245232
## Developers and license
246233

@@ -267,12 +254,23 @@ Donations to the Riecoin Project are welcome (you can also set a higher Donate v
267254
* Riecoin: ric1qr3yxckxtl7lacvtuzhrdrtrlzvlydane2h37ja
268255
* Bitcoin: bc1qr3yxckxtl7lacvtuzhrdrtrlzvlydaneqela0u
269256

257+
### Testing
258+
259+
Code for a testing server is provided. It acts like a mining pool, and tests the rieMiner's behavior in various network situations like whether it restarts properly when the Difficulty increases a lot or if it reconnects when there are disconnects. It was only tested on Debian 11. The server can be built and run with
260+
261+
```bash
262+
make testServer
263+
./rieMinerTestServer
264+
```
265+
266+
Then, launch rieMiner using the `Pool` Mode and Port `3004`. Watch whether strange things happen, if there are crashes or deadlocks, test with several machines and different rieMiner parameters, run several loops, also do not hesitate to changes some parameters in the code...
267+
270268
### Quick contributor's checklist
271269

272270
* Your code must compile and work on recent Debian based distributions, and Windows using MSYS;
273271
* If modifying the miner, you must ensure that your changes do not cause any performance loss. You have to do proper and long enough before/after benchmarks;
274272
* Document well non trivial contributions to the miner so other and future developers can understand easily and quickly the code;
275-
* rieMiner must work for any realistic setting, the Test Mode must work as expected;
273+
* rieMiner must work for any realistic setting, you should make tests with the Test Server;
276274
* Ensure that your changes did not break anything, even if it compiles. Examples (if applicable):
277275
* There should never be random (or not) segmentation faults or any other bug, try to do actual mining with Gdb, debugging symbols and Debug Mode enabled during hours or even days to catch possible bugs;
278276
* Ensure that valid work is produced (pools and Riecoin Core must not reject submissions);

TestServer.cpp

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// (c) 2021 Pttn (https://github.com/Pttn/rieMiner)
2+
3+
#include <chrono>
4+
#include <cstdio>
5+
#include <cstdlib>
6+
#include <iomanip>
7+
#include <iostream>
8+
#include <netinet/in.h>
9+
#include <sstream>
10+
#include <thread>
11+
#include <unistd.h>
12+
#include <vector>
13+
14+
constexpr uint16_t port(3004);
15+
16+
int serverFd, clientFd;
17+
sockaddr_in address;
18+
socklen_t addressLength(sizeof(address));
19+
20+
constexpr uint16_t maxMessageSize(256);
21+
char buffer[256] = {0};
22+
std::string receiveMessage() {
23+
/*int bytesRead(0);
24+
bytesRead = */read(clientFd, buffer, 256);
25+
// std::cout << "Got " << bytesRead << " bytes from rieMiner: " << buffer << std::endl;
26+
return buffer;
27+
}
28+
29+
void sendMessage(const std::string &message) {
30+
// std::cout << "Sending " << message.size() << " bytes: " << message << std::endl;
31+
send(clientFd , message.c_str(), message.size(), 0);
32+
}
33+
34+
void acceptMiner() {
35+
if ((clientFd = accept(serverFd, reinterpret_cast<sockaddr*>(&address), &addressLength)) < 0) {
36+
std::cerr << "Could not accept connection" << std::endl;
37+
exit(-1);
38+
}
39+
receiveMessage();
40+
sendMessage("{\"id\": 0, \"result\": [[[\"mining.notify\", \"00\"]], \"00\", 1], \"error\": null}\n");
41+
receiveMessage();
42+
sendMessage("{\"id\": 0, \"result\": true, \"error\": null}\n");
43+
}
44+
45+
template <class C> std::string formatContainer(const C& container) {
46+
std::ostringstream oss;
47+
for (auto it(container.begin()) ; it < container.end() ; it++) {
48+
oss << *it;
49+
if (it != container.end() - 1) oss << ", ";
50+
}
51+
return oss.str();
52+
}
53+
54+
uint64_t timestampNow() {
55+
const auto now(std::chrono::system_clock::now());
56+
return std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
57+
}
58+
59+
std::string formattedHeight(const uint32_t height) {
60+
std::ostringstream oss;
61+
if (height < 128)
62+
oss << "01" << std::setfill('0') << std::setw(2) << std::hex << height;
63+
else if (height < 32768)
64+
oss << "02" << std::setfill('0') << std::setw(2) << std::hex << height % 256 << std::setfill('0') << std::setw(2) << std::hex << (height/256) % 256;
65+
else
66+
oss << "03" << std::setfill('0') << std::setw(2) << std::hex << height % 256 << std::setfill('0') << std::setw(2) << std::hex << (height/256) % 256 << std::setfill('0') << std::setw(2) << std::hex << (height/65536) % 256;
67+
return oss.str();
68+
}
69+
std::string generateMiningNotify(const uint32_t height, const uint32_t nBits, const std::vector<std::vector<uint64_t>> acceptedPatterns, const bool cleanJobs) {
70+
uint64_t currentJobId(0);
71+
std::ostringstream oss;
72+
oss << "{\"id\": null, \"method\": \"mining.notify\", \"params\": [\"";
73+
oss << std::hex << currentJobId++ << "\"";
74+
oss << ", \"0000000000000000000000000000000000000000000000000000000000000000\"";
75+
oss << ", \"000000000000000000000000000000000000000000000000000000000000000000000000000000000000" << formattedHeight(height) << "\"";
76+
oss << ", \"\"";
77+
oss << ", []";
78+
oss << ", \"20000000\"";
79+
oss << ", \"" << std::setfill('0') << std::setw(8) << std::hex << nBits << "\"";
80+
oss << ", \"" << std::setfill('0') << std::setw(8) << std::hex << timestampNow() << "\"";
81+
oss << ", " << (cleanJobs ? "true" : "false");
82+
oss << ", 1";
83+
oss << ", [";
84+
for (uint64_t i(0) ; i < acceptedPatterns.size() ; i++) {
85+
oss << "[" << formatContainer(acceptedPatterns[i]) << "]";
86+
if (i + 1 != acceptedPatterns.size())
87+
oss << ", ";
88+
}
89+
oss << "]]}\n";
90+
return oss.str();
91+
}
92+
93+
int main() {
94+
std::cout << "rieMiner Test Server" << std::endl;
95+
std::cout << "-----------------------------------------------------------" << std::endl;
96+
if ((serverFd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
97+
std::cerr << "Could not get a File Descriptor for the Server" << std::endl;
98+
exit(-1);
99+
}
100+
int optval(1);
101+
if (setsockopt(serverFd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &optval, sizeof(decltype(optval))) != 0) {
102+
std::cerr << "Setsockopt could not set SO_REUSEADDR | SO_REUSEPORT" << std::endl;
103+
exit(-1);
104+
}
105+
address.sin_family = AF_INET;
106+
address.sin_addr.s_addr = INADDR_ANY;
107+
address.sin_port = htons(port);
108+
if (bind(serverFd, reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0) {
109+
std::cerr << "Could not bind" << std::endl;
110+
exit(-1);
111+
}
112+
if (listen(serverFd, 1) < 0) {
113+
std::cerr << "Could not listen" << std::endl;
114+
exit(-1);
115+
}
116+
117+
std::cout << "Please start rieMiner in Pool Mode with Port " << port << std::endl;
118+
uint32_t height(1U);
119+
while (true) {
120+
std::cout << "Test 1: connection to a Pool" << std::endl;
121+
std::cout << "Expected behavior: rieMiner initializes the miner and starts mining" << std::endl;
122+
acceptMiner();
123+
uint32_t difficulty(768U);
124+
std::cout << "-----------------------------------------------------------" << std::endl;
125+
std::cout << "Test 2: Varying Difficulty" << std::endl;
126+
std::cout << "Expected behavior: rieMiner finds shares and restarts correctly in reaction to the increasing and decreasing Difficulty" << std::endl;
127+
for ( ; difficulty < 896 ; difficulty += 8) {
128+
std::cout << "Current Difficulty: " << difficulty << " (Block " << height << ")" << std::endl;
129+
sendMessage(generateMiningNotify(height++, difficulty*256, {{0, 4, 2, 4, 2}, {0, 2, 4, 2, 4}}, true));
130+
for (uint16_t i(0) ; i < 8 ; i++) {
131+
receiveMessage();
132+
sendMessage("{\"id\": 0, \"result\": true, \"error\": null}\n");
133+
}
134+
}
135+
for ( ; difficulty > 640 ; difficulty -= 16) {
136+
std::cout << "Current Difficulty: " << difficulty << " (Block " << height << ")" << std::endl;
137+
sendMessage(generateMiningNotify(height++, difficulty*256, {{0, 4, 2, 4, 2}, {0, 2, 4, 2, 4}}, true));
138+
for (uint16_t i(0) ; i < 8 ; i++) {
139+
receiveMessage();
140+
sendMessage("{\"id\": 0, \"result\": true, \"error\": null}\n");
141+
}
142+
}
143+
std::cout << "-----------------------------------------------------------" << std::endl;
144+
std::cout << "Test 3: Disconnects" << std::endl;
145+
std::cout << "Expected behavior: rieMiner gets disconnected several times, but always reconnects and resumes mining properly" << std::endl;
146+
std::cout << "8 disconnections after 5 s" << std::endl;
147+
for (uint16_t i(0) ; i < 8 ; i++) {
148+
std::this_thread::sleep_for(std::chrono::seconds(5));
149+
std::cout << "Disconnect " << i << std::endl;
150+
close(clientFd);
151+
acceptMiner();
152+
std::cout << "Reconnected" << std::endl;
153+
sendMessage(generateMiningNotify(height, difficulty*256, {{0, 4, 2, 4, 2}, {0, 2, 4, 2, 4}}, true));
154+
}
155+
std::cout << "4 disconnections after 15 s" << std::endl;
156+
for (uint16_t i(0) ; i < 4 ; i++) {
157+
std::this_thread::sleep_for(std::chrono::seconds(15));
158+
std::cout << "Disconnect " << i << std::endl;
159+
close(clientFd);
160+
acceptMiner();
161+
std::cout << "Reconnected" << std::endl;
162+
sendMessage(generateMiningNotify(height, difficulty*256, {{0, 4, 2, 4, 2}, {0, 2, 4, 2, 4}}, true));
163+
}
164+
std::cout << "2 disconnections after 30 s" << std::endl;
165+
for (uint16_t i(0) ; i < 2 ; i++) {
166+
std::this_thread::sleep_for(std::chrono::seconds(30));
167+
std::cout << "Disconnect " << i << std::endl;
168+
close(clientFd);
169+
acceptMiner();
170+
std::cout << "Reconnected" << std::endl;
171+
sendMessage(generateMiningNotify(height, difficulty*256, {{0, 4, 2, 4, 2}, {0, 2, 4, 2, 4}}, true));
172+
}
173+
std::cout << "-----------------------------------------------------------" << std::endl;
174+
std::cout << "Test 4: Constellation Pattern Change/Hard Fork Simulation" << std::endl;
175+
std::cout << "Expected behavior: rieMiner finds shares and restarts correctly in reaction to the Pattern or Difficulty Change" << std::endl;
176+
difficulty = 540;
177+
sendMessage(generateMiningNotify(height++, difficulty*256, {{0, 2, 4, 2, 4, 6, 2}, {0, 2, 6, 4, 2, 4, 2}}, true));
178+
std::cout << "rieMiner should now mine 7-tuples. Current Difficulty: " << difficulty << " (Block " << height << ")" << std::endl;
179+
for (uint16_t i(0) ; i < 64 ; i++) {
180+
receiveMessage();
181+
sendMessage("{\"id\": 0, \"result\": true, \"error\": null}\n");
182+
}
183+
difficulty = 480;
184+
sendMessage(generateMiningNotify(height++, difficulty*256, {{0, 2, 4, 2, 4, 6, 2, 6}, {0, 2, 4, 6, 2, 6, 4, 2}, {0, 6, 2, 6, 4, 2, 4, 2}}, true));
185+
std::cout << "rieMiner should now mine 8-tuples. Current Difficulty: " << difficulty << " (Block " << height << ")" << std::endl;
186+
for (uint16_t i(0) ; i < 8 ; i++) {
187+
receiveMessage();
188+
sendMessage("{\"id\": 0, \"result\": true, \"error\": null}\n");
189+
}
190+
std::cout << "Test loop finished." << std::endl;
191+
close(clientFd);
192+
std::cout << "-----------------------------------------------------------" << std::endl;
193+
}
194+
return 0;
195+
}

main.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ bool Configuration::parse(const int argc, char** argv) {
9898
catch (...) {_options.debug = 0;}
9999
}
100100
else if (key == "Mode") {
101-
if (value == "Solo" || value == "Pool" || value == "Benchmark" || value == "Search" || value == "Test")
101+
if (value == "Solo" || value == "Pool" || value == "Benchmark" || value == "Search")
102102
_options.mode = value;
103103
else std::cout << "Invalid mode!" << std::endl;
104104
}
@@ -221,8 +221,6 @@ bool Configuration::parse(const int argc, char** argv) {
221221
if (_options.minerParameters.pattern.size() == 0) // Pick a default pattern if none was chosen
222222
_options.minerParameters.pattern = {0, 2, 4, 2, 4, 6, 2};
223223
}
224-
else if (_options.mode == "Test")
225-
std::cout << "Test Mode" << std::endl;
226224
else {
227225
if (_options.mode == "Solo") std::cout << "Solo mining";
228226
else if (_options.mode == "Pool") std::cout << "Pooled mining";
@@ -325,8 +323,6 @@ int main(int argc, char** argv) {
325323
client = std::make_shared<StratumClient>(configuration.options());
326324
else if (configuration.options().mode == "Search")
327325
client = std::make_shared<SearchClient>(configuration.options());
328-
else if (configuration.options().mode == "Test")
329-
client = std::make_shared<TestClient>();
330326
else
331327
client = std::make_shared<BMClient>(configuration.options());
332328
miner->setClient(client);

0 commit comments

Comments
 (0)