この投稿は、内部を調査することによってブロックチェーンの重要な側面を理解するための私の試みです。私は読むことから始めました オリジナルのビットコインホワイトペーパー 、しかし、ブロックチェーンを真に理解する唯一の方法は、新しい暗号通貨を最初から構築することだと感じました。
そのため、新しいCrystalプログラミング言語を使用して暗号通貨を作成することにし、それを吹き替えました CrystalCoin 。この記事では、アルゴリズムの選択、ハッシュの難易度、または同様のトピックについては説明しません。代わりに、具体的な例の詳細に焦点を当てます。これにより、ブロックチェーンの長所と制限について、より深く実践的に理解できるようになります。
まだ読んでいない場合は、アルゴとハッシュの背景について、DemirSelmanovicの記事をご覧になることをお勧めします。 ダミーのための暗号通貨:ビットコインとそれ以降 。
より良いデモンストレーションのために、私は次のような生産的な言語を使用したかった ルビー パフォーマンスを損なうことなく。暗号通貨には多くの時間のかかる計算があります(つまり 鉱業 そして ハッシュ )、そしてそれが、C ++やJavaなどのコンパイル言語が「実際の」暗号通貨を構築するための選択言語である理由です。そうは言っても、開発を楽しくし、読みやすさを向上させるために、よりクリーンな構文の言語を使用したかったのです。とにかくクリスタルの性能は良い傾向があります。
それで、なぜ私は使用することに決めました Crystalプログラミング言語 ? Crystalの構文はRubyの構文に大きく影響を受けているため、私にとっては、読みやすく、書きやすいと感じています。特に経験豊富なRuby開発者にとっては、学習曲線が短いという追加の利点があります。
これは、Crystallangチームが公式ウェブサイトに掲載する方法です。
Cのように速く、Rubyのように滑らかです。
ただし、インタープリター言語であるRubyやJavaScriptとは異なり、Crystalはコンパイルされた言語であるため、はるかに高速になり、メモリフットプリントが小さくなります。ボンネットの下で、それは使用します LLVM ネイティブコードにコンパイルするため。
Crystalも静的に型指定されます。つまり、コンパイラーはコンパイル時に型エラーをキャッチするのに役立ちます。
Crystal言語がこの記事の範囲を超えているため、なぜ素晴らしいと思うのかについては説明しませんが、私の楽観的な見方が納得できない場合は、お気軽にチェックしてください。 この記事 Crystalの可能性のより良い概要については。
ポーターの5つの力のスイッチングコスト
注意: この記事は、オブジェクト指向プログラミング(OOP)の基本をすでに理解していることを前提としています。
それで、ブロックチェーンとは何ですか?これは、デジタル指紋(暗号化ハッシュとも呼ばれます)によってリンクおよび保護されたブロックのリスト(チェーン)です。
それを考える最も簡単な方法は、リンクリストのデータ構造として考えることです。そうは言っても、リンクリストは前の要素への参照を持つ必要があるだけです。ブロックには、前のブロックの識別子に応じた識別子が必要です。つまり、後続のすべてのブロックを再計算せずにブロックを置き換えることはできません。
今のところ、ブロックチェーンは、いくつかのデータがチェーンにリンクされた一連のブロックと考えてください。チェーンは前のブロックのハッシュです。
ブロックチェーン全体は、ブロックチェーンと対話する各ノードに存在します。つまり、ネットワーク内の各ノードにコピーされます。単一のサーバーがそれをホストすることはありませんが、すべて ブロックチェーン開発会社 それを使う、それはそれを作る 分散型 。
はい、これは従来の集中型システムと比較して奇妙です。各ノードには、ブロックチェーン全体のコピーがあります(ビットコインブロックチェーンで> 149 Gb 2017年12月 )。
それで、このハッシュ関数は何ですか?ハッシュを関数と考えてください。これは、テキスト/オブジェクトを与えると一意のフィンガープリントを返します。入力オブジェクトのわずかな変更でさえ、フィンガープリントを劇的に変更します。
さまざまなハッシュアルゴリズムがあり、この記事ではSHA256
を使用します。 Bitcoin
で使用されるハッシュアルゴリズム。
SHA256
を使用する入力が256ビット未満または256ビットよりはるかに大きい場合でも、常に64の16進文字(256ビット)の長さになります。
入力 | ハッシュ化された結果 |
---|---|
非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト非常に長いテキスト | cf49bbb21c8b7c078165919d7e57c145ccb7f398e7b58d9a3729de368d86294a |
サルゲッチュ | 2e4e500e20f1358224c08c7fb7d3e0e9a5e4ab7a013bfd6774dfa54d7684dd21 |
サルゲッチュ。 | 12075307ce09a6859601ce9d451d385053be80238ea127c5df6e6611eed7c6f0 |
最後の例では、.
を追加するだけであることに注意してください。 (ドット)はハッシュに劇的な変化をもたらしました。
したがって、ブロックチェーンでは、次のブロックにリンクされるハッシュを生成するハッシュアルゴリズムにブロックデータを渡すことによってチェーンが構築され、以降、前のブロックのハッシュにリンクされた一連のブロックが形成されます。
それでは、Crystalプロジェクトの作成を開始して、SHA256
をビルドしましょう。暗号化。
あなたが持っていると仮定して Crystalプログラミング言語がインストールされています 、CrystalCoin
のスケルトンを作成しましょう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
各ブロックはtimestamp
で保存されますおよび、オプションで、index
。 CrystalCoin
では、両方を保存します。ブロックチェーン全体の整合性を確保するために、各ブロックには自己識別機能があります ハッシュ 。ビットコインと同様に、各ブロックのハッシュは、ブロックの暗号化ハッシュ(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のattr_accessor
、attr_getter
を置き換えますおよびattr_setter
新しいキーワードを持つメソッド:
Rubyキーワード | クリスタルキーワード |
---|---|
attr_accessor | プロパティ |
attr_reader | ゲッター |
attr_writer | セッター |
Crystalで気付いたかもしれないもう一つのことは、コードを通じて特定の型についてコンパイラーにヒントを与えたいということです。 Crystalは型を推測しますが、あいまいな場合はいつでも、型を明示的に宣言することもできます。そのため、String
を追加しましたcurrent_hash
のタイプ。
それでは実行しましょうblock.cr
2回、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
の値に依存するためです。 (とりわけ)。
図と地面のゲシュタルト法則
これを行わなかった場合、外部の関係者がデータを変更し、チェーンをまったく新しいものに置き換える方が簡単です。このハッシュチェーンは暗号化の証拠として機能し、ブロックがブロックチェーンに追加されると、それを置き換えたり削除したりできないようにするのに役立ちます。クラスメソッドを作成しましょう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
を示すために、5つの新しいブロックを作成します。
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の目標は、問題を解決する数を見つけることです。番号を見つけるのは難しいですが、ネットワーク上の誰もが計算で簡単に確認できる必要があります。これは、プルーフオブワークの背後にある中心的なアイデアです。
すべてが明確であることを確認するために、例を使ってデモンストレーションしましょう。ある整数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アルゴリズムを気にするのですか?ブロックごとに1つのハッシュを作成するだけではどうでしょうか。ハッシュは 有効 。この場合、ハッシュの最初の2文字が00
であれば、ハッシュは有効です。ハッシュが00......
で始まる場合、それは有効であると見なされます。これは、 困難 。難易度が高いほど、有効なハッシュを取得するのに時間がかかります。
ただし、ハッシュが最初に有効でない場合は、使用するデータに何か変更を加える必要があります。同じデータを何度も使用すると、同じハッシュが何度も取得され、ハッシュが有効になることはありません。 nonce
と呼ばれるものを使用しますハッシュ内(前の例ではy
)。これは、ハッシュが無効になるたびにインクリメントする単純な数値です。データ(日付、メッセージ、前のハッシュ、インデックス)と1のナンスを取得します。これらで取得したハッシュが有効でない場合は、2のノンスで試行します。有効なハッシュを取得するまでノンスをインクリメントします。 。
ビットコインでは、プルーフオブワークアルゴリズムは ハッシュキャッシュ 。ブロッククラスにプルーフオブワークを追加して始めましょう 鉱業 ナンスを見つけるために。ハードコードされたものを使用します 困難 2つの先行ゼロ「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では、各プライベートメソッドの前にprivateキーワードを付ける必要があります。これは、Rubyからの混乱を招く可能性があります。
Crystalの整数型にはInt8
、Int16
、Int32
、Int64
、UInt8
、UInt16
、| _ + _があることに気づいたかもしれません。 |、またはUInt32
RubyのUInt64
と比較して。 Fixnum
およびtrue
false
の値ですクラスの値ではなくクラスBool
またはTrueClass
Rubyで。
Crystalには、コア言語機能としてオプションの名前付きメソッド引数があり、引数を処理するための特別なコードを記述する必要はありません。これは非常に優れています。チェックアウトFalseClass
次に、Block#initialize(index = 0, data = 'data', previous_hash = 'hash')
のようなもので呼び出します。
CrystalとRubyプログラミング言語の違いの詳細なリストについては、チェックアウトしてください。 Rubyistsのためのクリスタル 。
それでは、以下を使用して5つのトランザクションを作成してみましょう。
Block.new(data: data, previous_hash: '0')
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
が見つかりました証明は、一致する難易度のハッシュ、つまり2つの先行ゼロnonce
です。
作成した最初のブロックで、一致するラッキーナンバーが見つかるまで17ナンスを試しました。
ブロック | ループ/ハッシュ計算の数 |
---|---|
#0 | 17 |
#1 | 24 |
#2 | 61 |
#3 | 149 |
#4 | 570 |
#5 | 475 |
それでは、4つの先行ゼロ(00
)の難易度を試してみましょう。
ブロック | ループ/ハッシュ計算の数 |
---|---|
#1 | 26 762 |
#2 | 68 419 |
#3 | 23 416 |
#4 | 15 353 |
最初のブロックでは、一致するラッキーナンバーが見つかるまで、26762ノンス(難易度「00」の17ノンスと比較)を試しました。
ここまでは順調ですね。シンプルなブロックチェーンを作成しましたが、比較的簡単に作成できました。しかし、ここでの問題はdifficulty='0000'
です。単一のマシンでのみ実行できます(分散/分散化されていません)。
これから、CrystalCoin
のJSONデータの使用を開始します。データはトランザクションになるため、各ブロックのデータフィールドはトランザクションのリストになります。
各トランザクションは、CrystalCoin
の詳細を示すJSONオブジェクトになりますコインのsender
コインの、そしてreceiver
転送されているCrystalCoinの:
amount
{ 'from': '71238uqirbfh894-random-public-key-a-alkjdflakjfewn204ij', 'to': '93j4ivnqiopvh43-random-public-key-b-qjrgvnoeirbnferinfo', 'amount': 3 }
へのいくつかの変更新しいBlock
をサポートするクラスフォーマット(以前はtransaction
と呼ばれていました)。したがって、混乱を避けて一貫性を維持するために、data
という用語を使用します。 transaction
を参照するこれからサンプルアプリケーションに投稿します。
新しい単純なクラスを紹介しますdata
:
Transaction
トランザクションはブロックにパックされるため、ブロックには1つまたは複数のトランザクションを含めることができます。トランザクションを含むブロックは頻繁に生成され、ブロックチェーンに追加されます。
ブロックチェーンは、ブロックのコレクションであると想定されています。すべてのブロックをCrystalリストに保存できるため、新しいクラスmodule CrystalCoin class Block class Transaction property from : String property to : String property amount : Int32 def initialize(@from, @to, @amount) end end end end
を導入します。
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
と呼ばれるブロックチェーンネットワーク内のコンピューターの1つにトランザクションを追加する方法が必要です。そのために、単純なHTTPサーバーを作成します。
4つのエンドポイントを作成します。
node
:ブロックへの新しいトランザクションを作成します/transactions/new
:サーバーに新しいブロックをマイニングするように指示します。/mine
:/chain
で完全なブロックチェーンを返しますフォーマット。JSON
:保留中のトランザクションを返します(/pending
)。使用します 成熟 Webフレームワーク。これは、エンドポイントをCrystal関数に簡単にマッピングできるようにするマイクロフレームワークです。ケマルは影響を強く受けています シナトラ Rubyistsのために、非常によく似た方法で動作します。あなたが探しているなら Ruby on Rails 同等の次にチェックアウト アンバー 。
サーバーは、ブロックチェーンネットワーク内で単一のノードを形成します。最初にuncommitted_transactions
を追加しましょうKemal
へとしてファイルし、依存関係をインストールします。
shard.yml
それでは、HTTPサーバーのスケルトンを構築しましょう。
dependencies: kemal: github: kemalcr/kemal
そしてサーバーを実行します:
# 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
を使用した配列。
c法人とsの違い
現時点では、トランザクションは最初はBlockchain#add_transaction
のプールに保存されます。未確認のトランザクションをブロックに入れ、プルーフオブワーク(PoW)を計算するプロセスは、 鉱業 ブロックの。一度uncommitted_transactions
制約を満たすことがわかったので、ブロックがマイニングされ、新しいブロックがブロックチェーンに配置されたと言えます。
nonce
では、前に作成した単純なプルーフオブワークアルゴリズムを使用します。新しいブロックを作成するには、マイナーのコンピューターは次のことを行う必要があります。
CrystalCoin
の最後のブロックを見つけます。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
を使用して最初の25 Array#shift(BLOCK_SIZE)
の配列を返し、インデックス0から始まる要素を削除します)。
それでは、uncommitted_transactions
を実装しましょう終点:
/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
を使用しますネットワークを介してAPIとやり取りします。
まず、サーバーを起動しましょう。
cURL
次に、crystal_coin [master] % crystal run src/server.cr [development] Kemal is ready to lead at http://0.0.0.0:3000
を作成して、2つの新しいトランザクションを作成しましょう。 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%
ご覧のとおり、前に作成した2つのトランザクションが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
を作成することによる2つのトランザクション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
これはカッコいい。トランザクションを受け入れ、新しいブロックをマイニングできる基本的なブロックチェーンを手に入れました。ただし、これまでに実装したコードは1台のコンピューターで実行することを目的としていますが、ブロックチェーンの要点は分散化する必要があるということです。しかし、それらが分散化されている場合、それらがすべて同じチェーンを反映するようにするにはどうすればよいでしょうか。
これが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
:URLの形式で新しいノードのリストを受け入れます。/nodes/register
:競合を解決するコンセンサスアルゴリズムを実装し、ノードに正しいチェーンがあることを確認します。ブロックチェーンのコンストラクターを変更し、ノードを登録するためのメソッドを提供する必要があります。
/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
のデータ構造ノードのリストを保持するために入力します。これは、新しいノードの追加がべき等であり、特定のノードを何度追加しても、それが1回だけ表示されることを保証する安価な方法です。
それでは、新しいモジュールを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
関数は、ノードのURLを解析し、フォーマットします。
そしてここでregister_node
を作成しましょう終点:
/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()
を発行しますGET
を使用したHTTPリクエスト〜HTTP::Client.get
終点。/chain
を解析します/chain
を使用したJSON応答。JSON.parse
の配列を抽出しますCrystalCoin::Block
を使用して返されたJSONBLOBからのオブジェクト。CrystalでJSONを解析する方法は複数あります。推奨される方法は、Crystalの非常に便利なArray(CrystalCoin::Block).from_json(node_chain)
を使用することです。以下を提供する機能:
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
終わった!あなたは見つけることができます 最終コード GitHubで。
プロジェクトの構造は次のようになります。
デザイン思考が重要な理由
/nodes/resolve
get '/nodes/resolve' do if blockchain.resolve 'Successfully updated the chain' else 'Current chain is up-to-date' end end
および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言語の例は魅力のように機能します。この長いチュートリアルが非常に明確であることがわかったと思いますが、駄洒落はご容赦ください。
このCrystal言語チュートリアルでは、パブリックブロックチェーンの基本について説明しました。従うと、ブロックチェーンを最初から実装し、ユーザーがブロックチェーン上の情報を共有できるようにする簡単なアプリケーションを構築しました。
この時点で、かなりのサイズのブロックチェーンを作成しました。さて、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は一般的な言語です。 Crystalを使用してRubyでできることは何でもでき、パフォーマンスが向上し、メモリ使用量が少なくなります。 Crystalのセールスポイントの1つは、Cで1行も記述せずに、既存のCライブラリにバインドできるため、Cライブラリとのインターフェイスが簡単なことです。
Crystalは、人間が簡単に理解でき、高速プログラムにコンパイルできるプログラミング言語です。これは静的に型付けされ、コンパイルされた言語であり、Rubyと同じくらい読みやすい構文を持ちながら、c / c ++に近いパフォーマンスを実現します。
CrystalにはRubyに似た構文がありますが、Rubyの実装ではなく、異なる言語です。コンパイルされた静的に型付けされた言語であるため、Rubyと比較すると言語にはいくつかの違いがあります。 Rubyを使用しているため、Crystalの学習曲線は低くなっています。
Railsの完全性が気に入った場合は、Amberフレームワークに慣れることができます。シナトラのシンプルさと簡単なカスタマイズがあなたのものであるなら、あなたはケマルでそのシンプルさを見つけるでしょう。
これは、今日最も有望なプログラミング言語の1つです。読みやすさと使いやすさで愛されているRuby / Pythonのような構文的に甘い解釈/動的言語と、C / C ++および低レベルシステム言語の生の馬力との間の障壁を打ち破ったようです。