Пишем блокчейн и криптовалюту с нуля за час

Всем привет. С самого начала своего пути програмирования я интересовался сетями и криптографией. И следовательно с самого начала пути я изучал С, читал документации Сатоси Накамото. Затем начал изучат шарп. Как только я увидел конкурс связаный с криптовалютами , сразу решил поучаствовать.

Почему я выбрал именно эту тему?

Поискав в инете Я не нашел почти никакой толковой инфы . Видосы по типу “скачайте исходник и смените название на свое” меня не устравал. По этому

я начал искать и изучать эту тему усердней.

На каком языке будем писать?

Первое что нужно было решить – На каком языке будем писать? Сразу же я подумал о плюсах. Но порыскав в поиске готовых библиотек сетей , не нашел ничего интересного лично для меня. По этому я понял что идевльно подойдет Goland(Go). У меня была даже подробная документация с написанием готовых библиотек . Но так как опыта работы с вышеупомянутым языком у меня не было, а терять время я не хотел, выбор пал на #, так как его я успел изучить достаточно не плохо , да и опыта разработки сетей , а тем более пирингових , на нем , у меня не было. Вообщем с языком определились.

Основные концепции криптовалют.

Да , безусловано мы знаем что такое криптовалюта. Это набор узлов в одноранговой децентрализованой сети (Не путать с распределенной), которые посылают какие-то пакеты. Зачастую это псевдоанонимная сеть, где коэффициент анонимности определяет количество узлов-посредников. Тоесть совершая какое либо действие , например транзакцию , сетевой адрес отправителя не скрывается, он находится в сети и маскеруется по средставм криптографического адреса. Иными словами узел-получатель знает адрес первичного отправителя. Именно не узла посредника , а отправителя. И если этот узел будет являться подконтрольным, то он действительно будет знать кто отправитель транзакции.

Что касается блокчейна , мы знает что это цепочка блоков , которые содержат хеш предыдущего блока , и список транзакий. В конце каждого подтвержденого блока, тоесть того который был прочитан и записан в блокчейн , майнера(тот кто прочитал блок) ждет вознаграждение.

И тут возникает первая проблема криптовалюты биткоин, а именно на её примере мы собираемся писать блокчейн, если больше 50% мощьности сети сосредоточены в одних руках, то транзакцию можно подделать(продублировать). Наши мощьности начинают майнить и запускат транзакцию через серый тунель , в то время как мы отправляем обычную тарнзакцию в сеть , она подтверждается в то время как серая подтверждена тоже . Затем основная сеть перепроверяет блокчен поддельной и замечает несостыковку возвращает средства назад. На сколько я понял что практически это никогда не проверялось , хоть и в 2013 году китайская майнинг ферма владела 55% мощьностей, название я не вспомню.

Ну и еще одна основная концепция или понятие это сложность сети. В чем оно заключается? во времени хеширования блока. Например каждый блок в биткоине должен подтверждатся в среднем 10 минут , что бы в сети не возникла инфляция и все битки не были добыты раньше времени. Так вот регулируется она каждые 2016 блоков или 14 дней соответствено.

По поводу строения баланса все просто. Это хоро будет видно в коде. Единственное что добавлю это что физического перевода битков нету. То что вы видете в мемпуле это лишь передача прав или хеширования части ваших битков чужим ключем. хорошая аналогия с передачей права собствености. Процесс не сложный. Каждый субъект имеет приватный ключ и публичный (представим как замок). Алгоритм транзакции прост. Получатель передает публичный ключ (Замок), отправитель берет свой приватный ключ , открывает и снимает свой замок со своих монет,одевает замок получателя на часть своих битков , которые хочет отправить и защелкивает. Теперь открыть их может только получатель. Вообщем с концепциями разобрались , идем дальше.

Когда мы определились с языком и знаем что нужно програмировать , остается просто воплатить это в жизнь. Писать я буду в вижуал студии на 9ой версии шарпа, .Net Core ,консольное приложение.

Начнем,

Это наш мейн. В нем мы и будем совершать все необходимые манипуляции. Подключаем JSON, для вывода блокчйена.

Инициализируем поля и екземпляры класов. Создаем наш перый пустой блок , от остальных он будет отличатся только тем что в нем не будет указателя на предыдущий блок. Дальше открываем наш сервер, так как сеть одноранговая то узел может как принимать пакеты так и отправлять их. Само меню по дефолту я сделал с помощью свича.

using Newtonsoft.Json;
using System;

