Всем категорический привет! На связи из Волго-Вятского экономического района — великий и могучий Нукрас!

Вдохновившись этой и этой статьями, я, ̶к̶а̶к̶ ̶к̶о̶р̶е̶н̶н̶о̶й̶ ̶о̶д̶е̶с̶с̶и̶т̶ не преминул возможностью подрезать интересную идейку. Поэтому, встречайте, Блокчейн на 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)


  1. korsetlr473
    28.11.2021 00:40

    1) какой длинны данная констукция сможет выдержить? например сейчас самая малая цепь мне известная около 140гб

    public static List<Block> blockchain = new List<Block>();

    2) я правильно понимаю что метод про нахождение hash с нулями убивает cpu в 100% ?


    1. Naikras Автор
      28.11.2021 01:14

      1) Да, список можно считать слабым звеном проекта. Но, в конце концов, никто не запрещает, например, хранить цепь на диске. Внесу в следующие статьи

      2) Собственно, как и в любом другом майнинге на ЦПУ. Быть может, руки дойдут, попробую вкрячить в проект Cud-y но надо будет как следует покурить гугл...


    1. Naikras Автор
      28.11.2021 01:17
      -3

      Упд, поискал инфу, на форумах пишут, что размер списка ограничивается int.maxValue и/или объёмом виртуальной памяти. Не так уж и мало, хотя 140гб, конечно, вряд ли залезпт


  1. suns
    28.11.2021 05:17

    Можно не смешивать логику майнинга и добавления блоков

    Тогда в базовой форме блокчейн - это простая структура данных, т.е. односвязный список, который помимо ссылки на предыдущий элемент, хранит ещё и его хеш. Собственно все, таймстемпы, nonce и прочее можно перенести в payload, по которому дальше можно строить некоторую динамику (в терминах алгоритмов), а майнинг реализовать на уровне валидации блоков


  1. dopusteam
    28.11.2021 06:38
    +3

    Зачем использовать статичные методы / классы?

    Дело в том, что в каждом уважающем себя блокчейне должен быть нулевой блок

    Если это нужно для корректной работы, пусть этот блок создаётся автоматически, а не по вызову при старте приложения

    Зачем именовать аргументы ts, dat, has, nc?

    Enumerable.Repeat можно вынести из цикла

    AddBlock может работать бесконечно, без возможности остановить процесс (

    Параметры по умолчанию, кстати, у него лучше убрать


  1. OkunevPY
    28.11.2021 12:30
    +3

    Народ что тут обсуждать, с точки зрения организации и построения кода, это хлам.

    На статью описывающую принцип блокчейн, вообше не тянет.

    Для использования хоть в каком то варианте не подходит, не то что нагрузки не выдержит, кроме одно пользовательского консольного запсука ни на что ни годно.


    1. Cryptochild
      28.11.2021 14:42
      +2

      Мне кажется, что автор просто смеётся над нами, выкладывая такой код


      1. dopusteam
        28.11.2021 14:54
        +1

        Ну у статьи плюс 10 рейтинг, странно, конечно


    1. nilf_ua
      28.11.2021 19:02
      +2

      Уважаемый, зачем столько энергии и времени тратить на подобный комментарий.

      Автор статьи старался, самым простым способом описал концепцию. Это ни что иное как демонстрационное пособие, написанное на коленке, за 10 минут.

      Разве где-то в начале статьи есть уточнения о законченности проекта, или о том, что автор претендует обучить нас организации кода?

      Нет, простым и понятным языком, для человека который слышал о БЧ, но не знает что это и с чего начать.

      Его пример можно взять за основу, переделать полностью, и продолжить в своем направлении. Как отправная точка - отлично.

      Да, переменные hs, dat и прочее меня смутили, но потом я закрыл глаза на эти мелочи, дочитал и остался доволен.

      Автору спасибо за потраченное время и за желание помочь сообществую, это похвально.

      А вот у вас Окунев, я не увидел в профиле ни одного примера. Покажите как правильно организовывать и строить код, иначе вы зря потратили мое время, на прочтение вашего бесполезного, бестолкового и ненужного комментария.


  1. ligor
    28.11.2021 15:09

    1. korsetlr473
      28.11.2021 20:33

      " public IList<Block> Blocks { set; get; }"

      дальше даже не читал