1. 1. Blockchain structure
    1. 1.1. Blockchain Network Architecture
    2. 1.2. Q&A: Review for [1.3]
      1. 1.2.1. How do distributed p2p networks agree on what data is valid without centeral adminstrator running the show?
      2. 1.2.2. How is block hash calculated?
      3. 1.2.3. What is a valid hash?
    3. 1.3. Notes from Blockchain demo
      1. 1.3.1. Blockchain
      2. 1.3.2. Block
      3. 1.3.3. Index
      4. 1.3.4. Timestamp
      5. 1.3.5. Hash
      6. 1.3.6. Valid Hash
      7. 1.3.7. Block Hash Calculation
      8. 1.3.8. Previous Hash
      9. 1.3.9. Data
      10. 1.3.10. Nonce
      11. 1.3.11. Mining a Block
      12. 1.3.12. Data Mutation
      13. 1.3.13. Mutation Effect
      14. 1.3.14. Adding a new Block
      15. 1.3.15. Adding valid blocks
      16. 1.3.16. Peer-to-peer Network
      17. 1.3.17. Add Peer
      18. 1.3.18. Peer Status
      19. 1.3.19. Peer Messages
      20. 1.3.20. Peer Communication
      21. 1.3.21. P2P Tour Pt.1
      22. 1.3.22. P2P Tour Pt.2
      23. 1.3.23. P2P Tour Pt.3
      24. 1.3.24. Immutability
      25. 1.3.25. 51% Attack
      26. 1.3.26. You might be wondering, when validating a chain from a peer, is our node going to validate each block by starting at genesis block or is there a much easier way?
      27. 1.3.27. Can you tell me how header-first synchronization works in simple terms?
      28. 1.3.28. Okay thanks but can you explain how a node validates a chain only using headers? Is it just comparing its headers to peer’s headers? That’s the bit I am missing.
      29. 1.3.29. How is acceptable timestamp range is determined for a block mined to be valid?
      30. 1.3.30. What is Network Time Protocol(NTP)?
      31. 1.3.31. Here’s a simplified explanation of how NTP works:
      32. 1.3.32. Why is so much computational power required to manipulate data in early blockchain blocks?
      33. 1.3.33. Has Bitcoin ever been attacked?
  2. 2. Task - 1: Blocks and Hashes
    1. 2.1. Blocks and Hashes
    2. 2.2. 🏁 Your Goal: Return a Hash
    3. 2.3. Answer for Task 1
    4. 2.4. Task Details
      1. 2.4.1. Hash Function
      2. 2.4.2. Crypto-JS
      3. 2.4.3. Hexadecimal
      4. 2.4.4. Why 64 Hexadecimal Characters?
    5. 2.5. Task -2 What’s in a Hash?
      1. 2.5.1. Adding Data to the Hash
      2. 2.5.2. 🏁 Your Goal: Hash the Data
    6. 2.6. Details to Task - 2:
      1. 2.6.1. Data Security
      2. 2.6.2. Answer to Task - 2 What’s in a Hash?
      3. 2.6.3. Tests to run against
    7. 2.7. Task 3 - Genesis Block
      1. 2.7.1. Blockchain Time
      2. 2.7.2. 🏁 Your Goal: Add the Genesis Block
    8. 2.8. Details for Task 3
      1. 2.8.1. Genesis Block
      2. 2.8.2. Bitcoin Genesis Block
      3. 2.8.3. Ethereum Genesis Block
      4. 2.8.4. Answer 3 for Genesis Block
      5. 2.8.5. Tests to run against
    9. 2.9. Task 4 - Your Goal: Create an addBlock Function
      1. 2.9.1. Answer for Task 4: Create an addBlock function
      2. 2.9.2. Tests to run against
    10. 2.10. Task -5 , Linking the Blocks
      1. 2.10.1. 🏁 Your Goal: Link Blocks
      2. 2.10.2. 💡 Hints
      3. 2.10.3. Details on Task - 5
      4. 2.10.4. Changing Data
      5. 2.10.5. Answer to Task - 5
      6. 2.10.6. Tests to run against:

Blockchain Structure

Blockchain structure

Blockchain Network Architecture

  • centeralized vs decentralized vs distributed server acrchitectures

  • server-based-networks-vs-peer-to-peer-network

####Is blockchain decentralized or distributed/p2p network?

  • Blockchain technology can be described as both decentralized and distributed/p2p, but it’s important to understand the distinctions between the two terms.

  • Decentralization refers to the distribution of authority or control across multiple entities, allowing for a more democratic and inclusive decision-making process.

  • In the context of blockchain, decentralization means that there is no central authority or single point of control governing the network.

  • Instead, control is distributed among a network of participants, often referred to as nodes or validators, who collectively maintain and validate the blockchain.

  • This decentralized nature helps ensure transparency, security, and resilience against censorship or single points of failure.

  • On the other hand, distribution/p2p refers to the physical dispersal of data or computing resources across multiple nodes in a network. In a distributed system, data is replicated and stored on multiple nodes, creating redundancy and enhancing fault tolerance. Blockchain networks are typically designed as distributed systems, where each node maintains a copy of the entire blockchain or a subset of it.

  • Therefore, blockchain technology can be considered both decentralized and distributed. It’s decentralized because there is no central authority controlling the network, and it’s distributed because the blockchain data is replicated and stored across multiple nodes in the network. These characteristics work together to create a robust and transparent system for managing transactions and digital assets.

Q&A: Review for [1.3]

How do distributed p2p networks agree on what data is valid without centeral adminstrator running the show?
  • this a problem that existed in computer science and it is called Byzantine generals problem this the problem Satoshi has solved using Poof of Work. So, the answer is consensus mechanism - Proof of work.

  • the problem in Byzantine generals problem is - coordination in decenteralized environment

  • Byzantine generals problem

  • to add more on the answer above - the answer is consensus mechanism. the bitcoin network decides validity of new data based on who is able to produce a valid proof of work.

How is block hash calculated?
  • A: a miner just constructs a candidate block and hashes it many many times by changing the nonce until a valid hash is found. you might ask what is a valid hash?