namespace BlockchainCore
{
class Program
{
public static int Port = 334;
private static P2PServer _server;
private static readonly P2PClient Client = new P2PClient();
public static Blockchain Coin = new Blockchain();
private static string _name = “2”;

static void Main(string[] args)
{
Coin.InitializeChain();

if (args.Length >= 1)
Port = int.Parse(args[0]);
if (args.Length >= 2)
_name = args[1];
Console.Clear();
if (Port > 0)
{
_server = new P2PServer();
_server.Start();
}
if (_name != “Unkown”)
{
Console.WriteLine($”Текущее имя пользователя {_name}”);
}

Console.WriteLine(“=========================”);
Console.WriteLine(“1. Подключиться к серверу”);
Console.WriteLine(“2. Добавить транзакцию”);
Console.WriteLine(“3. Напечатать блокчейн”);
Console.WriteLine(“3. Напечатать баланс”);
Console.WriteLine(“5. Выход”);
Console.WriteLine(“=========================”);

int selection = 0;
while (selection != 5)
{
switch (selection)
{
case 1:
Console.WriteLine(“Введите URL сервера”);
string serverUrl = Console.ReadLine();
Client.Connect($”{serverUrl}/Blockchain”);
break;
case 2:
Console.WriteLine(“Введите адрес получателя: “);
string receiverName = Console.ReadLine();
Console.WriteLine(“Введите сумму к отправке: “);
string amount = Console.ReadLine();
Console.WriteLine(“Введите коментарий: “);
string comment = Console.ReadLine();

Coin.CreateTransaction(new Transaction(_name, receiverName, int.Parse(amount),comment));
Coin.ProcessPendingTransactions(_name);
Client.Broadcast(JsonConvert.SerializeObject(Coin));
break;

case 3:
Console.WriteLine(“Blockchain”);
Coin.GetBalance(null) ;
Console.WriteLine(JsonConvert.SerializeObject(Coin, Formatting.Indented));
break;

case 4:
Console.WriteLine(“Баланс ” + Coin.GetBalance(_name));
;

break;
}

Console.WriteLine(“Выберете действие “);
string action = Console.ReadLine();
selection = int.Parse(action);
}

Client.Close();
}
}
}

Теперь детально по пунктам.

Первый пункт.

Это подключение к узлу. Тут метод Connect принимает в качестве аргумента адрес узла

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using WebSocketSharp;

namespace BlockchainCore
{
public class P2PClient
{
IDictionary wsDict = new Dictionary();

public void Connect(string url)
{
if (!wsDict.ContainsKey(url))
{
WebSocket ws = new WebSocket(url);
ws.OnMessage += (sender, e) =>
{
if (e.Data == “Hi Client”)
{
Console.WriteLine(e.Data);
}
else
{
var newChain = JsonConvert.DeserializeObject(e.Data);
if (!newChain.IsValid() || newChain.Chain.Count <= Program.Coin.Chain.Count) return; var newTransactions = new List();
newTransactions.AddRange(newChain.PendingTransactions);
newTransactions.AddRange(Program.Coin.PendingTransactions);

newChain.PendingTransactions = newTransactions;
Program.Coin = newChain;
}
};
ws.Connect();
ws.Send(“Hi Server”);
ws.Send(JsonConvert.SerializeObject(Program.Coin));
wsDict.Add(url, ws);
}
}

public void Send(string url, string data)
{
foreach (var item in wsDict)
{
if (item.Key == url)
{
item.Value.Send(data);
}
}
}

public void Broadcast(string data)
{
foreach (var item in wsDict)
{
item.Value.Send(data);
}
}

public IList GetServers()
{
IList servers = new List();
foreach (var item in wsDict)
{
servers.Add(item.Key);
}
return servers;
}

public void Close()
{
foreach (var item in wsDict)
{
item.Value.Close();
}
}
}
}

Разберем. Метод написан на базе библиотеки Websock. Скажу честно ,чатично эту часть я украл. Но что могу сказать что мы принимает блокчейн сети , если он больше собственого , инициализируем подключение и отправляем проверочное сообщение серверу. Методы ниже нужны для управление добавленым блокчейном и закрытия шлющего сокета.

Читайте также:  Dump website

Ну и конечно использованы методы создания транзакций. О них читайте ниже.

И так перейдем непосредственно к созданию транзакции. Это пункт 2 нашего меню.

Сдесь мы указываем получателя , сумму и коментарий.

Используем 2 метода из класса блокчейн , а именно CreateTransaction и ProcessPendingTransactions .

Расмотрим класс Blockchain и их реализацию.

using System;
using System.Collections.Generic;

