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

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