20分钟,简单的Python代码创建了一个完整的区块链!

举报
技术火炬手 发表于 2018/07/03 18:45:01 2018/07/03
【摘要】 作者 | Gerald Nash编译 | 科科、kou、木星这是篇技术文,我们会用Python一步步搭建一个完整的区块链。不过,在此之前,咱们还是先说说你什么你该学习如何从零搭建一个区块链。有人认为区块链是一个「等待问题」的解决方案,但毫无疑问,这项新技术是一项计算的奇迹。不过,区块链究竟是什么?我们可以把区块链看做一个公共数据库,其中新数据存储在一个称为区块的容器中,然后被添加到一条不可更...

image.png

作者 | Gerald Nash

编译 | 科科、kou、木星


这是篇技术文,我们会用Python一步步搭建一个完整的区块链。不过,在此之前,咱们还是先说说你什么你该学习如何从零搭建一个区块链。

有人认为区块链是一个「等待问题」的解决方案,但毫无疑问,这项新技术是一项计算的奇迹。不过,区块链究竟是什么?

我们可以把区块链看做一个公共数据库,其中新数据存储在一个称为区块的容器中,然后被添加到一条不可更改的链上(也就是区块链),同时,链上保存着之前的数据记录。这些数据是比特币或其他加密货币之前的交易信息,被分门别类地添加到链上

区块链的诞生,引发了以比特币和莱特币为首的加密货币的崛起。由于加密货币的去中心化属性。对于那些本来就不信任银行系统的人来说,简直是带来了新世界。此外,区块链还给分布式计算带来了革新,出现了很多诸如以太坊这样的新平台,也引入了智能合约的概念。

俗话说实践出真知。接下来,我将用不超过50行的 Python 代码创建一个简单的区块链,并给它取了一个名字叫SnakeCoin。以此帮助大家理解区块链。


一起动手创建一个极简的区块链

首先,我们先对区块链进行定义。在区块链中,每个区块上都有一个时间戳,有时还会有一个索引。在SnakeCoin 中,我们两个都有。同时,为了保证整个区块链的完整性,每一个区块都有一个唯一的哈希值,用于自我标识。比如比特币,每一个区块的哈希值是由区块的索引、时间戳、数据以及前一个区块的哈希,经过加密后得到的。其中,数据可以选取任意值。下面来看看定义区块的代码:

import hashlib as hasher

class Block:
    def __init__(self, index, timestamp, data, previous_hash):
        self.index = index
        self.timestamp = timestamp
        self.data = data
        self.previous_hash = previous_hash
        self.hash = self.hash_block()

  def hash_block(self):
      sha = hasher.sha256()
      sha.update(str(self.index) + 
                      str(self.timestamp) + 
                      str(self.data) + 
                        str(self.previous_hash))
    return sha.hexdigest()

完成了!区块链的基本框架就这样搭建出来了。考虑到我们要做的是「区块链」,因此,我们还需要往链上加区块。我之前提到过,其中每一个区块需要包含链上前一个区块的哈希值。你可能会问,区块链的第一个区块是怎么出现的呢??当然,作为第一个区块(也叫创世区块),自然很特殊。在多数情况下,它是手动地被添加到链上,或者通过独特的逻辑将它添加到链上。

下面,我们就简单一点,通过创建一个函数,让它返回一个创世区块。这个区块的索引为0,此外,它所包含的数据以及前一个区块的哈希值都是一个任意的值。创建创世区块的函数代码如下:

import datetime as date

def create_genesis_block():

    # Manually construct ablock with

    # index zero andarbitrary previous hash

    return Block(0, date.datetime.now(), "GenesisBlock""0")

这样,创世区块已经创建好了,我们还需要一个函数来生成链上更多的区块。该函数将链上前一个区块作为参数,为后面的区块生成数据,并返回具有带有数据的新区块。当生成的新区块包含了前一个区块的哈希值,区块链的完整性就会随着每个区块的增加而增加这样的操作虽然看起来有点复杂,但如果不这么做,其他人就会很容易篡改链上的数据,甚至把整条链都给换了。所以,链上区块的哈希值就充当了密码证明,确保区块一旦被添加到区块链上,就不能被替换或者删除。下面是这个函数的代码:

def next_block(last_block):

    this_index =last_block.index + 1

    this_timestamp =date.datetime.now()

    this_data = "Hey! I'm block " +str(this_index)

    this_hash = last_block.hash

    returnBlock(this_index, this_timestamp, this_data, this_hash)

到这里,一个简单的区块链就基本完成了!今天的的例子中我们通过Python列表来创建区块链,其中最重要的部分是创世区块(当然,还需要其他区块)。因为我们要创建的SnakeCoin 是一个比较简单的区块链,所以我会通过循环的方式,只添加20个新的后续区块。具体实现如下:

