22分钟阅读

在晶体编程语言中创建加密货币

EQBAL是一位高级全堆叠开发人员,拥有在Web和移动开发工作的十多年的经验。

这篇文章是我试图通过探索内部来了解区间区块的关键方面。我开始阅读 原始比特币白皮书,但我觉得真正了解区块链的唯一方法是通过从头开始建立一个新的加密货币。

这就是为什么我决定使用新的水晶编程语言创建加密电力,并且我被称为它 水晶素。本文不会讨论算法选择,哈希困难或类似主题。相反,重点将在详细说明一个具体的例子,这应该提供更深入的动手的理解,对块状的强度和局限性。

如果您还没有阅读它,对于allos和hashing的更多背景,我建议您查看Demir Selmanovic的文章 用于假人的加密货币:比特币及超越.

为什么我选择了水晶编程语言

为了更好的演示,我想使用富有成效的语言 红宝石 不影响性能。加密货币有许多耗时的计算(即 矿业哈希),这就是为什么C ++和Java等编译语言是建立“真实”加密货币的语言。已经说,我想用一种带有清洁语法的语言,所以我可以保持开发乐趣并允许更好的可读性。无论如何,晶体性能往往很好。

水晶编程语言例证

所以,我为什么决定使用 水晶编程语言? Crysty的语法受到Ruby的严重启发,所以对我来说,感觉很自然,阅读和易于写作。它具有较低的学习曲线的额外好处,特别是对于经验丰富的红宝石开发人员来说。

这就是水晶郎队如何将其放在官方网站上:

迅速像C一样,光滑为Ruby。

但是,与Ruby或JavaScript不同,这是解释语言的,Crystal是一个编译的语言,使其更快并提供较低的内存占用。在引擎盖下,它使用 llvm. 用于编译到本机代码。

水晶也是静态键入的,这意味着编译器将帮助您在编译时捕获类型错误。

我不会解释为什么我认为水晶语言很棒,因为它超出了本文的范围,但如果你没有找到我的乐观主义,请随时退房 本文 为了更好地概述水晶潜力。

笔记: 本文假设您已经对面向对象的编程(OOP)有了基本的理解。

区块链

那么,什么是区块链?这是由数字指纹链接和保护的块的列表(链)(也称为Crypto Hashes)。

最简单的方式的方式是作为链接列表数据结构。那个说,只需要对先前元素引用所需的链接列表;块必须具备根据先前块的标识符的标识符,这意味着您无法替换块而不会重新计算之后的每个单个块。

目前,将BlockChain视为一系列块,其中一些数据与链相关,链条是前一块的散列。

整个区块链将存在于想要与之交互的每个节点上,这意味着它被复制在网络中的每个节点上。没有单个服务器托管它,但所有 区块链发展公司 使用它,这使它成为 分散.

是的,与传统的集中系统相比,这很奇怪。每个节点都有整个区块链的副本(>149 GB在比特币区间 2017年12月)。

散列和数字签名

那么,这个哈希函数是什么?将哈希视为一个函数,当我们给它一个文本/对象时返回一个唯一的指纹。即使输入对象中最小的变化也会显着地改变指纹。

There are different hashing algorithms, and in this article, we’ll be using the SHA256 哈希 algorithm, which is the one used in Bitcoin.

Using SHA256 we’ll always result in 64 hexadecimal chars (256 bit) in length even if the input is less than 256 bits or much bigger than 256 bits:

输入 哈希结果
很长的文本长度长度长度长度长度文本长度长度长度文本长度长度长度长度长度长度长度长度长度长度长度长度很长的文字很长的文字 CF49BBB21C8B7C078165919D7E57C145CCB7F398E7B58D9A3729DE368D86294A
Toptal. 2E4E500E20F1358224C08C7FB7D3E0E9A5E4AB7A013BFD6774DFA54D7684DD21
toptal。 12075307CE09A6859601CE9D451D385053BE80238EA127C5DF6E6611ED7C6F0

Note with the last example, that just adding a . (dot) resulted in a dramatic change in the hash.

因此,在块链条中,通过将块数据传递到将产生散列的散列算法中来构建链,该散列算法与下一个块相关联,从此,从此,从此,形成与先前块的散列连接的一系列块。

在水晶中建立一个密码细胞

现在让我们 start creating our Crystal project and build our SHA256 encryption.

假设你有 安装了水晶编程语言, let’s create the skeleton of 水晶素 codebase by using Crystal’s built-in project tooling 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/

