Skip to content

Commit

Permalink
Remove the Test Mode and replace it by a Test Server
Browse files Browse the repository at this point in the history
Signed-off-by: Pttn <[email protected]>
  • Loading branch information
Pttn committed Oct 28, 2021
1 parent 051b500 commit 565db1a
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 97 deletions.
56 changes: 0 additions & 56 deletions Client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,59 +155,3 @@ void SearchClient::handleResult(const Job& job) {
else
ERRORMSG("Unable to write tuple to file " << _tuplesFilename);
}

void TestClient::connect() {
if (!_connected) {
_bh = BlockHeader();
_requests = 0;
_timer = std::chrono::steady_clock::now();
_connected = true;
_starting = true;
}
}

void TestClient::process() {
std::lock_guard<std::mutex> lock(_jobMutex);
if (!_starting && timeSince(_timer) >= _timeBeforeNextBlock) {
_height++;
_requests = 0;
_difficulty += 10;
if (_difficulty == 1630) {
_difficulty = 1200;
if (_currentPattern == std::vector<uint64_t>{0, 2, 4, 2, 4})
_currentPattern = {0, 2, 4, 2, 4, 6, 2}; // Fork simulation, triggering the miner restart
}
else if (_difficulty < 1600) {
_difficulty -= 30;
if (_difficulty <= 1040) {
_difficulty = 1600;
_connected = false; // Disconnect simulation
}
}
_timer = std::chrono::steady_clock::now();
}
_bh = BlockHeader();
reinterpret_cast<uint64_t*>(&_bh.previousblockhash[0])[0] = _height - 1;
reinterpret_cast<uint64_t*>(&_bh.merkleRoot[0])[0] = _requests;
_bh.bits = 256*_difficulty;
}

Job TestClient::getJob(const bool dummy) {
std::lock_guard<std::mutex> lock(_jobMutex);
if (_starting && !dummy) {
_timer = std::chrono::steady_clock::now();
_requests = 0;
_starting = false;
}
Job job;
job.clientData.bh = _bh;
job.height = _connected ? _height : 0;
job.powVersion = 1;
job.acceptedPatterns = {_currentPattern};
job.primeCountTarget = _currentPattern.size();
job.primeCountMin = job.primeCountTarget;
job.difficulty = _difficulty;
job.target = job.clientData.bh.target(job.powVersion);
if (!dummy) _requests++;
return job;
}
20 changes: 1 addition & 19 deletions Client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class BMClient : public Client {
public:
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.
void process();
Job getJob(const bool = false); // Dummy boolean to avoid prevent the block timer of Benchmark and Test Clients from starting when the miner initializes.
Job getJob(const bool = false); // Dummy boolean to prevent the block timer of the Benchmark Client from starting when the miner initializes.
uint32_t currentHeight() const {return _height;}
double currentDifficulty() const {return _difficulty;}
};
Expand All @@ -203,22 +203,4 @@ class SearchClient : public Client {
double currentDifficulty() const {return _difficulty;}
};

// Simulates various network situations to test/debug code.
class TestClient : public NetworkedClient { // Actually not networked, but behaves like one
BlockHeader _bh;
uint32_t _height, _difficulty, _requests, _timeBeforeNextBlock;
std::vector<uint64_t> _currentPattern;
bool _starting; // Used to set the timer so the time taken to initialize the miner the first time not counted
std::chrono::time_point<std::chrono::steady_clock> _timer;

bool _fetchWork();
public:
TestClient() : _height(1), _difficulty(1600), _requests(0), _timeBeforeNextBlock(10), _currentPattern{0, 2, 4, 2, 4} {}
void connect();
void process();
Job getJob(const bool = false);
uint32_t currentHeight() const {return _connected ? _height : 0;};
double currentDifficulty() const {return _difficulty;};
};

#endif
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ staticAVX2: LIBS := -Wl,-Bstatic -static-libstdc++ -L libs/ $(LIBS) -Wl,-Bdyna
staticAVX2: rieMinerAVX2
endif

testServer: rieMinerTestServer

rieMinerTestServer: TestServer.cpp
$(CXX) -Wall -Wextra -std=c++20 $^ -o $@

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
$(CXX) $(CFLAGS) -o rieMiner $^ $(LIBS)