# Create the blockchain and add the genesis block

blockchain = [create_genesis_block()]

previous_block = blockchain[0]


# How many blocks should we add to the chain

#after the genesis block

num_of_blocks_to_add= 20


# Addblocks to the chain

for iin range(0, num_of_blocks_to_add):

   block_to_add= next_block(previous_block)

   blockchain.append(block_to_add)

   previous_block = block_to_add

   # Telleveryone about it!

   print "Block #{} has been added to theblockchain!".format(block_to_add.index)

   print "Hash:{}\n".format(block_to_add.hash)

跑一下上边的代码,结果如下:

640?wx_fmt=png

 

从上面代码可以看出,区块链已经跑起来了,如果你想在控制台看到更多信息,可以修改源代码文件,将每一个区块的时间戳或者数据打印出来。

上面讲到的区块链是非常简洁的,区块的创造也相对简单。但是如果要使SnakeCoin成为一个真正的加密货币,我们需要控制每次产出的区块数量和币的数量。


瘦小的区块链增肥

SnakeCoin上的数据从交易中产生,每个区块上的数据均由一系列交易组成。我们将交易定义为:每笔交易均为一个JSON对象,这个JSON对象包括币的发送者、接受者和交易数量。

注意:下文我们会谈到为什么交易是以JSON格式保存的。

{

  "from":"71238uqirbfh894-random-public-key-a-alkjdflakjfewn204ij",

  "to":"93j4ivnqiopvh43-random-public-key-b-qjrgvnoeirbnferinfo",

  "amount"3

}

在了解了什么是交易之后,我们需要一个方法,将交易添加到之前的区块链网络节点中(这些节点由普通的电脑组成),为此,我们将创造一个简单的HTTP服务器,便于交易用户将交易信息上报节点。一个节点能够接收一个带有交易信息的POST请求来作为请求主体。这就是为什么交易是JSON格式的原因。我们需要将它们传送到服务器的请求主体中。

首先需要安装一个网络服务器框架:

from flask import Flask

from flask import request

node = Flask(__name__)

# Store the transactions that

# this node has in a list

this_nodes_transactions = []

@node.route('/txion',methods=['POST'])

def transaction():

    if request.method == 'POST':

      # On each new POST request,

      # we extract the transaction data

      new_txion = request.get_json()

      # Then we add the transaction to our list

      this_nodes_transactions.append(new_txion)

      # Because the transaction was successfully

      # submitted, we log it to our console

      print "New transaction"

      print "FROM:{}".format(new_txion['from'])

      print "TO:{}".format(new_txion['to'])

      print "AMOUNT:{}\n".format(new_txion['amount'])

      # Then we let the client know it worked out

      return "Transaction submissionsuccessful\n"

node.run()

这样我们就完成了对用户间互相发送SnakeCoin信息的保存。这就是为什么大家将区块链称为公共账本和分布式账本的原因:所有交易存储在网络内的每个节点之中,重要的是,对所有用户是可见的。

但是,问题来了,我们从哪里获取SnakeCoin呢?(目前)哪儿都不行!实际上SnakeCoin是不存在的。我们需要挖掘新的SnakeCoin区块把它创造出来,一个新的区块被挖出后,一个SnakeCoin就会产生出来,作为奖励给与挖矿者。矿工将SnakeCoin转给其他人之后,币就开始流通了。

我们不希望挖出SnakeCoin的过程过于简单,因为会产生太多的SnakeCoin,而且会变得越来越不值钱。同时,我们也不希望这个过程特别难,因为这样的话,SnakeCoin的数量不足,这会导致币价过高,同时流通缓慢。我们通过工作量证明算法控制挖矿的难度。工作证明算法本质上是生成一种难以创建但易于验证的算法。就像字面意思一样,它就是证明一个节点(计算机)完成了多少工作量。

SnakeCoin中,我们要创建的是一个简单的PoW算法。要创建一个新的区块,矿工的电脑需要增加一个数字。当该数字可被9“SnakeCoin”中的字母数)和最后一个区块的证明编号整除时,一个新的SnakeCoin区块就会被开采出来,矿工也会得到一个SnakeCoin作为奖励

# ...blockchain

# ...Block class definition

miner_address ="q3nf394hjg-random-miner-address-34nf3i4nflkn3oi"

def proof_of_work(last_proof):

    # Create a variable that wewill use to find

    # our next proof of work

    incrementor = last_proof + 1

    # Keep incrementing the incrementor until

    # it's equal to a number divisible by 9

    # and the proof of work of the previous

    # block in the chain

    while not (incrementor % 9 == 0 andincrementor % last_proof == 0):

      incrementor += 1

    # Once that number is found,

    # we can return it as a proof

    # of our work

    return incrementor

@node.route('/mine', methods =['GET'])

