区块链几乎是很多天间成为人尽皆知的名词,这个名词也勾起了我强烈的兴趣,可是经过在网上搜罗资料,多方阅读,发现不少介绍区块链的文献要么模棱两可,要么做者自己的理解也颇有限,致使不少关键的问题叙述不清。本人花了一些时间总结概括,但愿能够给读者一个比较全面清晰的认识。html
区块链的官方定义是:一个分布式帐本,一种经过去中心化、去信任的方式集体维护一个可靠数据库的技术方案。那么对于圈外人该如何理解呢?如下我会详细描述一个区块链的产生过程和实现意义,从而给你们构建出一个清晰的区块链概念。咱们先讲原理、再说特色、而后论用途、最后回归代码,这样你就会有一种恍然大悟的感受。python
咱们以btc为例:“区块链”,顾名思义,就是由一个个区块依次链接起来组成的链条,能够类比为一条无限长度的直线铁链,每一个铁环就是一个区块。那么区块的内容应该是什么呢?区块狭义上是有两种的,一个是普通区块,一个就是创世区块。创世区块就是一项区块链项目中的第一个区块,因为我的水平有限,对创世区块没有作过详细研究,可是根据个人了解,创世区块应该是具有与普通区块类似结构的,但会确定会加入一些创始人想说的东西,而且在有些项目中可能会多一条记录,就是coin的发行量,例如swtc的6000亿数目就是写在创世区块之中的,一旦发行,没法修改。git
那么,一个普通区块中到底有什么?程序员
1.index:就是从0-n依次产生的数字,能够称之为链高度。算法
2.hash:一个长度为256位随机数,是这个区块的惟一编号。数据库
3.previous hash:上一个区块的hash,一个普通区块有且仅有一个previous hash,这就是区块链之因此称为链的缘由,就是这么一环套一环连接而成的。json
4.tempstamp:用于记录该区块诞生的时间。安全
5.difficulty:直观体现创造该区块的难度。网络
6.nonce:随机数,用于产生下一个区块。数据结构
上述的都存在区块头中。
7.data:存储的交易记录。只有这个存在区块体中。
Ok,上述提到了一个区块须要具有的最基本的几条要素,可能你如今还处于一脸懵逼的状态:这些东西究竟是怎么工做的呢?下面我将一步步分析区块链的工做过程,不过,这里要先问两个问题:为何要产生新的区块?怎么产生新的区块?
以前说了,一个区块记录的就是一份帐单,帐单中存储着若干条交易记录,是买卖双方具体活动的最有力的证实,例如咱们在淘宝上的购买记录,就是咱们的消费帐单。人们天天的消费记录是不断增加的,不可能永远放在一个区块里,那么跟如今的中心化存储机制还有什么区别?因此,随着买卖记录的不断增长,就须要不断产生新的区块来存储这些数据。
我相信,最近除了区块链这个名词如雷贯耳之外,“挖矿”应该也没少听吧。挖矿实际上就是由那些矿工来生成新的区块的过程。在btc项目中,btc矿工挖矿成功(其实就是成功的建立了一个区块)就能够得到必定数量的被btc奖励,因此btc数量是在必定范围内慢慢增长的。在通常容许挖矿的区块链项目(也存在限制coin数量不容许挖矿的区块链项目)中,矿工的数量通常会大于6个,通常超过6个矿工承认的新区块就能够加入到区块链中。到此为止,有人会说:哇!btc这么值钱,挖矿不是很爽?其实否则,若是区块无限制的快速增长,会引发很大的问题,根据中本聪的设定,目前全网每10分钟只能产生一个新区块。并且这10分钟不是靠本身掐表算的,生成新的区块是须要大量的运算的,这10分钟是人家预先设计好的,让计算量大到全网10分钟通常只能产生一个。
好了,至此,区块链的基本概念已经介绍的差很少了,下面言归正传,讲下区块的工做流程:
1.若是A要和B达成一笔交易,好比A转给B一个btc,B给A打10w的RMB。A首先将本身的btc来源信息、交易人等发送给B,同时还要拷贝一份发到全网。什么?这样还有隐私可言吗?固然,聪明的中本聪固然不会犯这么低级的错误。在区块链中,每一个交易个体(也能够理解为每一个网络节点)都会有一对公钥和私钥,公钥至关于一个“收款地址”,而私钥是一个代表本身身份的256位的数字,目前通常是用sha265来生成的,这样,别人并不知道交易的双方是谁。发送报文时,发送方用一个哈希函数从报文文本中生成报文摘要,而后用本身的私钥对摘要进行加密,加密后的摘要将做为报文的数字签名和报文一块儿发送给接收方,接收方首先用与发送方同样的哈希函数从接收到的原始报文中计算出报文摘要,接着再用发送方的公钥来对报文附加的数字签名进行解密,若是这两个摘要相同、那么接收方就能确认该数字签名是发送方的。
2.那么此时,这笔交易是否就完成了呢?若是这就算完成了,那跟A直接用包裹装10w现金快递给B有什么区别呢?此时,全网的矿工都会获得这个交易记录,那么全网的矿工都会为了若干奖励开始建立区块的工做,矿工会利用hash函数生成一个256位的惟一编号赋予这个区块,可是这个编号并非简简单单随便生成的。编号是根据区块的具体内容如交易内容、nonce等惟一肯定的,换句话说,两块内容相同的区块所对应的编号必定是惟一的。但是你会问:这又怎么了?并不难啊。错!中本聪为了控制区块的生成时间,使区块的生成速率知足全网的每10分钟一个的标准,制定了严格的区块生成校验规则,也就是说,能不能生成一个成功的区块要看你的编号是否符合这个规则。例如:生成编号中的前n位必须为‘0’。
因为区块的交易内容是没法修改的,所以矿工们只能经过修改nonce去不断尝试这个函数,直到生成了一个成功的区块为止。若是当区块平均生成时间变快或者变慢,那么系统会对区块校验规则进行相应的调整,从而使平均的生成时间可以控制在规定范围。
若是一个矿工完成了一个区块,会马上告知其余矿工,若是其余矿工此时没有完成新的区块生成,则会停下手头的工做,对区块进行验证,须要确认的信息主要有以下几点:
1).区块的编号有效;这个只要将区块放入哈希函数中,看产生的编号是否和该区块中的编号一致便可。
2).区块的前一个区块有效;以前提过,区块是一个串联的,每个普通区块都会记载前一个区块的编号,这须要其余矿工对比当前的区块链的最后一个区块,看是否相同。
3).交易清单有效;就是说要验证A到底有没有这一个btc能够给B。在区块链的交易信息中,会记录交易中全部btc的前世此生,区块链能够作到追本溯源,所以每个btc在哪里,为何在这里均可以一目了然,因此这点也没问题。
当验证完一个全新的区块后,全网就会认为这个区块有效,会将它添加到现有的区块链末端,同时结束针对该区块的挖矿工做,投入到下一个挖矿周期中。
3.可是不难想象,这样的机制是存在冲突的隐患的,就是这么巧,两个矿工同时制做了一个正确的区块,那么此时没必要二选一,能够将原来线性的区块链改为树状:
可是这样会致使将来在A、B后都会增长相应的区块,那么谁长谁将做为主链延伸下去,另外一个也许会渐渐被遗忘,除非哪天它变得更长。
好啦,这就是区块链最基本的知识,接下来应该谈谈优缺点了。
世界上没有同样东西能够称为完美无瑕的,要知道区块链同样,虽然它被扣上了能够颠覆将来的帽子,可是仍然存在它的局限性:
1.时效性。很容易发现,区块链中存在不少的验证、传递环节,这就会致使其时效性较差。
二、能耗,这点也是显而易见的,区块链须要大量无用计算来控制区块的生成时间。因此区块链不适用于高时效的网络中。
至于区块链的优势,诸如安全、去中心化等等在网络上已经描述的很是清楚,这里就再也不赘述。接下来我用一段python代码来简单实现一个挖矿的流程。
首先建立一个表示区块链的类:
class BlockChain: def __init__(self, initialHash): # init block chain self.chain = [] # init pitman self.pitmen = [] for i in range(6): self.pitmen.append(Pitman) # collect mine results self.results = [] # generate GenesisBlock self.new_block(initialHash)
初始化函数中的chain表示当前的区块链,我会在其中存储区块对象;pitmen表示为这个区块链服务的矿工们,这个列表中也会存有矿工对象;results则会存储每一个阶段产生的区块;new_block方法是建立区块的方法,若是当前生成的区块为第一个区块,则产生创世区块。
下面看看区块链类型的方法:
@property def last_block(self): if len(self.chain): return self.chain[-1] else: return None
last_block会返回当前区块链的最后一个区块对象。
def get_trans(self): return json.dumps({ 'sender': ''.join(random.sample(string.ascii_letters + string.digits, 8)), 'recipient': ''.join(random.sample(string.ascii_letters + string.digits, 8)), 'amount': random.randrange(1, 10000) })
get_trans方法则用来随机生成一份交易信息。
def new_block(self, initialHash=None): if initialHash: # generate Genesis Block block = Block() block.index = 0 block.nonce = random.randrange(0, 99999) block.previousHash = '0' block.difficulty = 0 block.transactionData = self.get_trans() guess = f'{block.previousHash}{block.nonce}{block.transactionData}'.encode() block.hash = hashlib.sha256(guess).hexdigest() block.time = time() self.chain.append(block) else: for i in range(len(self.pitmen)): pm = MyThread(target=self.pitmen[i].mine, args=(self.pitmen[i], len(self.chain), self.last_block.get_block()['Hash'], self.get_trans())) pm.start() pm.join() self.results.append(pm.get_result()) # show all blocks print("All blocks generated by pitmen:") for result in self.results: print(result[0].get_block()) # get new block firstblock = self.results[0][0] mintime = Decimal(self.results[0][1]) for i in range(1, len(self.results)): if Decimal(self.results[i][1]) < mintime: firstblock = self.results[i][0] else: continue self.chain.append(firstblock) self.results = []
这是生成区块的核心部分,这个方法主要分红两个部分:根据传参区分是不是创世区块,若是须要的是创世区块,那就由该类型自动生成一个区块占据区块链的头一个位置。若是须要生成的是普通区块,那么则会将一些基本信息分发给矿工们进行挖矿操做。我在这里设置了6个矿工,为了公平起见,这里也开了个多线程尽可能让矿工们同时收到消息从而能够同时进行挖矿操做。按理说,最早挖矿成功的矿工会将消息发给其余矿工,其余矿工会马上中止进行校验,但因为时间有限,这一步校验环节我没有实现。在这里,我容许全部矿工都完成工做,并提交成果和相应的工做时间,生成耗时最短的区块讲做为正确的区块添加到区块链上。本质上也是依照了“快者优先”的区块链生成原则。
class Block: def __init__(self): self.index = None self.time = None self.difficulty = None self.nonce = None self.hash = None self.previousHash = None self.transactionData = None def get_block(self): return { 'Index': self.index, 'Time': self.time, 'Difficulty': self.difficulty, 'Hash': self.hash, 'Nonce': self.nonce, 'PreviousHash': self.previousHash, 'TransactionData': self.transactionData }
我用了一个相对简单的类型表示区块,在这里区块就是一个很是简单的数据结构,这里的全部字段在上述中已经有了详细的说明,所以再也不赘述。
那么咱们再看看矿工,毕竟矿工才是区块链的生产者和推进者,地位最为核心。矿工类中我设计了两个方法,一个mine方法,也就是挖矿的意思,用来生成新的区块,并会返回该区块生成的用时长短。
def mine(self, index, previousHash, transactionData): beginTime = time() block = Block() block.index = index block.previousHash = previousHash block.transactionData = transactionData block.difficulty, block.hash, block.nonce = self.generate_hash(previousHash, transactionData) block.time = time() endTime = time() return block, endTime - beginTime
另外一个方法是hash生成的方法,生成原则我本身意淫了一个,你们莫怪,由于本人计算机的实在不给力,区块的成功标准很简单,就是用sha256生成的编码最后结尾是0就知足要求,若是改为00,那会半天看不到结果。
下面咱们看下这套实例的运行结果,我设置了一个长度为6的区块链,因为第一个区块是创世区块,因此真正开始建立的区块是从第二个开始的,你们注意index。
这是6个矿工完成的第一区块:
All blocks generated by pitmen: {'Index': 1, 'Time': 1516268156.5971138, 'Difficulty': 2, 'Hash': '01f505a276e3f55a868d9ee18f70bcff75429e1de70f5ab59471a3551cc67a30', 'Nonce': 91554, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "OY8z0Rrx", "recipient": "iSGFJsEm", "amount": 8723}'} {'Index': 1, 'Time': 1516268156.5971138, 'Difficulty': 5, 'Hash': 'c3ba406bad0d87f816f629830a15e2997638bfa230484c224e5470eaa24d8790', 'Nonce': 62372, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "9o8UMDLe", "recipient": "qTOQu7kv", "amount": 2746}'} {'Index': 1, 'Time': 1516268156.5981123, 'Difficulty': 5, 'Hash': '8ff243885e9017296aa2ef1a611ef5b3927ddce818cb7255a04ff3228c982c60', 'Nonce': 67644, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "kIqy1c8C", "recipient": "WSdK0EXh", "amount": 9329}'} {'Index': 1, 'Time': 1516268156.5981123, 'Difficulty': 3, 'Hash': 'ff9716bf9379e2ab7a8640419e7c7b7c7329a5e6e1bbf83a1249f49d070ca8b0', 'Nonce': 37336, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "vBwU0luH", "recipient": "d7o6cRCj", "amount": 5628}'} {'Index': 1, 'Time': 1516268156.5981123, 'Difficulty': 3, 'Hash': '3410c70c31f9bacbfcbd74d63f25f69f27d36075e2d44bddaa60bd72fa042e60', 'Nonce': 34617, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "yzcNpBnh", "recipient": "vbIr7SKo", "amount": 6387}'} {'Index': 1, 'Time': 1516268156.5981123, 'Difficulty': 27, 'Hash': '91e3dc3ef1a151557a1edd837528410b916362bcfb77dbb14dc54c8929f5a0d0', 'Nonce': 49121, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "p1MguhVz", "recipient": "gVSom4D3", "amount": 7356}'}
很明显前两个是最快的,为了简单,我在最快的里面随便选取一个,意思到了就行。你们能够看到,难度值,根据上文所说,这是反应一个区块生成的难易程度的,难度高的,在我这里会表示这个矿工为了获得这个区块进行了多少次尝试。看到这里也许你会问:为何难度最大的时间并无明显长呢?也就是用时并无按照难度的增长而增长。我猜测应该是由于个人示例算法简单,所以结果也不是十分精确,若是计算量达到必定的规模,应该会有明显的差距。(若是有高人知道,能够回复我,在此谢过!)第三到第六个的区块建立结果格式是与之同样的,就不刷屏了。
{'Index': 0, 'Time': 1516268156.5971138, 'Difficulty': 0, 'Hash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'Nonce': 87688, 'PreviousHash': '0', 'TransactionData': '{"sender": "OuVCmHbs", "recipient": "kFxbwSLc", "amount": 503}'} {'Index': 1, 'Time': 1516268156.5971138, 'Difficulty': 2, 'Hash': '01f505a276e3f55a868d9ee18f70bcff75429e1de70f5ab59471a3551cc67a30', 'Nonce': 91554, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "OY8z0Rrx", "recipient": "iSGFJsEm", "amount": 8723}'} {'Index': 2, 'Time': 1516268156.5991132, 'Difficulty': 4, 'Hash': '098544436793881e8041c0c903c96c0055e16396113d73c63bc55e7ba78ec130', 'Nonce': 12875, 'PreviousHash': '01f505a276e3f55a868d9ee18f70bcff75429e1de70f5ab59471a3551cc67a30', 'TransactionData': '{"sender": "HJZSX1hk", "recipient": "j82k51yY", "amount": 3521}'} {'Index': 3, 'Time': 1516268156.6001143, 'Difficulty': 27, 'Hash': '7c10243223caf39bc5a6067de8d93f6ea46bad62c4a0fbcc0aa4e086585d8200', 'Nonce': 18663, 'PreviousHash': '098544436793881e8041c0c903c96c0055e16396113d73c63bc55e7ba78ec130', 'TransactionData': '{"sender": "cJrGxN5R", "recipient": "wkZI8QCv", "amount": 1224}'} {'Index': 4, 'Time': 1516268156.601114, 'Difficulty': 3, 'Hash': '60a099d3fe53e031800669fcc1d9b5ab6df1f80a40354135310a799892f1c3d0', 'Nonce': 51446, 'PreviousHash': '7c10243223caf39bc5a6067de8d93f6ea46bad62c4a0fbcc0aa4e086585d8200', 'TransactionData': '{"sender": "nCNJoy52", "recipient": "kYBT9f65", "amount": 3603}'} {'Index': 5, 'Time': 1516268156.605163, 'Difficulty': 2, 'Hash': '765f69163cf95584721015e3ce819c1980ce33752f8a4dea553d3bedd39f8920', 'Nonce': 31804, 'PreviousHash': '60a099d3fe53e031800669fcc1d9b5ab6df1f80a40354135310a799892f1c3d0', 'TransactionData': '{"sender": "FqOkiTEu", "recipient": "y9EDcSYA", "amount": 4185}'}
这就是由这6个矿工依次建立的六个区块,根据hash值环环相扣。
到底我想说的基本就结束了,最后我想说区块链是个神奇的技术,从听到它就深深的吸引着我,指望将来区块链能够真的带来巨大的变革。要知道,随着AI的兴起,区块链的问世,属于程序员的数字时代会进一步升华,互联网时代只是一个开始!谢谢阅读,本人水平有限,若是您发现问题或我理解误差的地方能够及时指出,再次表示感谢。文章末尾会附上完整代码。
import hashlib import random import string import json import threading from decimal import Decimal from time import time class MyThread(threading.Thread): def __init__(self, target, args=()): super(MyThread, self).__init__() self.func = target self.args = args def run(self): self.result = self.func(*self.args) def get_result(self): try: return self.result except Exception: return None class BlockChain: def __init__(self, initialHash): # init block chain self.chain = [] # init pitman self.pitmen = [] for i in range(6): self.pitmen.append(Pitman) # collect mine results self.results = [] # generate GenesisBlock self.new_block(initialHash) @property def last_block(self): if len(self.chain): return self.chain[-1] else: return None def get_trans(self): return json.dumps({ 'sender': ''.join(random.sample(string.ascii_letters + string.digits, 8)), 'recipient': ''.join(random.sample(string.ascii_letters + string.digits, 8)), 'amount': random.randrange(1, 10000) }) def new_block(self, initialHash=None): if initialHash: # generate Genesis Block block = Block() block.index = 0 block.nonce = random.randrange(0, 99999) block.previousHash = '0' block.difficulty = 0 block.transactionData = self.get_trans() guess = f'{block.previousHash}{block.nonce}{block.transactionData}'.encode() block.hash = hashlib.sha256(guess).hexdigest() block.time = time() self.chain.append(block) else: for i in range(len(self.pitmen)): pm = MyThread(target=self.pitmen[i].mine, args=(self.pitmen[i], len(self.chain), self.last_block.get_block()['Hash'], self.get_trans())) pm.start() pm.join() self.results.append(pm.get_result()) # show all blocks print("All blocks generated by pitmen:") for result in self.results: print(result[0].get_block()) # get new block firstblock = self.results[0][0] mintime = Decimal(self.results[0][1]) for i in range(1, len(self.results)): if Decimal(self.results[i][1]) < mintime: firstblock = self.results[i][0] else: continue self.chain.append(firstblock) self.results = [] def show_chain(self): print('This is mine first block chain!') for block in self.chain: print(block.get_block()) class Block: def __init__(self): self.index = None self.time = None self.difficulty = None self.nonce = None self.hash = None self.previousHash = None self.transactionData = None def get_block(self): return { 'Index': self.index, 'Time': self.time, 'Difficulty': self.difficulty, 'Hash': self.hash, 'Nonce': self.nonce, 'PreviousHash': self.previousHash, 'TransactionData': self.transactionData } class Pitman: def mine(self, index, previousHash, transactionData): beginTime = time() block = Block() block.index = index block.previousHash = previousHash block.transactionData = transactionData block.difficulty, block.hash, block.nonce = self.generate_hash(previousHash, transactionData) block.time = time() endTime = time() return block, endTime - beginTime @staticmethod def generate_hash(previousHash, transactionData): difficulty = 0 nonce = random.randrange(0, 99999) guess = f'{previousHash}{nonce}{transactionData}'.encode() myhash = hashlib.sha256(guess).hexdigest() while myhash[-1] != '0': difficulty += 1 nonce += difficulty guess = f'{previousHash}{nonce}{transactionData}'.encode() myhash = hashlib.sha256(guess).hexdigest() return difficulty, myhash, nonce if __name__ == '__main__': chain = BlockChain(1) length = 5 for i in range(length): chain.new_block() chain.show_chain()