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

  • Автор темы Admin

Admin

#1
Администратор
Регистрация
31.12.2019
Сообщения
7,535
Реакции
36
Всем привет. С самого начала своего пути програмирования я интересовался сетями и криптографией. И следовательно с самого начала пути я изучал С, читал документации Сатоси Накамото. Затем начал изучат шарп. Как только я увидел конкурс связаный с криптовалютами , сразу решил поучаствовать.
Почему я выбрал именно эту тему?
Поискав в инете Я не нашел почти никакой толковой инфы . Видосы по типу "скачайте исходник и смените название на свое" меня не устравал. По этому
я начал искать и изучать эту тему усердней.

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

Первое что нужно было решить - На каком языке будем писать? Сразу же я подумал о плюсах. Но порыскав в поиске готовых библиотек сетей , не нашел ничего интересного лично для меня. По этому я понял что идевльно подойдет 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;
        }

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

Ах да , чуть не забыл. Помните в начале я писал что сеть одноранговая и узел является как отправителем так и получателем. Нам нужен еще один класс который поднимет сервер на узле.

Код:
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. Запускам узел и подключаемся к нему же.

663cb0120e1e0db51a833.png


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

f326d124e691683cdb5f0.png


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

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

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

1. обозначена нагарда майнера "2" Т.е нас
2. Нулевой блок
3. Наш блок и транзакция самим себе в 100 монет.
Блок у нас содержит 2 транзакции после чего создаем новый.

1056361d9afb9cd3d4c2e.png


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

Исходники:
https://vk.cc/c1DYar

Полезные ресурсы:
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/Биткин
Ну и еще парочка сайтов с описаниями работы крипты ,уже и не вспомню.

Автор: Yasobaka
 

Members, viewing this thread

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