Expand Down
2 changes: 1 addition & 1 deletion Miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ void Miner::init(const MinerParameters &minerParameters) {

uint32_t bitsForOffset;
// The primorial times the maximum factor should be smaller than the allowed limit for the target offset.
if (_mode == "Solo" || _mode == "Pool" || _mode == "Test")
if (_mode == "Solo" || _mode == "Pool")
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
else if (_mode == "Search")
bitsForOffset = std::floor(_difficultyAtInit - 97.); // 1 . leading 16 bits . random 80 bits . remaining bits for the offset
Expand Down
30 changes: 14 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,20 +183,7 @@ rieMiner proposes the following Modes depending on what you want to do. Use the
* `Solo`: solo mining via GetBlockTemplate;
* `Pool`: pooled mining using Stratum;
* `Benchmark`: test performance with a simulated and deterministic network (use this to compare different settings or share your benchmark results);
* `Search`: pure prime constellation search (useful for record attempts);
* `Test`: simulates various network situations for testing, see below.

#### Test Mode

It does the following:

* Start at Difficulty 1600, the first time with constellation pattern 0, 2, 4, 2, 4;
* Increases Difficulty by 10 every 10 s two times;
* After 10 more seconds, sets Difficulty to 1200 and the constellation pattern to 0, 2, 4, 2, 4, 6, 2;
* 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);
* The miner restarts several times due to the Difficulty variation (this adjusts some parameters if not set);
* When the Difficulty reaches 1040, a disconnect is simulated;
* 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.
* `Search`: pure prime constellation search (useful for record attempts).

### Solo and Pooled Mining options

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

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.

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.
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.

## Developers and license

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

### Testing

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

```bash
make testServer
./rieMinerTestServer
```

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...

### Quick contributor's checklist

* Your code must compile and work on recent Debian based distributions, and Windows using MSYS;
* 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;
* Document well non trivial contributions to the miner so other and future developers can understand easily and quickly the code;
* rieMiner must work for any realistic setting, the Test Mode must work as expected;
* rieMiner must work for any realistic setting, you should make tests with the Test Server;
* Ensure that your changes did not break anything, even if it compiles. Examples (if applicable):
* 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;
* Ensure that valid work is produced (pools and Riecoin Core must not reject submissions);
Expand Down
195 changes: 195 additions & 0 deletions TestServer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// (c) 2021 Pttn (https://github.com/Pttn/rieMiner)

#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <netinet/in.h>
#include <sstream>
#include <thread>
#include <unistd.h>
#include <vector>

constexpr uint16_t port(3004);

int serverFd, clientFd;
sockaddr_in address;
socklen_t addressLength(sizeof(address));

constexpr uint16_t maxMessageSize(256);
char buffer[256] = {0};
std::string receiveMessage() {
/*int bytesRead(0);
bytesRead = */read(clientFd, buffer, 256);
// std::cout << "Got " << bytesRead << " bytes from rieMiner: " << buffer << std::endl;
return buffer;
}

void sendMessage(const std::string &message) {
// std::cout << "Sending " << message.size() << " bytes: " << message << std::endl;
send(clientFd , message.c_str(), message.size(), 0);
}

void acceptMiner() {
if ((clientFd = accept(serverFd, reinterpret_cast<sockaddr*>(&address), &addressLength)) < 0) {
std::cerr << "Could not accept connection" << std::endl;
exit(-1);
}
receiveMessage();
sendMessage("{\"id\": 0, \"result\": [[[\"mining.notify\", \"00\"]], \"00\", 1], \"error\": null}\n");
receiveMessage();
sendMessage("{\"id\": 0, \"result\": true, \"error\": null}\n");
}

template <class C> std::string formatContainer(const C& container) {
std::ostringstream oss;
for (auto it(container.begin()) ; it < container.end() ; it++) {
oss << *it;
if (it != container.end() - 1) oss << ", ";
}
return oss.str();
}

uint64_t timestampNow() {
const auto now(std::chrono::system_clock::now());
return std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
}