What is a valid hash?
  • a valid hash for blockchain is a hash that meets certain requirements. Some of the common requirements for a valid hash are:

    • a block index is one greater than the latest index.
    • block previous hash is equal to latest block hash
    • block hash meets difficulty requirement.
    • block hash is correctly calculated. say you found the hash that meets target difficulty, but you missed a prevblock lol.
  • https://blockchaindemo.io is best place to learn some blockchain terminologies. check it out.

  • start from blockchaindemo.io there are some cool notes to add here.

  • then have a look at https://coindemo.io/.

Notes from Blockchain demo

  • A blockchain is a distributed database secured by cryptography. It is the technology behind Bitcoin.
Blockchain
  • A blockchain has a list of blocks.

  • It starts with a single block, called the genesis block

  • code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // Blockchain.js
    const Block = require('./Block.js');

    class Blockchain {
    constructor() {
    this.blockchain = [Block.genesis()];
    }

    get() {
    return this.blockchain;
    }

    get latestBlock() {
    return this.blockchain[this.blockchain.length - 1];
    }
    }

    module.exports = Blockchain;
Block
  • each block stores the following information:

    1. index
    2. timestamp
    3. hash
    4. data
    5. previous hash
    6. nonce
  • Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // Block.js
    class Block {
    constructor(index, previousHash, timestamp, data, hash, nonce) {
    this.index = index;
    this.previousHash = previousHash;
    this.timestamp = timestamp;
    this.data = data;
    this.hash = hash;
    this.nonce = nonce;
    }

    get genesis() {
    new Block(
    0,
    '0',
    1508270000000,
    'Welcome to Blockchain Demo 2.0!',
    '000dc75a315c77a1f9c98fb6247d03dd18ac52632d7dc6a9920261d8109b37cf',
    604
    );
    }
    }
    module.exports = Block;
Index
  • The index is the position of the block in the chain.

  • The genesis block has an index of 0. The next block will have an index of 1.

Timestamp
  • A record of when the block was created.

  • You might ask is the timestamp, when block is created or after a block is successfully mined?

    • Answer: the timestamp in a block typically refers to the time when the block is created, not necessarily when it is successfully mined. When a new block is created, it includes a timestamp that represents the approximate time at which the block was generated by a miner.

    • Once the block is successfully mined and added to the blockchain, its timestamp remains unchanged.

  • The timestamp helps to keep the blockchain in order.

Hash
  • A hash looks like a bunch of random numbers and letters.

  • It is a alphanumeric value that uniquely identifies data, or the "digital fingerprint" of data.

  • Properties of a hash:

    • Hash has a fixed length.
    • Same data always maps to same hash.
    • Different data always maps to a different hash (within practical limitations).
    • Is easy to compute.
    • Is infeasible to convert hash to data.
    • A small change in data leads to a large change in hash.
Valid Hash
  • A valid hash for a blockchain is a hash that meets a certain requirement. For example, For this blockchain, having three zeros at the beginning of the hash is the requirement for a valid hash.

  • The number of leading zeros required is the difficulty.

  • Code:

    1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const Block = require('./Block.js');
class Blockchain {
constructor() {
this.blockchain = [Block.genesis()];
this.difficulty = 3; // notice the difficulty here
}

// get() { ... }
// get latestBlock() { ... }

// this method checks if the hash meet the block difficulty, by making sure the "hash" has at least 3 leading zeros
isValidHashDifficulty(hash) {
for (var i = 0; i < hash.length; i++) {
if (hash[i] !== '0') {
break;
}
}
return i >= this.difficulty; // notice the difficulty here
}
}

module.exports = Blockchain;
Block Hash Calculation
  • A hashing function takes data as input, and returns a unique hash.

  • f ( data ) = hash

  • Since the hash is a "digital fingerprint" of the entire block, the data is the combination of index, timestamp, previous hash, block data, and nonce.

  • f ( index + previous hash + timestamp + data + nonce ) = hash

  • When we replace the values for our genesis block, we get:

  • f ( 0 + "0" + 1508270000000 + "Welcome to Blockchain Demo 2.0!" + 604 ) = 000dc75a315c77a1f9c98fb6247d03dd18ac52632d7dc6a9920261d8109b37cf

  • Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    // Blockchain.js
const Block = require('./Block.js');
const crypto = require('crypto');
// class Blockchain {
// constructor() { ... }
// get() { ... }
// get latestBlock() { ... }
// isValidHashDifficulty(hash) { ... }

calculateHashForBlock(block) {
const { index, previousHash, timestamp, transactions, nonce } = block;
return this.calculateHash(
index,
previousHash,
timestamp,
transactions,
nonce
);
}
// returns a SHA256 hash of a block data using the npm package called "crypto"
calculateHash(index, previousHash, timestamp, data, nonce) {
return crypto
.createHash("sha256") // SHA256 Hash Function
.update(index + previousHash + timestamp + data + nonce)
.digest("hex");
}
// };

// module.exports = Blockchain;

Previous Hash
  • The previous hash is the hash of the previous block.
  • The genesis block's previous hash is "0" because there is no previous block.
Data
  • Each block can store data against it.
  • In cryptocurrencies such as Bitcoin, the data would include money transactions.
Nonce
  • The nonce is the number used to find a valid hash.
  • To find a valid hash, we need to find a nonce value that will produce a valid hash when used with the rest of the information from that block.
  • Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

// const Block = require("./Block.js");
// const crypto = require("crypto");

// class Blockchain {
// constructor() { ... }
// get() { ... }
// get latestBlock() { ... }
// isValidHashDifficulty(hash) { ... }
// calculateHashForBlock(block) { ... }
// calculateHash(...) { ... }
// mine(data) { ... }