This command will create the basic structure for the project, with an already initialized git repository, license and readme files. It also comes with stubs for tests, and the shard.yml file for describing the project and managing dependencies, also known as shards.

Let’s add the openssl shard, which is needed to build SHA256 algorithm:

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

Once that’s in, head back into your terminal and run crystal deps. Doing this will pull down openssl 和 its dependencies for us to utilize.

Now we have the required library installed in our code, let’s start by defining 堵塞 class and then building the hash function.

# 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

You can now test your application by running crystal run crystal src/crystal_coin/block.cr from your terminal.

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

设计我们的区块链

Each block is stored with a timestamp and, optionally, an index. In 水晶素, we’re going to store both. To help ensure integrity throughout the blockchain, each block will have a self-identifying 哈希. Like Bitcoin, each block’s hash will be a cryptographic hash of the block’s (index, timestamp, data, and the hash of the previous block’s hash previous_hash)。 The data can be anything you want for now.

module CrystalCoin
  class Block

    property current_hash : String

    def initialize(index = 0, data = "data", previous_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

In Crystal lang, we replace Ruby’s attr_accessor., attr_getterattr_setter methods with new keywords:

红宝石关键词 水晶关键词
attr_accessor. 财产
attr_reader. 吸手
attr_writer. 骗局

Another thing you may have noticed in Crystal is that we want to hint the compiler about specific types through our code. Crystal infers the types, but whenever you have ambiguity you can explicitly declare types as well. That’s why we added String types for current_hash.

现在让我们 run block.cr twice and note that the same data will generate different hashes because of the different timestamp:

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

Now we have our block structure, but we’re creating a blockchain. We need to start adding blocks to form an actual chain. As I mentioned earlier, each block requires information from the previous block. But how does the first block in the blockchain get there? Well, the first block, or 创世纪 block, is a special block (a block with no predecessors). In many cases, it’s added manually or has unique logic allowing it to be added.

We’ll create a new function that returns a genesis block. This block is of index=0, and it has an arbitrary data value and an arbitrary value in the previous_hash parameter.

Let’s build or class method 堵塞.first that generates the genesis block:

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

And let’s test it out using p CrystalCoin::Block.first:

#<CrystalCoin::Block:0x10b33ac80 @current_hash="acb701a9b70cff5a0617d654e6b8a7155a8c712910d34df692db92455964d54e", @data="Genesis Block", @index=0, @timestamp=2018-05-13 17:54:02 +03:00, @previous_hash="0">

现在我们能够创建一个 创世纪 块,我们需要一个将在区块链中生成后续块的函数。

此函数将在链中将上一个块作为参数,创建要生成的块的数据,并将新块与适当的数据返回。当新的块散列信息来自以前的块时,BlockChain的完整性随着每个新块增加。

An important consequence is that a block can’t be modified without changing the hash of every consecutive block. This is demonstrated in the example below. If the data in block 44 is changed from LOOP to EAST, all hashes of the consecutive blocks must be changed. This is because the hash of the block depends on the value of the previous_hash (among other things).

水晶加密电脑散列图

If we didn’t do this, it would be easier for an outside party to change the data and replace our chain with an entirely new one of their own. This chain of hashes acts as cryptographic proof and helps ensure that once a block is added to the blockchain it cannot be replaced or removed. Let’s create the class method 堵塞.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   

To try it out all together, we’ll create a simple blockchain. The first element of the list is the genesis block. And of course, we need to add the succeeding blocks. We’ll create five new blocks to demonstrate 水晶素:

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
[#<CrystalCoin::Block:0x108c57c80

  @current_hash=

   "df7f9d47bee95c9158e3043ddd17204e97ccd6e8958e4e41dacc7f0c6c0df610",

  @index=0,

  @previous_hash="0",

  @timestamp=2018-06-04 12:13:21 +03:00,

  @data="Genesis Block>,

 #<CrystalCoin::Block:0x109c89740

  @current_hash=

   "d188fcddd056c044c783d558fd6904ceeb9b2366339af491a293d369de4a81f6",

  @index=1,

  @previous_hash=

   "df7f9d47bee95c9158e3043ddd17204e97ccd6e8958e4e41dacc7f0c6c0df610",

  @timestamp=2018-06-04 12:13:21 +03:00,

  @data="Transaction data number (1)">,

 #<CrystalCoin::Block:0x109cc8500

  @current_hash=

   "0b71b61118b9300b4fe8cdf4a7cbcc1dac4da7a8a3150aa97630994805354107",

  @index=2,

  @previous_hash=

   "d188fcddd056c044c783d558fd6904ceeb9b2366339af491a293d369de4a81f6",

  @timestamp=2018-06-04 12:13:21 +03:00,

  @transactions="Transaction data number (2)">,

 #<CrystalCoin::Block:0x109ccbe40

  @current_hash=

   "9111406deea4f07f807891405078a3f8537416b31ab03d78bda3f86d9ae4c584",

  @index=3,

  @previous_hash=

   "0b71b61118b9300b4fe8cdf4a7cbcc1dac4da7a8a3150aa97630994805354107",

  @timestamp=2018-06-04 12:13:21 +03:00,

  @transactions="Transaction data number (3)">,

 #<CrystalCoin::Block:0x109cd0980

  @current_hash=

   "0063bfc5695c0d49b291a8813c566b047323f1808a428e0eb1fca5c399547875",

  @index=4,

  @previous_hash=

   "9111406deea4f07f807891405078a3f8537416b31ab03d78bda3f86d9ae4c584",

  @timestamp=2018-06-04 12:13:21 +03:00,

  @transactions="Transaction data number (4)">,

 #<CrystalCoin::Block:0x109cd0100

  @current_hash=

   "00a0c70e5412edd7389a0360b48c407ce4ddc8f14a0bcf16df277daf3c1a00c7",

  @index=5,

  @previous_hash=

   "0063bfc5695c0d49b291a8813c566b047323f1808a428e0eb1fca5c399547875",

  @timestamp=2018-06-04 12:13:21 +03:00,

  @transactions="Transaction data number (5)">

验证

工作算法证明(POW)是创建新块的方式 开采 在区块链上。战俘的目标是发现一个解决问题的数字。这些数字必须难以找到,但易于通过网络上的任何人计算地验证。这是工作证明背后的核心理念。

Let’s demonstrate with an example to make sure everything is clear. We’ll assume that the hash of some integer x multiplied by another y must start with 00. So:

哈希(x * y) = 00ac23dc...

And for this simplified example, let’s fix x=5 和 implement this in 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

As you can see this number y=530 was hard to find (brute-force) but easy to verify using the hash function.

为什么打扰这个战俘算法?为什么我们每块刚刚创建一个哈希,那就是它?哈希必须是 有效的. In our case, a hash will be valid if the first two characters of our hash are 00. If our hash starts with 00......, it is considered valid. This is called the 困难。难度越高,获得有效哈希需要越长。

But, if the hash is not valid the first time, something must change in the data we use. If we use the same data over and over, we will get the same hash over and over and our hash will never be valid. We use something called nonce in our hash (in our previous example it’s the y)。 It is simply a number that we increment each time the hash is not valid. We get our data (date, message, previous hash, index) and a nonce of 1. If the hash we get with these is not valid, we try with a nonce of 2. And we increment the nonce until we get a valid hash.

在比特币中,调用工作算法证明 哈希cash.。让我们向我们的街区类添加工作验证并开始 矿业 找到omce。我们将使用硬编码 困难 两个领先的零'00':

现在让我们 redesign our Block class to support that. Our 水晶素 堵塞 will contain the following attributes:

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

图像alt text.

I’ll create a separate module to do the hashing and find the nonce so we keep our code clean and modular. I’ll call it 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

Our 堵塞 class would look something like:

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 = "哈希")
      @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 Code和Crystal语言示例的一些事情要注意。在晶体中,默认情况下是公共的。 Crystal要求每种私有方法使用私有关键字前缀,这可能会困惑来自Ruby。

You may have noticed that Crystal’s Integer types there are Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, or UInt64 compared to Ruby’s Fixnum. truefalse are values in the Bool class rather than values in classes TrueClass or FalseClass in Ruby.

水晶 has optional and named method arguments as core language features, and does not require writing special code for handling the arguments which is pretty cool. Check out 堵塞#initialize(index = 0, data = "data", previous_hash = "哈希") 和 then calling it with something like 堵塞.new(data: data, previous_hash: "0").

有关Crystal和Ruby编程语言之间的更详细的差异列表 Rublyists的水晶.

现在,让我们尝试使用以下内容创建五项交易:

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
[#<CrystalCoin::Block:0x108f8fea0 @current_hash="0088ca080a49334e1cb037ed4c42795d635515ef1742e6bcf439bf0f95711759", @index=0, @nonce=17, @timestamp=2018-05-14 17:20:46 +03:00, @data="Genesis Block", @previous_hash="0">]
#<CrystalCoin::Block:0x108f8f660 @current_hash="001bc2b04d7ad8ef25ada30e2bde19d7bbbbb3ad42348017036b0d4974d0ccb0", @index=1, @nonce=24, @timestamp=2018-05-14 17:20:46 +03:00, @data="Transaction data number (1)", @previous_hash="0088ca080a49334e1cb037ed4c42795d635515ef1742e6bcf439bf0f95711759">
#<CrystalCoin::Block:0x109fc5ba0 @current_hash="0019256c998028111838b872a437cd8adced53f5e0f8f43388a1dc4654844fe5", @index=2, @nonce=61, @timestamp=2018-05-14 17:20:46 +03:00, @data="Transaction data number (2)", @previous_hash="001bc2b04d7ad8ef25ada30e2bde19d7bbbbb3ad42348017036b0d4974d0ccb0">
#<CrystalCoin::Block:0x109fdc300 @current_hash="0080a30d0da33426a1d4f36d870d9eb709eaefb0fca62cc68e497169c5368b97", @index=3, @nonce=149, @timestamp=2018-05-14 17:20:46 +03:00, @data="Transaction data number (3)", @previous_hash="0019256c998028111838b872a437cd8adced53f5e0f8f43388a1dc4654844fe5">
#<CrystalCoin::Block:0x109ff58a0 @current_hash="00074399d51c700940e556673580a366a37dec16671430141f6013f04283a484", @index=4, @nonce=570, @timestamp=2018-05-14 17:20:46 +03:00, @data="Transaction data number (4)", @previous_hash="0080a30d0da33426a1d4f36d870d9eb709eaefb0fca62cc68e497169c5368b97">
#<CrystalCoin::Block:0x109fde120 @current_hash="00720bb6e562a25c19ecd2b277925057626edab8981ff08eb13773f9bb1cb842", @index=5, @nonce=475, @timestamp=2018-05-14 17:20:46 +03:00, @data="Transaction data number (5)", @previous_hash="00074399d51c700940e556673580a366a37dec16671430141f6013f04283a484">

See the difference? Now all hashes start with 00. That’s the magic of proof-of-work. Using ProofOfWork we found the nonce 和 proof is the hash with the matching difficulty, that is, the two leading zeros 00.

注意我们创建的第一个块,我们尝试了17个无数,直到找到匹配的幸运号码:

堵塞 循环/哈希计算数量
#0 17
#1 24
#2 61
#3 149
#4 570
#5 475

现在让我们 try a difficulty of four leading zeros (困难="0000"):

堵塞 循环/哈希计算数量
#1 26 762
#2 68 419
#3 23 416
#4 15 353

在第一个块中尝试了26762诺(比较了17个无困难'00'),直到找到匹配的幸运号码。

我们作为API的区块链

So far, so good. We created our simple blockchain and it was relatively easy to do. But the problem here is that 水晶素 can only run on one single machine (it’s not distributed/decentralized).

From now on, we’ll start using JSON data for 水晶素. The data will be transactions, so each block’s data field will be a list of transactions.

Each transaction will be a JSON object detailing the sender of the coin, the receiver of the coin, and the amount of CrystalCoin that is being transferred:

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

A few modifications to our 堵塞 class to support the new transaction format (previously called data)。 So, just to avoid confusion and maintain consistency, we’ll be using the term transaction to refer to data posted in our example application from now on.

We’ll introduce a new simple class 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

事务包装为块,因此块可以包含一个或多个交易。频繁生成包含交易的块并添加到区块链中。

The blockchain is supposed to be a collection of blocks. We can store all of the blocks in the Crystal list, and that’s why we introduce the new class 区块链:

区块链 will have chainuncommitted_transactions arrays. The chain will include all the mined blocks in the blockchain, and uncommitted_transactions will have all the transactions that have not been added to the blockchain (still not mined). Once we initialize 区块链, we create the genesis block using 堵塞.first 和 add it to chain array, and we add an empty uncommitted_transactions array.

We will create 区块链#add_transaction method to add transactions to uncommitted_transactions array.

Let’s build our new 区块链 class:

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

In 堵塞 class we will start using transactions instead of data:

module CrystalCoin
  class Block
    include ProofOfWork

    def initialize(index = 0, transactions = [] of Transaction, previous_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

Now that we know what our transactions will look like, we need a way of adding them to one of the computers in our blockchain network, called a node. To do that, we’ll create a simple HTTP server.

我们将创建四个端点:

  • [POST]/transactions/new: to create a new transaction to a block
  • [GET] /mine: to tell our server to mine a new block.
  • [GET] /chain: to return the full blockchain in JSON format.
  • [GET] /pending: to return the pending transactions (uncommitted_transactions)。

我们将使用 kemal. Web框架。这是一个微框架,使映射到晶体功能的终点。 kemal受到严重影响 Sinatra 对于Rubyists并以非常相似的方式工作。如果你正在寻找 红宝石在Rails. 等同于那么退房 琥珀色.

Our server will form a single node in our blockchain network. Let’s first add kemal. to the shard.yml file as a and install the dependency:

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%

So far so good. Now, we can proceed with implementing each of the endpoints. Let’s start by implementing /transactions/newpending end-points:

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

Straightforward implementation. We just create a 水晶素::Block::Transaction object and add the transaction to the uncommitted_transactions array using 区块链#add_transaction.

At the moment, the transactions are initially stored in a pool of uncommitted_transactions. The process of putting the unconfirmed transactions in a block and computing Proof of Work (PoW) is known as the 矿业 of blocks. Once the nonce satisfying our constraints is figured out, we can say that a block has been mined, and the new block is put into the blockchain.

In 水晶素, we’ll use the simple Proof-of-Work algorithm we created earlier. To create a new block, a miner’s computer will have to:

  • Find the last block in the chain.
  • Find pending transactions (uncommitted_transactions)。
  • Create a new block using 堵塞.next.
  • Add the mined block to chain array.
  • Clean up uncommitted_transactions array.

So to implement /mine end-point, let’s first implement the above steps in 区块链#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

We make sure first we have some pending transactions to mine. Then we get the last block using @chain.last, and the first 25 transactions to be mined (we are using Array#shift(BLOCK_SIZE) to return an array of the first 25 uncommitted_transactions, and then remove the elements starting at index 0).

现在让我们 implement /mine end-point:

get "/mine" do
  blockchain.mine
  "堵塞 with index=#{blockchain.chain.last.index} is mined."
end

And for /chain end-point:

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

与我们区块链互动

We’ll be using cURL to interact with our API over a network.

首先,让我们开火服务器:

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

Then let’s create two new transactions by making a POST request to http://localhost:3000/transactions/new with a body containing our transaction structure:

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 #<CrystalCoin::Block::Transaction:0x10c4159f0 @from="eki", @to="iron_man", @amount=1000_i64> 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 #<CrystalCoin::Block::Transaction:0x10c415810 @from="eki", @to="hulk", @amount=700_i64> 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
    }
  ]
}

As we can see, the two transactions we created earlier have been added to uncommitted_transactions.

现在让我们 the two transactions by making a GET request to http://0.0.0.0:3000/mine:

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

Looks like we successfully mined the first block and added it to our chain. Let’s double check our chain 和 if it includes the mined block:

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"
    }
  ]
}

