Almost everyone has heard of crypto currencies like Bitcoin and Ethereum, but only a handful of people know the technology behind them. In this blog, I'll use JavaScript to create a simple blockchain to demonstrate how their internals work. I'm going to call it savjeecoin!. The full text is divided into three parts:
Implement a basic blockchain
Block chain
A blockchain is a public database made up of chunks that anyone can access. This seems like nothing special, but they have an interesting property: they're immutable.
Once a chunk is added to the blockchain, it is no longer changed unless the remaining chunks are invalidated.
That's why crypto currencies are based on blockchain. You certainly don't want people to change the deal after the deal is done!
Create a chunk
Blockchain is linked by a lot of chunks (it sounds like there's nothing wrong with it.) )。 The chunks on the chain allow us to detect whether anyone has manipulated any of the previous chunks in some way.
So how do we ensure the integrity of the data? Each chunk contains a hash that is computed based on its content. It also contains the hash of the previous block.
The following is a block class that is written in JavaScript in roughly the same way:
const SHA256 = require("crypto-js/sha256");
class Block {
constructor(index, timestamp, data, previousHash = ‘‘) {
this.index = index;
this.previousHash = previousHash;
this.timestamp = timestamp;
this.data = data;
this.hash = this.calculateHash();
}
calculateHash() {
return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data)).toString();
}
}
Because SHA256 is not supported in JavaScript, I introduced the CRYPTO-JS library. Then I define a constructor to initialize the properties of the chunk.
Each chunk is given the index property to tell us where the block is located throughout the chain. We also generate a timestamp and some data that needs to be stored in chunks. Finally, the hash of the previous block.
Create a chain
Now we can link the chunks in the Blockchain class. Here is the code implemented with JavaScript:
Class blockchain{
Constructor () {
This.chain = [This.creategenesisblock ()];
}
Creategenesisblock () {
return new block (0, "01/01/2017", "Genesis Block", "0");
}
Getlatestblock () {
return this.chain[this.chain.length-1];
}
Addblock (Newblock) {
Newblock.previoushash = This.getlatestblock (). hash;
Newblock.hash = Newblock.calculatehash ();
This.chain.push (Newblock);
}
Ischainvalid () {
for (Let i = 1; i < this.chain.length; i++) {
Const CURRENTBLOCK = This.chain[i];
Const PREVIOUSBLOCK = this.chain[i-1];
if (Currentblock.hash!== Currentblock.calculatehash ()) {
return false;
}
if (Currentblock.previoushash!== previousblock.hash) {
return false;
}
}
return true;
}
}
In the constructor, I initialize the entire chain by creating an array that contains the Genesis block. The first chunk is special because it cannot point to the previous block.
I've also added the following two methods:
To do this, we add the hash of the previous chunk to our new chunk. In this way, we can maintain the integrity of the entire chain.
Because as soon as we change the content of the latest chunk, we need to recalculate its hash. When the calculation is complete, I will put this block in the push chain (an array).
Finally, I create a ischainvalid () to make sure that no one has tampered with the blockchain. It iterates through all the chunks to check if the hash is correct for each chunk.
It checks to see if each chunk points to the correct previous block by comparing Previoushash. If everything is fine, it will return true otherwise it will return false.
using Blockchain
Our blockchain class has been finished and can really start using it.
let savjeeCoin = new Blockchain();
savjeeCoin.addBlock(new Block(1, "20/07/2017", { amount: 4 }));
savjeeCoin.addBlock(new Block(2, "20/07/2017", { amount: 8 }));
Here I just created an instance of the blockchain and named it savjeecoin. Then I added some chunks to the chain.
The chunk can contain any data you want to put, but in the code above, I chose to add an object with the amount attribute.
try to do it!
I have said in the introduction that blockchain is immutable. Once added, the chunks are no longer possible to change. Let's try.
// 检查是否有效(将会返回true)
console.log(‘Blockchain valid? ‘ + savjeeCoin.isChainValid());
// 现在尝试操作变更数据
savjeeCoin.chain[1].data = { amount: 100 };
// 再次检查是否有效 (将会返回false)
console.log("Blockchain valid? " + savjeeCoin.isChainValid());
I will verify the integrity of the entire chain at the outset by running Ischainvalid (). We have manipulated any chunk, so it will return true.
I then changed the data for the first chunk (indexed as 1) on the chain. Then I check the integrity of the whole chain again and find it returns false. Our entire chain is no longer in effect.
Conclusion
The little chestnut is far from complete. It has not implemented the POW (proof-of-work mechanism) or peer network to communicate with other miners.
But it does prove how the blockchain works. Many people think that the principle is very complex, but this article proves that the basic concept of blockchain is very easy to understand and implement.
Implement POW
In the above we have created a simple blockchain with JavaScript to demonstrate how the blockchain works.
But this implementation is not complete, and many people find that the system can still be tampered with. That's right! Our blockchain requires another mechanism to defend against attacks. Let's see how we can do that.
problem
Now we can create chunks quickly and then quickly add them into our blockchain.
However, this leads to three questions:
People can quickly create chunks and then fill our chain with rubbish. A lot of chunks can overload our blockchain and make it unusable.
Because it's so easy to create a valid chunk, one can tamper with a chunk in the chain and then recalculate the hash of all chunks. Even if they have tampered with chunks, they can still end up with valid chunks.
You can effectively control the blockchain by combining these two flaws. A blockchain is driven by a peer-network, where nodes add chunks to the longest chain available.
So you can tamper with chunks, then compute all the other chunks, and finally add as many chunks as you want to add. You'll end up with a longest chain, all the other nodes will accept it, and then add your own chunks.
Obviously we need a solution to solve these problems: POW (proof-of-work: proof of workload).
What is POW?
POW is a mechanism that existed before the first blockchain was created. This is a simple technique to prevent misuse by a certain number of calculations.
Workload is the key to preventing garbage filling and tampering. If it requires a lot of calculation, it is no longer worthwhile to fill the garbage.
Bitcoin implements the POW by requiring a hash to be a specific 0 number. This is also called difficulty, but wait a minute! How can a chunk of hash be changed?
In the bitcoin scenario, a chunk contains information about various financial transactions. We certainly don't want to confuse the data in order to get the right hash.
To solve this problem, the blockchain adds a Nonce value. The Nonce is used to find the number of valid hashes.
Furthermore, because the output of the hash function cannot be predicted, only a large number of combinations can be attempted before obtaining a hash that satisfies the difficulty condition. Find a valid hash (create a new chunk) in the circle called Mining.
In the bitcoin scenario, the POW ensures that only one chunk can be added every 10 minutes. You can imagine how much power the garbage filler needs to create a new chunk, and it's hard to cheat the network, let alone tamper with the whole chain.
Implement POW
How do we do that? Let's start by modifying our chunk class and adding a Nonce variable to its constructor. I'll initialize it and set its value to 0.
constructor(index, timestamp, data, previousHash = ‘‘) {
this.index = index;
this.previousHash = previousHash;
this.timestamp = timestamp;
this.data = data;
this.hash = this.calculateHash();
this.nonce = 0;
}
We also need a new way to increase the Nonce until we get a valid hash. Emphasize that this is determined by the difficulty. So we'll get the difficulty as a parameter.
mineBlock(difficulty) {
while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
this.nonce++;
this.hash = this.calculateHash();
}
console.log("BLOCK MINED: " + this.hash);
}
Finally, we need to change the Calculatehash () function. Because at the moment it has not used the Nonce to calculate the hash.
calculateHash() {
return SHA256(this.index +
this.previousHash +
this.timestamp +
JSON.stringify(this.data) +
this.nonce
).toString();
}
By combining them together, you get the Block class as shown below:
Class Block {
Constructor (index, timestamp, data, Previoushash = ") {
This.index = index;
This.previoushash = Previoushash;
This.timestamp = timestamp;
This.data = data;
This.hash = This.calculatehash ();
this.nonce = 0;
}
Calculatehash () {
Return SHA256 (This.index + this.previoushash + this.timestamp + json.stringify (this.data) + this.nonce). toString ();
}
Mineblock (difficulty) {
while (this.hash.substring (0, difficulty)!== Array (difficulty + 1). Join ("0")) {
this.nonce++;
This.hash = This.calculatehash ();
}
Console.log ("BLOCK mined:" + this.hash);
}
}
Modify Blockchain
Now that our chunks are already Nonce and can be mined, we also need to make sure that our blockchain supports this new behavior.
Let's start by adding a new attribute to the blockchain to track the whole chain's difficulty. I'll set it to 2 (meaning that the chunk's hash must start with 2 0).
constructor() {
this.chain = [this.createGenesisBlock()];
this.difficulty = 2;
}
All that's left to do now is to change the Addblock () method so that you actually dig into the block before adding it to the chain. Below we pass the difficulty to the block.
addBlock(newBlock) {
newBlock.previousHash = this.getLatestBlock().hash;
newBlock.mineBlock(this.difficulty);
this.chain.push(newBlock);
}
Done! Our blockchain now has a POW to defend against attacks.
Test
Now let's test our blockchain to see what the effect is to add a new chunk under the POW.
I will use the previous code, we will create a new blockchain instance, and then add 2 chunks to it.
let savjeeCoin = new Blockchain();
console.log(‘Mining block 1‘);
savjeeCoin.addBlock(new Block(1, "20/07/2017", { amount: 4 }));
console.log(‘Mining block 2‘);
savjeeCoin.addBlock(new Block(2, "20/07/2017", { amount: 8 }));
If you run the code above, you will find that adding new blocks is still very fast. This is because the current level of difficulty is only 2 (or your computer is performing very well).
If you create a blockchain instance with a difficulty of 5, you will find that your computer will take about 10 seconds to dig mine. As the difficulty increases, the protection of your defense attacks is greater.
Disclaimer
As I said before: this is by no means a complete blockchain. It still lacks a lot of features (like peer-network). This is just to illustrate how the blockchain works.
And: Because of the single thread, digging with JavaScript is not fast.
Trading and mining incentives
In the first two sections we created a simple blockchain and added the POW to defend against the attack.
But we also stole the lazy way: our blockchain can only store a single transaction in one chunk, and the miners are not rewarded. Now, let's solve this problem!
Refactoring block Classes
Now a chunk has Index,previoushash,timestamp,data,hash and nonce attributes.
This index property is not very useful, in fact I don't even know why I started to add it.
So I removed it and renamed data to transactions to be more semantic.
class Block{
constructor(timestamp, transactions, previousHash = ‘‘) {
this.previousHash = previousHash;
this.timestamp = timestamp;
this.transactions = transactions;
this.hash = this.calculateHash();
this.nonce = 0;
}
}
When we change the Block class, we also have to change the Calculatehash () function. Now it's still using the old index and data properties.
calculateHash() {
return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.transactions) + this.nonce).toString();
}
Trading Class
Within the block, we will be able to store multiple transactions. So we also need to define a trading class so that we can lock down the attributes that the trade should have:
class Transaction{
constructor(fromAddress, toAddress, amount){
this.fromAddress = fromAddress;
this.toAddress = toAddress;
this.amount = amount;
}
}
The trading example is very simple and contains only the initiator (fromaddress) and the receiver (toaddress) and the number. If there is a need, you can also add more fields in it, but this is for minimal implementation.
Adjust our blockchain
Current biggest task: adjust our blockchain to accommodate these new changes. The first thing we need to do is to store the pending transactions.
As you know, because of the POW, the blockchain can create blocks stably. In the bitcoin scenario, the difficulty is set to create a new chunk every 10 minutes. However, you can submit a new transaction between the two blocks created.
To do this, we first need to change the constructor of our blockchain so that he can store pending transactions.
We will also create a new attribute that defines how much money a miner receives as a reward:
class Blockchain{
constructor() {
this.chain = [this.createGenesisBlock()];
this.difficulty = 5;
// 在区块产生之间存储交易的地方
this.pendingTransactions = [];
// 挖矿回报
this.miningReward = 100;
}
}
Next, we will adjust our Addblock () method. But my adjustment means to erase and rewrite it! We will no longer allow people to add chunks directly to the chain. Instead, they must add the transaction to the next chunk.
And we renamed Addblock () to CreateTransaction (), which looks more semantic:
createTransaction(transaction) {
// 这里应该有一些校验!
// 推入待处理交易数组
this.pendingTransactions.push(transaction);
}
Digging Mine
People can now add new transactions to the list of pending transactions. But anyway, we need to clean them up and move them into the actual chunks.
To do this, let's create a Minependingtransactions () method. This method not only digs up all new chunks to be traded, but also sends a reward to the miners.
minePendingTransactions(miningRewardAddress) {
// 用所有待交易来创建新的区块并且开挖..
let block = new Block(Date.now(), this.pendingTransactions);
block.mineBlock(this.difficulty);
// 将新挖的看矿加入到链上
this.chain.push(block);
// 重置待处理交易列表并且发送奖励
this.pendingTransactions = [
new Transaction(null, miningRewardAddress, this.miningReward)
];
}
Please note that this method takes the parameter miningrewardaddress. If you start mining, you can pass your wallet address to this method.
Once successfully dug into the mine, the system will create a new deal to give you a mining bonus (in this Li Zili is 100 coins).
One thing to note is that in this pest, we add all pending transactions to a chunk. But in fact, because the size of the block is limited, so this is not feasible.
In Bitcoin, a chunk is about 2MB in size. If more trades are able to squeeze into a block, the miners can choose which deals are not reached (usually the higher-cost trades tend to win).
the balance of the address
Let's do one more thing before testing our code! It would be better if we could check the balance of the addresses on our blockchain.
Getbalanceofaddress (address) {
Let balance = 0; You-start at zero!
Traverse each chunk and the transactions within each chunk
For (const block of This.chain) {
For (const trans of block.transactions) {
If the address is the initiator, reduce the balance
if (trans.fromaddress = = = Address) {
Balance-= Trans.amount;
}
If the address is the receiver, increase the balance
if (trans.toaddress = = = Address) {
Balance + = Trans.amount;
}
}
}
return balance;
}
Test
Well, we've finished and can work properly. to do this, we created a number of transactions:
let savjeeCoin = new Blockchain();
console.log(‘Creating some transactions...‘);
savjeeCoin.createTransaction(new Transaction(‘address1‘, ‘address2‘, 100));
savjeeCoin.createTransaction(new Transaction(‘address2‘, ‘address1‘, 50));
These transactions are currently in a waiting state, and in order for them to be confirmed, we must start digging:
console.log(‘Starting the miner...‘);
savjeeCoin.minePendingTransactions(‘xaviers-address‘);
When we start digging, we also pass an address where we want to get mining rewards. In this case, my address is xaviers-address (very complex!). )。
After that, let's check xaviers-address's account balance:
console.log(‘Balance of Xaviers address is‘, savjeeCoin.getBalanceOfAddress(‘xaviers-address‘));
// 输出: 0
My account output turned out to be 0?!. Wait, why? Shouldn't I get my mine-mining rewards? If you look closely at the code, you'll see that the system creates a trade and then adds your mining bonus as a new pending deal.
This transaction will be included in the next block. So if we start digging again, we will receive our 100 coin bonus!
console.log(‘Starting the miner again!‘);
savjeeCoin.minePendingTransactions("xaviers-address");
console.log(‘Balance of Xaviers address is‘, savjeeCoin.getBalanceOfAddress(‘xaviers-address‘));
// 输出: 100
Limitations and conclusions
Now our blockchain has been able to store multiple trades on a single block, and can bring rewards to miners.
However, there are some deficiencies: when sending money, we do not check whether the initiator has enough balance to actually trade.
However, this is actually an easy thing to solve. We also did not create a new wallet and signature trade (traditionally done with public/private key encryption).
This is by no means a complete blockchain implementation! It still lacks a lot of functionality. This is just to verify some concepts to help you understand how the blockchain works.
ZT-----Write a blockchain with Javascrip