1 // ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm.
2 // Copyright 2018 Pawel Bylica.
3 // Licensed under the Apache License, Version 2.0. See the LICENSE file.
4 
5 #include <ethash/ethash.hpp>
6 
7 #include <atomic>
8 #include <chrono>
9 #include <future>
10 #include <iomanip>
11 #include <iostream>
12 #include <string>
13 #include <unordered_map>
14 #include <vector>
15 
16 
17 using namespace std::chrono;
18 using timer = std::chrono::steady_clock;
19 
20 namespace
21 {
22 class ethash_interface
23 {
24 public:
25     virtual ~ethash_interface() noexcept = default;
26 
27     virtual void search(const ethash::hash256& header_hash, uint64_t nonce, size_t iterations) const
28         noexcept = 0;
29 };
30 
31 class ethash_light : public ethash_interface
32 {
33     const ethash::epoch_context& context;
34 
35 public:
ethash_light(int epoch_number)36     explicit ethash_light(int epoch_number)
37       : context(ethash::get_global_epoch_context(epoch_number))
38     {}
39 
search(const ethash::hash256 & header_hash,uint64_t nonce,size_t iterations) const40     void search(const ethash::hash256& header_hash, uint64_t nonce, size_t iterations) const
41         noexcept override
42     {
43         ethash::search_light(context, header_hash, {}, nonce, iterations);
44     }
45 };
46 
47 class ethash_full : public ethash_interface
48 {
49     const ethash::epoch_context_full& context;
50 
51 public:
ethash_full(int epoch_number)52     explicit ethash_full(int epoch_number)
53       : context(ethash::get_global_epoch_context_full(epoch_number))
54     {}
55 
search(const ethash::hash256 & header_hash,uint64_t nonce,size_t iterations) const56     void search(const ethash::hash256& header_hash, uint64_t nonce, size_t iterations) const
57         noexcept override
58     {
59         ethash::search(context, header_hash, {}, nonce, iterations);
60     }
61 };
62 
63 
64 std::atomic<int> shared_block_number{0};
65 std::atomic<int> num_hashes{0};
66 
worker(bool light,const ethash::hash256 & header_hash,uint64_t start_nonce,int batch_size)67 void worker(bool light, const ethash::hash256& header_hash, uint64_t start_nonce, int batch_size)
68 {
69     int current_epoch = -1;
70     std::unique_ptr<ethash_interface> ei;
71     uint64_t i = 0;
72     size_t w = static_cast<size_t>(batch_size);
73     while (true)
74     {
75         int block_number = shared_block_number.load(std::memory_order_relaxed);
76         if (block_number < 0)
77             break;
78 
79         int e = ethash::get_epoch_number(block_number);
80 
81         if (current_epoch != e)
82         {
83             ei.reset(
84                 light ? static_cast<ethash_interface*>(new ethash_light{e}) : new ethash_full{e});
85             current_epoch = e;
86         }
87 
88         ei->search(header_hash, start_nonce + i, w);
89         num_hashes.fetch_add(batch_size, std::memory_order_relaxed);
90         i += w;
91     }
92 }
93 }  // namespace
94 
main(int argc,const char * argv[])95 int main(int argc, const char* argv[])
96 {
97     int num_blocks = 10;
98     int start_block_number = 0;
99     int block_time = 6;
100     int work_size = 100;
101     int num_threads = static_cast<int>(std::thread::hardware_concurrency());
102     uint64_t start_nonce = 0;
103     bool light = false;
104 
105     for (int i = 0; i < argc; ++i)
106     {
107         const std::string arg{argv[i]};
108 
109         if (arg == "--light")
110             light = true;
111         else if (arg == "-i" && i + 1 < argc)
112             num_blocks = std::stoi(argv[++i]);
113         else if (arg == "-b" && i + 1 < argc)
114             start_block_number = std::stoi(argv[++i]);
115         else if (arg == "-t" && i + 1 < argc)
116             num_threads = std::stoi(argv[++i]);
117         else if (arg == "-n" && i + 1 < argc)
118             start_nonce = std::stoul(argv[++i]);
119     }
120 
121     auto flags = std::cout.flags();
122     std::cout << std::fixed << std::setprecision(2);
123 
124     // clang-format off
125     std::cout << "Fakeminer Benchmark\n\nParameters:"
126               << "\n  dataset:     " << (light ? "light" : "full")
127               << "\n  threads:     " << num_threads
128               << "\n  blocks:      " << num_blocks
129               << "\n  block time:  " << block_time
130               << "\n  batch size:  " << work_size
131               << "\n  start nonce: " << start_nonce
132               << "\n\n";
133     // clang-format on
134 
135     const uint64_t divisor = static_cast<uint64_t>(num_threads);
136     const uint64_t nonce_space_per_thread = std::numeric_limits<uint64_t>::max() / divisor;
137 
138     const ethash::hash256 header_hash{};
139 
140     shared_block_number.store(start_block_number, std::memory_order_relaxed);
141     std::vector<std::future<void>> futures;
142 
143     for (int t = 0; t < num_threads; ++t)
144     {
145         futures.emplace_back(
146             std::async(std::launch::async, worker, light, header_hash, start_nonce, work_size));
147         start_nonce += nonce_space_per_thread;
148     }
149 
150     std::cout << "Progress:\n"
151               << "                  |-----    hashrate    -----|  |-----    bandwidth   -----|\n"
152               << "  epoch    block        current       average         current       average\n";
153 
154     int all_hashes = 0;
155     auto start_time = timer::now();
156     auto time = start_time;
157     static constexpr int khps_mbps_ratio =
158         ethash::num_dataset_accesses * ethash::full_dataset_item_size / 1024;
159 
160     double current_duration = 0;
161     double all_duration = 0;
162     double current_khps = 0;
163     double average_khps = 0;
164     double current_bandwidth = 0;
165     double average_bandwidth = 0;
166 
167     const milliseconds block_time_ms{block_time * 1000};
168     milliseconds sleep_time = block_time_ms;
169     const int end_block_number = start_block_number + num_blocks;
170     for (int block_number = start_block_number; block_number < end_block_number; ++block_number)
171     {
172         std::this_thread::sleep_for(sleep_time);
173         int current_hashes = num_hashes.exchange(0, std::memory_order_relaxed);
174         all_hashes += current_hashes;
175 
176         auto now = timer::now();
177         current_duration = double(duration_cast<milliseconds>(now - time).count());
178         all_duration = double(duration_cast<milliseconds>(now - start_time).count());
179         time = now;
180 
181         shared_block_number.store(block_number + 1, std::memory_order_relaxed);
182 
183         int e = ethash::get_epoch_number(block_number);
184 
185         current_khps = double(current_hashes) / current_duration;
186         average_khps = double(all_hashes) / all_duration;
187         current_bandwidth = double(current_hashes * khps_mbps_ratio) / 1024 / current_duration;
188         average_bandwidth = double(all_hashes * khps_mbps_ratio) / 1024 / all_duration;
189 
190         std::cout << std::setw(7) << e << std::setw(9) << block_number << std::setw(10)
191                   << current_khps << " kh/s" << std::setw(9) << average_khps << " kh/s"
192                   << std::setw(10) << current_bandwidth << " GiB/s" << std::setw(8)
193                   << average_bandwidth << " GiB/s\n";
194 
195         sleep_time = block_time_ms - duration_cast<milliseconds>(timer::now() - now);
196     }
197 
198     shared_block_number.store(-1, std::memory_order_relaxed);
199     for (auto& future : futures)
200         future.wait();
201 
202     auto total_seconds = all_duration / 1000;
203 
204     std::cout << "\nSummary:\n  time:                     " << std::setw(7) << total_seconds
205               << " s\n  latest hashrate:          " << std::setw(7) << current_khps
206               << " kh/s\n  average hashrate:         " << std::setw(7) << average_khps
207               << " kh/s\n  latest memory bandwitdh:  " << std::setw(7) << current_bandwidth
208               << " GiB/s\n  average memory bandwitdh: " << std::setw(7) << average_bandwidth
209               << " GiB/s\n";
210 
211     std::cout.flags(flags);
212     return 0;
213 }
214