generateNextBlock(data) {
// prepare correct block properties like, (index, prevHash, timestamp, nonce)
const nextIndex = this.latestBlock.index + 1;
const previousHash = this.latestBlock.hash;
let timestamp = new Date().getTime();
let nonce = 0;
let nextHash = this.calculateHash(
nextIndex,
previousHash,
timestamp,
data,
nonce
);
// while target difficulty is met, calculate "hash" by incrementing "nonce"
while (!this.isValidHashDifficulty(nextHash)) {
nonce = nonce + 1;
timestamp = new Date().getTime();
nextHash = this.calculateHash(
nextIndex,
previousHash,
timestamp,
data,
nonce
);
}

// at this stage we have found the nonce that resulted a correct 'hash' that meets target difficulty.
const nextBlock = new Block(
nextIndex,
previousBlock.hash,
nextTimestamp,
data,
nextHash,
nonce
);

return nextBlock;
}
// };

// module.exports = Blockchain;
Mining a Block
  • The process of determining this nonce is called mining.
  • We start with a nonce of 0 and keep incrementing it by 1 until we find a valid hash.
  • As difficulty increases, the number of possible valid hashes decreases. With fewer possible valid hashes, it takes more processing power to find a valid hash.
  • Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Blockchain.js
// const Block = require("./Block.js");
// const crypto = require("crypto");

// class Blockchain {
// constructor() { ... }
// get() { ... }
// get latestBlock() { ... }
// isValidHashDifficulty(hash) { ... }
// calculateHashForBlock(block) { ... }
// calculateHash(...) { ... }


mine(data) {
// try to generate a next block
const newBlock = this.generateNextBlock(data);
try {
// add a valid block to blockchain
this.addBlock(newBlock);
} catch (err) {
throw err;
};
}
// };

// module.exports = Blockchain;
Data Mutation
  • Since data is an input variable for the hash, changing the data will change the hash.
Mutation Effect
  • Subsequent blocks will also be invalid.
  • A hash change will cause a mutation in the previous hash of subsequent blocks. Since previous hash is used to calculate the hash, subsequent hashes will also change.
  • This will lead to a cascading invalidation of blocks.
Adding a new Block
  • Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// const Block = require("./Block.js");
// const crypto = require("crypto");

// class Blockchain {
// constructor() { ... }
// get() { ... }
// get latestBlock() { ... }
// isValidHashDifficulty(hash) { ... }
// calculateHashForBlock(block) { ... }
// calculateHash(...) { ... }
// mine(data) { ... }
// generateNextBlock(data) { ... }

addBlock(newBlock) {
// check if the new block is valid, i.e has the required properties, has correct prev hash, has correct 'index'
if (this.isValidNewBlock(newBlock, this.latestBlock)) {
this.blockchain.push(newBlock);
} else {
throw "Error: Invalid block";
}
}
// };

// module.exports = Blockchain;
Adding valid blocks
  • When adding a new block to the blockchain, the new block needs to meet these requirements.
    • Block index one greater than latest block index.
    • Block previous hash equal to latest block hash.
    • Block hash meets difficulty requirement.
    • Block hash is correctly calculated.
    • Other peers on the network will be adding blocks to the blockchain, so new blocks need to be validated.
Peer-to-peer Network
  • A global network of computers work together to keep the blockchain secure, correct, and consistent.
  • Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// P2p.js
const wrtc = require('wrtc');
const Exchange = require('peer-exchange');
const p2p = new Exchange('Blockchain Demo 2.0', { wrtc: wrtc });
const net = require('net');

class PeerToPeer {
constructor(blockchain) {
this.peers = [];
this.blockchain = blockchain;
}

startServer(port) {
const server = net
.createServer((socket) =>
p2p.accept(socket, (err, conn) => {
if (err) {
throw err;
} else {
this.initConnection.call(this, conn);
}
})
)
.listen(port);
}
}

module.exports = PeerToPeer;
Add Peer
  • Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// const wrtc = require('wrtc');
// const Exchange = require('peer-exchange');
// const p2p = new Exchange(...);
// const net = require("net");

// class PeerToPeer {
// constructor(blockchain) { ... }
// startServer(port) { ... }

discoverPeers() {
// look for new peer, and if found establish a connection with the peer
p2p.getNewPeer((err, conn) => {
if (err) {
throw err;
} else {
this.initConnection.call(this, conn);
}
});
}
// }

// module.exports = PeerToPeer;
Peer Status
  • Peers have three states:
    1. Currently Active
    2. Connected
    3. Disconnected
  • Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// const wrtc = require('wrtc');
// const Exchange = require('peer-exchange');
// const p2p = new Exchange(...);
// const net = require("net");

// class PeerToPeer {
// constructor(blockchain) { ... }
// startServer(port) { ... }
// discoverPeers() { ... }
// connects to a peer
connectToPeer(host, port) {
const socket = net.connect(port, host, () =>
p2p.connect(socket, (err, conn) => {
if (err) {
throw err;
} else {
this.initConnection.call(this, conn);
}
})
);
}
// disconnects from a peer
closeConnection() {
p2p.close(err => {
throw err;
})
}
// }

// module.exports = PeerToPeer;
Peer Messages
  • Peers ask for each other's blocks to determine who has the most up-to-date blockchain
  • Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// P2p.js
// const wrtc = require('wrtc');
// const Exchange = require('peer-exchange');
// const p2p = new Exchange(...);
// const net = require("net");
const messageType = {
REQUEST_LATEST_BLOCK: 0,
RECEIVE_LATEST_BLOCK: 1,
REQUEST_BLOCKCHAIN: 2,
RECEIVE_BLOCKCHAIN: 3,
};
const {
REQUEST_LATEST_BLOCK,
RECEIVE_LATEST_BLOCK,
REQUEST_BLOCKCHAIN,
RECEIVE_BLOCKCHAIN,
REQUEST_TRANSACTIONS,
RECEIVE_TRANSACTIONS,
} = messageType;

// class PeerToPeer { ... }
// module.exports = PeerToPeer;

// Messages.js
class Messages {
static getLatestBlock() {
return {
type: REQUEST_LATEST_BLOCK,
};
}

static sendLatestBlock(block) {
return {
type: RECEIVE_LATEST_BLOCK,
data: block,
};
}

static getBlockchain() {
return {
type: REQUEST_BLOCKCHAIN,
};
}

static sendBlockchain(blockchain) {
return {
type: RECEIVE_BLOCKCHAIN,
data: blockchain,
};
}
}
Peer Communication
  • peer communication
  • Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// const wrtc = require('wrtc');