namespace BlockchainCore
{
public class Blockchain
{
public IList PendingTransactions = new List();
public IList Chain { set; get; }
private int Difficulty { set; get; } = 2;
private int _reward = 5; //5 cryptocurrency
private string comment = null;
public void InitializeChain()
{
Chain = new List();
AddGenesisBlock();
}

private Block CreateGenesisBlock()
{
Block block = new Block(DateTime.Now, null, PendingTransactions);
block.Mine(Difficulty);
PendingTransactions = new List();
return block;
}

private void AddGenesisBlock()
{
Chain.Add(CreateGenesisBlock());
}

private Block GetLatestBlock()
{
return Chain[Chain.Count – 1];
}

public void CreateTransaction(Transaction transaction)
{
PendingTransactions.Add(transaction);
}
public void ProcessPendingTransactions(string minerAddress)
{
Block block = new Block(DateTime.Now, GetLatestBlock().Hash, PendingTransactions);
AddBlock(block);

PendingTransactions = new List();
CreateTransaction(new Transaction(null, minerAddress, _reward,comment));
}

private void AddBlock(Block block)
{
Block latestBlock = GetLatestBlock();
block.Index = latestBlock.Index + 1;
block.PreviousHash = latestBlock.Hash;
block.Hash = block.CalculateHash();
block.Mine(Difficulty);
Chain.Add(block);
}

public bool IsValid()
{
for (int i = 1; i < Chain.Count; i++) { Block currentBlock = Chain[i]; Block previousBlock = Chain[i - 1]; if (currentBlock.Hash != currentBlock.CalculateHash()) { return false; } if (currentBlock.PreviousHash != previousBlock.Hash) { return false; } } return true; } public int GetBalance(string address) { int balance = 0; for (int i = 0; i < Chain.Count; i++) { for (int j = 0; j < Chain[i].Transactions.Count; j++) { var transaction = Chain[i].Transactions[j]; if (transaction.FromAddress == address) { balance -= transaction.Amount; } if (transaction.ToAddress == address) { balance += transaction.Amount; } } } return balance; } } } Снова инициализируем поля, масив транзакций , аксессоры, размер награды. Не забываем об инкапсуляции полей, нам же не хочеся что бы кто то изменил размер награды. И так напомню метод который нам нужен это CreateTransaction, он принимает нашу транзакцию. И добавляет ее в список. namespace BlockchainCore { public class Transaction { public string FromAddress { get; set; } public string ToAddress { get; set; } public int Amount { get; set; } public string Comment { get; set; } public Transaction(string fromAddress, string toAddress, int amount,string comment) { FromAddress = fromAddress; ToAddress = toAddress; Amount = amount; Comment = comment; } } } Далее идет метод оплаты транзакции ProcessPendingTransactions , который в свою очередь принимает адрес майнера или же в нашем случае отправителя. Дело в том, что наша сеть очеень маленькая и разделять узлы как майнеры и юзеры я не хотел . Да и для обучаения базе это не нужно. По этому отправитель и подтвердит свою транзакцию. и получит награджерние. AddBlock добавляем транзакцию в блок и создает его. Расмотрим клас блока. using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; namespace BlockchainCore { public class Block { public int Index { get; set; } private DateTime TimeStamp { get; set; } public string PreviousHash { get; set; } public string Hash { get; set; } public IList Transactions { get; set; }
private int Nonce { get; set; }

public Block(DateTime timeStamp, string previousHash, IList transactions)
{
Index = 0;
TimeStamp = timeStamp;
PreviousHash = previousHash;
Transactions = transactions;
}

public string CalculateHash()
{
var sha256 = SHA256.Create();

var inputBytes = Encoding.ASCII.GetBytes($”{TimeStamp}-{PreviousHash ?? “”}-{JsonConvert.SerializeObject(Transactions)}-{Nonce}”);
var outputBytes = sha256.ComputeHash(inputBytes);

Console.WriteLine(Convert.ToBase64String(outputBytes));

return Convert.ToBase64String(outputBytes);
}

public void Mine(int difficulty)
{
var leadingZeros = new string(‘0’, difficulty);
while (Hash == null || Hash.Substring(0, difficulty) != leadingZeros)
{
Nonce++;
Hash = CalculateHash();
}
}
}
}

