portaldacalheta.pt
  • Основен
  • Инструменти И Уроци
  • Agile Talent
  • Продукти Хора И Екипи
  • Растеж На Приходите
Технология

Създаване на криптовалута в езика за програмиране на Crystal



Тази публикация е опитът ми да разбера ключовите аспекти на блокчейна, като изследвам вътрешните органи. Започнах с четене на оригинален документ за биткойн , но почувствах, че единственият начин да разбера истински блокчейна е чрез изграждането на нова криптовалута от нулата.

Ето защо реших да създам криптовалута, използвайки новия език за програмиране Crystal, и я нарекох CrystalCoin . Тази статия няма да обсъжда избора на алгоритъм, хеш трудността или подобни теми. Вместо това фокусът ще бъде върху детайлизирането на конкретен пример, който трябва да осигури по-задълбочено, практическо разбиране на силните страни и ограниченията на блокчейните.



Ако още не сте го прочели, за повече информация относно алгоритмите и хеширането, ви предлагам да разгледате статията на Демир Селманович Криптовалута за манекени: Биткойн и след това .



Защо избрах езика за програмиране Crystal

За по-добра демонстрация исках да използвам продуктивен език като Руби без да се нарушава производителността. Криптовалутата има много отнемащи време изчисления (а именно минен и хеширане ) и затова компилираните езици като C ++ и Java са избраните езици за изграждане на „истински“ криптовалути. Като се има предвид това, исках да използвам език с по-изчистен синтаксис, за да мога да запазя развитието забавно и да позволя по-добра четливост. Кристалните характеристики и без това са добри.



Илюстрация на език за програмиране на кристал

И така, защо реших да използвам Кристален език за програмиране ? Синтаксисът на Crystal е силно вдъхновен от Ruby’s, така че за мен е естествено да чета и да пиша лесно. Той има допълнителната полза от по-ниската крива на обучение, особено за опитни разработчици на Ruby.



Ето как екипът на Crystal lang го поставя на официалния си уебсайт:

Бърз като C, хлъзгав като Ruby.



Въпреки това, за разлика от Ruby или JavaScript, които са интерпретирани езици, Crystal е компилиран език, което го прави много по-бърз и предлага по-малък отпечатък на паметта. Под капака използва LLVM за компилиране в собствен код.

Crystal също се набира статично, което означава, че компилаторът ще ви помогне да улавяте грешки в типа по време на компилация.



Няма да обяснявам защо считам Crystal езика за страхотен, защото е извън обхвата на тази статия, но ако не смятате, че оптимизмът ми е убедителен, не се колебайте да проверите тази статия за по-добър преглед на потенциала на Crystal.

как да направим език за кодиране

Забележка: Тази статия предполага, че вече имате основни познания за обектно-ориентираното програмиране (OOP).



Блокчейн

И така, какво е блокчейн? Това е списък (верига) от блокове, свързани и защитени с цифрови пръстови отпечатъци (известни също като крипто хешове).

Най-лесният начин да мислите за него е като свързана структура от данни на списъка. Като се има предвид това, свързан списък се изисква само да има препратка към предишния елемент; блокът трябва да има идентификатор в зависимост от идентификатора на предишния блок, което означава, че не можете да замените блок, без да преизчислявате всеки отделен блок, който идва след това.



Засега мислете за блокчейн като поредица от блокове с някои данни, свързани с верига, като веригата е хеш на предишния блок.

Цялата блокчейн би съществувала на всеки възел, който иска да взаимодейства с него, което означава, че се копира на всеки един от възлите в мрежата. Нито един сървър не го хоства, но всички компании за разработка на блокчейн използвайте го, което го прави децентрализиран .

Да, това е странно в сравнение с конвенционалните централизирани системи. Всеки от възлите ще има копие на цялата блокчейн (> 149 Gb в биткойн блокчейн от Декември 2017 ).

Хеширане и цифров подпис

И така, каква е тази хеш функция? Мислете за хеш като за функция, която връща уникален пръстов отпечатък, когато му дадем текст / обект. Дори най-малката промяна във входния обект би променила драстично пръстовия отпечатък.

Има различни алгоритми за хеширане и в тази статия ще използваме SHA256 хеш алгоритъм, който се използва в Bitcoin.

Използване на SHA256 винаги ще имаме 64 шестнадесетични символа (256 бита) с дължина, дори ако входът е по-малък от 256 бита или много по-голям от 256 бита:

Вход Хеширани резултати
Много дълго ТЕКСТ много дълго ТЕКСТ много дълго ТЕКСТ много дълго ТЕКСТ много дълго ТЕКСТ много дълго TEX много дълго ТЕКСТ много дълго много дълго ТЕКСТ много дълго ТЕКСТ много дълго ТЕКСТ много дълго ТЕКСТ много дълго ТЕКСТ много дълго ТЕКСТ много дълго ТЕКСТ много дълго ТЕКСТ много дълго ТЕКСТ МНОГО ДЪЛГ ТЕКСТ МНОГО ДЪЛГ ТЕКСТ cf49bbb21c8b7c078165919d7e57c145ccb7f398e7b58d9a3729de368d86294a
Маймунско бягство 2e4e500e20f1358224c08c7fb7d3e0e9a5e4ab7a013bfd6774dfa54d7684dd21
Маймунско бягство. 12075307ce09a6859601ce9d451d385053be80238ea127c5df6e6611eed7c6f0