// const Exchange = require('peer-exchange');
// const p2p = new Exchange(...);
// const net = require("net");
// const messageType = { ... };
// const { ... } = messageType;

// class PeerToPeer {
// constructor(blockchain) { ... }
// startServer(port) { ... }
// discoverPeers() { ... }
// connectToPeer(host, port) { ... }
// closeConnection() { ... }

broadcastLatest() {
this.broadcast(Messages.sendLatestBlock(this.blockchain.latestBlock));
}

broadcast(message) {
this.peers.forEach(peer => this.write(peer, message));
}

write(peer, message) {
peer.write(JSON.stringify(message));
}

initConnection(connection) {
this.peers.push(connection);
this.initMessageHandler(connection);
this.initErrorHandler(connection);
this.write(connection, Messages.getLatestBlock());
}

initMessageHandler(connection) {
connection.on("data", data => {
const message = JSON.parse(data.toString("utf8"));
this.handleMessage(connection, message);
});
}

initErrorHandler(connection) {
connection.on("error", err => {
throw err;
});
}

handleMessage(peer, message) {
switch (message.type) {
case REQUEST_LATEST_BLOCK:
this.write(peer, Messages.sendLatestBlock(this.blockchain.latestBlock));
break;
case REQUEST_BLOCKCHAIN:
this.write(peer, Messages.sendBlockchain(this.blockchain.get()));
break;
case RECEIVE_LATEST_BLOCK:
this.handleReceivedLatestBlock(message, peer);
break;
case RECEIVE_BLOCKCHAIN:
this.handleReceivedBlockchain(message);
break;
default:
throw "Received invalid message.";
}
}
// }

// module.exports = PeerToPeer;
// class Messages { ... }

P2P Tour Pt.1
  • ask a peer for latest block, and a peer is 1 block ahead of us
  • ask for peers latest block
  • after peer sends a its latest block, we will compare peer's previous hash to our latest hash
  • if peer's prev hash is the same as our latest hash, that means that peer is one block a head of us.
  • Next, will make sure if peer's latest block is valid, then will append the new block to our blockchain then we will broadcast it to the network.
  • Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// const wrtc = require('wrtc');
// const Exchange = require('peer-exchange');
// const p2p = new Exchange(...);
// const net = require("net");
// const messageType = { ... };
// const { ... } = messageType;

// class PeerToPeer {
// constructor(blockchain) { ... }
// startServer(port) { ... }
// discoverPeers() { ... }
// connectToPeer(host, port) { ... }
// closeConnection() { ... }
// broadcastLatest() { ... }
// broadcast(message) { ... }
// write(peer, message) { ... }
// initConnection(connection) { ... }
// initMessageHandler(connection) { ... }
// initErrorHandler(connection) { ... }
// handleMessage(peer, message) { ... }


handleReceivedLatestBlock(message, peer) {
// get peers latest block
const receivedBlock = message.data;
const latestBlock = this.blockchain.latestBlock;
// compare if peers prev hash to our latest hash
if (latestBlock.hash === receivedBlock.previousHash) {
try {
// peer is one block ahead of us, so validate the new block and add it to our chain.
this.blockchain.addBlock(receivedBlock);
} catch(err) {
throw err;
}
} else if (receivedBlock.index > latestBlock.index) {
// peer is more than one block ahead of us, so, we'll request peers entire blockchain.
this.write(peer, Messages.getBlockchain());
} else {
// peer is in a same state with us.
// Do nothing.
}
}
// }

// module.exports = PeerToPeer;
// class Messages { ... }
P2P Tour Pt.2
  • ask a peer for latest block, and a peer has a index that's greater than ours

  • ask for peers latest block

  • after peer sends a its latest block, we will compare peer's previous hash to our latest hash

  • if peer's prev hash is NOT the same as our latest hash.

  • Then we will compare peers index to our index.

  • if peer's index is greater than our index, that means that peer is more than one block ahead of us. i.e it has longer chain.

  • Ask a peer to send its entire blockchain, and peer sends.

  • Make sure peer's chain is valid and longer

  • if peer's chain is valid then replace our chain with peers chain

  • The longer valid chain takes precedent over a shorter chain. Also called longest chain rule.

  • Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// const wrtc = require('wrtc');
// const Exchange = require('peer-exchange');
// const p2p = new Exchange(...);
// const net = require("net");
// const messageType = { ... };
// const { ... } = messageType;

// class PeerToPeer {
// constructor(blockchain) { ... }
// startServer(port) { ... }
// discoverPeers() { ... }
// connectToPeer(host, port) { ... }
// closeConnection() { ... }
// broadcastLatest() { ... }
// broadcast(message) { ... }
// write(peer, message) { ... }
// initConnection(connection) { ... }
// initMessageHandler(connection) { ... }
// initErrorHandler(connection) { ... }
// handleMessage(peer, message) { ... }

handleReceivedLatestBlock(message, peer) {
// if (latestBlock.hash === receivedBlock.previousHash) {
// ...
} else if (receivedBlock.index > latestBlock.index) {
this.write(peer, Messages.getBlockchain());
} else {
// Do nothing.
// }
}

handleReceivedBlockchain(message) {
// we got the longer chain we requested
const receivedChain = message.data;

try {
// validate the new chain, and replace our chain
this.blockchain.replaceChain(receivedChain);
} catch(err) {
throw err;
}
}
// }

// module.exports = PeerToPeer;
// class Messages { ... }
P2P Tour Pt.3
    • ask a peer for latest block, and a peer has a index that's not greater than ours
  • ask for peers latest block
  • after peer sends a its latest block, we will compare peer's previous hash to our latest hash
  • if peer's prev hash is NOT the same as our latest hash.
  • Then we will compare peers index to our index.
  • in this case, peers index is not greater than ours. i.e both us and the peer has equal chain.
  • Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// const Block = require("./Block.js");
// const crypto = require("crypto");

// class Blockchain {
// constructor() { ... }
// get() { ... }
// get latestBlock() { ... }
// isValidHashDifficulty(hash) { ... }
// calculateHashForBlock(block) { ... }
// calculateHash(...) { ... }
// mine(data) { ... }
// generateNextBlock(data) { ... }
// addBlock(newBlock) { ... }
// isValidNextBlock(nextBlock, previousBlock) { ... }