std::string formattedHeight(const uint32_t height) {
std::ostringstream oss;
if (height < 128)
oss << "01" << std::setfill('0') << std::setw(2) << std::hex << height;
else if (height < 32768)
oss << "02" << std::setfill('0') << std::setw(2) << std::hex << height % 256 << std::setfill('0') << std::setw(2) << std::hex << (height/256) % 256;
else
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;
return oss.str();
}
std::string generateMiningNotify(const uint32_t height, const uint32_t nBits, const std::vector<std::vector<uint64_t>> acceptedPatterns, const bool cleanJobs) {
uint64_t currentJobId(0);
std::ostringstream oss;
oss << "{\"id\": null, \"method\": \"mining.notify\", \"params\": [\"";
oss << std::hex << currentJobId++ << "\"";
oss << ", \"0000000000000000000000000000000000000000000000000000000000000000\"";
oss << ", \"000000000000000000000000000000000000000000000000000000000000000000000000000000000000" << formattedHeight(height) << "\"";
oss << ", \"\"";
oss << ", []";
oss << ", \"20000000\"";
oss << ", \"" << std::setfill('0') << std::setw(8) << std::hex << nBits << "\"";
oss << ", \"" << std::setfill('0') << std::setw(8) << std::hex << timestampNow() << "\"";
oss << ", " << (cleanJobs ? "true" : "false");
oss << ", 1";
oss << ", [";
for (uint64_t i(0) ; i < acceptedPatterns.size() ; i++) {
oss << "[" << formatContainer(acceptedPatterns[i]) << "]";
if (i + 1 != acceptedPatterns.size())
oss << ", ";
}
oss << "]]}\n";
return oss.str();
}

