#0:介绍
在本教程中,我们将从头开始编写一些工作加密货币所需的基本概念。角度总是以最简单的方式实现。
我们将在本教程中构建的项目称为“Naivecoin”。编程语言是Typescript。Naivecoin在某些方面是200行代码中Naivechain - 区块链的延伸。
Naivecoin的最终版本决不是“加密货币”的“生产准备”实现,而是试图表明加密货币的基本原则可以以简明的方式实现。
我希望这个教程能帮助你理解更多的加密货币的技术方面。
#1:最小的工作区块链
概观
区块链的基本概念非常简单:分布式数据库保持有序记录的不断增长的列表。在本章中,我们将实施这种区块链的玩具版本。在本章的最后,我们将具有区块链的以下基本功能:
一个定义的块和区块链结构
用任意数据将新块添加到区块链的方法
与其他节点进行通信并同步区块链的区块链节点
一个简单的HTTP API来控制节点
本章中将要实现的完整代码可以在这里找到。
块结构
我们将从定义块结构开始。在这一点上,只有最重要的属性包含在这个块中。
index:区块链中区块的高度
数据:块中包含的任何数据。
时间戳:时间戳
散列:从块的内容中获取的sha256散列
previousHash:对前一个块的散列的引用。该值显式定义了前一个块。
块结构的代码如下所示:
class Block { public index: number; public hash: string; public previousHash: string; public timestamp: number; public data: string; constructor(index: number, hash: string, previousHash: string, timestamp: number, data: string) { this.index = index; this.previousHash = previousHash; this.timestamp = timestamp; this.data = data; this.hash = hash; } }
块散列
块散列是该块最重要的属性之一。哈希计算块的所有数据。这意味着,如果块中的任何内容发生更改,则原始散列不再有效。块散列也可以被认为是块的唯一标识符。例如,可以出现具有相同索引的块,但它们都具有独特的哈希值。
我们使用以下代码计算块的哈希值:
const calculateHash = (index: number, previousHash: string, timestamp: number, data: string): string => CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
需要注意的是,由于没有工作证明的问题需要解决,所以块哈希还没有和挖掘无关。我们使用块散列来保持块的完整性,并明确引用前面的块。
特性的一个重要结果hash
,并previousHash
是一个块不能在不改变的哈希来修改每一个连续的块。
这在下面的例子中演示。如果块44中的数据从“DESERT”改变为“STREET”,则必须改变连续块的所有散列。这是因为hash
块的取决于previousHash
(除其他之外)的值。
在介绍工作证明时,这是一个特别重要的特性。块在区块链越深,修改它就越困难,因为它需要修改每个连续的块。
创世纪块
创世纪街区是区块链中的第一个街区。这是唯一的块没有previousHash
。我们将硬编码的起源块的源代码:
const genesisBlock: Block = new Block( 0, '816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7', null, 1465154705, 'my genesis block!!' );
生成一个块
要生成一个块,我们必须知道前一个块的散列,并创建剩余的所需内容(= index,hash,data和timestamp)。块数据是由最终用户提供的,但其余参数将使用以下代码生成:
const generateNextBlock = (blockData: string) => { const previousBlock: Block = getLatestBlock(); const nextIndex: number = previousBlock.index + 1; const nextTimestamp: number = new Date().getTime() / 1000; const nextHash: string = calculateHash(nextIndex, previousBlock.hash, nextTimestamp, blockData); const newBlock: Block = new Block(nextIndex, nextHash, previousBlock.hash, nextTimestamp, blockData); return newBlock; };
存储区块链
现在我们将只使用内存中的Javascript数组来存储区块链。这意味着当节点终止时数据不会被保存。
const blockchain: Block[] = [genesisBlock];
验证块的完整性
在任何时候,我们必须能够验证一个块或一个块链是否在完整性方面是有效的。特别是当我们从其他节点收到新的块时,并且必须决定是否接受它们。
要使块有效,必须遵守以下规定:
块的索引必须比前一个数字大一个数字
该
previousHash
块匹配hash
以前块该
hash
块本身必须是有效的
这是用下面的代码演示的:
const isValidNewBlock = (newBlock: Block, previousBlock: Block) => { if (previousBlock.index + 1 !== newBlock.index) { console.log('invalid index'); return false; } else if (previousBlock.hash !== newBlock.previousHash) { console.log('invalid previoushash'); return false; } else if (calculateHashForBlock(newBlock) !== newBlock.hash) { console.log(typeof (newBlock.hash) + ' ' + typeof calculateHashForBlock(newBlock)); console.log('invalid hash: ' + calculateHashForBlock(newBlock) + ' ' + newBlock.hash); return false; } return true; };
我们还必须验证块的结构,以便对等体发送的格式错误的内容不会使我们的节点崩溃。
const isValidBlockStructure = (block: Block): boolean => { return typeof block.index === 'number' && typeof block.hash === 'string' && typeof block.previousHash === 'string' && typeof block.timestamp === 'number' && typeof block.data === 'string'; };
现在我们有了验证一个块的方法,我们可以继续验证一个完整的块链。我们首先检查链中的第一块与匹配genesisBlock
。之后,我们使用之前描述的方法验证每个连续的块。这是演示使用下面的代码:
const isValidChain = (blockchainToValidate: Block[]): boolean => { const isValidGenesis = (block: Block): boolean => { return JSON.stringify(block) === JSON.stringify(genesisBlock); }; if (!isValidGenesis(blockchainToValidate[0])) { return false; } for (let i = 1; i < blockchainToValidate.length; i++) { if (!isValidNewBlock(blockchainToValidate[i], blockchainToValidate[i - 1])) { return false; } } return true; };
选择最长的链
在给定的时间内,链中总是只应该有一组明确的块。在冲突的情况下(例如,两个节点都生成块号72),我们选择具有最长块数的链。在下面的例子中,块72:a350235b00中引入的数据将不包含在区块链中,因为它将被较长的链覆盖。
这是使用以下代码实现的逻辑:
const replaceChain = (newBlocks: Block[]) => { if (isValidChain(newBlocks) && newBlocks.length > getBlockchain().length) { console.log('Received blockchain is valid. Replacing current blockchain with received blockchain'); blockchain = newBlocks; broadcastLatest(); } else { console.log('Received blockchain invalid'); } };
与其他节点通信
节点的一个重要部分是与其他节点共享和同步区块链。以下规则用于保持网络同步。
当一个节点产生一个新的块时,它将它广播给网络
当一个节点连接到一个新的对等体时,它将查询最新的块
当一个节点遇到一个索引大于当前已知块的块时,它会将该块添加到当前链中,或者查询完整的块链。
我们将使用websockets进行点对点通信。每个节点的活动套接字都存储在const sockets: WebSocket[]
变量中。没有使用自动对等体发现。必须手动添加对等点的位置(= Websocket URL)。
控制节点
用户必须能够以某种方式控制节点。这是通过建立一个HTTP服务器来完成的。
const initHttpServer = ( myHttpPort: number ) => { const app = express(); app.use(bodyParser.json()); app.get('/blocks', (req, res) => { res.send(getBlockchain()); }); app.post('/mineBlock', (req, res) => { const newBlock: Block = generateNextBlock(req.body.data); res.send(newBlock); }); app.get('/peers', (req, res) => { res.send(getSockets().map(( s: any ) => s._socket.remoteAddress + ':' + s._socket.remotePort)); }); app.post('/addPeer', (req, res) => { connectToPeers(req.body.peer); res.send(); }); app.listen(myHttpPort, () => { console.log('Listening http on port: ' + myHttpPort); }); };
如所看到的,用户能够通过以下方式与节点交互:
列出所有的块
用用户给出的内容创建一个新块
列出或添加同龄人
控制节点最直接的方法是例如使用Curl:
#get all blocks from the node > curl http://localhost:3001/blocks
建筑
需要注意的是,该节点实际上暴露了两个Web服务器:一个用于控制节点(HTTP服务器),一个用于节点之间的点对点通信。(Websocket HTTP服务器)
结论
Naivecoin现在只是一个玩具“通用”区块链。此外,本章还介绍了区块链的一些基本原理是如何以相当简单的方式实现的。在下一章中,我们将把证明工作算法(挖掘)添加到naivecoin。
第一章手杖的完整代码在这里找到。
本文暂时没有评论,来添加一个吧(●'◡'●)