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