共识和权力下放

这很酷。我们自己得到了一个接受事务的基本区块链,并允许我们挖掘新块。但是,我们实现的代码是目前的旨在在一台计算机上运行,​​而整个点的整体点是它们应该分散。但如果他们分散,我们如何确保他们都反映了同一链?

This is the problem of Consensus.

如果我们在网络中想要多个节点,我们必须实施共识算法。

注册新节点

要实现共识算法,我们需要一种方法来让节点了解网络上的相邻节点。我们网络上的每个节点都应保留网络上其他节点的注册表。因此,我们需要更多的终点:

  • [POST] /nodes/register: to accept a list of new nodes in the form of URLs.
  • [GET] /nodes/resolve: to implement our Consensus Algorithm, which resolves any conflicts—to ensure a node has the correct chain.

我们需要修改BlockChain的构造函数并提供注册节点的方法:

--- 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)

Note that we’ve used a Set data structure with String type to hold the list of nodes. This is a cheap way of ensuring that the addition of new nodes is idempotent and that no matter how many times we add a specific node, it appears exactly once.

现在让我们 add a new module to Consensus 和 implement the first method 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

The register_node function, will parse the URL of the node and format it.

And here let’s create /nodes/register end-point:

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

现在使用此实现,我们可能会面临多个节点的问题。一些节点的链副本可能有所不同。在这种情况下,我们需要同意某些版本的链条,以保持整个系统的完整性。我们需要达成共识。