isValidChain(chain) {
if (JSON.stringify(chain[0]) !== JSON.stringify(Block.genesis)) {
return false;
}

const tempChain = [chain[0]];
for (let i = 1; i < chain.length; i = i + 1) {
if (this.isValidNextBlock(chain[i], tempChain[i - 1])) {
tempChain.push(chain[i]);
} else {
return false;
}
}
return true;
}

isChainLonger(chain) {
return chain.length > this.blockchain.length;
}

replaceChain(newChain) {
if (this.isValidChain(newChain) && this.isChainLonger(newChain)) {
this.blockchain = JSON.parse(JSON.stringify(newChain));
} else {
throw "Error: invalid chain";
}
}
// };

// module.exports = Blockchain;
Immutability
  • If a block is mutated, the block, and subsequent blocks become invalid.
  • Invalid blocks are rejected by the peers on the network. They need to be re-mined to be valid.
  • Earlier blocks will be harder to corrupt because there are more subsequent invalid blocks to re-mine.
  • Because peers on the network are always adding new valid blocks, the hacker would have to outmine the network, which requires majority processing power.
51% Attack
  • If a participant has more than 51% of the network, he could out-mine the network and hack the blockchain.
  • When there are more miners in the network, the processing power becomes more distributed and no one has majority power. This leads to a more secure blockchain.
You might be wondering, when validating a chain from a peer, is our node going to validate each block by starting at genesis block or is there a much easier way?
  • Answer:

  • When a node with a shorter chain wants to replace its chain with a longer chain from another peer, it does not need to validate the entire new chain starting from the genesis block.

  • Validating the entire chain would be time-consuming and resource-intensive.

  • Instead, the node can use a more efficient method called "headers-first synchronization" or "fast sync." In this approach, the node requests and validates only the block headers of the new chain.

  • Block headers contain important information about each block, such as the hash of the previous block, timestamp, and difficulty.

  • By validating the block headers, the node can quickly determine if the new chain is valid and if it should be adopted.

  • If the block headers pass validation, the node can then request and validate only the full content of the specific blocks that are missing in its current chain.

  • This headers-first synchronization method allows the node to efficiently verify the integrity of the longer chain and update its local copy without having to validate all the blocks from the genesis block.

Can you tell me how header-first synchronization works in simple terms?

Answer:

  • Headers-first synchronization, also known as fast sync, is a method used by nodes in a peer-to-peer network to quickly update their blockchain with a longer chain from another peer.

  • Here’s a simplified explanation of how headers-first synchronization works:

  1. Requesting headers: The node with the shorter chain requests the headers (metadata) of the blocks in the longer chain from the other peer. The headers contain important information about each block but not the full content.

  2. Validating headers: The node then validates the received headers to ensure they are valid according to the consensus rules of the blockchain. This involves checking the integrity, timestamps, and other necessary details.

  3. Determining missing blocks: Based on the validated headers, the node identifies the blocks that are missing from its own chain.

  4. Requesting missing blocks: The node then requests only the full content of the missing blocks from the other peer. This step helps to minimize the amount of data transferred and speeds up the synchronization process.

  5. Validating missing blocks: The node validates the received missing blocks to ensure they are correct and fit into the blockchain's rules. This includes verifying transactions, signatures, and other relevant information.

  6. Updating the local chain: Finally, once all the missing blocks are validated, the node can add them to its local chain, effectively replacing its shorter chain with the longer, more up-to-date chain from the other peer.

Okay thanks but can you explain how a node validates a chain only using headers? Is it just comparing its headers to peer’s headers? That’s the bit I am missing.

Answer:

  • When a node validates a chain using only headers, it involves comparing its own headers with the headers received from a peer.
  • Here’s a simplified step-by-step explanation of how the validation process works:
  1. Requesting headers: The node requests headers from a peer, specifically those belonging to a longer chain that it wishes to adopt.

  2. Checking the genesis block: The node first verifies that the genesis block's header in the received chain matches its own local copy. This ensures that the chains are rooted in the same genesis block.

  3. Validating block headers: The node iterates through the received headers, starting from the block immediately after the one it has in its local chain. It verifies the validity of each header by performing the following checks:

    • Ensuring the block's hash is correctly linked to the previous block's hash.
    • Checking that the timestamp is valid (within an acceptable range).
    • Verifying the proof-of-work or consensus requirements, such as the difficulty target.
  4. Detecting invalid headers: If the node encounters any invalid headers during the validation process, it can reject the entire chain received from the peer.

  5. Identifying missing blocks: As the node validates the headers, it keeps track of any missing blocks in its local chain. These are the blocks that exist in the peer’s longer chain but not in the node’s current chain.

  6. Requesting missing blocks: Once the node completes header validation, it requests the full content of the missing blocks from the peer. This allows it to obtain the complete data necessary for validation.

  • By comparing and validating headers, the node can efficiently determine if a longer chain from a peer is valid and adopt it if it passes the necessary checks. This process minimizes the amount of data transferred and allows the node to synchronize with the longer chain without needing to validate every individual block in the chain.