def mine():

    # Get the last proof of work

    last_block = blockchain[len(blockchain) - 1]

    last_proof = last_block.data['proof-of-work']

    # Find the proof of work for

    # the current block being mined

    # Note: The program will hang here until a new

    #      proof of work is found

    proof = proof_of_work(last_proof)

    # Once we find a valid proof of work,

    # we know we can mine a blockso

    # we reward the miner by addinga transaction

    this_nodes_transactions.append(

      { "from":"network""to": miner_address, "amount"1 }

  )

    # Now we can gather the dataneeded

    # to create the new block

    new_block_data = {

      "proof-of-work":proof,

      "transactions":list(this_nodes_transactions)

    }

    new_block_index = last_block.index + 1

    new_block_timestamp = this_timestamp =date.datetime.now()

    last_block_hash = last_block.hash

    # Empty transaction list

    this_nodes_transactions[:] = []

    # Now create the

    # new block!

    mined_block = Block(

        new_block_index,

        new_block_timestamp,

        new_block_data,

        last_block_hash

  )

  blockchain.append(mined_block)

  # Let the client know we mined a block

  return json.dumps({

        "index":new_block_index,

        "timestamp":str(new_block_timestamp),

        "data": new_block_data,

        "hash": last_block_hash

  }) + "\n"

现在,我们可以控制在一个特定时间段内可开采的区块数量,到这里,我们就可以自由掌握一定时间内多少个区块可以被开发出来了。但是就像前面所说的,这一切操作只是在一个节点上进行的。但区块链应该是去中心的,我们怎么保证这条链在其他节点上也是一样的呢?我们可以让每个节点对外广播自己链的版本,其他节点既可以接收广播,又可以自己对外广播同样的内容。此后,每个节点需要去对其他节点所发出的信息进行验证,验证通过后,网络上的所有节点则可以达成一致。这称为一致性算法。

在这个例子中,我们所采用的一致性算法相当简单:如果一个节点的链与其他节点的链不同(即有争议时),那么网络上最长的链会保留而较短的链将会被删除,如果所有节点都达成一致,那么则进行下一步:

@node.route('/blocks',methods=['GET'])
def get_blocks():
  chain_to_send = blockchain
  # Convert our blocks into dictionaries
  # so we can send them as json objects later
  for block in chain_to_send:
     block_index = str(block.index)
     block_timestamp =str(block.timestamp)
     block_data = str(block.data)
     block_hash = block.hash
     block = {
        "index": block_index,
        "timestamp": block_timestamp,
        "data": block_data,
        "hash": block_hash
    }
  # Send our chain to whomever requested it
  chain_to_send = json.dumps(chain_to_send)
  return chain_to_send

def find_new_chains():
    # Get the blockchains of every
    # other node
    other_chains = []
    for node_url in peer_nodes:
       # Get their chains using a GETrequest
       block = requests.get(node_url +"/blocks").content
       # Convert the JSON object to aPython dictionary
       block = json.loads(block)
       # Add it to our list
       other_chains.append(block)
  return other_chains

def consensus():
   # Get the blocks from other nodes
   other_chains =find_new_chains()
   # If our chain isn't longest,
   # then we store the longest chain
   longest_chain = blockchain
   for chain in other_chains:
      if len(longest_chain) < len(chain):
        longest_chain = chain
  # If the longest chain wasn't ours,
  # then we set our chain to the longest
  blockchain = longest_chain

在这一步中,我们会运行完整的snakecoin服务器代码,到这里我们离大功告成就只差一步了。运行完snakecoin的代码后,在终端里运行以下命令(假设你用的是cURL)。

1. 创建一个交易。

curl"localhost:5000/txion" \

     -H "Content-Type:application/json" \

     -d '{"from""akjflw","to":"fjlakdj""amount"3}'

2. 开采新的区块。

curl localhost:5000/mine

3. 查看结果。我们通过客户端窗口看这个。

640?wx_fmt=png 

经过上边的打印输出,在新区块上得到了一些信息。

{

  "index"2,

  "data": {

    "transactions": [

      {

        "to""fjlakdj",

        "amount"3,

        "from""akjflw"

      },

      {

        "to":"q3nf394hjg-random-miner-address-34nf3i4nflkn3oi",

        "amount"1,

        "from""network"

      }

    ],

    "proof-of-work"36

  },

  "hash""151edd3ef6af2e7eb8272245cb8ea91b4ecfc3e60af22d8518ef0bba8b4a6b18",

  "timestamp""2017-07-2311:23:10.140996"

}

大功告成我们自己创建了一条完整的区块链!现在 SnakeCoin 可以在多台节点上运行,SnakeCoin也可以被开采了。


本文章来源:CSDN社区,用户:区块链大本营,版权归原作者所有

阅读原文


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。