Забележете с последния пример, че просто добавяне на . (точка) доведе до драстична промяна в хеша.

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

Изграждане на криптовалута в Crystal

Сега нека започнем да създаваме нашия Crystal проект и да изградим нашите SHA256 криптиране.

Ако приемем, че имате Инсталиран е език за програмиране Crystal , нека създадем скелета на CrystalCoin codebase с помощта на вградения инструмент за проектиране на Crystal crystal init app [name]:

% crystal init app crystal_coin create crystal_coin/.gitignore create crystal_coin/.editorconfig create crystal_coin/LICENSE create crystal_coin/README.md create crystal_coin/.travis.yml create crystal_coin/shard.yml create crystal_coin/src/crystal_coin.cr create crystal_coin/src/crystal_coin/version.cr create crystal_coin/spec/spec_helper.cr create crystal_coin/spec/crystal_coin_spec.cr Initialized empty Git repository in /Users/eki/code/crystal_coin/.git/

Тази команда ще създаде основната структура за проекта, с вече инициализирано git хранилище, файлове за лиценз и readme. Той също така се предлага с заглушки за тестове и shard.yml файл за описание на проекта и управление на зависимости, известен също като парчета.

Нека добавим openssl парче, което е необходимо за изграждане SHA256 алгоритъм:

# shard.yml dependencies: openssl: github: datanoise/openssl.cr

След като влезете, върнете се обратно в терминала си и стартирайте crystal deps. Правейки това ще изтегли openssl и неговите зависимости, които да използваме.

Сега имаме инсталирана необходимата библиотека в нашия код, нека започнем с дефиниране Block клас и след това изграждане на хеш функцията.

# src/crystal_coin/block.cr require 'openssl' module CrystalCoin class Block def initialize(data : String) @data = data end def hash hash = OpenSSL::Digest.new('SHA256') hash.update(@data) hash.hexdigest end end end puts CrystalCoin::Block.new('Hello, Cryptos!').hash

Вече можете да тествате приложението си, като стартирате crystal run crystal src/crystal_coin/block.cr от вашия терминал.

crystal_coin [master●] % crystal src/crystal_coin/block.cr 33eedea60b0662c66c289ceba71863a864cf84b00e10002ca1069bf58f9362d5

Проектиране на нашата Blockchain

Всеки блок се съхранява с timestamp и, по желание, index. В CrystalCoin ще съхраним и двете. За да се гарантира целостта на цялата верига блокчета, всеки блок ще има самоидентифициране хеш . Подобно на Bitcoin, хешът на всеки блок ще бъде криптографски хеш на блока (index, timestamp, data и хешът на хеш на предишния блок previous_hash). Засега данните могат да бъдат всичко, което искате.

module CrystalCoin class Block property current_hash : String def initialize(index = 0, data = 'data', previous_hash = 'hash') @data = data @index = index @timestamp = Time.now @previous_hash = previous_hash @current_hash = hash_block end private def hash_block hash = OpenSSL::Digest.new('SHA256') hash.update('#{@index}#{@timestamp}#{@data}#{@previous_hash}') hash.hexdigest end end end puts CrystalCoin::Block.new(data: 'Same Data').current_hash

В Crystal lang заместваме Ruby’s attr_accessor, attr_getter и attr_setter методи с нови ключови думи:

Ключова дума Ruby Кристална ключова дума
attr_accessor Имот
attr_reader гетер
attr_writer сетер

Друго нещо, което може би сте забелязали в Crystal е, че искаме да намекнем на компилатора за конкретни типове чрез нашия код. Crystal извежда типовете, но винаги, когато имате неяснота, можете изрично да декларирате и типове. Ето защо добавихме String типове за current_hash.

Сега нека пуснем block.cr два пъти и имайте предвид, че едни и същи данни ще генерират различни хешове поради различните timestamp:

crystal_coin [master●] % crystal src/crystal_coin/block.cr 361d0df74e28d37b71f6c5f579ee182dd3d41f73f174dc88c9f2536172d3bb66 crystal_coin [master●] % crystal src/crystal_coin/block.cr b1fafd81ba13fc21598fb083d9429d1b8a7e9a7120dbdacc7e461791b96b9bf3

Сега имаме наша блокова структура, но ние създаваме блокчейн. Трябва да започнем да добавяме блокове, за да образуваме действителна верига. Както споменах по-рано, всеки блок изисква информация от предишния блок. Но как първият блок в блокчейна стига до там? Е, първият блок или genesis блок, е специален блок (блок без предшественици). В много случаи се добавя ръчно или има уникална логика, позволяваща добавянето му.

Ще създадем нова функция, която връща блок за генезис. Този блок е от index=0 и има произволна стойност на данните и произволна стойност в previous_hash параметър.

Нека да изградим или метод на клас Block.first който генерира генезисния блок:

module CrystalCoin class Block ... def self.first(data='Genesis Block') Block.new(data: data, previous_hash: '0') end ... end end

И нека тестваме, използвайки p CrystalCoin::Block.first:

#

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

Тази функция ще вземе предишния блок във веригата като параметър, ще създаде данните за генерирания блок и ще върне новия блок със съответните данни. Когато новите блокове хешират информация от предишни блокове, целостта на блокчейна се увеличава с всеки нов блок.