How is acceptable timestamp range is determined for a block mined to be valid?
  • The acceptable timestamp range for blocks in a blockchain is typically determined by the consensus protocol and the rules established by the blockchain network.

  • The specific details can vary depending on the blockchain protocol.

  • In most blockchain protocols, including Bitcoin, the acceptable timestamp range for a block is based on a combination of factors:

  • Network Time: Blockchain networks often rely on a synchronized network time to maintain consistency across the network. Peers in the network typically have a mechanism to synchronize their clocks using protocols like Network Time Protocol (NTP) or similar methods. The network time helps ensure that blocks’ timestamps are relatively accurate and consistent.

  • Block Interval: The blockchain protocol defines a target block interval, which is the expected time between the creation of two consecutive blocks. For example, in Bitcoin, the target block interval is approximately 10 minutes. The acceptable timestamp range for a new block is often determined by allowing some flexibility around this target interval.

  • Median Time: To prevent malicious manipulation or inaccuracies in individual node clocks, many blockchain protocols calculate the block's timestamp based on the median timestamp of the previous several blocks. This helps smooth out any discrepancies caused by individual node clocks and provides a more reliable measure of time.

  • Combining these factors, the acceptable timestamp range for a new block is typically defined as the median timestamp of the previous blocks, plus or minus a specific allowable range. For example, if the previous block’s median timestamp is T, the acceptable timestamp range for the new block might be defined as T - X seconds to T + Y seconds.

  • The specific values for X and Y are determined by the blockchain protocol's design and consensus rules. These values are chosen to strike a balance between allowing some flexibility for network conditions and preventing significant deviations or manipulation of block timestamps.

  • It’s worth noting that different blockchain protocols may have variations in how they determine the acceptable timestamp range, but the principles mentioned above provide a general understanding of the process.

What is Network Time Protocol(NTP)?
  • Network Time Protocol (NTP) is a protocol used to synchronize the clocks of devices connected to a network. It ensures that the time maintained by different devices is as accurate and consistent as possible.
Here’s a simplified explanation of how NTP works:
  • Imagine you have several clocks (devices) in different rooms (networked computers) within a building (network). Each clock may have a slightly different time, and your goal is to get them all showing the same time.

  • Time Servers: In NTP, there are special devices called time servers that are considered highly accurate and act as references for time.

  • Requesting Time: The clocks in the rooms (devices) periodically send requests to the time servers, asking for the current time.

  • Time Response: The time servers receive these requests and respond with the correct time, including information about the server’s own clock accuracy.

  • Clock Adjustment: When the clocks receive the time response, they compare it to their own time. They make adjustments to gradually bring their time closer to the time provided by the time server.

  • Synchronization: Over time, the clocks continue to exchange requests and responses with the time server, fine-tuning their time until they are synchronized. The clocks periodically make small adjustments to compensate for any drift or discrepancies in their internal clocks.

  • NTP employs a hierarchical structure, where lower-level clocks synchronize with higher-level clocks, and those higher-level clocks synchronize with even more accurate reference clocks. This hierarchy allows for a cascading synchronization across the network, ensuring that the time remains accurate across various devices.

  • By using NTP, networked devices can achieve a consistent and accurate understanding of time, which is crucial for many applications and systems that rely on time synchronization, such as financial transactions, network security, data logging, and distributed computing.

  • In summary, NTP helps networked devices maintain accurate and synchronized time by periodically exchanging time information with highly accurate time servers, gradually adjusting their own clocks to match the reference time.

  • Out-of-context: Malory means Malicious Actor trying to hack a blockchain. Or create a 51% attack.