要解决此问题,我们将赋予最长有效链是要使用的规则。使用此算法,我们在网络中的节点中达成共识。这种方法背后的原因是最长的链是对最多的工作所做的估计。

图像alt text.

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

Bear in mind that resolve is a method which loops through all our neighboring nodes, downloads their chains and verifies them using the 有效的_chain? method. If a valid chain is found, whose length is greater than ours, we replace ours.

现在让我们 implement parse_chain()有效的_chain?() private methods:

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

For parse_chain() we:

  • Issue a GET HTTP request using HTTP::Client.get to /chain end-point.
  • Parse the /chain JSON response using JSON.parse.
  • Extract an array of 水晶素::Block objects from the JSON blob that was returned using Array(CrystalCoin::Block).from_json(node_chain).

There is more than one way of parsing JSON in Crystal. The preferred method is to use Crystal’s super-handy JSON.mapping(key_name: Type) functionality that gives us the following:

  • A way to create an instance of that class from a JSON string by running Class.from_json.
  • A way to serialize an instance of that class into a JSON string by running instance.to_json.
  • 在该类中定义的键的Getter和Setter。

In our case, we had to define JSON.mapping in 水晶素::Block object, and we removed 财产 usage in the class, like so:

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

Now for 区块链#valid_chain?, we iterate through all of the blocks, and for each we:

  • Recalculate the hash for the block using 堵塞#recalculate_hash 和 check that the hash of the block is correct:
