Время на прочтение
11 мин
Количество просмотров 27K
Не знаю кому как, а меня прошедший 2017 год шокировал стремительным взлетом биткоина. Сейчас, конечно, ажиотаж уже ушел, а в 17-м году про криптовалюты говорили и писали все кому не лень.
Я видел, что люди пытаются зарабатывать на криптовалютах. Кто как умеет. Кто-то на все сбережения скупал видеокарты и начинал самостоятельно майнить в гараже. Кто-то вкладывался в облачный майнинг. Кто-то пытается организовать свой пул. Кто-то запустил в производство шоколадные биткоины, а кто-то выпускает минеральную воду:
Я тоже стал изучать, что же такое эти самые биткоины. Когда-то я даже начал свое собственное иследование алгоритма SHA256 и написал статью здесь на хабре «Можно ли вычислять биткоины быстрее, проще или легче?». Мои исследования алгоритмов хеширования до сих пор продолжаются и еще и близко не завершены… Может быть когда нибудь напишу про это отдельную статью. А сейчас пока вот это..
Я попробовал запустить bitcoin майнер в FPGA. Я понимал, что время уже ушло, но хотелось все же прикоснуться к технологии. Уже в конце прошлого года я вдруг почему-то вспомнил, что у меня совершенно без дела лежит плата Terasic DE10-Standard с ПЛИС Intel Cyclone V 5CSXFC6D6F31C6 — это тот чип, который со встроенным процессором ARM. Я подумал, что было бы интересно запустить какой нибудь альткоин майнер в этой плате. А что? Инвестировать в оборудование мне уже не надо, оно и так есть. Главное, чтобы плата зарабатывала больше, чем потребляет энергии.
Поиск подходящего альткоина был весьма прост. Я искал готовые проекты для FPGA, которые я смогу адаптировать под свою плату. Таковых оказалось не очень много. На самом деле как я понимаю во всем мире есть всего несколько человек, которые делали FPGA проекты и главное публиковали их в открытом доступе, например, на github.
Таким образом, я взял проект github.com/kramble/FPGA-Blakecoin-Miner и адаптировал его под имеющуюся у меня плату Марсоход3, а так же адаптировал этот проект для DE10-Standard.
Собственно о том, как я адаптировал проект для платы Марсоход3 написано здесь. Для Cyclone V в принципе все то же самое — только ревизия проекта квартуса blake_cv, мои исходники вот.
К моему сожалению в имеющийся у меня Cyclone V помещается только три хэш функции blake.
Чуть-чуть не хватает емкости ПЛИС до четырех хэшеров. Я запускаю проект на частоте 120МГц и за один такт рабочей частоты вычисляется один хэш blake. Значит производительность моего проекта 120*3=360MH/sec. Не очень много честно говоря, однако, как я уже сказал, плата у меня уже была, и возвращаеть ее стоимость мне не нужно… Тут еще Quartus говорит, что Fmax=150MHz. Можно попытаться поднять частоту, но боюсь придется ставить кулер, будет гудеть — ну не на столько мне нужны эти крипты, чтоб еще гул в комнате слушать.
Общая задумка проекта такая: плата имеет микросхему у которой есть и ПЛИС и Dual-ARM:
Когда плата стартует, то из U-BOOT первым делом загружается ПЛИС, затем стартует Linux и в нем программа майнинга cgminer. Я сперва думал, что я смогу устроить виртуальный канал связи между ARM и FPGA, и это на самом деле возможно, но так не получилось. Дело в том, что программа майнера cgminer работает с аппаратными майнерами через USB и использует библиотеку libusb. То есть мне проще подключить ПЛИС к Linux системе через преобразователь USB-COM на FTDI, чем городить городушку соединяя ПЛИС на шину ARMа. Я таким уже как-то занимался и это было не очень просто.
Сейчас мой «майнер» выглядит вот так (на Cyclone V поставил радиатор на термопасте, а то сильно греется):
Сказать по правде основные проблемы у меня как раз возникли не с FPGA проектом, а с cgminer.
Проблемы следующие:
1) Какой cgminer брать за основу своей разработки? И связанный с этим вопрос «Куда подключаться, чтобы начать майнить?». А какая связь между этими вопросами? Казалось бы, где тут проблема — бери самый свежий cgminer, какой найдешь. Но позвольте: на github есть 98 форков программы cgminer. Все они чем-то отличаются, какой есть хороший, а какой плохой, какой есть вообще хотя бы рабочий? Вот вам и опенсоурс. Каждый автор чего-то там себе добавлял и исправлял, или ломал… или делал свою монету. Разобраться не просто. Нашел для себя сайт, где на одной странице есть ссылка и на github проект и на github проект для FPGA. То есть эти два проекта видимо как-то могут и должны пересекаться.
2) Поскольку я взял за основу FPGA проект от автора kramble, то на самом деле, конечно, логично было бы взять его патчи, которые он приложил к своему проекту. Но и тут не без проблем. У него есть патчи к программе cgminer-3.1.1 и cgminer-3.4.3. Я решил, что лучше брать ту, что новее 3.4.3, но только потерял с ней время. Похоже автор начал адаптировать для этой версии, но что-то там не довел до конца и эта версия совсем сырая. Пришлось брать 3.1.1 а это кажется вообще старючая версия.
3) Авторы изменяющие программу cgminer в своих форках для своих альткоинов не следят за правильностью комментариев и именованием функций в коде. Зачастую в коде тут и там встречается слово bitcoin, а сам этот форк cgminer-а уже кажется не может считать для биткоина, а может только в альткоин.
4) Тесты. ГДЕ ТЕСТЫ? Я чего-то не понимаю, как можно делать сложный продукт без тестов? Я их не нашел.
Сказать по правде даже начинать что-то делать было не просто. Представьте себе, что нужно запустить некоторый проект в FPGA, но не очень понятно, что он должен делать, как получать данные, какие данные и в каком виде нужно выдавать результат. К этому FPGA проекту должна прилагаться некоторая программа, которую не известно точно где взять, но она должна обнаружить плату майнера, что-то туда посылать (неизвестно что) и что-то из нее получать. В каком формате, какими блоками, как часто — ничего не известно.
На самом деле, изучая патчи cgminer от kramble я примерно представляю себе как оно должно работать.
В файле usbutils.c прописаны устройства, которые могут рассматриваться как аппаратные внешние майнеры на шине USB:
static struct usb_find_devices find_dev[] = {
#ifdef USE_BFLSC
{
.drv = DRV_BFLSC,
.name = "BAS",
.ident = IDENT_BAS,
.idVendor = IDVENDOR_FTDI,
.idProduct = 0x6014,
//.iManufacturer = "Butterfly Labs",
.iProduct = "BitFORCE SHA256 SC",
.kernel = 0,
.config = 1,
.interface = 0,
.timeout = BFLSC_TIMEOUT_MS,
.latency = LATENCY_STD,
.epcount = ARRAY_SIZE(bas_eps),
.eps = bas_eps },
#endif
...
{
.drv = DRV_ICARUS,
.name = "BLT",
.ident = IDENT_BLT,
.idVendor = IDVENDOR_FTDI,
.idProduct = 0x6010,
//.iProduct = "Dual RS232-HS",
.iProduct = "USB <-> Serial Cable",
.kernel = 0,
.config = 1,
.interface = 1,
.timeout = ICARUS_TIMEOUT_MS,
.latency = LATENCY_STD,
.epcount = ARRAY_SIZE(ftdi2232h_eps),
.eps = ftdi2232h_eps },
Я в эту структуру добавил описатель своего USB-to-COM преобразователя FTDI-2232H. Теперь, если cgminer обнаружит устройство с VendorId/DeviceId = 0x0403:0x6010, то он попробует работать с этим устройством, как с платой Icarus, хоть она таковой и не является.
Дальше смотрим файл driver-icarus.c и тут есть функция icarus_detect_one:
static bool icarus_detect_one(struct libusb_device *dev, struct usb_find_devices *found)
{
int this_option_offset = ++option_offset;
struct ICARUS_INFO *info;
struct timeval tv_start, tv_finish;
/* Blakecoin detection hash
N.B. golden_ob MUST take less time to calculate than the timeout set in icarus_open()
0000007002685447273026edebf62cf5e17454f35cc7b1f2da57caeb008cf4fb00000000dad683f2975c7e00a8088275099c69a3c589916aaa9c7c2501d136c1bf78422d5256fbaa1c01d9d1b48b4600000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
{ midstate, data } = { 256'h553bf521cf6f816d21b2e3c660f29469f8b6ae935291176ef5dda6fe442ca6e4, 96'hd1d9011caafb56522d4278bf };
*/
const char golden_ob[] =
// "553bf521cf6f816d21b2e3c660f29469"
// "f8b6ae935291176ef5dda6fe442ca6e4"
// "00000000000000000000000000000000"
// "00000000d1d9011caafb56522d4278bf";
//-----------
"a8c369073d7dc0a63168f5fcf0246e4f"
"eb916bda12787ad1607d2303186ed8f1"
"00000000000000000000000000000000"
"0142b9a0e7b4001cf8b35852a3accab0";
const char golden_nonce[] = "0142b9b1"; //"000187a2";
const uint32_t golden_nonce_val = 0x0142b9b1; //0x000187a2;
unsigned char ob_bin[64];
unsigned char nonce_bin[ICARUS_READ_SIZE];
char *nonce_hex;
int baud, uninitialised_var(work_division), uninitialised_var(fpga_count);
struct cgpu_info *icarus;
int ret, err, amount, tries;
bool ok;
char tmpbuf[256]; //lancelot52
unsigned char* wr_buf = ob_bin;
int bufLen = sizeof(ob_bin);
icarus = usb_alloc_cgpu(&icarus_drv, 1);
if (!usb_init(icarus, dev, found))
goto shin;
usb_buffer_enable(icarus);
get_options(this_option_offset, icarus, &baud, &work_division, &fpga_count);
hex2bin(ob_bin, golden_ob, sizeof(ob_bin));
tries = 2;
ok = false;
while (!ok && tries-- > 0) {
icarus_initialise(icarus, baud);
err = usb_write_ica(icarus, (char *)wr_buf, bufLen, &amount, C_SENDTESTWORK);
if (err != LIBUSB_SUCCESS || amount != bufLen)
continue;
memset(nonce_bin, 0, sizeof(nonce_bin));
ret = icarus_get_nonce(icarus, nonce_bin, &tv_start, &tv_finish, NULL, 500);
Смысл такой. Программа передает плате заведомо известное задание на поиск хэша, причем в задании сказано с какого нонсе начинать вычисление и это нонсе немного меньше настоящего GOLDEN nonce. Таким образом, плата начнет считать с указанного места и буквально сразу в считанные доли секунды наткнется на GOLDEN nonce и вернет его. Программа тут же получит этот результат, сравнит его с правильным ответом и сразу становится понятно — это действительно тот HW майнер с которым можно работать или нет.
И вот тут была ужасная проблема — в проекте есть патчи на языке C, есть тестовая программа на питоне и тестбенч для FPGA.
В патчах на C тестовые данные выглядят вот так:
1) патч для cgminer-3.1.1
const char golden_ob[] =
"553bf521cf6f816d21b2e3c660f29469"
"f8b6ae935291176ef5dda6fe442ca6e4"
"00000000000000000000000000000000"
"00000000d1d9011caafb56522d4278bf";
const char golden_nonce[] = "00468bb4";
const uint32_t golden_nonce_val = 0x00468bb4;
1) патч для cgminer-3.4.3
const char golden_ob[] =
"553bf521cf6f816d21b2e3c660f29469"
"f8b6ae935291176ef5dda6fe442ca6e4"
"00000000000000000000000000000000"
"00000000d1d9011caafb56522d4278bf";
const char golden_nonce[] = "000187a2";
const uint32_t golden_nonce_val = 0x000187a2;
И что тут правильно, а что нет? Исходные данные одинаковые, а golden nonce объявлен разным!!! Парадокс… (заранее скажу, что в патче для cgminer-3.4.3 ошибка — нонсе 0x000187a2 не верный, а сколько времени я на это потратил..)
В проекте есть тестовая программа на питоне, которая читает текстовый файл, извлекает из него данные и передает в плату через последовательный порт… Там тестовые данные вот такие:
0000007057711b0d70d8682bd9eace78d4d1b42f82da7d934fac0db4001124d600000000cfb48fb35e8c6798b32e0f08f1dc3b6819faf768e1b23cc4226b944113334cc45255cc1f1c085340967d6c0e000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
0000007057711b0d70d8682bd9eace78d4d1b42f82da7d934fac0db4001124d6000000008fa40da64f312f0fa4ad43e2075558faf4e6d910020709bb1f79d0fe94e0416f5255cc521c085340df6b6e01000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
0000007095696e4529ae6568e4b2a0057a18e82ccf8d370bf87e358900f8ab5000000000253c6078c7245036a36c8e25fb2c1f99c938aeb8fac0be157c3b2fe34da2fa0952587a471c00fa391d2e5b02000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
000000704445e0446fcf2a84c47ce7305722c76507ba74796eaf39fe0007d44d00000000cac961f63513134a82713b172f45c9b5e5eea25d63e27851fac443081f453de1525886fe1c01741184a5c70e000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
00000070a3ac7627ca52f2b9d9a5607ac8212674e50eb8c6fb1219c80061ccd500000000ed5222b4f77e0d1b434e1e1c70608bc5d8cd9d363a59cbeb890f6cd433a6bd8d5258a0141c00b4e770777200000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
000000706c90b789e84044d5be8b2fac01fafe3933ca3735269671e90043f8d900000000d74578c643ab8e267ab58bf117d61bb71a04960a10af9a649c0060cdb0caaca35258b3f81c00b4e7b1b94201000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
00000070171d2644781cccf873ce3b6e54967afda244c47fc963bb240141b4ad00000000d56c4fbdc326e8f672834c8dbca53a087147fe0996d0c3a908a860e3db0589665258da3d1c016a2a14603a0a000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
00000070d03c78cb0bb0b41a5a2c6ce75402e5be8a705a823928a5640011110400000000028fb80785a6310685f66a4e81e8f38800ea389df7f16cf2ffad16bb98e0c4855258dda01c016a2ae026d404000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
0000007091a7eef446c4cb686aff8908ab5539d03a9ab2e975b9fe5700ed4ca9000000000f83bb385440decc66c10c0657fcd05f94c0bc844ebc744bba25b5bc2a7a557b5258e27c1c016a2a6ce1900a000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
00000070856bd0a3fda5dac9ede45137e0c5648d82e64fbe72477f5300e96aec0000000026ca273dbbd919bdd13ba1fcac2106e1f63b70f1f5f5f068dd1da94491ed0aa45258e51b1c017a7644697709000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
Ну то есть совершенно другие! Потом я уже понял, что это не те даннае, что посылаются в плату, из этих только извлекаются данные, специальным образом конвертируются в задание и отсылаются в плату.
Но все равно, среди этих тестовых данных для программы на питоне НЕТ задания похожего на то, которое описано в программе на C!!!
Ну хорошо, тогда смотрю тестовую программу-тестбенч на verilog:
blakeminer #(.comm_clk_frequency(comm_clk_frequency)) uut
(clk, RxD, TxD, led, extminer_rxd, extminer_txd, dip, TMP_SCL, TMP_SDA, TMP_ALERT);
// TEST DATA (diff=1) NB target, nonce, data, midstate (shifted from the msb/left end) - GENESIS BLOCK
reg [415:0] data = 416'h000007ffffbd9207ffff001e11f35052d554469e3171e6831d493f45254964259bc31bade1b5bb1ae3c327bc54073d19f0ea633b;
// ALSO test starting at -1 and -2 nonce to check for timing issues
// reg [415:0] data = 416'h000007ffffbd9206ffff001e11f35052d554469e3171e6831d493f45254964259bc31bade1b5bb1ae3c327bc54073d19f0ea633b;
// reg [415:0] data = 416'h000007ffffbd9205ffff001e11f35052d554469e3171e6831d493f45254964259bc31bade1b5bb1ae3c327bc54073d19f0ea633b;
reg serial_send = 0;
wire serial_busy;
reg [31:0] data_32 = 0;
reg [31:0] start_cycle = 0;
serial_transmit #(.comm_clk_frequency(comm_clk_frequency), .baud_rate(baud_rate)) sertx (.clk(clk), .TxD(RxD), .send(serial_send), .busy(serial_busy), .word(data_32));
Здесь есть предполагаемый пакет данных, который плата должна принять. Но опять этот предполагаемый пакет данных никак не похож на пакет данных в программе на C или на данные для тестовой программы на питоне.
Вот это отсутствие общих тестовых данных для программы на питоне, C и Verilog очень сильно портит картину. Получается, что между компонентами как бы нет общих точек соприкосновения, общих тестов и это печально.
Вообще, в верилог проекте blakecoin майнера было скрыто еще одно форменное издевательство над моим организмом.
Если проводить симуляцию проекта с verilog тестбенчем, то в симуляторе с вот этими тестовыми данными 416’h000007ffffbd9207ffff001e11f35052d5544… замечательно находится и возвращается результат GOLDEN nonce.
Потом проект компилирую для реальной FPGA платы, эти же самые данные подаю из программы на питоне и… плата не находит GOLDEN nonce…
Оказывается, что тестовые данные в verilog тестбенче «немного плохие». Они для низкой сложности, когда в результирующем хэше всего 24 ведущих нуля, а не 32, как требуется.
В файле experimental/LX150-FourPiped/BLAKE_CORE_FOURPIPED.v есть вот такой код
reg gn_match_d = 1'b0;
always @(posedge clk)
`ifndef SIM
gn_match_d <= (IV7 ^ b76 ^ d74) == 0;
`else
gn_match_d <= (IV7[23:0] ^ b76[23:0] ^ d74[23:0]) == 0;
`endif
В Verilog симуляторе проверяется не так, как будет будет работать в железе! То есть для реальной FPGA платы будем проверять на 32 бита ведущих нулей, а в симуляции будем проверять только 24 бита. Это просто прелестно. Хочется побить автора.
Я конечно, все это победил. По крайней мере, тестовая программа на питоне выдает бодрые сообщения:
Ладно, что в результате? Сколько намайнил? К сожалению нисколько.
Как только я был уже готов начать майнить, буквально в конце января сложность блейка сильно возросла:
Теперь я мог оставить на сутки плату и она хоть и находила решения, но их не принимал пул — все еще мало ведущих нулей.
Я пробовал переключиться на другую валюту — VCASH. С этой валютой пул хотя бы иногда выдавал мне бодрящие сообщения вроде вот этого:
Но все равно и VCASH пул ничего не начисляет. Печаль-беда.
Пользуясь случаем хотел бы спросить у знающих людей. Вот у меня есть видеокарта Nvidia 1060. Она выдает 1,25GHash/sec на блейкоине и за час два-три раза выдает nonce, который принимает пул (и начисляет копеечку). Я думал, что если моя FPGA плата считает 360MHash/sec, ну то есть примерно в 3 раза хуже, чем видеокарта, то я за два часа получу хотя бы один нонсе принятый пулом. Однако, этого не происходит. Даже за сутки нет ни одной копеечки… Где тут подвох для меня так и осталось загадка…
Сейчас я на досуге пытаюсь понять можно ли как-то оптимизировать имеющийся FPGA проект, скажем задействовать встроенную память или еще что-то. Может быть, если повезет, что-то и придумаю.
Электронная валюта уже ни для кого не новость, а вот собственная реализация валюты на Python обещает быть интересной. Создаем новый Bitcoin.
Как же создать новый Bitcoin, и что для этого нужно – рассмотрим в этой статье.
Простая монета (SimpleCoin) – простая, небезопасная и не до конца реализованная версия блокчейн криптовалюты на Python. Основной задумкой проекта была идея реализовать максимально похожую, простую и рабочую версию Bitcoin. Если вы тоже хотите создать что-то свое, вам стоит обратиться к Bitcoin Repository.
Вступление
Понятие блокчейн уже не раз рассматривалось, но повторение – мать учения. Блокчейн – это база транзакций, совместно используемая всеми узлами, участвующими в системе на основе биткойн-протокола. Полная копия цепочки блоков валюты содержит каждую транзакцию, когда-либо выполняемую в валюте. С помощью этой информации можно узнать, какое значение принадлежит каждому адресу в любой точке истории.
С чего начать?
Первое, что необходимо сделать, – установить requirements.txt.
pip install -r requirements.txt
В проекте должен быть файл конфига miner_config.py
с таким содержимым:
"""Этот файл нужно изменять до того, как вы запустите майнер. Для лучшего понимания смотрите в wallet.py. """ # Тут указываем сгенерированный адрес. Все монеты пойдут сюда. MINER_ADDRESS = "q3nf394hjg-random-miner-address-34nf3i4nflkn3oi" # Тут укажите URL-адрес ноды или ее ip. Если все запущено на localhost, то пишем так: MINER_NODE_URL = "http://localhost:5000" # А здесь храним URL-адреса каждого узла в сети, чтобы можно было общаться с ними. PEER_NODES = []
Далее два важных шага:
- Запустить
miner.py
, чтобы создать ноду и начать майнить; - запустить
wallet.py
, чтобы стать пользователем и отправлять транзакциии (для этого нужно также запуститьminer.py
).
Важное замечание: не запускайте майнер в среде разработке Python, а только в консоли, т. к. он использует параллельные процессы, которые не работают в IDL-e.
Как это работает?
Самый важный файл в этом проекте – miner.py
. Запустив его, вы создаете сервер, который подключается к блокчейну и обрабатывает транзакции (которые отправляют пользователи) путем майнинга. За это вы получаете несколько монет. Чем больше нод создается, тем безопаснее становится вся цепочка.
miner.py
запускает 2 параллельных процесса:
- первый следит за добычей, обновляет цепочки и создает отчеты о работе;
- второй запускает сервер, к которому могут подключаться пиры и пользователи для запроса всей цепочки, отправки транзакций и прочего.
import time import hashlib as hasher import json import requests import base64 from flask import Flask from flask import request from multiprocessing import Process, Pipe import ecdsa from miner_config import MINER_ADDRESS, MINER_NODE_URL, PEER_NODES node = Flask(__name__) class Block: def __init__(self, index, timestamp, data, previous_hash): """Возвращает новый объект Block. Каждый блок «привязан» к предыдущему по уникальному хэшу Аргументы: index (int): Номер блока. timestamp (int): Timestamp создания блока. data (str): Данные для отправки. previous_hash(str): Строка с хэшем предыдущего блока. Атрибуты: index (int): Номер блока. timestamp (int): Timestamp создания блока. data (str): Данные для отправки. previous_hash(str): Строка с хэшем предыдущего блока. hash(str): Хэш текущего блока. """ self.index = index self.timestamp = timestamp self.data = data self.previous_hash = previous_hash self.hash = self.hash_block() def hash_block(self): """Создание уникального хэша для блока при помощи sha256.""" sha = hasher.sha256() sha.update((str(self.index) + str(self.timestamp) + str(self.data) + / str(self.previous_hash)).encode('utf-8')) return sha.hexdigest() def create_genesis_block(): """Для создания нового блока. ему нужен хэш предыдущего. Первыйблок не знает хэш предыдущего, поэтому его нужно создать руками (нулевой индекс и произвольный хэш)""" return Block(0, time.time(), {"proof-of-work": 9,"transactions": None}, "0") # Копирование блокчейн-ноды BLOCKCHAIN = [] BLOCKCHAIN.append(create_genesis_block()) """ Тут хранятся транзакции, которые относятся к текущей ноде. Если нода, которой была отправлена транзакция добавляет новый блок, он успешно принимается, но есть вероятность того, что заявка будет отклонена и транзакция вернется """ NODE_PENDING_TRANSACTIONS = [] def proof_of_work(last_proof,blockchain): # Создаем переменную, которая будет использоваться для проверки работы incrementor = last_proof + 1 # Получаем время начала start_time = time.time() # Продолжаем увеличивать инкрементатор до тех пор, пока он не будет равен числу, которое # делится на 9, и доказательству работы предыдущего блока while not (incrementor % 7919 == 0 and incrementor % last_proof == 0): incrementor += 1 start_time = time.time() # Каждые 60сек проверяем, нашла ли нода подтверждение работы if (int((time.time()-start_time)%60)==0): # Если нашла - прекращаем проверку new_blockchain = consensus(blockchain) if new_blockchain != False: #(False:другая нода первая нашла подтверждение работы) return (False,new_blockchain) # Как только число найдено, можно вернуть его как доказательство return (incrementor,blockchain) def mine(a,blockchain,node_pending_transactions): BLOCKCHAIN = blockchain NODE_PENDING_TRANSACTIONS = node_pending_transactions while True: """Майнинг - единственный способ создания новых монет. Чтобы предотвратить создание большого количества монет, процесс замедляется с помощью алгоритма доказательства работы. """ # Получаем последнее доказательство last_block = BLOCKCHAIN[len(BLOCKCHAIN) - 1] last_proof = last_block.data['proof-of-work'] # Ищем доказательство работы в текущем блоке # Программа будет ждать пока новое подтверждение не будет найдено proof = proof_of_work(last_proof, BLOCKCHAIN) # Если доказательство не нашлось - начинаем майнить опять if proof[0] == False: # Обновляем блокчейн и сохраняемся в файл BLOCKCHAIN = proof[1] a.send(BLOCKCHAIN) continue else: # Как только мы найдем действительное доказательство работы, мы можем разбить блок, # и добавить транзакцию # Загружаем все ожидающие транзакции и отправляем их на сервер NODE_PENDING_TRANSACTIONS = requests.get(MINER_NODE_URL + " /txion?update=" + MINER_ADDRESS).content NODE_PENDING_TRANSACTIONS = json.loads(NODE_PENDING_TRANSACTIONS) # Затем добавляется вознаграждение за майнинг NODE_PENDING_TRANSACTIONS.append( { "from": "network", "to": MINER_ADDRESS, "amount": 1 } ) # Теперь мы можем собрать данные, необходимые для создания нового блока new_block_data = { "proof-of-work": proof[0], "transactions": list(NODE_PENDING_TRANSACTIONS) } new_block_index = last_block.index + 1 new_block_timestamp = time.time() last_block_hash = last_block.hash # Список пустых транзакций NODE_PENDING_TRANSACTIONS = [] # Теперь создаем новый блок mined_block = Block(new_block_index, new_block_timestamp, new_block_data, last_block_hash) BLOCKCHAIN.append(mined_block) # Сообщаем клиентам, что нода готова майнить print(json.dumps({ "index": new_block_index, "timestamp": str(new_block_timestamp), "data": new_block_data, "hash": last_block_hash }) + "n") a.send(BLOCKCHAIN) requests.get(MINER_NODE_URL + "/blocks?update=" + MINER_ADDRESS) def find_new_chains(): # Получаем данные о других нодах other_chains = [] for node_url in PEER_NODES: # Получаем их цепочки GET-запросом block = requests.get(node_url + "/blocks").content # Конвертим объект JSON в словарь Python block = json.loads(block) # Проверяем, чтобы другая нода была корректной validated = validate_blockchain(block) if validated == True: # Добавляем ее в наш список other_chains.append(block) return other_chains def consensus(blockchain): # Получаем блоки из других нод other_chains = find_new_chains() # Если наша цепочка не самая длинная, то мы сохраняем самую длинную цепочку BLOCKCHAIN = blockchain longest_chain = BLOCKCHAIN for chain in other_chains: if len(longest_chain) < len(chain): longest_chain = chain # Если самая длинная цепочка не наша, делаем ее самой длинной if longest_chain == BLOCKCHAIN: # Продолжаем искать подтверждение return False else: # Сдаемся, обновляем цепочку и ищем снова BLOCKCHAIN = longest_chain return BLOCKCHAIN def validate_blockchain(block): """Проверяем отправленную цепочку. Если хэши неверны, возвращаем false block(str): json """ return True @node.route('/blocks', methods=['GET']) def get_blocks(): # Загружаем текущий блокчейн. if request.args.get("update") == MINER_ADDRESS: global BLOCKCHAIN BLOCKCHAIN = b.recv() chain_to_send = BLOCKCHAIN else: # Любая нода, которая будет подключаться, будет делать так: chain_to_send = BLOCKCHAIN # Конвертим наши блоки в словари и можем отправить им json объект chain_to_send_json = [] for block in chain_to_send: block = { "index": str(block.index), "timestamp": str(block.timestamp), "data": str(block.data), "hash": block.hash } chain_to_send_json.append(block) # Отправляем нашу цепочку тому, кто попросил chain_to_send = json.dumps(chain_to_send_json) return chain_to_send @node.route('/txion', methods=['GET','POST']) def transaction(): """Каждая отправленная транзакция в эту ноду проверяется и отправляется. Потом она ждет добавления в блокчейн. Транзакции не создают новые монеты, а только перемещают их. """ if request.method == 'POST': # При каждом новом POST-запросе мы извлекаем данные транзакции new_txion = request.get_json() # Добавляем транзакцию в список if validate_signature(new_txion['from'],new_txion['signature'],new_txion['message']): NODE_PENDING_TRANSACTIONS.append(new_txion) # Транзакция успешно отправлена - сообщаем это в консоль print("New transaction") print("FROM: {0}".format(new_txion['from'])) print("TO: {0}".format(new_txion['to'])) print("AMOUNT: {0}n".format(new_txion['amount'])) return "Transaction submission successfuln" else: return "Transaction submission failed. Wrong signaturen" # Отправляем ожидающие транзакции майнеру elif request.method == 'GET' and request.args.get("update") == MINER_ADDRESS: pending = json.dumps(NODE_PENDING_TRANSACTIONS) NODE_PENDING_TRANSACTIONS[:] = [] return pending def validate_signature(public_key,signature,message): """Проверяем правильность подписи. Это используется для доказательства того, что это вы (а не кто-то еще), пытающийся совершить транзакцию за вас. Вызывается, когда пользователь пытается отправить новую транзакцию. """ public_key = (base64.b64decode(public_key)).hex() signature = base64.b64decode(signature) vk = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_key), curve=ecdsa.SECP256k1) try: return(vk.verify(signature, message.encode())) except: return False def welcome_msg(): print(""" =========================================n SIMPLE COIN v1.0.0 - BLOCKCHAIN SYSTEMn =========================================nn You can find more help at: https://github.com/cosme12/SimpleCoinn Make sure you are using the latest version or you may end in a parallel chain.nnn""") if __name__ == '__main__': welcome_msg() # Запускаем майнинг a,b=Pipe() p1 = Process(target = mine, args=(a,BLOCKCHAIN,NODE_PENDING_TRANSACTIONS)) p1.start() # Запускаем сервер для приема транзакций p2 = Process(target = node.run(), args=b) p2.start()
wallet.py
используется для пользователей. Запуск этого файла позволит вам генерировать новые адреса, отправлять монеты и проверять историю транзакций. Помните, что если вы его запускаете на локальном сервере, вам нужен процесс miner.py
.
"""Это ваш кошелек. Здесь вы можете сделать несколько вещей: - Создать новый адрес (открытый и закрытый ключ). Вы будете использовать этот адрес (открытый ключ) для отправки или получения любых транзакций. У вас может быть столько адресов, сколько пожелаете, но если вы потеряете доступ - восстановить его вы уже не сможете. - Отправлять монеты на другой адрес. - Извлекать целую цепочку и проверять баланс. Если вы впервые используете этот скрипт, не забудьте сгенерировать новый адрес и отредактируйте файл конфигурации miner. Временная метка захэширована. Когда вы отправляете транзакцию, она будет получена несколькими узлами. Если какой-либо узел майнит блок, ваша транзакция будет добавлена в blockchain, а другие узлы будут ожидать. Если какой-либо узел видит, что ваша транзакция с той же меткой времени, они должны удалить ее из node_pending_transactions, чтобы избежать ее обработки более 1 раза. """ import requests import time import base64 import ecdsa def welcome_msg(): print(""" =========================================n SIMPLE COIN v1.0.0 - BLOCKCHAIN SYSTEMn =========================================nn You can find more help at: https://github.com/cosme12/SimpleCoinn Make sure you are using the latest version or you may end in a parallel chain.nnn""") def wallet(): response = False while response not in ["1","2","3"]: response = input("""What do you want to do? 1. Generate new wallet 2. Send coins to another wallet 3. Check transactionsn""") if response in "1": # Создаем новый кошелек print("""=========================================n IMPORTANT: save this credentials or you won't be able to recover your walletn =========================================n""") generate_ECDSA_keys() elif response in "2": addr_from = input("From: introduce your wallet address (public key)n") private_key = input("Introduce your private keyn") addr_to = input("To: introduce destination wallet addressn") amount = input("Amount: number stating how much do you want to sendn") print("=========================================nn") print("Is everything correct?n") print("From: {0}nPrivate Key: {1}nTo: {2}nAmount: {3}n".format (addr_from,private_key,addr_to,amount)) response = input("y/nn") if response.lower() == "y": send_transaction(addr_from,private_key,addr_to,amount) elif response == "3": check_transactions() def send_transaction(addr_from,private_key,addr_to,amount): """Отправляем транзакцию на разные узлы. Как только главная нода начнет майнить блок, транзакция добавляется в блокчейн. Несмотря на это, существует небольшая вероятность того, что ваша транзакция будет отменена из-за других узлов, имеющих более длинную цепочку. Поэтому убедитесь, что ваша транзакция глубоко в цепочке, прежде чем утверждать, что она одобрена! """ if len(private_key) == 64: signature,message = sign_ECDSA_msg(private_key) url = 'http://localhost:5000/txion' payload = {"from": addr_from, "to": addr_to, "amount": amount, "signature": / signature.decode(), "message": message} headers = {"Content-Type": "application/json"} res = requests.post(url, json=payload, headers=headers) print(res.text) else: print("Wrong address or key length! Verify and try again.") def check_transactions(): """Извлекаем весь блокчейн. Тут вы можете проверить свой баланс. Если блокчейн очень длинный, загрузка может занять время. """ res = requests.get('http://localhost:5000/blocks') print(res.text) def generate_ECDSA_keys(): """Эта функция следит за созданием вашего private и public ключа. Очень важно не потерять ни один из них т.к. доступ к кошельку будет потерян. Если кто-то получит доступ к вашему кошельку, вы рискуете потерять свои монеты. private_key: str public_ley: base64 """ sk = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1) # private ключ private_key = sk.to_string().hex() # конвертим private ключ в hex vk = sk.get_verifying_key() # public ключ public_key = vk.to_string().hex() print("Private key: {0}".format(private_key)) # кодируем public ключ, чтобы сделать его короче public_key = base64.b64encode(bytes.fromhex(public_key)) # используем decode(), чтобы удалить b'' из строки print("Wallet address / Public key: {0}".format(public_key.decode())) def sign_ECDSA_msg(private_key): """Подписываем сообщение для отправки private ключ должен быть hex return signature: base64 message: str """ # получаем timestamp, округляем, переводим в строку и кодируем message=str(round(time.time())) bmessage = message.encode() sk = ecdsa.SigningKey.from_string(bytes.fromhex(private_key), curve=ecdsa.SECP256k1) signature = base64.b64encode(sk.sign(bmessage)) return signature,message if __name__ == '__main__': welcome_msg() wallet() input("Press any key to exit...")
Заключение
Автор призывает участвовать всех желающих в этом проекте. Главная задача – упрощение кода и повышение его читабельности. Перевод на русский осуществлен Библиотекой Программиста.
Оригинал
Другие материалы по теме:
- Python: взлом криптографической хеш-функции через BruteForce
- Блокчейн, ИИ, бессерверные вычисления: ТОП-10 технологий 2018
- 5 тенденций в программировании для 2018 года
- Логика в программировании: логические задачи с собеседований
Начнём с того, что большинство майнеров из интернета мне не нравились. Дизайн плохой, медленно майнит, вирусов куча. Вот я, с многолетним опытом гугления, решил написать свой майнер. Я писал его несколько дней и не мог спать и есть, пока его не закончу. И вот я его закончил. Но в продакшн этот кусок кода пускать нельзя, так как он не протестирован. Сам я его буду тестировать месяц-два. Долго, согласитесь? Вот я и решил вынести его на свет.
И так, каковы его особенности?
Это CPU майнер.
Он не мешает работе, так как использует 50% CPU.
Окно скрыто, и не будет мельтешить перед глазами.
Он автоматически определяет разрядность системы.
Автоматический выбор наилучшего пула из вшитого списка (будет обновляться).
Требуется лишь вписать номер кошелька и название валюты в специальный конфигурационный файл (config.txt) и, с помощью установочника, создать майнер, который может использовать любой человек, а деньги будут капать Вам.
Пример конфигурационного файла: bitcoin 6a4d6f5468785468d45rg46dr468 (все числа вымышлены).
Внимание! Вводите данные осторожно, лучше перепроверьте их несколько раз. Если введёте неправильные данные — деньги будут уходить в пустоту.
P.s. Можно создать много майнеров на разные кошельки .
Время ответов на вопросы!
Q: Какая тебе от этого выгода?
A: Я получаю лог-файлы и исправляю ошибки.
Q: Почему отдаёшь это нам?
A: Мне нужны тестировщики.
Q: Что ты собираешься делать с майнером?
A: Вполне возможно, что я его продам.
Инструкция: Пишете в файле config.txt свои данные как на примере выше. Затем запускаете установочник, выбираете директорию установки, и на выходе получаете файл майнера в формате .exe.
P.s. Можно распространить свой майнер, только тссс.
Ссылка: http://dropmefiles.com/oGoU5
Спрашивайте, если что-то непонятно.
Но могу ответить чуть позднее, чем следует.
-
#1
Добрый день. Подскажите темы по созданию совего майнера на Грин, эфир или бим. Есть какие-либо посты по этому поводу. Какой уровень програмирования необходимо иметь. Буду премного благодарен.
-
#2
Сходите на github любого из майнер проектов, скачайте исходники (если доступны) и посмотрите.
-
#3
А заголовок темы то какой! И все
В принципе, задачи не столь сложные, подобные примеры приводятся в физматах. Можно проконсультироваться с профессорами по математическому решению алгоритма и пытатья его правильнт реализовать. Я поддерживаю инициативу.
-
#4
Добрый день. Подскажите темы по созданию совего майнера на Грин, эфир или бим. Есть какие-либо посты по этому поводу. Какой уровень програмирования необходимо иметь. Буду премного благодарен.
Есть открытые проекты на гитхабе, под эфир точно, посмотрите и оцените уровень. Заодно сможете по быстрому на основе свой запилить.
esq
Свой человек
-
#6
гугл —> основы объектно-ориентированного программирования —> дальше сам
-
#7
гугл —> основы объектно-ориентированного программирования —> дальше сам
ну нахера тут ООП?
Vai
Свой человек
-
#8
это чтобы показать что знаешь умные слова
-
#9
это чтобы показать что знаешь умные слова
самоутверждение забавная штука
-
#11
Как приятно ТСу, когда Семёныч за тебя гуглом работает
esq
Свой человек
-
#12
Мне так друг говорил который с перфокартами на заводе работал
-
#13
Какой уровень програмирования необходимо иметь
В данном случае его явно недостаточно.
-
#14
Иногда лучше х*й сосать …
Какая разносторонняя публика на форуме про майнинг, однако.
-
#15
Добрый день. Подскажите темы по созданию совего майнера на Грин, эфир или бим. Есть какие-либо посты по этому поводу. Какой уровень програмирования необходимо иметь. Буду премного благодарен.
Никак.
Сначала ты должен в идеале знать английский язык.
Скачать документацию по данному алгоритму. Изучить.
Скачать документацию пула по обработки запросов и ответов (майнер же должен отправлять шары и получать задание от куда-то)
Сказать документацию по работе устройств видеокарт. От производителя Амд или Нвидеа. Изучить. Документацию по Cuda, Pascal, RTX, OpenGL. В зависимости от поколения твоих видеокарт. Изучить как передавать команды ядру, памяти, обрабатывать запросы, исключения, ошибки, получать инфу о температуре, загруженности контроллера.
Изучить документацию по драйверам видеокарт. Что нового в последних версиях, тонкости.
Скачать документацию по Windows MSDN или Linux. В частности работа аппаратной части взаимодействия устройств процессора, памяти.
Изучить потоки, приоритеты потоков, как создавать многопоточных приложения в зависимости от версии ОS.
Найти и изучить недокументированные особенности Windows. (Если хочешь, чтобы твой хешрейт был максимально высоким ).
И только после этого выбирать язык на котором будешь писать.
Ну, вероятно это будет Си+
Если не программировал, то сразу полгода накидывай на изучение для таких задач. Это как минимум.
Далее скачиваешь на гитхабе исходники открытых майнеров. Чтобы не изобретать велосипед. Изучаешь, на основе их изменяешь код под новый алгоритм, если его нет.
Тестируешь, исправляешь баги.
И только посте этого твой майнер не будет отправлять комиссию разработчикам.
Правда майнинг к этому времени будет уже не выгоден.
-
#16
Какой уровень программирования необходимо иметь.
Обратись к профессионалам
В наше время каждая бабушка слышала о криптовалютах, курсы майнинга проводят даже серьезные учебные заведения, а антивирусы все чаще кричат о заражении сайта или игрушки майнером. Пришло время на практике разобраться, что это такое, как работает, и написать свой криптомайнер.
В качестве криптовалюты возьмем Electroneum. Это довольно перспективная криптовалюта из семейства Monero. Как заверяют разработчики, она защищена от майнинга на специальном оборудовании, точнее, оборудование будет стоить больше, чем можно получить прибыли. Это дает примерно равные шансы всем майнерам. Так как в качестве основы была использована Monero, многое из написанного будет правдиво и для других криптовалют этого семейства.
Для начала разберемся, что же такое майнинг. По сути это проверка транзакций различных пользователей криптовалют. Нет никакого центрального органа, а подтвердить, что один участник сети не использовал свои деньги дважды или не попытался как-то еще обмануть систему, могут все остальные. За это майнеры получают награду в виде небольшого количества криптоденег. В эту сумму входит награда за создание нового блока и оплата за транзакции, которая взимается с пользователей, проводящих транзакцию, и уже включена в нее.
Создание нового блока представляет собой решение определенной математической задачи. Необходимо найти такой хеш блока, который был бы меньше значения, определяемого сетью. Это значение называется сложность (difficulty). Оно регулируется сетью, чтобы время создания блока было более-менее предсказуемо. Майнер, который первый решит задачу, получает всю награду. Награда за блок на сегодняшний день составляет 11 300,93 ETN, что примерно равно 146,2 доллара.
В блоке не обязательно должны быть транзакции других пользователей, может быть только одна транзакция создания новых денег. Зачем нужно просто раздавать деньги? Во-первых, это привлекает больше участников сети, во-вторых, снижает риск атаки на сеть, так как заработать легально получается проще.
Чтобы стать участником сети Electroneum, необходимо скачать пакет программ с официального сайта. Выбираем direct miner для своей платформы. После скачивания и распаковки нужно синхронизироваться с сетью — скачать все уже сгенерированные блоки. Для разработки и тестирования лучше пользоваться тестовой сетью с пониженной сложностью.
К сожалению, синхронизация «из коробки» может зависнуть на блоке 155750. Это связано с найденным критичным багом и кардинальными изменениями из-за этого в сети Electroneum (подробнее). Поэтому прежде чем запускать синхронизацию, нужно скачать файлик с правильной цепочкой блоков и положить его в папку
.electroneum/testnet/export/blockchain.raw. Затем выполнить импорт:
> ./electroneum—blockchain—import —testnet —verify 0 |
Теперь смело запускаем синхронизацию:
> ./electroneumd —testnet |
Далее создаем кошелек для начисления заработка:
> electoneum—wallet—cli —testnet |
Ответив на все вопросы, получаем публичный адрес в файлике
<название кошелька>.address.txt. Если лениво заморачиваться с развертыванием сервера Electroneum, можно воспользоваться онлайн-сервисом
nodes.hashvault.pro:26968.
Настало время запустить свой любимый редактор и приступать к кодированию. Для связи с сервисом Electroneum используется протокол
jsonrpc. Нам понадобится всего две команды: получить шаблон блока и отправить решение. Начнем с простого HTTP-клиента:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public String sendRpcCommand(String command) { // Определяем URL для связи с сервером. Для реальной, не тестовой сети порт будет 26968 URL url = new URL(«http://127.0.0.1:34568/json_rpc»); HttpURLConnection con = (HttpURLConnection) url.openConnection(); // Задаем параметры соединения. Разрешаем вывод, чтобы забрать ответ сервера con.setDoOutput(true); // Передавать будем данные в формате JSON con.setRequestProperty(«Content-Type», «application/json; charset=UTF-8»); con.setRequestProperty(«Accept», «application/json»); con.setRequestMethod(«POST»); // Отправляем команду OutputStream os = con.getOutputStream(); os.write(command.getBytes(«UTF-8»)); os.close(); StringBuilder sb = new StringBuilder(); int HttpResult = con.getResponseCode(); if (HttpResult == HttpURLConnection.HTTP_OK) { // Если соединение успешно, то забираем ответ сервера BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), «utf-8»)); String line; while ((line = br.readLine()) != null) { sb.append(line).append(«n»); } br.close(); return sb.toString(); } else { // Если соединение не удалось, то бросаем исключение с описанием проблемы throw new IOException(con.getResponseMessage()); } } |
Чтобы получить шаблон блока для вычисления, отправим команду
{ «jsonrpc»:«2.0», «id»:«0», «method»:«get_block_template», «params»:{ «wallet_address»:«44GBHzv6ZyQdJkjqZje6KLZ3xSyN1hBSFAnLP6EAqJtCRVzMzZmeXTC2AHKDS9aEDTRKmo6a6o9r9j86pYfhCWDkKjbtcns», «reserve_size»:8 } } |
В качестве параметра wallet_address указываем адрес из файла
<название кошелька>.address.txt. Адрес используется, чтобы сразу сгенерировать транзакцию получения награды за расчет блока. Параметр
reserve_size задает, сколько выделить зарезервированных байтов, которые потом можно использовать при майнинге. Максимальное число — 255 байт.
В результате получаем:
{ «id»: «0», «jsonrpc»: «2.0», «result»: { «blockhashing_blob»: «070784e5dbda054486739aac8830906e18272012b97b98993afccf89d0044241193d1788f760cb0000000057754af7e8324054869263b355ede600c2381cbdf6acf2dc8f2b26f4a9a82bae14», «blocktemplate_blob»: «070784e5dbda054486739aac8830906e18272012b97b98993afccf89d0044241193d1788f760cb000000000183d71401fff1d61401eff844020117b5d2cead5bd512ab4b0a2e73377049c49c69ffc916687e811bbb0f5f65322b01d67fec53c3f1cab976537a4ab4ebba03c89849d554963df6ed1a0023e6d5d9e90208000000000000000013d5c0b347172631f9b0175365936f98b00198d8caf3dbb77edc6c002dbb6c302776e7d543da92fdf5c30e91d4b21762eb6fe5daf8959b519f3de65a3cd80adda1e5674fedeb2a5038577ea2fe9eb6a3fd2162a3a09cbe6d3b62c9b04a29d47c5c14c119f0812448ab4e14a76f1c2ddc2ff6ac0b97f1fb9e4cabf0ef2adf79221a3e865b8d9252f41f31e110326b78b0c506e9f18eb094305b6216221c2bd3f9d996bedf54dbb4c0bfe4fea6f2240181c91789270a48cae44d7662e1a13aae45c3edc3247736879f6aa2670b8816e551856b912f11269979fac1c97203365247eaee476ed815e3fa597b5230db7e0162816b55b23d2bfb8b9506492e8359f8ba33807eab0972a7837893163cadf314888dbb64190fa00553156dc7b05574eacd3b9a268666201ab202b23ecf960565c01a6a61fe5f03ba5b6c22d7e6639e7708941c876ecdc191cec4c5797e520855d9cc34ef9c3866ded9a4722c6437363bb7a47c9dbd303c15a18dfb72028054cd438924978f5c5d32be3bcbc622e0fb4b9aef865fea52a09f518952ec0aa94bbfa969f192a93b80a50fe7af2728cbd76e739e9af80aee2644fb2bbe1c82724bdc4678a5a206a945a3e49dabcb10ae0f25d473aa76e0275c4f9fa1cffc3e1d8748278561b99953966606a5d891717b4fb0366a77e38db4c267c3724e994532ae97fc7b12842157d8a11bc97926eb9978c82a07afc573a04660247a94c5c4f14556fbcc9aa367b7bef4fdf18b626b4342d4e84850f133076dcd26c16d3efe9f85fa29c757acda5dff2fe26fbf87d937be455d4053e4246a3055ace5fcb6d6545aa3cd0b2e21ea3648f0dd6cde386933381b7116», «difficulty»: 237219196877, «expected_reward»: 1129583, «height»: 338801, «prev_hash»: «4486739aac8830906e18272012b97b98993afccf89d0044241193d1788f760cb», «reserved_offset»: 126, «status»: «OK» } } |
Рассмотрим подробнее первые два поля.
Electroneum предоставляет две возможности для майнинга. Можно использовать готовый для расчета хеша blockhashing_blob, подбирая четыре байта nonce. Из достоинств — не нужно рассчитывать самому корень Меркле для транзакций. Из недостатков — довольно скудный набор возможных значений, среди которых может и не найтись нужного.
Второй вариант — использовать сырой блок blocktemplate_blob. Тут уже можно перебирать как четыре байта nonce, так и значение блока дополнительных данных, что заметно расширяет вероятность нахождения нужного значения. Но приходится считать хеш первой транзакции и корень Меркле, а только потом рассчитывать хеш самого блока.
Для начала попробуем первый вариант. Напишем небольшой метод, который будет перебирать значения nonce.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public static boolean findProperNonce(byte[] blockheader, int nonceByteIndex, long difficulty) { byte nonceByte = Byte.MIN_VALUE; while (nonceByte != Byte.MAX_VALUE) { blockheader[39 + nonceByteIndex] = nonceByte; if (nonceByteIndex < 3) { boolean found = findProperNonce(blockheader, nonceByteIndex + 1, difficulty); if (found) { return true; } } else { byte[] hash = calculateHash(blockheader); if (hasRequiredDifficulty(hash, difficulty)) return true; } nonceByte++; } return false; } |
Electroneum использует алгоритм хеширования CryptoNight. Описание алгоритма можно посмотреть тут. Хорошая новость — есть много готовых реализаций, плохая — практически все они написаны на С. К счастью, Java-машина прекрасно умеет запускать код на С. Поэтому, чтобы сократить время, возьмем готовую реализацию алгоритма и сделаем для нашего майнера подключаемую DLL’ку.
Для этого нам понадобится Cygwin. Это набор опенсорсных линуксовых утилит, которые можно запускать под виндой. При установке нужно выбрать пакеты
mingw64—x86_64—gcc—core и
mingw64—x86_64—gcc—g++.
Для загрузки библиотеки создадим класс CryptoNight в пакете
com.gogaworm.electroneumminer.
public class Cryptonight { // Загружаем библиотеку с именем minerhashing, расширение писать не нужно static { System.loadLibrary(«minerhashing»); } // Метод расчета хеша из библиотеки public native static void calculateHash(byte[] output, byte[] input); } |
Метод calculateHash объявлен как native, это означает, что он реализован на другом языке. Далее нужно сгенерировать файл заголовка:
> %JAVA_HOME%binjavah.exe —jni —v —d com/gogaworm/electroneumminer com.gogaworm.electroneumminer.Cryptonight |
В результате получим файл
com_gogaworm_electroneumminer_Cryptonight.h. В нем объявлен метод
Java_com_gogaworm_electroneumminer_Cryptonight_hash, который нужно реализовать на С. Для этого создадим файл с таким же именем, но расширением .c. Оба файла нужно перенести в папку с исходниками libcryptonight.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// Обязательно нужно включить файл — заголовок для jni. Он находится в папке с установленной Java #include <jni.h> JNIEXPORT void JNICALL Java_com_gogaworm_electroneumminer_Cryptonight_calculateHash (JNIEnv *env, jclass clazz, jbyteArray output, jbyteArray input) { // Копируем массивы данных в новую область памяти. Работать с массивами напрямую в JavaHeap нельзя, так как сборщик мусора может перемещать их в памяти unsigned char* inputBuffer = (*env)—>GetByteArrayElements(env, input, NULL); unsigned char* outputBuffer = (*env)—>GetByteArrayElements(env, output, NULL); // Определяем размеры массивов jsize inputSize = (*env)—>GetArrayLength(env, input); jsize outputSize = (*env)—>GetArrayLength(env, output); // Рассчитываем хеш cryptonight_hash(outputBuffer, inputBuffer, inputSize); // Освобождаем область памяти, использованную для массивов (*env)—>ReleaseByteArrayElements(env, input, inputBuffer, JNI_ABORT); (*env)—>ReleaseByteArrayElements(env, output, outputBuffer, JNI_COMMIT); } |
Теперь запускаем Cygwin-консоль и собираем DLL:
> x86_64—w64—mingw32—gcc —I«$JAVA_HOME/include» —I«$JAVA_HOME/include/win32» —shared —o minerhashing.dll —g com_gogaworm_electroneumminer_Cryptonight.c cryptonight.c crypto/aesb.c crypto/c_blake256.c crypto/c_groestl.c crypto/c_jh.c crypto/c_keccak.c crypto/c_skein.c crypto/oaes_lib.c |
Чтобы наш майнер увидел библиотеку, необходимо определить системную переменную Java
java.library.path=<путь к библиотеке>.
Проверим, что библиотека работает правильно. В документе, описывающем алгоритм CryptoNight, есть два примера для проверки. Запустим один из них:
@Test public void testHashMethod() throws UnsupportedEncodingException { byte[] outputBuffer = new byte[32]; byte[] input = hexStringToByteArray(block); Cryptonight.calculateHash(outputBuffer, «This is a test».getBytes(«US-ASCII»)); assertEquals(«a084f01d1437a09c6985401b60d43554ae105802c5f5d8a9b3253649c0be6605», bytesToHex(outputBuffer).toLowerCase()); } |
Остался метод проверки, найдено ли нужно значение. Команда get_block_template вернула в результате параметр difficulty. Этот параметр показывает условный коэффициент сложности нахождения нужного хеша. По спецификации сложность = ( 2^265 — 1 ) / целевое значение (target). Для этой формулы хеш блока нужно перевести из больших индейцев в мелкие. Затем сравним с текущей сложностью, чтобы понять, найдено ли нужное значение:
public static boolean hasRequiredDifficulty(byte[] hash, BigInteger difficulty) { BigInteger diff1 = new BigInteger(«FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF», 16); BigInteger reversed = new BigInteger(bytesToHex(Arrays.reverse(hash)), 16); BigInteger hashdiff = diff1.divide(difficulty); if (hashdiff.compareTo(difficulty) >= 0) { return true; } return false; } |
Чтобы проверить, верно ли работает метод, испытаем его на уже готовом блоке из сети. Получить его можно командой getblock. Возьмем блок с высотой 338 401. Его хеш равен
13b3cf8b04b6bb78f0c7c1a50f7e8656963c1f48a56ba89999eddf0531750b15 |
а сложность —
252087628780. В результате вычислений получаем, что hashdiff больше difficulty.
Когда найдено нужное значение nonce, можно отправлять блок в сеть. Это делает команда
{ «jsonrpc»:«2.0», «id»:«0», «method»:«submitblock», «params»:{ «Block «:«blob template с нужным nonce» } } |
Осталось перенести методы работы с сервером и майнинга в отдельные потоки, и простой майнер готов.
Вместо заключения
Как заявляют разработчики криптовалюты Electroneum, ее можно майнить даже на смартфонах. Приложение для майнинга уже лежит в Google Play. Но на самом деле там только симуляция майнинга: вместо того чтобы решать сложную криптографическую задачу, измеряют доступную производительность CPU, которую теоретически можно было бы использовать для майнинга, и на основе этого значения начисляется заработок. Поэтому майнер для Андроида будет выглядеть несколько иначе.
Но это уже совсем другая история.
(2 оценок, среднее: 5,00 из 5)
Загрузка…