int main() {
std::cout << "rieMiner Test Server" << std::endl;
std::cout << "-----------------------------------------------------------" << std::endl;
if ((serverFd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
std::cerr << "Could not get a File Descriptor for the Server" << std::endl;
exit(-1);
}
int optval(1);
if (setsockopt(serverFd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &optval, sizeof(decltype(optval))) != 0) {
std::cerr << "Setsockopt could not set SO_REUSEADDR | SO_REUSEPORT" << std::endl;
exit(-1);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(serverFd, reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0) {
std::cerr << "Could not bind" << std::endl;
exit(-1);
}
if (listen(serverFd, 1) < 0) {
std::cerr << "Could not listen" << std::endl;
exit(-1);
}

std::cout << "Please start rieMiner in Pool Mode with Port " << port << std::endl;
uint32_t height(1U);
while (true) {
std::cout << "Test 1: connection to a Pool" << std::endl;
std::cout << "Expected behavior: rieMiner initializes the miner and starts mining" << std::endl;
acceptMiner();
uint32_t difficulty(768U);
std::cout << "-----------------------------------------------------------" << std::endl;
std::cout << "Test 2: Varying Difficulty" << std::endl;
std::cout << "Expected behavior: rieMiner finds shares and restarts correctly in reaction to the increasing and decreasing Difficulty" << std::endl;
for ( ; difficulty < 896 ; difficulty += 8) {
std::cout << "Current Difficulty: " << difficulty << " (Block " << height << ")" << std::endl;
sendMessage(generateMiningNotify(height++, difficulty*256, {{0, 4, 2, 4, 2}, {0, 2, 4, 2, 4}}, true));
for (uint16_t i(0) ; i < 8 ; i++) {
receiveMessage();
sendMessage("{\"id\": 0, \"result\": true, \"error\": null}\n");
}
}
for ( ; difficulty > 640 ; difficulty -= 16) {
std::cout << "Current Difficulty: " << difficulty << " (Block " << height << ")" << std::endl;
sendMessage(generateMiningNotify(height++, difficulty*256, {{0, 4, 2, 4, 2}, {0, 2, 4, 2, 4}}, true));
for (uint16_t i(0) ; i < 8 ; i++) {
receiveMessage();
sendMessage("{\"id\": 0, \"result\": true, \"error\": null}\n");
}
}
std::cout << "-----------------------------------------------------------" << std::endl;
std::cout << "Test 3: Disconnects" << std::endl;
std::cout << "Expected behavior: rieMiner gets disconnected several times, but always reconnects and resumes mining properly" << std::endl;
std::cout << "8 disconnections after 5 s" << std::endl;
for (uint16_t i(0) ; i < 8 ; i++) {
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "Disconnect " << i << std::endl;
close(clientFd);
acceptMiner();
std::cout << "Reconnected" << std::endl;
sendMessage(generateMiningNotify(height, difficulty*256, {{0, 4, 2, 4, 2}, {0, 2, 4, 2, 4}}, true));
}
std::cout << "4 disconnections after 15 s" << std::endl;
for (uint16_t i(0) ; i < 4 ; i++) {
std::this_thread::sleep_for(std::chrono::seconds(15));
std::cout << "Disconnect " << i << std::endl;
close(clientFd);
acceptMiner();
std::cout << "Reconnected" << std::endl;
sendMessage(generateMiningNotify(height, difficulty*256, {{0, 4, 2, 4, 2}, {0, 2, 4, 2, 4}}, true));
}
std::cout << "2 disconnections after 30 s" << std::endl;
for (uint16_t i(0) ; i < 2 ; i++) {
std::this_thread::sleep_for(std::chrono::seconds(30));
std::cout << "Disconnect " << i << std::endl;
close(clientFd);
acceptMiner();
std::cout << "Reconnected" << std::endl;
sendMessage(generateMiningNotify(height, difficulty*256, {{0, 4, 2, 4, 2}, {0, 2, 4, 2, 4}}, true));
}
std::cout << "-----------------------------------------------------------" << std::endl;
std::cout << "Test 4: Constellation Pattern Change/Hard Fork Simulation" << std::endl;
std::cout << "Expected behavior: rieMiner finds shares and restarts correctly in reaction to the Pattern or Difficulty Change" << std::endl;
difficulty = 540;
sendMessage(generateMiningNotify(height++, difficulty*256, {{0, 2, 4, 2, 4, 6, 2}, {0, 2, 6, 4, 2, 4, 2}}, true));
std::cout << "rieMiner should now mine 7-tuples. Current Difficulty: " << difficulty << " (Block " << height << ")" << std::endl;
for (uint16_t i(0) ; i < 64 ; i++) {
receiveMessage();
sendMessage("{\"id\": 0, \"result\": true, \"error\": null}\n");
}
difficulty = 480;
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));
std::cout << "rieMiner should now mine 8-tuples. Current Difficulty: " << difficulty << " (Block " << height << ")" << std::endl;
for (uint16_t i(0) ; i < 8 ; i++) {
receiveMessage();
sendMessage("{\"id\": 0, \"result\": true, \"error\": null}\n");
}
std::cout << "Test loop finished." << std::endl;
close(clientFd);
std::cout << "-----------------------------------------------------------" << std::endl;
}
return 0;
}
6 changes: 1 addition & 5 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ bool Configuration::parse(const int argc, char** argv) {
catch (...) {_options.debug = 0;}
}
else if (key == "Mode") {
if (value == "Solo" || value == "Pool" || value == "Benchmark" || value == "Search" || value == "Test")
if (value == "Solo" || value == "Pool" || value == "Benchmark" || value == "Search")
_options.mode = value;
else std::cout << "Invalid mode!" << std::endl;
}
Expand Down Expand Up @@ -221,8 +221,6 @@ bool Configuration::parse(const int argc, char** argv) {
if (_options.minerParameters.pattern.size() == 0) // Pick a default pattern if none was chosen
_options.minerParameters.pattern = {0, 2, 4, 2, 4, 6, 2};
}
else if (_options.mode == "Test")
std::cout << "Test Mode" << std::endl;
else {
if (_options.mode == "Solo") std::cout << "Solo mining";
else if (_options.mode == "Pool") std::cout << "Pooled mining";
Expand Down Expand Up @@ -325,8 +323,6 @@ int main(int argc, char** argv) {
client = std::make_shared<StratumClient>(configuration.options());
else if (configuration.options().mode == "Search")
client = std::make_shared<SearchClient>(configuration.options());
else if (configuration.options().mode == "Test")
client = std::make_shared<TestClient>();
else
client = std::make_shared<BMClient>(configuration.options());
miner->setClient(client);
Expand Down

0 comments on commit 565db1a

Please sign in to comment.