module CrystalCoin
  class Block
    ...
    
    def recalculate_hash
      @nonce = proof_of_work
      @current_hash = calc_hash_with_nonce(@nonce)
    end
  end
end
  
  • 检查每个块是否正确链接到他们之前的哈希值。
  • Check the block’s hash is valid for the number of zeros (困难 in our case 00)。

And finally we implement /nodes/resolve end-point:

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

完成!你可以找到 最终代码 on 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

让我们试试看

  • Grab a different machine, and run different nodes on your network. Or spin up processes using different ports on the same machine. In my case, I created two nodes on my machine, on a different port to have two nodes: http://localhost:3000http://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 #<CrystalCoin::Block::Transaction:0x1039c29c0> has been added to the node%
  • 让将事务放入第二个节点的块:
crystal_coin [master●●] % curl http://0.0.0.0:3001/mine
Block with index=1 is mined.%
  • 此时,第一节点仅具有一个块(Genesis Block),第二节点具有两个节点(Genesis和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"}]}%

图像alt text.

h我们的水晶语言示例类似于魅力,我希望您发现这一冗长的教程清晰,请原谅双关语。

包起来

这种水晶语言教程涵盖了公共区块链的基础。如果您跟踪,您将从划痕实现了一个区块链,并构建了一个简单的应用程序,允许用户在区块链中共享信息。

We’ve made a fairly sized blockchain at this point. Now, 水晶素 can be launched on multiple machines to create a network, and real 水晶素s can be mined.

我希望这激发了你创造新的东西,或者至少仔细看看水晶编程。

笔记: 本教程中的代码尚未准备好实现真实的使用。请仅将此称为普通指南。

理解基础知识

水晶编程语言的用途是什么?

水晶是一种通用语言。您可以使用Crystal在Ruby中进行任何操作,具有更好的性能和更少的内存使用。一个用于水晶的卖点是您可以与C库界面的轻松,因为Crystal允许您绑定到现有的C库,而无需在C中写入单行。

我为什么要学习水晶编程语言?

水晶是一种编程语言,可以通过人类容易地理解并编译到快速节目。它是一种静态键入的编译语言,可实现靠近C / C ++的性能,同时具有像Ruby可读的语法。

红宝石和水晶有什么区别?

水晶有一种类似Ruby的语法,但它是一种不同的语言,而不是Ruby实现。因为它是一个编译的静态类型语言,而语言与Ruby相比,语言存在一些差异。来自Ruby,水晶有一个低的学习曲线。

框架怎么样?

如果你喜欢铁路的完整性,你会在家里遇到琥珀框架。如果简单易于自定义Sinatra更多你的事情,你会发现简单的kemal。

水晶是一个有前途的编程语言吗?

它是今天更有前途的编程语言之一。它似乎打破了像Ruby / Python这样的句法甜蜜的解释/动态语言之间的屏障,这些语言被喜爱的可读性和易用性,以及C / C ++和低级系统语言的原始马力。