Всем категорический привет! На связи из Волго-Вятского экономического района — великий и могучий Нукрас!
Вдохновившись этой и этой статьями, я, ̶к̶а̶к̶ ̶к̶о̶р̶е̶н̶н̶о̶й̶ ̶о̶д̶е̶с̶с̶и̶т̶ не преминул возможностью подрезать интересную идейку. Поэтому, встречайте, Блокчейн на C#, часть номер ноль!
Так как данная статья, по сути, является ̶ф̶о̶р̶к̶о̶м̶ сиквелом вышеуказанных статей, я не буду останавливаться на объяснении таких терминов как "блокчейн", "сложность", "майнинг" и так далее. В тех статьях, в принципе, все вполне понятно рассказали.
Ну и заранее отмечу, что с программированием я знаком на уровне младшего братика джуна, и вообще олень.
Поехали!
Первым делом определим класс "Block"
class Block
{
public Block(string ts, string dat, string hs, int nc)
{timestamp = ts; data = dat; hash = hs; nonce = nc; }
public string timestamp;
public string data;
public string hash;
public int nonce;
}
В классе "Block" мы будем хранить время, когда блок был создан, данные, который мы в него записываем, его хэш и значение nonce. Что-либо еще хранить в блоке считаю излишним.
Цепочку блоков мы будем хранить в списке:
public static List<Block> blockchain = new List<Block>();
А данный список будет лежать в статическом классе "Blockchain":
static class Blockchain
{
public static List<Block> blockchain = new List<Block>();
}
Для того, чтобы добавлять блоки, напишем метод "AddBlock". Данный метод будет находиться в статическом классе "Blockchain", весь процесс майнинга будет происходить именно здесь.
public static void AddBlock(string ts, string dat = "genesis", string prvHash = "")
{
}
Что такое "string dat = "genesis" и "string prvHash = ""? Дело в том, что в каждом уважающем себя блокчейне должен быть нулевой блок. В качестве нулевого блока у нас будет блок, в который будет записано слово "genesis", потому, что я так захотел.
А написанные аргументы с заранее присвоенными значениями - это необязательные аргументы. в методе "Main" у нас есть вот такая вот строчка:
Blockchain.AddBlock();
Эта строчка - первая в методе "Main", именно она создает нулевой блок, прямо во время старта программы, даже если вы хотели, например, загрузить ваш замечательный блокчейн с диска. Что? Да, это будет в статье номер два.
Программистам не читать
Зачем все это надо? Ну вот надо. Вы не представляете, каких мук мне стоило отказаться от передачи в метод AddBlock строки "type" и последующей проверки, какой блок нам надо создать - генезис или стандартный
Продолжим изучение метода добавления блока!
int nonce = 0; //Число, которое будет менять блокчейн для соответствия сложности
string timestamp = Convert.ToString(DateTime.Now); //Время, когда блок отправили на расчет
Думаю, не нужно объяснять, что это такое
while (true)
{
//Цикл расчета хэша
}
Далее в цикле:
string newHash = getHash(timestamp, dat, prvHash, nonce);
Здесь мы и передаем в метод "getHash" время добавления, данные, хэш предыдущего блока и значение nonce.
Метод "getHash":
static string getHash(string ts, string dat, string prvHash, int nonce)
{
using (SHA256 hash = SHA256Managed.Create())
{
return String.Concat(hash
.ComputeHash(Encoding.UTF8.GetBytes(ts + dat + prvHash + nonce))
.Select(item => item.ToString("x2"))); ;
}
}
Так как метод getHash вряд ли с первой попытки возвратит нам нужный хэш, придется проверять его на вхождение нужного количества нулей.
if (newHash.StartsWith(String.Concat(Enumerable.Repeat("0", difficulity))))
{
Console.WriteLine("Ношол!!! {0}, nonce - {1}", newHash, nonce);
blockchain.Add(new Block(timestamp, dat, newHash, nonce));
break;
}
else //Иначе - считать со следующим значением nonce
{
nonce++;
}
Воувоувоу! Полегче, ковбой! Что все это значит?!
Объясняем!
if (newHash.StartsWith(String.Concat(Enumerable.Repeat("0", difficulity))))
Если в начале полученного хэша будут нули в количестве difficulity, то мы сделаем
blockchain.Add(new Block(timestamp, dat, newHash, nonce));
break;
Что такое difficulity? это количество нулей, находящихся в начале хэша блока, необходимое для добавления блока в блокчейн. Именно так мы решаем проблему сложности в нашем блокчейн-проекте. Кстати, сложность должна изменяться динамически, но об этом трошки позже.
else //Иначе - считать со следующим значением nonce
{
nonce++;
}
В противном случае мы добавляем к nonce единичку и по-новой запускаем расчет хэша, получая абсолютно новое значения, и так по кругу, пока не найдем необходимое значение
Целиком код метода "AddBlock" выглядит так:
public static void AddBlock(string dat = "genesis", string prvHash = "") //В эту функцию передаются все данные для создания блока
{
int nonce = 0; //Число, которое будет менять блокчейн для соответствия сложности
string timestamp = Convert.ToString(DateTime.Now);
while (true)
{
string newHash = getHash(timestamp, dat, prvHash, nonce); //Вычисляем хэш, дополнительно передавая число сложности
if (newHash.StartsWith(String.Concat(Enumerable.Repeat("0", difficulity))))
{
Console.WriteLine("Ношол!!! {0}, nonce - {1}", newHash, nonce);
blockchain.Add(new Block(timestamp, dat, newHash, nonce));
break;
}
else //Иначе - считать со следующим значением nonce
{
nonce++;
}
}
}
За кадром я написал простенькую логику, прямо в методе "Main" которая позволяла бы нам из консоли добавлять новый блок и выводить на экран все блоки.
Тест!
Добавим блок!
И еще!
Вроде работает
Какой же блокчейн без блокчейн эксплорера? (К счастью, его написать крайне просто)
int i = 0;
foreach(Block blc in Blockchain.blockchain)
{
Console.WriteLine("{0}, {1}, {2}, {3}, {4}", i, blc.data, blc.hash, blc.timestamp, blc.nonce);
i++;
}
Этот всего лишь выводит на экран все блоки. Демонстрирую:
Все точно, как в аптеке
Но что будет, если какой-нибудь сумрачный гений решит всех обмануть и подменит данные в каком-нибудь блоке? Например Саня, который не хочет возвращать банку сотку вместе с кровной десяткой? На этот случай и был придуман метод "Verification"!
public static void Verification()
{
for (int i = 1; i != blockchain.Count; i++)
{
string verHash = getHash(blockchain[i].timestamp, blockchain[i].data, blockchain[i - 1].hash, blockchain[i].nonce);
if(verHash == blockchain[i].hash)
{
Console.WriteLine("Block {0} - OK", i);
}
else
{
return;
}
}
Console.WriteLine("All blocks are confirmed");
}
Ну как-то так. В этом замечательном методе мы берем данные и время блока, к ним добавляем его значение nonce и хэш предыдущего блока, суем это все в метод "getHash" и сравниваем с хэшем текущего блока. Если все ОК - берем следующий блок и проводим те же манипуляции. Если нет - останавливаем проверку. Тест!
Теперь отредактируем третий блок, сделав вид, что мы ̶м̶о̶н̶а̶р̶х̶и̶с̶т̶ы̶
А теперь сисадмин Валера решил проверить, что творится на вверенном ему сервере, и запустил верификацию:
Сисадмин Валера спалил несанкционированное вмешательство в блокчейн, теперь он может сделать примерно то же, что мечтают сделать все пассажиры автобусов, когда видят тот самый молоточек: разбить стекло и выдернуть патч-корды (Я честно искал тот мем, но не нашел, может вы найдете)
В дополнение ко всему этому неплохо бы добавить динамически изменяющуюся сложность, что мы сделаем в статье номер один, и что-то вроде... GUI? Фу, мерзость... Также я хочу запихнуть это все на сервер, который будет заниматься лишь хранением и предоставлением блокчейна, считать хэш же будут должны юзеры, все как у взрослых криптовалют. Но это в будущем.
Всем большое спасибо за прочтение моей статьи, исходный код проекта вы сможете найти на гитхабе, ковыряйте в свое удовольствие!
Буду рад услышать критику и предложения, первая статья, все-таки!
Ну и, разумеется, ждите продолжения, оно не за горами! (Я еще не начинал, но все говорят именно так...)
(Эй, @ruvds не одолжите сервачок для третьей статьи?))
Комментарии (11)
suns
28.11.2021 05:17Можно не смешивать логику майнинга и добавления блоков
Тогда в базовой форме блокчейн - это простая структура данных, т.е. односвязный список, который помимо ссылки на предыдущий элемент, хранит ещё и его хеш. Собственно все, таймстемпы, nonce и прочее можно перенести в payload, по которому дальше можно строить некоторую динамику (в терминах алгоритмов), а майнинг реализовать на уровне валидации блоков
dopusteam
28.11.2021 06:38+3Зачем использовать статичные методы / классы?
Дело в том, что в каждом уважающем себя блокчейне должен быть нулевой блок
Если это нужно для корректной работы, пусть этот блок создаётся автоматически, а не по вызову при старте приложения
Зачем именовать аргументы ts, dat, has, nc?
Enumerable.Repeat можно вынести из цикла
AddBlock может работать бесконечно, без возможности остановить процесс (
Параметры по умолчанию, кстати, у него лучше убрать
OkunevPY
28.11.2021 12:30+3Народ что тут обсуждать, с точки зрения организации и построения кода, это хлам.
На статью описывающую принцип блокчейн, вообше не тянет.
Для использования хоть в каком то варианте не подходит, не то что нагрузки не выдержит, кроме одно пользовательского консольного запсука ни на что ни годно.
nilf_ua
28.11.2021 19:02+2Уважаемый, зачем столько энергии и времени тратить на подобный комментарий.
Автор статьи старался, самым простым способом описал концепцию. Это ни что иное как демонстрационное пособие, написанное на коленке, за 10 минут.
Разве где-то в начале статьи есть уточнения о законченности проекта, или о том, что автор претендует обучить нас организации кода?
Нет, простым и понятным языком, для человека который слышал о БЧ, но не знает что это и с чего начать.
Его пример можно взять за основу, переделать полностью, и продолжить в своем направлении. Как отправная точка - отлично.
Да, переменные hs, dat и прочее меня смутили, но потом я закрыл глаза на эти мелочи, дочитал и остался доволен.
Автору спасибо за потраченное время и за желание помочь сообществую, это похвально.
А вот у вас Окунев, я не увидел в профиле ни одного примера. Покажите как правильно организовывать и строить код, иначе вы зря потратили мое время, на прочтение вашего бесполезного, бестолкового и ненужного комментария.
ligor
28.11.2021 15:09Вот еще для вдохновения https://putukusuma.medium.com/developing-simple-crypto-application-using-c-48258c2d4e45
korsetlr473
1) какой длинны данная констукция сможет выдержить? например сейчас самая малая цепь мне известная около 140гб
public static List<Block> blockchain = new List<Block>();
2) я правильно понимаю что метод про нахождение hash с нулями убивает cpu в 100% ?
Naikras Автор
1) Да, список можно считать слабым звеном проекта. Но, в конце концов, никто не запрещает, например, хранить цепь на диске. Внесу в следующие статьи
2) Собственно, как и в любом другом майнинге на ЦПУ. Быть может, руки дойдут, попробую вкрячить в проект Cud-y но надо будет как следует покурить гугл...
Naikras Автор
Упд, поискал инфу, на форумах пишут, что размер списка ограничивается int.maxValue и/или объёмом виртуальной памяти. Не так уж и мало, хотя 140гб, конечно, вряд ли залезпт