Блокчейн на C# (.Net Core 3.1), Часть 0

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

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

А данный список будет лежать в статическом классе «Blockchain»:

static class Blockchain
{
  	public static List blockchain = new List();
}

Для того, чтобы добавлять блоки, напишем метод «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» которая позволяла бы нам из консоли добавлять новый блок и выводить на экран все блоки.

Тест!

Генезис-блок создается сразу же, с тридцать третьей попыткиГенезис-блок создается сразу же, с тридцать третьей попытки

Добавим блок!

9942d206601f8f220f0160468db2f660.jpg

И еще!

808813658c644a5543c59ed3d6e66082.jpg

Вроде работает

Какой же блокчейн без блокчейн эксплорера? (К счастью, его написать крайне просто)

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++;
}

Этот всего лишь выводит на экран все блоки. Демонстрирую:

4a727e1e8f225774d7f6eda352307cf9.jpg

Все точно, как в аптеке

Но что будет, если какой-нибудь сумрачный гений решит всех обмануть и подменит данные в каком-нибудь блоке? Например Саня, который не хочет возвращать банку сотку вместе с кровной десяткой? На этот случай и был придуман метод «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» и сравниваем с хэшем текущего блока. Если все ОК — берем следующий блок и проводим те же манипуляции. Если нет — останавливаем проверку. Тест!

Блоки добавляются. Это хорошоБлоки добавляются. Это хорошоВсе блоки прошли проверку. ОтличноВсе блоки прошли проверку. Отлично

Теперь отредактируем третий блок, сделав вид, что мы ̶м̶о̶н̶а̶р̶х̶и̶с̶т̶ы̶

Нам удалось подделать блок! Прекрасно!Нам удалось подделать блок! Прекрасно!

А теперь сисадмин Валера решил проверить, что творится на вверенном ему сервере, и запустил верификацию:

91dc36237cd4b3f498357ab9971fad21.jpg

Сисадмин Валера спалил несанкционированное вмешательство в блокчейн, теперь он может сделать примерно то же, что мечтают сделать все пассажиры автобусов, когда видят тот самый молоточек: разбить стекло и выдернуть патч-корды (Я честно искал тот мем, но не нашел, может вы найдете)

В дополнение ко всему этому неплохо бы добавить динамически изменяющуюся сложность, что мы сделаем в статье номер один, и что-то вроде… GUI? Фу, мерзость… Также я хочу запихнуть это все на сервер, который будет заниматься лишь хранением и предоставлением блокчейна, считать хэш же будут должны юзеры, все как у взрослых криптовалют. Но это в будущем.

Всем большое спасибо за прочтение моей статьи, исходный код проекта вы сможете найти на гитхабе, ковыряйте в свое удовольствие!

Буду рад услышать критику и предложения, первая статья, все-таки!

Ну и, разумеется, ждите продолжения, оно не за горами! (Я еще не начинал, но все говорят именно так…)

(Эй, @ruvds не одолжите сервачок для третьей статьи?))

© Habrahabr.ru