Пишем блокчейн и криптовалюту с нуля за час
Ты, наверное, думаешь, что криптовалюты – это супер сложно? Блокчейн, шифрование, ключи? На самом деле все намного проще, чем тебе кажется. И сегодня ты в этом убедишься. Сегодня мы напишем свой блокчейн со своей криптой. Всего за один час. Хочешь большего – делай. Выбор за тобой.
Цель как я и писал – обучение. Школьний блокчейг от боевово отличается только в модернизации , но идея та же. Поняв материал в этой статье ты сможешь написать практически любой альткоин , просто нужно больше временных затрат.
А если ты смотришь на все с комерчиской точки зрения , то я объясню. Сейчас тенденции разработки плавно движутся к децентрализации. Файлообменники, форумы , мессенджеры, и даже валюты можно децентрализировать . Освоив и поняв эти не сложные темы можно будет очень хорошо зарабатывать.
К примеру на основе статьи можно банально написать децентрализированый ботнет.
Я считаю, что статья должна быть обучающая , а не супер схема зароботка 2000. Которую все равно никто не попробует. Да и контингент тут врядли схемы зароботка ищут или темки как воровать крипту , тп.
С самого начала своего пути программирования я интересовался сетями и криптографией. И следовательно с самого начала пути я изучал С, читал документации Сатоши Накамото. Затем начал изучат шарп.
Поискав в инете Я не нашел почти никакой толковой инфы . Видосы по типу “скачайте исходник и смените название на свое” меня не устравали. По этому я начал искать и изучать эту тему усердней.
На каком языке будем писать?
Первое что нужно было решить – На каком языке будем писать? Сразу же я подумал о плюсах. Но порыскав в поиске готовых библиотек сетей , не нашел ничего интересного лично для меня. По этому я понял что идеально подойдет 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<string, WebSocket> wsDict = new Dictionary<string, WebSocket>();
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<Blockchain>(e.Data);
if (!newChain.IsValid() || newChain.Chain.Count <= Program.Coin.Chain.Count) return;
var newTransactions = new List<Transaction>();
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<string> GetServers()
{
IList<string> servers = new List<string>();
foreach (var item in wsDict)
{
servers.Add(item.Key);
}
return servers;
}
public void Close()
{
foreach (var item in wsDict)
{
item.Value.Close();
}
}
}
}
Разберем. Метод написан на базе библиотеки Websock. Скажу честно, частично эту часть я украл. Но что могу сказать что мы принимает блокчейн сети , если он больше собственного , инициализируем подключение и отправляем проверочное сообщение серверу. Методы ниже нужны для управление добавленным блокчейном и закрытия шлющего сокета.
Ну и конечно использованы методы создания транзакций. О них читайте ниже.
И так перейдем непосредственно к созданию транзакции. Это пункт 2 нашего меню.
Сдесь мы указываем получателя , сумму и коментарий.
Используем 2 метода из класса блокчейн , а именно CreateTransaction и ProcessPendingTransactions .
Рассмотрим класс Blockchain и их реализацию.
using System;
using System.Collections.Generic;
namespace BlockchainCore
{
public class Blockchain
{
public IList<Transaction> PendingTransactions = new List<Transaction>();
public IList<Block> 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<Block>();
AddGenesisBlock();
}
private Block CreateGenesisBlock()
{
Block block = new Block(DateTime.Now, null, PendingTransactions);
block.Mine(Difficulty);
PendingTransactions = new List<Transaction>();
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<Transaction>();
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<Transaction> Transactions { get; set; }
private int Nonce { get; set; }
public Block(DateTime timeStamp, string previousHash, IList<Transaction> 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;
}
Далее идет метод оплаты транзакции 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<Transaction> Transactions { get; set; } private int Nonce { get; set; } public Block(DateTime timeStamp, string previousHash, IList<Transaction> 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); // Добавляем блок в блокчейн }
Ничего сложного. Просто как веник. Собераем и сумируем все входящие транзакции , кроме тех что были отправлены сами себе. Позже я закоментирую эту часть , так как создать сеть хотя бы из двух узлов у меня нету возможности , да и ради визуализации . На логику программы это повлияет минимально.
Ах да , чуть не забыл. Помните в начале я писал что сеть одноранговая и узел является как отправителем так и получателем. Нам нужен еще один класс который поднимет сервер на узле.
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<P2PServer>("/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<Blockchain>(e.Data); if (newChain.IsValid() && newChain.Chain.Count > Program.Coin.Chain.Count) { List<Transaction> newTransactions = new List<Transaction>(); 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. Запускам узел и подключаемся к нему же.
Создаем транзакцию. Пишем получателя (Сами себя):
Сумму и коментарий
Начинается процесс майнинга , его полностью я не покажу так как комбинаций перебора очень много.
Дальше печатем наш Блокчейн. В нем пока 2 блока и 1 транзакция.
1. обозначена нагарда майнера “2” Т.е нас
2. Нулевой блок
3. Наш блок и транзакция самим себе в 100 монет.
Блок у нас содержит 2 транзакции после чего создаем новый.
И так , если скомпановать это все в кучу , мы получаем рабочий блокчейн , который написали за час. Все концепции и изначальные логики криптовалют были соблюдены. И теперь мы знаем как же работают криптосети. Хочу сказать, и сразу предупредить : я не гуру и мастер , изучать крипту так низкоуровнево я начал не так давно, и в моей статье могут быть ошибки , если вы нашли их напишите , я постараюсь исправить. Также хочу напомнить что это не супер мега готовый криптопроект , а всего лишь простенький макет , который помжет вам на старте изучения сетевого програмирования и криптографии. Не стоит воспринимать это предвзято , я понимаю что сейчас много интересных проектов , которые используют полную анонимность с полиморфизмом пакетов и отправки их всем узлам сети . Чего стоит только CHIA,с их новой концепцией Proof of Space . Возможно в будущем я напишу статью что это такое и как оно работает. А пока у меня все.
Полезные ресурсы:
https://github.com/bitcoin/bitcoin
https://github.com/Number571/gopeer/blob/master/hiddensystems.pdf
https://github.com/Number571/Blockchain/blob/master/_example/blockchain.pdf
https://ru.wikipedia.org/wiki/Биткоин
Ну и еще парочка сайтов с описаниями работы крипты, уже и не вспомню.
А гугл за час слабо?)
Самая безграмотная статья в моей жизни. Ужасно, дальше читать не стал.
Объясните пожалуйста, почему вы так считаете? Мы с удовольствием исправим
Это, конечно, хорошо, но хорошо бы и источник публиковать
github – Amine-Smahi/Blockchain-P2P-Network