Важно последствие е, че блок не може да бъде модифициран, без да се променя хешът на всеки последователен блок. Това е показано в примера по-долу. Ако данните в блок 44 се променят от LOOP до EAST, всички хешове на последователните блокове трябва да бъдат променени. Това е така, защото хешът на блока зависи от стойността на previous_hash (наред с други неща).

Диаграма за хеширане на кристална криптовалута

как да получите терминал на bloomberg

Ако не направихме това, би било по-лесно за външна страна да промени данните и да замени нашата верига с изцяло нова собствена. Тази верига от хешове действа като криптографско доказателство и помага да се гарантира, че след като блокът бъде добавен към блокчейна, той не може да бъде заменен или премахнат. Нека създадем метода на класа Block.next:

module CrystalCoin class Block ... def self.next(previous_node, data = 'Transaction Data') Block.new( data: 'Transaction data number (#{previous_node.index + 1})', index: previous_node.index + 1, previous_hash: previous_hash.hash ) end ... end end

За да изпробваме всичко заедно, ще създадем проста блокчейн. Първият елемент от списъка е блокът на генезиса. И разбира се, трябва да добавим следващите блокове. Ще създадем пет нови блока, за да демонстрираме CrystalCoin:

blockchain = [ CrystalCoin::Block.first ] previous_block = blockchain[0] 5.times do new_block = CrystalCoin::Block.next(previous_block: previous_block) blockchain << new_block previous_block = new_block end p blockchain [#, #, #, #, #, #

Доказателство за работа

Алгоритъм за доказателство за работа (PoW) е начинът, по който се създават или блокират нови блокове добит на блокчейна. Целта на PoW е ​​да открие число, което решава проблем. Номерът трябва да е труден за намиране, но лесен за изчислителна проверка от всеки в мрежата. Това е основната идея зад Proof of Work.

Нека демонстрираме с пример, за да сме сигурни, че всичко е ясно. Ще приемем, че хешът на някакво цяло число x, умножено по друго y, трябва да започва с 00. Така:

hash(x * y) = 00ac23dc...

И за този опростен пример, нека поправим x=5 и внедрете това в Crystal:

x = 5 y = 0 while hash((x*y).to_s)[0..1] != '00' y += 1 end puts 'The solution is y = #{y}' puts 'Hash(#{x}*#{y}) = #{hash((x*y).to_s)}'

Нека пуснем кода:

crystal_coin [master●●] % time crystal src/crystal_coin/pow.cr The solution is y = 530 Hash(5*530) = 00150bc11aeeaa3cdbdc1e27085b0f6c584c27e05f255e303898dcd12426f110 crystal src/crystal_coin/pow.cr 1.53s user 0.23s system 160% cpu 1.092 total

Както можете да видите това число y=530 беше трудно да се намери (груба сила), но беше лесно да се провери с помощта на хеш функцията.

Защо да се занимавам с този алгоритъм на PoW? Защо просто не създадем по един хеш на блок и това е всичко? Хеш трябва да бъде валиден . В нашия случай хеш ще бъде валиден, ако първите два знака от нашия хеш са 00. Ако нашият хеш започва с 00......, той се счита за валиден. Това се нарича трудност . Колкото по-голяма е трудността, толкова по-дълго е необходимо да се получи валиден хеш.

Но ако хешът не е валиден за първи път, нещо трябва да се промени в данните, които използваме. Ако използваме едни и същи данни отново и отново, ще получаваме един и същ хеш отново и отново и нашият хеш никога няма да бъде валиден. Използваме нещо, наречено nonce в нашия хеш (в нашия предишен пример това е y). Това е просто число, което увеличаваме всеки път, когато хешът не е валиден. Получаваме нашите данни (дата, съобщение, предишен хеш, индекс) и nonce от 1. Ако хешът, който получаваме с тях, не е валиден, опитваме с nonce от 2. И увеличаваме nonce, докато получим валиден хеш .

В биткойн се извиква алгоритъмът Proof of Work Hashcash . Нека добавим доказателство за работа към нашия клас Block и да започнем минен за да намерим нонса. Ще използваме твърдо кодиран трудност от две водещи нули „00“:

Сега нека преработим нашия клас Block, за да го подкрепим. Нашите CrystalCoin Блокът ще съдържа следните атрибути:

1) index: indicates the index of the block ex: 0,1 2) timestamp: timestamp in epoch, number of seconds since 1 Jan 1970 3) data: the actual data that needs to be stored on the blockchain. 4) previous_hash: the hash of the previous block, this is the chain/link between the blocks 5) nonce: this is the number that is to be mined/found. 6) current_hash: The hash value of the current block, this is generated by combining all the above attributes and passing it to a hashing algorithm

алтернативен текст на изображението

Ще създам отделен модул, за да направя хеширането и да намеря nonce така че поддържаме нашия код чист и модулен. Ще го нарека proof_of_work.cr:

require 'openssl' module CrystalCoin module ProofOfWork private def proof_of_work(difficulty = '00') nonce = 0 loop do hash = calc_hash_with_nonce(nonce) if hash[0..1] == difficulty return nonce else nonce += 1 end end end private def calc_hash_with_nonce(nonce = 0) sha = OpenSSL::Digest.new('SHA256') sha.update('#{nonce}#{@index}#{@timestamp}#{@data}#{@previous_hash}') sha.hexdigest end end end

Нашите Block клас би изглеждал по следния начин:

require './proof_of_work' module CrystalCoin class Block include ProofOfWork property current_hash : String property index : Int32 property nonce : Int32 property previous_hash : String def initialize(index = 0, data = 'data', previous_hash = 'hash') @data = data @index = index @timestamp = Time.now @previous_hash = previous_hash @nonce = proof_of_work @current_hash = calc_hash_with_nonce(@nonce) end def self.first(data = 'Genesis Block') Block.new(data: data, previous_hash: '0') end def self.next(previous_block, data = 'Transaction Data') Block.new( data: 'Transaction data number (#{previous_block.index + 1})', index: previous_block.index + 1, previous_hash: previous_block.current_hash ) end end end

Малко неща, които трябва да се отбележат за кода на Crystal и примери за език на Crystal като цяло. В Crystal методите са публични по подразбиране. Crystal изисква всеки частен метод да бъде с префикс с частната ключова дума, която може да е объркваща, идваща от Ruby.

Може би сте забелязали, че Integer типовете на Crystal има Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, или UInt64 в сравнение с Ruby’s Fixnum. true и false са стойности в Bool клас, а не стойности в класове TrueClass или FalseClass в Руби.

Crystal има незадължителни и именувани аргументи на метода като основни функции на езика и не изисква писане на специален код за обработка на аргументите, което е доста готино. Вижте Block#initialize(index = 0, data = 'data', previous_hash = 'hash') и след това да го извикате с нещо като Block.new(data: data, previous_hash: '0').

За по-подробен списък на разликите между езика за програмиране Crystal и Ruby вижте Кристал за рубисти .

Сега, нека се опитаме да създадем пет транзакции, като използваме:

blockchain = [ CrystalCoin::Block.first ] puts blockchain.inspect previous_block = blockchain[0] 5.times do |i| new_block = CrystalCoin::Block.next(previous_block: previous_block) blockchain << new_block previous_block = new_block puts new_block.inspect end [#] # # # # #

Виждате ли разликата? Сега всички хешове започват с 00. Това е магията на доказателството за работа. Използване на ProofOfWork намерихме nonce а доказателство е хешът със съвпадение на трудността, тоест двете водещи нули 00.

Забележете с първия блок, който създадохме, опитахме 17 нонса, докато намерим съответстващото щастливо число:

Блок Цикли / брой изчисления на хеш
# 0 17
#one 24
# 2 61
# 3 149
# 4 570
# 5 475

Сега нека опитаме трудност от четири водещи нули (difficulty='0000'):

Блок Цикли / брой изчисления на хеш
#one 26 762
# 2 68 419
# 3 23 416
# 4 15 353

В първия блок се опитаха 26762 нонса (сравнете 17 нонса с трудност ‘00’), докато се намери съответстващото щастливо число.

Нашият блокчейн като API

Дотук добре. Създадохме нашата проста блокчейн и беше относително лесно да се направи. Но проблемът тук е, че CrystalCoin може да работи само на една машина (не е разпределена / децентрализирана).

Отсега нататък ще започнем да използваме JSON данни за CrystalCoin. Данните ще бъдат транзакции, така че полето за данни на всеки блок ще бъде списък с транзакции.

Всяка транзакция ще бъде JSON обект с подробности за sender на монетата, receiver на монетата, а amount на CrystalCoin, който се прехвърля:

{ 'from': '71238uqirbfh894-random-public-key-a-alkjdflakjfewn204ij', 'to': '93j4ivnqiopvh43-random-public-key-b-qjrgvnoeirbnferinfo', 'amount': 3 }

Няколко модификации на нашите Block клас за поддръжка на новия transaction формат (наречен по-рано data). Така че, за да избегнем объркване и да запазим последователност, ще използваме термина transaction да се позовава на data публикувано в нашето примерно приложение оттук нататък.

Ще представим нов прост клас Transaction:

module CrystalCoin class Block class Transaction property from : String property to : String property amount : Int32 def initialize(@from, @to, @amount) end end end end

Транзакциите са пакетирани в блокове, така че блок може да съдържа само една или множество транзакции. Блоковете, съдържащи транзакциите, се генерират често и се добавят към блокчейна.

Предполага се, че блокчейнът е колекция от блокове. Можем да съхраняваме всички блокове в списъка Crystal и затова представяме новия клас Blockchain:

Blockchain ще има chain и uncommitted_transactions масиви. chain ще включва всички извлечени блокове в блокчейна и uncommitted_transactions ще има всички транзакции, които не са добавени към блокчейна (все още не са минирани). След като инициализираме Blockchain, ние създаваме генезисния блок, използвайки Block.first и го добавете към chain масив и добавяме празен uncommitted_transactions масив.

Ще създадем Blockchain#add_transaction метод за добавяне на транзакции към uncommitted_transactions масив.

Нека да изградим нашия нов Blockchain клас:

require './block' require './transaction' module CrystalCoin class Blockchain getter chain getter uncommitted_transactions def initialize @chain = [ Block.first ] @uncommitted_transactions = [] of Block::Transaction end def add_transaction(transaction) @uncommitted_transactions << transaction end end end

В Block клас ще започнем да използваме transactions вместо data:

module CrystalCoin class Block include ProofOfWork def initialize(index = 0, transactions = [] of Transaction, previous_hash = 'hash') @transactions = transactions ... end .... def self.next(previous_block, transactions = [] of Transaction) Block.new( transactions: transactions, index: previous_block.index + 1, previous_hash: previous_block.current_hash ) end end end

Сега, когато знаем как ще изглеждат транзакциите ни, имаме нужда от начин да ги добавим към един от компютрите в нашата блокчейн мрежа, наречен | node За целта ще създадем прост HTTP сървър.

Ще създадем четири крайни точки:

  • [POST] /transactions/new: за създаване на нова транзакция към блок
  • [GET] /mine: да кажем на нашия сървър да копае нов блок.
  • [GET] /chain: за връщане на пълната блокчейн в JSON формат.
  • [GET] /pending: за връщане на чакащите транзакции (uncommitted_transactions).

Ще използваме Зрелост уеб рамка. Това е микро-рамка, която улеснява картографирането на крайните точки към функциите на Crystal. Кемал е силно повлиян от Синатра за рубистите и работи по много подобен начин. Ако търсите Рубин на релси еквивалент, след това проверете Амбър .

Нашият сървър ще формира един възел в нашата блокчейн мрежа. Нека първо добавим Kemal към shard.yml файл като a и инсталирайте зависимостта:

dependencies: kemal: github: kemalcr/kemal

Сега нека изградим скелета на нашия HTTP сървър:

# src/server.cr require 'kemal' require './crystal_coin' # Generate a globally unique address for this node node_identifier = UUID.random.to_s # Create our Blockchain blockchain = Blockchain.new get '/chain' do 'Send the blockchain as json objects' end get '/mine' do 'We'll mine a new Block' end get '/pending' do 'Send pending transactions as json objects' end post '/transactions/new' do 'We'll add a new transaction' end Kemal.run

И стартирайте сървъра:

crystal_coin [master●●] % crystal run src/server.cr [development] Kemal is ready to lead at http://0.0.0.0:3000

Нека се уверим, че сървърът работи нормално:

списък на фондове за частни капиталови инвестиции
% curl http://0.0.0.0:3000/chain Send the blockchain as json objects%

Дотук добре. Сега можем да продължим с прилагането на всяка от крайните точки. Нека започнем с внедряване /transactions/new и pending крайни точки:

get '/pending' do { transactions: blockchain.uncommitted_transactions }.to_json end post '/transactions/new' do |env| transaction = CrystalCoin::Block::Transaction.new( from: env.params.json['from'].as(String), to: env.params.json['to'].as(String), amount: env.params.json['amount'].as(Int64) ) blockchain.add_transaction(transaction) 'Transaction #{transaction} has been added to the node' end

Правоприлагане. Ние просто създаваме CrystalCoin::Block::Transaction обект и добавете транзакцията към uncommitted_transactions масив, използващ Blockchain#add_transaction.

В момента транзакциите първоначално се съхраняват в пул от uncommitted_transactions. Процесът на поставяне на непотвърдените транзакции в блок и изчисляване на доказателство за работа (PoW) е известен като минен на блокове. След като nonce удовлетворяването на нашите ограничения е разбрано, можем да кажем, че блок е добит и новият блок е поставен в блокчейна.

В CrystalCoin ще използваме простия алгоритъм Proof-of-Work, който създадохме по-рано. За да създаде нов блок, компютърът на миньор трябва да:

  • Намерете последния блок в chain.
  • Намерете чакащи транзакции (uncommitted_transactions).
  • Създайте нов блок с помощта на Block.next.
  • Добавете добития блок към chain масив.
  • Почистване uncommitted_transactions масив.

Така че да се приложи /mine крайна точка, нека първо приложим горните стъпки в Blockchain#mine:

module CrystalCoin class Blockchain include Consensus BLOCK_SIZE = 25 ... def mine raise 'No transactions to be mined' if @uncommitted_transactions.empty? new_block = Block.next( previous_block: @chain.last, transactions: @uncommitted_transactions.shift(BLOCK_SIZE) ) @chain << new_block end end end

Първо се уверяваме, че имаме някои изчакващи транзакции за копаене. След това получаваме последния блок, използвайки @chain.last, и първия 25 транзакции, които трябва да бъдат извлечени (използваме Array#shift(BLOCK_SIZE), за да върнем масив от първите 25 uncommitted_transactions и след това да премахнем елементите, започващи от индекс 0).

Сега нека приложим /mine крайна точка:

get '/mine' do blockchain.mine 'Block with index=#{blockchain.chain.last.index} is mined.' end

И за /chain крайна точка:

get '/chain' do { chain: blockchain.chain }.to_json end

Взаимодействие с нашия блокчейн

Ще използваме cURL за взаимодействие с нашия API през мрежа.

Първо, нека запалим сървъра:

crystal_coin [master] % crystal run src/server.cr [development] Kemal is ready to lead at http://0.0.0.0:3000

След това нека създадем две нови транзакции, като направим POST искане до http://localhost:3000/transactions/new с тяло, съдържащо нашата структура на транзакциите:

crystal_coin [master●] % curl -X POST http://0.0.0.0:3000/transactions/new -H 'Content-Type: application/json' -d '{'from': 'eki', 'to':'iron_man', 'amount': 1000}' Transaction # has been added to the node% crystal_coin [master●] % curl -X POST http://0.0.0.0:3000/transactions/new -H 'Content-Type: application/json' -d '{'from': 'eki', 'to':'hulk', 'amount': 700}' Transaction # has been added to the node%

Сега нека изброим чакащите транзакции (т.е. транзакции, които все още не са добавени към блока):

crystal_coin [master●] % curl http://0.0.0.0:3000/pendings { 'transactions':[ { 'from':'ekis', 'to':'huslks', 'amount':7090 }, { 'from':'ekis', 'to':'huslks', 'amount':70900 } ] }

Както виждаме, двете транзакции, които създадохме по-рано, бяха добавени към uncommitted_transactions.

Сега нека моята двете транзакции чрез извършване на GET заявка до http://0.0.0.0:3000/mine:

crystal_coin [master●] % curl http://0.0.0.0:3000/mine Block with index=1 is mined.

Изглежда, че успешно копахме първия блок и го добавихме към нашите chain. Нека проверим отново нашите chain и ако включва минирания блок:

crystal_coin [master●] % curl http://0.0.0.0:3000/chain { 'chain': [ { 'index': 0, 'current_hash': '00d469d383005b4303cfa7321c02478ce76182564af5d16e1a10d87e31e2cb30', 'nonce': 363, 'previous_hash': '0', 'transactions': [ ], 'timestamp': '2018-05-23T01:59:52+0300' }, { 'index': 1, 'current_hash': '003c05da32d3672670ba1e25ecb067b5bc407e1d5f8733b5e33d1039de1a9bf1', 'nonce': 320, 'previous_hash': '00d469d383005b4303cfa7321c02478ce76182564af5d16e1a10d87e31e2cb30', 'transactions': [ { 'from': 'ekis', 'to': 'huslks', 'amount': 7090 }, { 'from': 'ekis', 'to': 'huslks', 'amount': 70900 } ], 'timestamp': '2018-05-23T02:02:38+0300' } ] }

Консенсус и децентрализация

Това е готино. Набавихме си основна блокчейн, която приема транзакции и ни позволява да копаем нови блокове. Но кодът, който сме прилагали досега, е предназначен да работи на един компютър, докато целият смисъл на блокчейн е, че те трябва да бъдат децентрализирани. Но ако те са децентрализирани, как гарантираме, че всички те отразяват една и съща верига?

Това е проблемът на Consensus.

Ще трябва да внедрим консенсус алгоритъм, ако искаме повече от един възел в нашата мрежа.

Регистриране на нови възли

За да приложим консенсусен алгоритъм, ни е необходим начин да информираме възел за съседните възли в мрежата. Всеки възел в нашата мрежа трябва да поддържа регистър на други възли в мрежата. Следователно ще ни трябват повече крайни точки:

  • [POST] /nodes/register: за приемане на списък с нови възли под формата на URL адреси.
  • [GET] /nodes/resolve: за прилагане на нашия консенсусен алгоритъм, който разрешава всички конфликти - за да се гарантира, че възелът има правилната верига.

Трябва да модифицираме конструктора на нашия блокчейн и да предоставим метод за регистриране на възли:

--- a/src/crystal_coin/blockchain.cr +++ b/src/crystal_coin/blockchain.cr @@ -7,10 +7,12 @@ module CrystalCoin getter chain getter uncommitted_transactions + getter nodes def initialize @chain = [ Block.first ] @uncommitted_transactions = [] of Block::Transaction + @nodes = Set(String).new [] of String end def add_transaction(transaction)

Имайте предвид, че сме използвали Set структура на данните с String въведете, за да задържите списъка с възли. Това е евтин начин да се гарантира, че добавянето на нови възли е идемпотентно и че независимо колко пъти добавяме конкретен възел, той се появява точно веднъж.

Сега нека добавим нов модул към Consensus и внедрете първия метод register_node(address):

require 'uri' module CrystalCoin module Consensus def register_node(address : String) uri = URI.parse(address) node_address = '#{uri.scheme}:://#{uri.host}' node_address = '#{node_address}:#{uri.port}' unless uri.port.nil? @nodes.add(node_address) rescue raise 'Invalid URL' end end end

register_node функция, ще анализира URL адреса на възела и ще го форматира.

И тук нека създадем /nodes/register крайна точка:

post '/nodes/register' do |env| nodes = env.params.json['nodes'].as(Array) raise 'Empty array' if nodes.empty? nodes.each do |node| blockchain.register_node(node.to_s) end 'New nodes have been added: #{blockchain.nodes}' end

Сега с тази реализация може да се сблъскаме с проблем с множество възли. Копието на вериги от няколко възли може да се различава. В този случай трябва да се споразумеем за някаква версия на веригата, за да поддържаме целостта на цялата система. Трябва да постигнем консенсус.

За да разрешим това, ще направим правилото, че най-дългата валидна верига е тази, която ще се използва. Използвайки този алгоритъм, ние постигаме консенсус между възлите в нашата мрежа. Причината зад този подход е, че най-дългата верига е добра оценка за най-много свършена работа.

алтернативен текст на изображението

module CrystalCoin module Consensus ... def resolve updated = false @nodes.each do |node| node_chain = parse_chain(node) return unless node_chain.size > @chain.size return unless valid_chain?(node_chain) @chain = node_chain updated = true rescue IO::Timeout puts 'Timeout!' end updated end ... end end

Имайте предвид, че resolve е метод, който циклично преминава през всички наши съседни възли, изтегля техните вериги и ги проверява с помощта на valid_chain? метод. Ако бъде намерена валидна верига, чиято дължина е по-голяма от нашата, ние заместваме нашата.

Сега нека приложим parse_chain() и valid_chain?() частни методи:

module CrystalCoin module Consensus ... private def parse_chain(node : String) node_url = URI.parse('#{node}/chain') node_chain = HTTP::Client.get(node_url) node_chain = JSON.parse(node_chain.body)['chain'].to_json Array(CrystalCoin::Block).from_json(node_chain) end private def valid_chain?(node_chain) previous_hash = '0' node_chain.each do |block| current_block_hash = block.current_hash block.recalculate_hash return false if current_block_hash != block.current_hash return false if previous_hash != block.previous_hash return false if current_block_hash[0..1] != '00' previous_hash = block.current_hash end return true end end end

За parse_chain() ние:

  • Издайте a GET HTTP заявка с използване на HTTP::Client.get до /chain крайна точка.
  • Анализирайте /chain JSON отговор, използващ JSON.parse.
  • Извличане на масив от CrystalCoin::Block обекти от JBON петно, което беше върнато с помощта на Array(CrystalCoin::Block).from_json(node_chain).

Има повече от един начин за анализ на JSON в Crystal. Предпочитаният метод е да се използва супер-удобният Crystal's JSON.mapping(key_name: Type) функционалност, която ни дава следното:

  • Начин за създаване на екземпляр на този клас от JSON низ чрез стартиране Class.from_json.
  • Начин за сериализиране на екземпляр от този клас в JSON низ чрез стартиране instance.to_json.
  • Гетери и сетери за ключове, дефинирани в този клас.

В нашия случай трябваше да определим JSON.mapping в CrystalCoin::Block обект и премахнахме property използване в класа, така:

module CrystalCoin class Block JSON.mapping( index: Int32, current_hash: String, nonce: Int32, previous_hash: String, transactions: Array(Transaction), timestamp: Time ) ... end end

Сега за Blockchain#valid_chain? итерираме през всички блокове и за всеки:

  • Преизчислете хеша за блока, като използвате Block#recalculate_hash и проверете дали хешът на блока е правилен:
module CrystalCoin class Block ... def recalculate_hash @nonce = proof_of_work @current_hash = calc_hash_with_nonce(@nonce) end end end
  • Проверете всеки от блоковете, свързани правилно с предишните им хешове.
  • Проверете дали хешът на блока е валиден за броя на нулите (difficulty в нашия случай 00).

И накрая изпълняваме /nodes/resolve крайна точка:

get '/nodes/resolve' do if blockchain.resolve 'Successfully updated the chain' else 'Current chain is up-to-date' end end

Готово е! Можете да намерите окончателен код на GitHub.

Структурата на нашия проект трябва да изглежда така:

защо Гърция има толкова дългове
crystal_coin [master●] % tree src/ src/ ├── crystal_coin │ ├── block.cr │ ├── blockchain.cr │ ├── consensus.cr │ ├── proof_of_work.cr │ ├── transaction.cr │ └── version.cr ├── crystal_coin.cr └── server.cr

Нека опитаме

  • Вземете различна машина и стартирайте различни възли във вашата мрежа. Или завъртете процеси, използвайки различни портове на една и съща машина. В моя случай създадох два възела на моята машина, на различен порт, за да има два възела: http://localhost:3000 и http://localhost:3001.
  • Регистрирайте адреса на втория възел на първия възел, като използвате:
crystal_coin [master●●] % curl -X POST http://0.0.0.0:3000/nodes/register -H 'Content-Type: application/json' -d '{'nodes': ['http://0.0.0.0:3001']}' New nodes have been added: Set{'http://0.0.0.0:3001'}%
  • Нека добавим транзакция към втория възел:
crystal_coin [master●●] % curl -X POST http://0.0.0.0:3001/transactions/new -H 'Content-Type: application/json' -d '{'from': 'eqbal', 'to':'spiderman', 'amount': 100}' Transaction # has been added to the node%
  • Нека изкопаем транзакциите в блок на втория възел:
crystal_coin [master●●] % curl http://0.0.0.0:3001/mine Block with index=1 is mined.%
  • В този момент първият възел има само един блок (генезисен блок), а вторият възел има два възела (генезис и добивания блок):
crystal_coin [master●●] % curl http://0.0.0.0:3000/chain {'chain':[{'index':0,'current_hash':'00fe9b1014901e3a00f6d8adc6e9d9c1df03344dda84adaeddc8a1c2287fb062','nonce':157,'previous_hash':'0','transactions':[],'timestamp':'2018-05-24T00:21:45+0300'}]}% crystal_coin [master●●] % curl http://0.0.0.0:3001/chain {'chain':[{'index':0,'current_hash':'007635d82950bc4b994a91f8b0b20afb73a3939e660097c9ea8416ad614faf8e','nonce':147,'previous_hash':'0','transactions':[],'timestamp':'2018-05-24T00:21:38+0300'},{'index':1,'current_hash':'00441a4d9a4dfbab0b07acd4c7639e53686944953fa3a6c64d2333a008627f7d','nonce':92,'previous_hash':'007635d82950bc4b994a91f8b0b20afb73a3939e660097c9ea8416ad614faf8e','transactions':[{'from':'eqbal','to':'spiderman','amount':100}],'timestamp':'2018-05-24T00:23:57+0300'}]}%
  • Нашата цел е да актуализираме веригата в първия възел, за да включим новогенерирания блок във втория. Така че нека разрешим първия възел:
crystal_coin [master●●] % curl http://0.0.0.0:3000/nodes/resolve Successfully updated the chain%

Нека проверим дали веригата в първия възел е актуализирана:

crystal_coin [master●●] % curl http://0.0.0.0:3000/chain {'chain':[{'index':0,'current_hash':'007635d82950bc4b994a91f8b0b20afb73a3939e660097c9ea8416ad614faf8e','nonce':147,'previous_hash':'0','transactions':[],'timestamp':'2018-05-24T00:21:38+0300'},{'index':1,'current_hash':'00441a4d9a4dfbab0b07acd4c7639e53686944953fa3a6c64d2333a008627f7d','nonce':92,'previous_hash':'007635d82950bc4b994a91f8b0b20afb73a3939e660097c9ea8416ad614faf8e','transactions':[{'from':'eqbal','to':'spiderman','amount':100}],'timestamp':'2018-05-24T00:23:57+0300'}]}%

алтернативен текст на изображението

Ура! Нашият пример за език на Crystal работи като чар и се надявам, че този дълъг урок е кристално ясен, извинете за каламбура.

Обобщавайки

Този урок за Crystal език обхваща основите на публичната блокчейн. Ако последвахте, внедрихте блокчейн от нулата и създадохте просто приложение, което позволява на потребителите да споделят информация за блокчейна.

На този етап направихме доста голяма блокчейн. Сега, CrystalCoin може да се стартира на множество машини за създаване на мрежа и реално CrystalCoins може да се добива.

Надявам се, че това ви е вдъхновило да създадете нещо ново или поне да разгледате по-отблизо програмирането на Crystal.

Забележка: Кодът в този урок не е готов за реална употреба. Моля, вижте това като само общо ръководство.

Разбиране на основите

Какви са ползите от езика за програмиране Crystal?

Crystal е родов език. Можете да правите всичко, което можете да правите в Ruby, използвайки Crystal, с по-добра производителност и по-малко използване на паметта. Една от точките за продажба на Crystal е лекотата, с която можете да взаимодействате с C библиотеки, тъй като Crystal ви позволява да се свързвате със съществуващи C библиотеки, без да пишете нито един ред в C.

Защо трябва да уча езика за програмиране Crystal?

Crystal е език за програмиране, който може лесно да се разбере от хората и се компилира в бързи програми. Това е статично написан, компилиран език, който постига производителност, близка до c / c ++, като същевременно има синтаксис, толкова четим, колкото Ruby.

Каква е разликата между Ruby и Crystal?

Crystal има подобен на Ruby синтаксис, но това е различен език, а не Ruby изпълнение. Тъй като това е компилиран, статически типизиран език, езикът има някои разлики в сравнение с Ruby. Идвайки от Ruby, Crystal има ниска крива на обучение.

Какво ще кажете за рамки?

Ако обичате пълнотата на Rails, ще се почувствате като у дома си с рамката Amber. Ако простотата и лесното персонализиране на Sinatra са по-вашите неща, ще откриете тази простота с Kemal.

Crystal обещаващ език за програмиране?

Това е един от най-обещаващите езици за програмиране днес. Изглежда, че е разрушил бариерата между синтактично сладко интерпретирани / динамични езици като Ruby / Python, които са обичани заради четливостта и лекотата на използване, и суровите конски сили на C / C ++ и системните езици на ниско ниво.

Киберсигурност: Какво трябва да знае всеки изпълнителен директор и финансов директор

Финансови Процеси

Киберсигурност: Какво трябва да знае всеки изпълнителен директор и финансов директор
Вицепрезидент по маркетинг на марките

Вицепрезидент по маркетинг на марките

Други

Популярни Публикации
Как да изградим безкраен бегач на iOS: Cocos2D, автоматизация и др
Как да изградим безкраен бегач на iOS: Cocos2D, автоматизация и др
Напишете важни тестове: Първо се справете с най-сложния код
Напишете важни тестове: Първо се справете с най-сложния код
Разработване на SmartWatch: Струва ли си проблемът на SmartWatch?
Разработване на SmartWatch: Струва ли си проблемът на SmartWatch?
Polymer.js: Бъдещето на разработването на уеб приложения?
Polymer.js: Бъдещето на разработването на уеб приложения?
Как да създадете персонализирани шрифтове: 7 стъпки и 3 казуса
Как да създадете персонализирани шрифтове: 7 стъпки и 3 казуса
 
Силните страни и предимствата на Micro Frontends
Силните страни и предимствата на Micro Frontends
Как да използваме помощници на релси: Демонстрация на въртележка за Bootstrap
Как да използваме помощници на релси: Демонстрация на въртележка за Bootstrap
Поглед към бъдещето на JavaScript
Поглед към бъдещето на JavaScript
Изграждане на ASP.NET уеб API с ASP.NET Core
Изграждане на ASP.NET уеб API с ASP.NET Core
Урок за OpenCV: Откриване на обекти в реално време с помощта на MSER в iOS
Урок за OpenCV: Откриване на обекти в реално време с помощта на MSER в iOS
Популярни Публикации
  • как да наемете разработчик на софтуер
  • най-добри практики за уеб дизайн ux
  • как да направите жетон
  • производителност на php 7 срещу java
  • какво е информационна архитектура?
  • как да хакна информация за кредитна карта
Категории
  • Инструменти И Уроци
  • Agile Talent
  • Продукти Хора И Екипи
  • Растеж На Приходите
  • © 2022 | Всички Права Запазени

    portaldacalheta.pt