我们首先创建了一个Blockchain类,并且在初始函数中创建了两个初始化的空列表,其中chain用于存储我们的区块,current_transcations用于存储交易
classBlockchain(object):def__init__(self):self.chain=[]self.current_transactions=[]#存储区块链和交易defnew_block(self):#创建一个新区块并加入区块链网络中passdefnew_transaction(self):#增加一笔新交易pass
staticmethod#staticmethod返回函数的静态方法defhash(block):#hash一个区块passproperty#property把一个方法变成属性调用的deflast_block(self):#返回区块链中最后一个区块passdefproof_of_work(self,last_proof):#一个简单的工作量证明算法:returnproofstaticmethoddefvalid_proof(last_proof,proof):#验证声明returnnew_block和new_transaction将新交易打包入区块中,last_block和hash函数给每个区块返回一个唯一的哈希值,proof_of_work和valid_proof组成工作量证明算法
我们会在下文中一步步完善Blockchain的具体框架
2)一个区块长什么样?每个区块都有一个索引,一个Unix时间戳(timestamp),一个事务列表,一个校验和前一个区块的哈希值
block={#1)索引index:1,#2)Unix时间戳timestamp:.,#3)事务列表transactions:[{sender:"fe1ff9ddde4b27ee00",recipient:"a77f5cdfadfa5c7c7da5df1f",amount:5,}],#4)校验proof:,#5)前一个块的散列previous_hash:"2cf24dba5fb0a30e26e83b2ac5b9e29e1be5c1faeb"}
每个区块之所以能够组成区块链,正是因为每个新的区块都包含前一个区块的哈希值
这便是区块链不可更改的原因:如果攻击者修改了区块链中较早的区块,则会改变接下来所有区块的哈希值
3)将交易打包入区块defnew_transaction(self,sender,recipient,amount):paramsender:发送者的地址strparamrecipient:接收者的地址strparamamount:数量intreturn:包含这笔交易的区块索引intself.current_transactions.append({sender:sender,recipient:recipient,amount:amount,})#返回包含该交易的区块索引returnself.last_block[index]+1
new_transaction()方法将交易添加入了交易列表中,并且返回包含该交易的区块的索引
4)创建新的区块我们首先需要在Blockchain的初始函数中添加创世区块(第一个区块),并且要为起源块添加一个挖矿结果的工作证明
classBlockchain(object):def__init__(self):self.current_transactions=[]self.chain=[]self.new_block(provious_hash=1,proof=)#创世区块defnew_block(self,proof,previous_hash=None):创建一个新的区块到区块链中paramproof:工作证明算法生成的证明intparamprevious_hash:前一个区块的hash值strreturn:新区块dictblock={index:len(self.chain)+1,timestamp:time(),transactions:self.current_transcations,proof:proof,previous_hash:previous_hashorself.hash(self.chain[-1])#前一个区块的hash值}self.current_transactions=[]#重置当前交易记录self.chain.append(block)#将新区块加入区块链中returnblockdefnew_transaction(self,sender,recipent,amount):创建一笔新交易到下一个被挖掘的区块中paramsender:发送人地址strparamrecipient:接收人地址strparamamount:数量intreturn:包含本次交易的区块索引intself.current_transactions.append({sender:sender,recipient:recipient,amount:amount})returnself.last_block[index]+1#返回本次交易的区块索引
propertydeflast_block(self):returnself.chain[-1]staticmethoddefhash(block):给一个区块生成SHA-值paramblock:Blockdictreturn:值int#我们必须确保这个字典(区块)是经过排序的,否则我们将会得到不一致的散列block_string=json.dumps(block,sort_keys=True).encode()returnhashlib.sha(block_string).hexdigest()#hexdigest()返回摘要,作为十六进制数据字符串值到此我们的框架基本搭建完毕,接下来我们需要了解新的区块是怎么被创建挖掘的
5)什么是工作量证明算法(PoW)工作量证明算法用于在区块链上创建/挖掘新的区块:PoW的目标是计算出一个符合特定条件的数字,并且这个数字虽然计算困难但是易于验证
举个简单的例子:假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ab23d……0
设x=5,求y
fromhashlibimportshax=5y=0#因为我们还不知道y应该是多少whilesha(f{x*y}.encode()).hexdigest()[-1]!="0":y+=1print(fThesolutionisy={y})
结果是y=21,因为生成的Hash值结尾必须为0
hash(5*21)=ee...5ee
通常计算难度与目标字符串需要满足的特定字符数量成正比,接下来我们实现一个相似PoW算法:找到一个数字P,使它与前一个区块的proof拼接成的字符串的Hash值以4个0开头
classBlockchain(object):......#省略defproof_of_work(self,last_proof):一个简单的工作量证明算法:1)找到一个数字p,使hash(pp)以4个0开头,其中p是前一个区块的proof2)p是之前的proof,p是新的proofparamlast_proof:上一个proofintreturn:proofint#从0开始验证,如果验证成功返回数字p,否则继续验证proof=0whileself.valid_proof(last_proof,proof)isFalse:proof+=1returnproof
staticmethoddefvalid_proof(last_proof,proof):验证声明:hash(last_proof,proof)开头是否包含4个0?paramlast_proof:之前的proofintparamproof:现在的proofintreturn:验证正确返回True,错误返回Falseguess=f{last_proof}{proof}.encode()#找到数字proofwhich与前一个区块的lastproof拼接成的字符串的Hash值以4个0开头guess_hash=hashlib.sha(guess).hexdigest()#guess_hash是数字p与上一个区块的proof拼接成的字符串returnguess_hash[:4]==""同时我们发现修改0开头的个数可以修改算法的复杂度,每多一个零都会大大增加计算所需要的时间
二、创建API接口我们使用PythonFlask框架分别创建三个接口:
/transactions/new:创建一个交易并添加到区块
/mine:告诉服务器去挖掘新的区块
/chain:返回整个区块链
1)创建节点我们的Flask服务器将扮演区块链网络中的一个节点
Flask是一个轻量Web应用框架,适用于中小型项目,而Django更适用于大型网站开发,小伙伴们可以按照需要学习自己喜欢的框架
importhashlibimportjsonfromtimeimporttime#textwrap通过调整换行符的位置来格式化文本fromtextwrapimportdedent#通用唯一标识:对于所有的UUID它可以保证在空间和时间上的唯一性fromuuidimportuuid4#轻量级Web应用框架fromflaskimportFlaskclassBlockchain(object):......#省略#实例化我们的节点app=Flask(__name__)#为该节点生成一个全局唯一的地址node_identifier=str(uuid4()).replace(-,)#uuid.uuid4基于随机数来生成UUID#实例化Blockchain类blockchain=Blockchain()#/mine接口:告诉服务器去挖掘新的区块
app.route(/mine,methods=[GET])defmine():return"WellmineanewBlock"#/transactions/new接口:创建一个交易并添加到区块app.route(/transactions/new,methods=[POST])#POST方式请求,可以给接口发送交易数据defnew_transaction():return"Welladdanewtransaction"#/chain接口:返回整个区块链deffull_chain():response={chain:blockchain.chain,length:len(blockchain.chain)}returnjsonify(response),if__name__==__main__:app.run(host=0.0.0.0,port=)#服务器运行端口2)发送交易发送到节点的数据结构如下
{sender:myaddressrecipient:someoneelse`saddressamount:5}3)发送交易与挖矿的接口app.route(/transactions/new,methods=[POST])#POST方式请求,可以给接口发送交易数据defnew_transaction():values=request.get_json()#检查必填字段是否在POST数据中required=[sender,recipient,amount]ifnotall(kinvaluesforkinrequired):returnMissingvalues,#创建一笔新交易index=blockchain.new_transaction(values[sender],values[recipient],values[amount])response={message:fTransactionwillbeaddedtoBlock{index}}returnjsonify(response),
app.route(/mine,methods=[GET])#/mine接口:告诉服务器去挖掘新的区块defmine():#我们运行工作证明算法以获取下一个证明last_block=blockchain.last_block#上一个区块last_proof=last_block[proof]#获取上一个区块的证明proof=blockchain.proof_of_work(last_proof)#调用proof_of_work#找到证明以后我们必须收到奖励blockchain.new_transaction(sender=0,#发件人为0表示此节点已开采新硬币recipient=node_identifier,#收件人使用本节点地址amount=1#奖励一枚硬币)#新块入链previous_hash=blockchain.hash(last_block)#上一个区块的hashblock=blockchain.new_block(proof,previous_hash)#将新块添加入链中#本区块数据response={message:NewBlockForged,index:block[index],transactions:block[transaction],proof:block[proof],previous_hash:block[previous_hash]}returnjsonify(response),注意,这里的交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互
在没有打造共识算法之前,我们的区块链系统还不能在多台设备之间运行
三、运行区块链我们选择postman作为接口调试工具,去与API进行交互
首先让我们通过请求