Skip to content

Instantly share code, notes, and snippets.

@ismaelsadeeq
Last active October 29, 2024 15:44
Show Gist options
  • Save ismaelsadeeq/48e7f704ccf481bd5ff5ec5328f033a4 to your computer and use it in GitHub Desktop.
Save ismaelsadeeq/48e7f704ccf481bd5ff5ec5328f033a4 to your computer and use it in GitHub Desktop.
This code snippet is used to bench Bitcoin core `get_block` RPC for various verbosity level using sequential and thread pool strategy
#include <algorithm>
#include <chrono>
#include <curl/curl.h>
#include <future>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <numeric>
#include <queue>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
#include "json.hpp"
using json = nlohmann::json;
// Configuration
constexpr int START_BLOCK = 840000;
constexpr int BLOCKS_TO_FETCH = 1000;
constexpr int BATCH_SIZE = 2;
constexpr int ITERATIONS = 3;
struct Config {
std::string url;
std::string auth;
};
// Benchmark result structure
struct BenchmarkResult {
std::string method;
int verbosity;
std::vector<double> times;
double mean() const {
return std::accumulate(times.begin(), times.end(), 0.0) / times.size();
}
double stddev() const {
if (times.size() <= 1) return 0.0;
double m = mean();
double sq_sum = std::accumulate(times.begin(), times.end(), 0.0,
[m](double acc, double x) { return acc + (x - m) * (x - m); });
return std::sqrt(sq_sum / (times.size() - 1));
}
};
// Helper function for curl write callback
size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) {
userp->append((char*)contents, size * nmemb);
return size * nmemb;
}
// RPC request class
class RPCRequest {
private:
CURL* curl;
std::string response;
struct curl_slist* headers;
Config config;
public:
RPCRequest(const Config& cfg) : config(cfg) {
curl = curl_easy_init();
headers = curl_slist_append(nullptr, "content-type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, config.url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_USERPWD, config.auth.c_str());
}
~RPCRequest() {
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
json makeRequest(const json& payload) {
std::string data = payload.dump();
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
response.clear();
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
throw std::runtime_error(curl_easy_strerror(res));
}
return json::parse(response)["result"];
}
CURL* getCurl() { return curl; }
};
class BlockFetcher {
protected:
int num_threads;
Config config;
public:
BlockFetcher(const Config& cfg) : num_threads(std::thread::hardware_concurrency()), config(cfg) {}
virtual ~BlockFetcher() = default;
virtual double fetchBlocks(int start_block, int num_blocks, int verbosity) = 0;
std::string getBlockHash(RPCRequest& rpc, int block_number) {
json payload = {
{"method", "getblockhash"},
{"params", {block_number}},
{"id", 1}
};
return rpc.makeRequest(payload);
}
json getBlock(RPCRequest& rpc, const std::string& hash, int verbosity) {
json payload = {
{"method", "getblock"},
{"params", {hash, verbosity}},
{"id", 1}
};
return rpc.makeRequest(payload);
}
};
class SequentialFetcher : public BlockFetcher {
public:
using BlockFetcher::BlockFetcher;
double fetchBlocks(int start_block, int num_blocks, int verbosity) override {
auto start_time = std::chrono::high_resolution_clock::now();
RPCRequest rpc(config);
for (int i = 0; i < num_blocks; ++i) {
auto hash = getBlockHash(rpc, start_block + i);
std::cout << " Sequential Getting info for block " << start_block + i << std::endl;
auto block = getBlock(rpc, hash, verbosity);
}
auto end_time = std::chrono::high_resolution_clock::now();
return std::chrono::duration<double>(end_time - start_time).count();
}
};
class ThreadPoolFetcher : public BlockFetcher {
public:
using BlockFetcher::BlockFetcher;
double fetchBlocks(int start_block, int num_blocks, int verbosity) override {
auto start_time = std::chrono::high_resolution_clock::now();
std::vector<std::future<void>> futures;
for (int i = 0; i < num_threads; ++i) {
futures.push_back(std::async(std::launch::async, [&, i]() {
RPCRequest rpc(config);
for (int j = i; j < num_blocks; j += num_threads) {
auto hash = getBlockHash(rpc, start_block + j);
std::cout << "thread approach getting block for height " << start_block + j << std::endl;
auto block = getBlock(rpc, hash, verbosity);
}
}));
}
for (auto& future : futures) {
future.wait();
}
auto end_time = std::chrono::high_resolution_clock::now();
return std::chrono::duration<double>(end_time - start_time).count();
}
};
void runBenchmark() {
Config config{"http://127.0.0.1:8332", "abubakar:sadeeq"};
SequentialFetcher sequential(config);
ThreadPoolFetcher threadPool(config);
std::vector<std::pair<std::string, BlockFetcher*>> strategies = {
{"Sequential", &sequential},
{"Thread Pool", &threadPool},
};
for (int verbosity = 1; verbosity <= 3; ++verbosity) {
std::cout << "Verbosity: " << verbosity << std::endl;
for (const auto& [name, fetcher] : strategies) {
std::cout << "Strategy: " << name << std::endl;
std::vector<double> times;
for (int iteration = 0; iteration < ITERATIONS; ++iteration) {
double time = fetcher->fetchBlocks(START_BLOCK, BLOCKS_TO_FETCH, verbosity);
times.push_back(time);
std::cout << "Iteration " << iteration + 1 << ": " << time << " seconds" << std::endl;
}
double mean = std::accumulate(times.begin(), times.end(), 0.0) / times.size();
double sq_sum = std::inner_product(times.begin(), times.end(), times.begin(), 0.0);
double stdev = std::sqrt(sq_sum / times.size() - mean * mean);
std::cout << "Mean time: " << mean << " seconds" << std::endl;
std::cout << "Standard deviation: " << stdev << " seconds" << std::endl;
}
}
}
int main() {
try {
runBenchmark();
} catch (const std::exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment