Introduces some concepts and mechanisms about Bitcoin. For a better understanding, this paper implements a simple block chain prototype based on JavaScript, and then enriches it.
1. Overview
Components of the block chain model described earlier include blocks, block chains made of blocks, and data persistence layers that hold block chains.A very simple UML class diagram follows:
Since I'm front-end, I've looked at the theory of block chains for such a long time in my spare time, or itchy thank you code to implement this kind of thing in JavaScript.After writing it, I find that it is still too simple for the block chain prototype at this stage, but if it is used for front-end interviews, it is a good idea to examine the following objects and knowledge points such as Proise.
2. Define the block data model
Extract the details of the bitcoin block and modify it to remove all the redundant information, leaving only the most basic information describing the block, declaring the block class as follows:
class Block { constructor(data) { // Attribute values for blocks this.hash = ""; this.height = 0; this.body = data; this.time = 0; this.previousBlockHash = ""; } } module.exports.Block = Block;
3. Data persistence layer
The simplest prototype is to implement block chains using arrays, but each time the arrays are restarted, the data is not persistent.So here, levelDB database is introduced as a persistence layer to store data, which can be referred to level .Because calling the API directly is too cumbersome for the application tier, declare here a data operation class, LevelSandbox, which does not have all the functions of adding, deleting, altering, searching like traditional relational databases. Because of the immutability of data on block chains, this class only contains operations of adding and searching.
3.1 Get data from database based on key
The following asynchronous implementations are implemented in a Promise fashion rather than a callback, and the benefits as front-end engineers are not covered here, but asynchronous implementations that need to be understood Introduction to Promise To expand your reading.
getLevelDBData(key) { let self = this; return new Promise(function(resolve, reject) { self.db.get(key) .then(value => { console.log('Value = ' + value); resolve(value) }) .catch(err => { console.log('Not found!'); reject(err) }) }); }
3.2 Insert key/value data into database
Stored in a database as key/value, whose key is optional, consider using the height field declared in the block class, which identifies the order of a block in the chain and is unique and appropriate.
addLevelDBData(key, value) { let self = this; return new Promise(function(resolve, reject) { self.db.put(key, value) .then(() => resolve()) .catch((err) => { console.log('Block ' + key + ' submission failed'); reject(err) }) }); }
3.3 Get the total number of blocks in the database
The createReadStream() method creates a stream that reads the database, where the purpose is to traverse the entire library to get the total number of blocks stored. It also sets the traversal order by passing parameters, which can be detailed in the documentation.
getBlocksCount() { let self = this; return new Promise(function(resolve, reject){ let height = 0; self.db.createReadStream() .on('data', function () { height++; }) .on('error', function (error) { reject('Unable to read data stream!', error); }) .on('close', function () { resolve(height); }); }); }
4. Block Chain Classes
This class is responsible for adding newly created blocks to the block chain and verifying the data integrity of the blocks in the chain.This process involves hashing block data, using third-party libraries for convenience crypto-js The SHA256 method implemented.
The main ways to conceive of this category include:
- CreateGenesis Block (): Generate start block
- getBlockHeight(): Get block chain length
- getBlock(height): Gets the specified block
- AddBlock: Add a new block to the block chain
- ValidateBlock: Validate a block
- validateChain(): Verify block chains
The main methods are as follows:
4.1 Add new blocks
Each block is linked through the previousBlockHash attribute, which in turn points to the previous block, except for the first block, which is empty.
addBlock(block) { return this.getBlockHeight() .then(height => { //block height block.height = height; // UTC timestamp block.time = new Date().getTime().toString().slice(0, -3); if (height > 0) { this.getBlock(height - 1) .then(preBlock => { // Hash value of previous block block.previousBlockHash = preBlock.hash; // Hash blocks block.hash = SHA256(JSON.stringify(block)).toString(); // Quick Save New Zone in Library this.bd.addLevelDBData(height, JSON.stringify(block)); }) .catch(error => console.log(error)); } else { block.hash = SHA256(JSON.stringify(block)).toString(); this.bd.addLevelDBData(height, JSON.stringify(block)); } }) .catch( error => console.log(error)); }
4.2 Verify single block integrity
The validation method is to apply the properties of the hash algorithm: the same data after hash generates the same hash value.
validateBlock(height) { // Get the value of the block return this.getBlock(height) .then(block => { const objBlock = JSON.parse(block); let blockHash = objBlock.hash; objBlock.hash = ''; // Regenerate hash values for blocks let validBlockHash = SHA256(JSON.stringify(objBlock)).toString(); objBlock.hash = blockHash; // Compare to verify integrity if (blockHash === validBlockHash) { return Promise.resolve({isValidBlock: true, block: objBlock}); } else { console.log('Block #'+blockHeight+' invalid hash:\n'+blockHash+'<>'+validBlockHash); return Promise.resolve({isValidBlock: false, block: objBlock}); } }) }
4.3 Verify the entire block chain
Verify the integrity of the entire chain by checking each block in turn.
validateChain() { let errorLog = []; let previousHash = ''; this.getBlockHeight() .then(height => { for (let i = 0; i < height; i++) { this.getBlock(i) .then(block => this.validateBlock(block.height)) .then(({isValidBlock, block}) => { if (!isValidBlock) errorLog.push(i); if (block.previousBlockHash !== previousHash) errorLog.push(i); previousHash = block.hash; if (i === height - 1) { if (errorLog.length > 0) { console.log(`Block errors = ${errorLog.length}`) console.log(`Blocks: ${errorLog}`) } else { console.log('No errors detected') } } }) } }) }
5. Generate test data
(function theLoop (i) { setTimeout(function () { let blockTest = new Block.Block("Test Block - " + (i + 1)); myBlockChain.addBlock(blockTest).then((result) => { console.log(result); i++; if (i < 10) theLoop(i); }); }, 10000); })(0);
The look of a block chain prototype is fairly primitive, but it's still very simple in terms of its current functionality. It's said that the prototype is elevated, but it's getting richer later.This is only a practical subsection of the previous one.
This article lists the main code snippets, the overall implementation is not difficult, not all the code posted is mainly to express ideas more clearly, if there are problems in the implementation process of a friend, you can leave a message to exchange.