Why is so much computational power required to manipulate data in early blockchain blocks?
  • Let’s look at a simple scenario:

  • The Bitcoin genesis block hash is 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f

  • Mallory (crypto term for malicious actor!) manipulates a piece of data, producing a brand new hash for the same block: eb3e5df5eefceb8950e4a444507ce7df1cc534f54a5113f2792ab64830392db0

  • Because of this change, Mallory has causes a data mutation of the genesis block hash! 😱This is where the blockchain data structure is very powerful with data integrity: since the hash of the genesis block changed to be invalid, the block that was previously pointing to it (Block #1) no longer does (because pointers are based on block hashes!). This effect trickles down all the way to the end of the blockchain.

  • At this point, Mallory has caused a data mutation along the entire chain just by changing one tiny piece of data. In order to continue pushing, Mallory now needs to:

  • Hash the genesis block data until a “valid hash” is found

  • So Mallory, now attacking the chain data integrity, must now hash the manipulated block many times in order to find a hash that meets the Bitcoin network difficulty target at the time.

  • Once a valid hash is found on the manipulated block, Mallory must repeat the hashing process for EVERY block thereafter in order to successfully “attack” the chain.

  • This would take Mallory trillions and trillions of years of constant computation via hashing. All while the rest of the miner network continues to hash

Attack unsuccessful! The blockchain data integrity remains intact.

Has Bitcoin ever been attacked?
  • Bitcoin, the first and most well-known cryptocurrency, has experienced various attacks and security incidents throughout its history. While Bitcoin’s underlying technology, the blockchain, has proven to be resilient, there have been instances of attacks targeting Bitcoin exchanges, wallets, and network vulnerabilities. Here are a few notable incidents:

  • Double Spending: In the early days of Bitcoin, there were a few instances of successful double spending attacks. Double spending occurs when someone spends the same bitcoins in two separate transactions. However, as the network grew and adoption increased, the risk of double spending decreased significantly.

  • Mt. Gox: One of the most infamous attacks in Bitcoin's history was the collapse of the Mt. Gox exchange in 2014. Mt. Gox was once the largest Bitcoin exchange, but it suffered a security breach resulting in the loss of approximately 850,000 bitcoins, both belonging to customers and the exchange itself.

  • 51% Attacks: A 51% attack happens when a single entity or a group of miners control more than 50% of the total computational power of the Bitcoin network. With this majority control, they can potentially manipulate transactions, double spend, or disrupt the consensus process. While 51% attacks on Bitcoin itself have not occurred, smaller cryptocurrencies with less mining power have been susceptible to such attacks.

  • Security Vulnerabilities: Over the years, vulnerabilities have been discovered in Bitcoin software implementations, such as bugs or weaknesses in the code. These vulnerabilities have sometimes allowed attackers to exploit the system, but the open-source nature of Bitcoin has enabled prompt identification and patching of these issues.

  • It’s important to note that while Bitcoin has faced attacks and security incidents, the core principles and cryptographic mechanisms of the Bitcoin protocol have generally proven to be secure. The attacks mentioned above primarily targeted centralized exchanges, individual wallets, or vulnerabilities in specific software implementations, rather than the Bitcoin network itself.

Task - 1: Blocks and Hashes

Blocks and Hashes

  • Blockchain is aptly named! It is, in fact, a chain of blocks. 🅱️⛓️

  • Each block contains transactional data, some metadata describing the block itself, and a link to the previous block before it. These components are fed into a hash function to create a unique sequence of bits to represent the block.

🏁 Your Goal: Return a Hash

  • In your Block.js file, we have a class Block. Using the SHA256 function from the Crypto JS Library, return a valid hash in the toHash function.

  • For now, there’s no need to hash anything in particular since the block contains none of the components we mentioned above.

  • 🎨 Feel free to hash a message, your own name, or nothing at all! So long as it is a 64 character hexadecimal string you will pass this stage.

Answer for Task 1

1
2
3
4
5
6
7
8
9
const SHA256 = require('crypto-js/sha256');

class Block {
toHash() {
return SHA256('benyam7');
}
}

module.exports = Block;

Test to run against:

1
2
3
4
5
6
7
8
9
10
const Block = require('../Block');
const assert = require('assert');

describe('Block', function () {
const newBlock = new Block();

it('should have a hash property', function () {
assert(/^[0-9A-F]{64}$/i.test(newBlock.toHash()));
});
});

Task Details

Hash Function
  • Hash functions are used to take input data of any size and output a unique series of bits of a specific size representing that original data.

  • An ideal cryptographic hash function can, given any input, return a consistent yet seemingly random output.

  • It’s important that the output is consistent so we can depend on putting the same inputs in and receiving the same output.

  • It’s also important for the randomness to be strong enough where it's impossible to re-construct the input from the output. This way, we know it’s tamper-proof.

  • For example the SHA256 algorithm will take an input like Dan and return a consistent output:

1
2
const hash = SHA256('Dan');
console.log(hash.toString()); // b12595…1cbe7e
  • ☝️ The log is shortened, it is actually 64 hexadecimal characters long. SHA256 outputs 256 bits. Since a hexadecimal character requires 4 bits, there are 64 hexadecimal characters in a SHA256 hash.

  • If, instead my input was the lower case dan, the result would be completely different:

1
2
const hash = SHA256('dan');
console.log(hash.toString()); // ec4f2d…56f1cb
  • These hash outputs are seemingly random in relation to their inputs: “Dan” and “dan”. They are also consistent, putting in these inputs will always yield these same outputs. For these reasons sha256 is an ideal cryptographic hash function and is often used in cryptographic programs
Crypto-JS
  • The crypto-js library provides us with several cryptographic utilities. Specifically the SHA256 method is an implementation of the SHA256 algorithm designed by the NSA.

  • This function will take any string as an argument, regardless of size, and hash it to a 256 bit array. If we call toString() on that returned object we'll receive a 64 character hexadecimal string.

Hexadecimal
  • You’ll notice that the outputs shown consist of a set of characters ranging from a to f and 0 to 9.

  • These are hexadecimal characters.

  • It has become commonplace to use hexadecimal when displaying a hash.

  • 🧐 You’ll also often see a hash with a 0x in front of it. This prefix means that hexadecimal notation is being used. So if you see a string of characters "0x123abc", the "0x" is denoting the use of hexadecimals and the string's value is actually just "123abc".

  • For the test file in this stage you'll notice that the hash of the block is being tested by the regular expression (regex) /^[0-9A-F]{64}$/i. It's simply testing to see that this is a hexadecimal output of 64 characters.

  • 🔡 Regular expressions can help define a search pattern for input data. Learn more about regular expressions on MDN

Why 64 Hexadecimal Characters?
  • A bit can represent two values: 0 and 1.
  • Two bits can represent four values 00, 01, 10 and 11. Four bits can represent 16 values 0000 through 1111.
  • We can map each of these values to a character in the hexadecimal alphabet since it contains 16 characters! Since SHA256 outputs 256 bits, we divide that by the number of bits to represent a hexadecimal character (4) to get 64 characters.

Task -2 What’s in a Hash?

Adding Data to the Hash
  • Now it’s time to add data to our hash. This will ensure that the block’s hash is tied to its contents!
🏁 Your Goal: Hash the Data
  • When creating a new block, data will be passed to its constructor:
1
2
3
const block = new Block('Alice sent Bob 1 BTC');

console.log(block.data); // Alice sent Bob 1 BTC
  • ☝️ As shown above, let’s add a data property to the Block.

  • Add a constructor to our Block class that takes one argument data and assigns it to this.data
    Once you have added data to the block, use this data to calculate the block’s hash in the toHash function!

Details to Task - 2:

Data Security
  • In this stage we use data to represent some arbitrary data that can be stored in a block. We hash the data to create a small, unique representation of that data. If the data ever changed inside of a block, we would see that reflected in the block’s hash. It would be entirely different!

  • 📖 For real blockchains, data is generally a set of transactions stored in a merkle tree. We’ll get to that in a future lesson.

  • We can add other properties to the hash like a timestamp for the time the block was mined. This way, the block is tied to a specific point in time. It would be virtually impossible for someone to create a hash from the same data and a different timestamp.

  • In the upcoming stages, we’ll learn to make this hash record even more powerful by linking each of these blocks together.

Answer to Task - 2 What’s in a Hash?
1
2
3
4
5
6
7
8
9
10
11
12
13
//Block.js
const SHA256 = require('crypto-js/sha256');

class Block {
constructor(data) {
this.data = data;
}
toHash() {
return SHA256(this.data);
}
}

module.exports = Block;
Tests to run against
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//blockSHATest.js
const Block = require('../Block');
const assert = require('assert');
const SHA256 = require('crypto-js/sha256');

describe('Block', function () {
it('should store a random name', function () {
const randomName = require('faker').name.findName();
assert.equal(randomName, new Block(randomName).data);
});

it('should hash some random data', function () {
const randomEmail = require('faker').internet.email();
const myHash = SHA256(randomEmail).toString();
const yourHash = new Block(randomEmail).toHash().toString();
assert.equal(myHash, yourHash);
});
});

Task 3 - Genesis Block

Blockchain Time
  • We have a new file: Blockchain.js. How exciting! 😁

  • This stage is going to focus on adding the first block to our new Blockchain class! The first block is often referred to as the Genesis Block.

🏁 Your Goal: Add the Genesis Block
  • The Blockchain.js file contains the Blockchain class with a chain array.
  • Let’s add the Genesis Block to this array.
  • Create a new Block in the Blockchain constructor then add it to the chain array.

Details for Task 3

Genesis Block
  • The genesis block is the first block in the chain, where it all kicks off! Every block after the genesis block links back to the first one, but the genesis block has no previous block! This is important to keep in mind for the next few stages. 🧠

  • Here are some examples of genesis blocks on live blockchains displayed in their respective block explorers:

Bitcoin Genesis Block

Here’s Bitcoin’s Genesis Block on Block Explorer on January 3rd, 2009.

Ethereum Genesis Block
Answer 3 for Genesis Block
1
2
3
4
5
6
7
8
9
10
// Blockchain.js
const Block = require('./Block');

class Blockchain {
constructor() {
this.chain = [new Block('da genesis block')];
}
}

module.exports = Blockchain;
Tests to run against
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Blockchain = require('../Blockchain');
const Block = require('../Block');
const assert = require('assert');

describe('Blockchain', function () {
it('should have a genesis block', function () {
const blockchain = new Blockchain();
const genesisBlock = blockchain.chain[0];
assert(genesisBlock, 'Could not find the genesis block!');
assert(
genesisBlock instanceof Block,
'genesis block should be a block!'
);
});
});

Task 4 - Your Goal: Create an addBlock Function

  • Let’s create an addBlock function on our Blockchain class.

  • This function should take in a new block and add it to the chain array:

1
2
3
4
5
6
const blockchain = new Blockchain();
const block = new Block('Charlie sent Dave 2 BTC');

blockchain.addBlock(block);

console.log(blockchain.chain.length); // 2````
  • ☝️ Remember we should have both the genesis block and the new block now.
Answer for Task 4: Create an addBlock function
1
2
3
4
5
6
7
8
9
10
11
12
13
const Block = require('./Block');

class Blockchain {
constructor() {
this.chain = [new Block('da genesis block')];
}
// add new block to the chain
addBlock(block) {
this.chain.push(block);
}
}

module.exports = Blockchain;
Tests to run against
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const Blockchain = require('../Blockchain');
const Block = require('../Block');
const assert = require('assert');

let blockchain;

describe('Blockchain', function () {
before(() => {
blockchain = new Blockchain();
});

it('should have an addBlock function', function () {
assert.equal(typeof blockchain.addBlock, 'function');
});

describe('adding new blocks', function () {
let block1;
let block2;
before(() => {
block1 = new Block('Some data');
block2 = new Block('Some other data');
blockchain.addBlock(block1);
blockchain.addBlock(block2);
});

it('should be a chain of three blocks', function () {
assert.equal(blockchain.chain.length, 3);
});

it('should include block1 and block2', function () {
assert(
blockchain.chain.some((x) => x === block1),
'Could not find block1. Remember to push the block argument in addBlock!'
);
assert(
blockchain.chain.some((x) => x === block2),
'Could not find block1. Remember to push the block argument in addBlock!'
);
});
});
});

Task -5 , Linking the Blocks

  • It’s time to add one more crucial input to our block’s hash calculation: the hash of the previous block in the chain.

  • This creates a chain where any change to the data of an earlier block will affect each subsequent block.

  • To link the blocks you have to accomplish two things:

  • Add a previousHash property to each block. The value of this property should be the hash of the block before it in the chain.

  • Use this previousHash property in the calculation of the block’s hash.

💡 Hints
  • A good spot to add the previousHash property on the block would be in the addBlock function, where a block is placed on the chain.
  • So far, the Block class in your Block.js file does not yet contain a previousHash property and currently only hashes this.data of a block - you must also include the block’s this.previousHash property in the toHash function!
  • You can add multiple inputs to the SHA256 function by using the + operator, for example:
1
2
const cat = '';
const hash = SHA256('dog' + cat); // hash of dog and cat together
Details on Task - 5
Changing Data
  • 🧠 The reason blockchains are secure is that a massive network is continuously working to compute a single block while a hacker would need to compute multiple blocks to actually affect the history of a blockchain. This process of computation is called mining and we’ll talk about why it’s computationally expensive in a future stage.
Answer to Task - 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Block.js

const SHA256 = require('crypto-js/sha256');

class Block {
constructor(data, previousHash) {
this.data = data;
this.previousHash = previousHash;
}
toHash() {
return SHA256(this.data + this.previousHash);
}
}

module.exports = Block;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Block = require('./Block');

class Blockchain {
constructor() {
this.chain = [new Block('da genesis block', '0')];
}

addBlock(block) {
block.previousHash = new Block(
block.data,
this.chain[this.chain.length - 1].previousHash
).toHash();
this.chain.push(block);
}
}

module.exports = Blockchain;
Tests to run against:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const Blockchain = require('../Blockchain');
const Block = require('../Block');
const assert = require('assert');
const SHA256 = require('crypto-js/sha256');

let blockchain;
describe('Linking Blocks', function () {
beforeEach(() => {
blockchain = new Blockchain();
});

describe('adding a new block to our blockchain', function () {
let genesisBlock;
let block1;
beforeEach(() => {
genesisBlock = new Block(5);
block1 = new Block(5);
blockchain.addBlock(genesisBlock);
blockchain.addBlock(block1);
});

it('should have a previousHash property equal to the previous blocks hash', function () {
assert.equal(
block1.previousHash.toString(),
genesisBlock.toHash().toString()
);
});

describe('after changing the genesis block data', () => {
let initialGenesisHash;
let initialBlock1Hash;
beforeEach(() => {
initialGenesisHash = genesisBlock.toHash().toString();
initialBlock1Hash = block1.toHash().toString();
genesisBlock.data = 10;
});

it('should alter the genesis hash', () => {
const newHash = genesisBlock.toHash().toString();
assert.notEqual(
initialGenesisHash,
newHash,
'Expected changing the genesis blocks data to change its hash calculation!'
);
});

it('should alter the second blocks hash', () => {
const newHash = genesisBlock.toHash().toString();
assert.notEqual(
initialBlock1Hash,
newHash,
'Expected changing the genesis blocks data to change the second blocks hash calculation!'
);
});
});
});
});