Блокчейн Криптовалюты Обучение

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

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

Ты, наверное, думаешь, что криптовалюты – это супер сложно? Блокчейн, шифрование, ключи? На самом деле все намного проще, чем тебе кажется. И сегодня ты в этом убедишься. Сегодня мы напишем свой блокчейн со своей криптой. Всего за один час. Хочешь большего – делай. Выбор за тобой.

Цель как я и писал – обучение. Школьний блокчейг от боевово отличается только в модернизации , но идея та же. Поняв материал в этой статье ты сможешь написать практически любой альткоин , просто нужно больше временных затрат. 

А если ты смотришь на все с комерчиской точки зрения , то я объясню. Сейчас тенденции разработки плавно движутся к децентрализации.  Файлообменники,  форумы , мессенджеры,  и даже валюты можно децентрализировать . Освоив и поняв эти не сложные темы можно будет очень хорошо зарабатывать. 

К примеру на основе статьи можно банально написать децентрализированый ботнет.

Я считаю, что статья должна быть обучающая , а не супер схема зароботка 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/Биткоин

Ну и еще парочка сайтов с описаниями работы крипты, уже и не вспомню.

Очень злой админ
Очень злой админ Автор статьи

Админ сайта. Публикует интересные статьи с других ресурсов, либо их переводы. Если есть настроение, бывает, что пишет и что-то своё.

Комментарии

  1. Самая безграмотная статья в моей жизни. Ужасно, дальше читать не стал.

  2. Это, конечно, хорошо, но хорошо бы и источник публиковать
    github – Amine-Smahi/Blockchain-P2P-Network

Leave a Reply

Your email address will not be published. Required fields are marked *