private void AddBlock(Block block)
{
Block latestBlock = GetLatestBlock(); // Получает номер предыдущего блока
block.Index = latestBlock.Index + 1; // Указывает номер текущего , создаваемого
block.PreviousHash = latestBlock.Hash; // Указывает хеш предыдущего блока
block.Hash = block.CalculateHash(); // Хеширует текущий блок
block.Mine(Difficulty); // Вызывет медот хеширования и передает сложность , банально какое количество
// нулей нужно что бы хеш считался валидным.
Chain.Add(block); // Добавляем блок в блокчейн
}

Реализация методов описана выше в класе Block.

Третий пункт . В обяснении не нуждается. Спомощью стандартніх пунктов выводим блокчейн на консоль.

Четвертый пункт . Узнаем баланс .

public int GetBalance(string address)
{
int balance = 0;

for (int i = 0; i < Chain.Count; i++) { for (int j = 0; j < Chain[i].Transactions.Count; j++) { var transaction = Chain[i].Transactions[j]; if (transaction.FromAddress == address) { balance -= transaction.Amount; } if (transaction.ToAddress == address) { balance += transaction.Amount; } } } return balance; } Ничего сложного. Просто как веник. Собераем и сумируем все входящие транзакции , кроме тех что были отправлены сами себе. Позже я закоментирую эту часть , так как создать сеть хотя бы из двух узлов у меня нету возможности , да и ради визуализации . На логику программы это повлияет минимально. Ах да , чуть не забыл. Помните в начале я писал что сеть одноранговая и узел является как отправителем так и получателем. Нам нужен еще один класс который поднимет сервер на узле. using Newtonsoft.Json; using System; using System.Collections.Generic; using WebSocketSharp; using WebSocketSharp.Server; namespace BlockchainCore { public class P2PServer: WebSocketBehavior { private bool _chainSynched; private WebSocketServer _wss; public void Start() { _wss = new WebSocketServer($"ws://127.0.0.1:{Program.Port}"); _wss.AddWebSocketService(“/Blockchain”);
_wss.Start();
Console.WriteLine($”Started server at ws://127.0.0.1:{Program.Port}”);

}

protected override void OnMessage(MessageEventArgs e)
{
if (e.Data == “Hi Server”)
{
Console.WriteLine(e.Data);
Send(“Hi Client”);
}
else
{
Blockchain newChain = JsonConvert.DeserializeObject(e.Data);

if (newChain.IsValid() && newChain.Chain.Count > Program.Coin.Chain.Count)
{
List newTransactions = new List();
newTransactions.AddRange(newChain.PendingTransactions);
newTransactions.AddRange(Program.Coin.PendingTransactions);

newChain.PendingTransactions = newTransactions;
Program.Coin = newChain;
}

if (!_chainSynched)
{
Send(JsonConvert.SerializeObject(Program.Coin));
_chainSynched = true;
}
}
}
}
}

Логика не сложная , чем то похожа на клиента. Поднимаем серв, в моем случае на локалке,и принимает или отправляем моенты, страхуемся от ошибок .

Скрины работы.

1. Запускам узел и подключаемся к нему же.

image.png.5aced90234fcc50255dc560fc0ec6f87.png

Создаем транзакцию. Пишем получателя (Сами себя)

image.png.6cd0f3a1ecf9b007b917f48fda9fdd47.png

Сумму и коментарий

Начинается процесс майнинга , его полностью я не покажу так как комбинаций перебора очень много.

Дальше печатем наш Блокчейн. В нем пока 2 блока и 1 транзакция.

1. обозначена нагарда майнера “2” Т.е нас

2. Нулевой блок

3. Наш блок и транзакция самим себе в 100 монет.

Блок у нас содержит 2 транзакции после чего создаем новый.

image.png.42a40dff9ae4bae1dfdc23fb5de827b1.png

И так , если скомпановать это все в кучу , мы получаем рабочий блокчейн , который написали за час. Все концепции и изначальные логики криптовалют были соблюдены. И теперь мы знаем как же работают криптосети. Хочу сказать, и сразу предупредить : я не гуру и мастер , изучать крипту так низкоуровнево я начал не так давно, и в моей статье могут быть ошибки , если вы нашли их напишите , я постараюсь исправить. Также хочу напомнить что это не супер мега готовый криптопроект , а всего лишь простенький макет , который помжет вам на старте изучения сетевого програмирования и криптографии. Не стоит воспринимать это предвзято , я понимаю что сейчас много интересных проектов , которые используют полную анонимность с полиморфизмом пакетов и отправки их всем узлам сети . Чего стоит только CHIA,с их новой концепцией Proof of Space . Возможно в будущем я напишу статью что это такое и как оно работает. А пока у меня все.

Учите програмирования и благодарю вас за прочтение и оценку моей статьи!