Skip to content

Commit a392a8b

Browse files
authored
Merge pull request #520 from ainblockchain/release/v0.9.0
Merge Release/v0.9.0 into Master branch
2 parents 6e291e3 + d937408 commit a392a8b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+8191
-4307
lines changed

.github/workflows/github-actions.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
steps:
2020
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
2121
- uses: actions/checkout@v2
22+
with:
23+
ref: ${{ github.event.pull_request.head.sha }}
2224
# Setup node environment for testing
2325
- uses: actions/setup-node@v2
2426
with:

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ source setup_tracker_gcp.sh
4949
- Start tracker server job
5050
```
5151
cd ain-blockchain/
52-
sh start_tracker_gcp.sh
52+
sh start_tracker_genesis_gcp.sh
5353
```
5454

5555
<!--
@@ -136,7 +136,7 @@ source setup_node_gcp.sh
136136
```
137137
- Start Node server job (set shard index to 0 if you're running a root chain node)
138138
```
139-
sh start_node_gcp.sh {dev|spring|summer} <SHARD_INDEX> <SERVER_INDEX>
139+
sh start_node_genesis_gcp.sh {dev|spring|summer} <SHARD_INDEX> <SERVER_INDEX>
140140
```
141141

142142
<!--

blockchain/block.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,20 @@ class Block {
129129
`[${LOG_HEADER}] Last votes or last_votes_hash is incorrect for block ${block.hash}`);
130130
return false;
131131
}
132-
logger.info(
133-
`[${LOG_HEADER}] Hash check successfully done for block: ${block.number} / ${block.epoch}`);
132+
return true;
133+
}
134+
135+
static validateValidators(validators) {
136+
if (!CommonUtil.isDict(validators)) return false;
137+
for (const [address, info] of Object.entries(validators)) {
138+
if (!CommonUtil.isCksumAddr(address)) {
139+
return false;
140+
}
141+
if (!CommonUtil.isDict(info) || !CommonUtil.isNumber(info[PredefinedDbPaths.STAKE]) ||
142+
!CommonUtil.isBool(info[PredefinedDbPaths.PROPOSAL_RIGHT])) {
143+
return false;
144+
}
145+
}
134146
return true;
135147
}
136148

@@ -157,6 +169,12 @@ class Block {
157169
}
158170
nonceTracker[tx.address] = tx.tx_body.nonce;
159171
}
172+
if (!Block.validateValidators(block.validators)) {
173+
logger.error(
174+
`[${LOG_HEADER}] Invalid validators format: ${JSON.stringify(block.validators)} ` +
175+
`(${block.number} / ${block.epoch})`);
176+
return false;
177+
}
160178

161179
logger.info(`[${LOG_HEADER}] Validated block: ${block.number} / ${block.epoch}`);
162180
return true;
@@ -261,7 +279,7 @@ class Block {
261279

262280
static buildGenesisStakingTxs(timestamp) {
263281
const txs = [];
264-
Object.entries(GENESIS_VALIDATORS).forEach(([address, amount], index) => {
282+
Object.entries(GENESIS_VALIDATORS).forEach(([address, info], index) => {
265283
const privateKey = _.get(GenesisAccounts,
266284
`${AccountProperties.OTHERS}.${index}.${AccountProperties.PRIVATE_KEY}`);
267285
if (!privateKey) {
@@ -274,7 +292,7 @@ class Block {
274292
operation: {
275293
type: 'SET_VALUE',
276294
ref: PathUtil.getStakingStakeRecordValuePath(PredefinedDbPaths.CONSENSUS, address, 0, timestamp),
277-
value: amount
295+
value: info[PredefinedDbPaths.STAKE]
278296
}
279297
};
280298
txs.push(Transaction.fromTxBody(txBody, privateKey));
@@ -303,7 +321,7 @@ class Block {
303321
tempGenesisDb.initDbStates();
304322
const resList = [];
305323
for (const tx of genesisTxs) {
306-
const res = tempGenesisDb.executeTransaction(Transaction.toExecutable(tx));
324+
const res = tempGenesisDb.executeTransaction(Transaction.toExecutable(tx), true);
307325
if (CommonUtil.isFailedTx(res)) {
308326
logger.error(`Genesis transaction failed:\n${JSON.stringify(tx, null, 2)}` +
309327
`\nRESULT: ${JSON.stringify(res)}`)

blockchain/index.js

Lines changed: 77 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
const rimraf = require('rimraf');
21
const path = require('path');
32
const fs = require('fs');
43
const logger = require('../logger')('BLOCKCHAIN');
54
const { Block } = require('./block');
65
const FileUtil = require('../common/file-util');
76
const {
87
CHAINS_DIR,
9-
CHAINS_N2B_DIR_NAME,
8+
CHAIN_SEGMENT_LENGTH,
9+
ON_MEMORY_CHAIN_LENGTH,
1010
} = require('../common/constants');
11-
const CHAIN_SEGMENT_LENGTH = 20;
12-
const ON_MEM_CHAIN_LENGTH = 20;
11+
const CommonUtil = require('../common/common-util');
1312

1413
class Blockchain {
1514
constructor(basePath) {
@@ -19,25 +18,28 @@ class Blockchain {
1918
this.initSnapshotBlockNumber = -1;
2019
}
2120

21+
/**
22+
* Initializes the blockchain and returns whether there are block files to load.
23+
*/
2224
init(isFirstNode, latestSnapshotBlockNumber) {
23-
let lastBlockWithoutProposal;
2425
this.initSnapshotBlockNumber = latestSnapshotBlockNumber;
25-
if (FileUtil.createBlockchainDir(this.blockchainPath)) {
26+
const wasBlockDirEmpty = FileUtil.createBlockchainDir(this.blockchainPath);
27+
let isGenesisStart = false;
28+
if (wasBlockDirEmpty) {
2629
if (isFirstNode) {
2730
logger.info('\n');
2831
logger.info('############################################################');
2932
logger.info('## Starting FIRST-NODE blockchain with a GENESIS block... ##');
3033
logger.info('############################################################');
3134
logger.info('\n');
32-
this.chain.push(Block.genesis());
33-
this.writeChain();
35+
this.writeBlock(Block.genesis());
36+
isGenesisStart = true;
3437
} else {
3538
logger.info('\n');
3639
logger.info('#############################################################');
3740
logger.info('## Starting NON-FIRST-NODE blockchain with EMPTY blocks... ##');
3841
logger.info('#############################################################');
3942
logger.info('\n');
40-
this.writeChain();
4143
}
4244
} else {
4345
if (isFirstNode) {
@@ -53,24 +55,11 @@ class Blockchain {
5355
logger.info('################################################################');
5456
logger.info('\n');
5557
}
56-
const newChain = this.loadChain(latestSnapshotBlockNumber);
57-
if (newChain) {
58-
// NOTE(minsulee2): Deal with the case the only genesis block was generated.
59-
if (newChain.length > 1) {
60-
lastBlockWithoutProposal = newChain.pop();
61-
const lastBlockPath = FileUtil.getBlockPath(
62-
this.blockchainPath, lastBlockWithoutProposal.number);
63-
fs.unlinkSync(lastBlockPath);
64-
}
65-
this.chain = newChain;
66-
}
6758
}
68-
if (!this.getBlockByNumber(0)) {
69-
const genesisBlock = Block.genesis();
70-
FileUtil.writeBlock(this.blockchainPath, genesisBlock);
71-
FileUtil.writeHashToNumber(this.blockchainPath, genesisBlock.hash, genesisBlock.number);
72-
}
73-
return lastBlockWithoutProposal;
59+
return {
60+
wasBlockDirEmpty,
61+
isGenesisStart,
62+
};
7463
}
7564

7665
/**
@@ -83,7 +72,7 @@ class Blockchain {
8372
getBlockByHash(hash) {
8473
if (!hash) return null;
8574
const blockPath = FileUtil.getBlockPath(this.blockchainPath,
86-
FileUtil.readHashToNumber(this.blockchainPath, hash));
75+
FileUtil.readH2nFile(this.blockchainPath, hash));
8776
if (!blockPath) {
8877
return this.chain.find((block) => block.hash === hash);
8978
} else {
@@ -99,9 +88,11 @@ class Blockchain {
9988
*/
10089
getBlockByNumber(number) {
10190
if (number === undefined || number === null) return null;
102-
const blockPath = FileUtil.getBlockPath(this.blockchainPath, number);
103-
if (!blockPath || number > this.lastBlockNumber() - ON_MEM_CHAIN_LENGTH) {
104-
return this.chain.find((block) => block.number === number);
91+
const blockNumber = CommonUtil.toNumberOrNaN(number);
92+
if (!CommonUtil.isNumber(blockNumber)) return null;
93+
const blockPath = FileUtil.getBlockPath(this.blockchainPath, blockNumber);
94+
if (!blockPath || blockNumber > this.lastBlockNumber() - ON_MEMORY_CHAIN_LENGTH) {
95+
return this.chain.find((block) => block.number === blockNumber);
10596
} else {
10697
return Block.parse(FileUtil.readCompressedJson(blockPath));
10798
}
@@ -141,6 +132,13 @@ class Blockchain {
141132
return lastBlock.timestamp;
142133
}
143134

135+
addBlockToChain(block) {
136+
const LOG_HEADER = 'addBlockToChain';
137+
138+
this.chain.push(block);
139+
logger.info(`[${LOG_HEADER}] Successfully added block ${block.number} to chain.`);
140+
}
141+
144142
addNewBlockToChain(newBlock) {
145143
const LOG_HEADER = 'addNewBlockToChain';
146144

@@ -155,56 +153,66 @@ class Blockchain {
155153
if (!(newBlock instanceof Block)) {
156154
newBlock = Block.parse(newBlock);
157155
}
158-
this.chain.push(newBlock);
159-
this.writeChain();
160-
// Keep up to latest ON_MEM_CHAIN_LENGTH blocks
161-
while (this.chain.length > ON_MEM_CHAIN_LENGTH) {
156+
this.addBlockToChain(newBlock);
157+
this.writeBlock(newBlock);
158+
// Keep up to latest ON_MEMORY_CHAIN_LENGTH blocks
159+
while (this.chain.length > ON_MEMORY_CHAIN_LENGTH) {
162160
this.chain.shift();
163161
}
164162
return true;
165163
}
166164

167-
static isValidChain(chain, latestSnapshotBlockNumber) {
168-
if (!chain.length) {
169-
return true;
170-
}
171-
const firstBlock = Block.parse(chain[0]);
172-
if (!firstBlock) {
165+
static validateBlock(block, prevBlockNumber = null, prevBlockHash = null) {
166+
const LOG_HEADER = 'validateBlock';
167+
168+
if (prevBlockNumber != null && block.number !== prevBlockNumber + 1) {
169+
logger.error(`Invalid block number (expected: ${prevBlockNumber}) of block: ${block.number}`);
173170
return false;
174171
}
175-
if (latestSnapshotBlockNumber > 0 && latestSnapshotBlockNumber + 1 !== firstBlock.number) {
176-
logger.error(`Missing blocks between ${latestSnapshotBlockNumber + 1} and ${firstBlock.number}`);
172+
if (prevBlockHash !== null && block.last_hash !== prevBlockHash) {
173+
logger.error(
174+
`Invalid block last_hash (expected: ${prevBlockHash}) of block: ${block.last_hash}`);
177175
return false;
178176
}
179-
if (firstBlock.number === 0 && firstBlock.hash !== Block.genesis().hash) {
180-
logger.error(`Invalid genesis block: ${firstBlock}\n${Block.genesis()}`);
177+
if (!Block.validateHashes(block)) {
178+
logger.error(`Invalid block hashes of block: ${block.number}`);
181179
return false;
182180
}
183-
return Blockchain.isValidChainSegment(chain);
181+
logger.info(`[${LOG_HEADER}] Successfully validated block: ${block.number} / ${block.epoch}`);
182+
return true;
184183
}
185184

186-
static isValidChainSegment(chainSegment) {
187-
if (chainSegment.length) {
188-
if (!Block.validateHashes(chainSegment[0])) {
185+
186+
static validateChainSegment(chainSegment) {
187+
let prevBlockNumber;
188+
let prevBlockHash;
189+
if (chainSegment.length > 0) {
190+
const block = chainSegment[0];
191+
if (!Blockchain.validateBlock(block)) {
189192
return false;
190193
}
194+
prevBlockNumber = block.number;
195+
prevBlockHash = block.hash;
191196
}
192197
for (let i = 1; i < chainSegment.length; i++) {
193198
const block = chainSegment[i];
194-
const lastBlock = Block.parse(chainSegment[i - 1]);
195-
if (block.last_hash !== lastBlock.hash || !Block.validateHashes(block)) {
199+
if (!Blockchain.validateBlock(block, prevBlockNumber, prevBlockHash)) {
196200
return false;
197201
}
202+
prevBlockNumber = block.number;
203+
prevBlockHash = block.hash;
198204
}
199205
return true;
200206
}
201207

202-
writeChain() {
203-
for (let i = 0; i < this.chain.length; i++) {
204-
const block = this.chain[i];
205-
FileUtil.writeBlock(this.blockchainPath, block);
206-
FileUtil.writeHashToNumber(this.blockchainPath, block.hash, block.number);
207-
}
208+
writeBlock(block) {
209+
FileUtil.writeBlockFile(this.blockchainPath, block);
210+
FileUtil.writeH2nFile(this.blockchainPath, block.hash, block.number);
211+
}
212+
213+
deleteBlock(block) {
214+
FileUtil.deleteBlockFile(this.blockchainPath, block.number);
215+
FileUtil.deleteH2nFile(this.blockchainPath, block.hash);
208216
}
209217

210218
getValidBlocksInChainSegment(chainSegment) {
@@ -231,7 +239,7 @@ class Blockchain {
231239
return validBlocks;
232240
}
233241
}
234-
if (!Blockchain.isValidChainSegment(chainSegment)) {
242+
if (!Blockchain.validateChainSegment(chainSegment)) {
235243
logger.error(`Invalid chain segment`);
236244
return validBlocks;
237245
}
@@ -244,38 +252,24 @@ class Blockchain {
244252
return validBlocks;
245253
}
246254

247-
/**
248-
* Reads the block files at the chains n2b directory and returns a list of blocks starting from
249-
* the latestSnapshotBlockNumber + 1.
250-
* @param {Number} latestSnapshotBlockNumber
251-
* @returns {list} A list of Blocks
252-
*/
253-
loadChain(latestSnapshotBlockNumber) {
254-
const chainPath = this.blockchainPath;
255-
const newChain = [];
256-
const numBlockFiles = fs.readdirSync(path.join(chainPath, CHAINS_N2B_DIR_NAME)).length;
257-
const blockPaths = FileUtil.getBlockPaths(chainPath, latestSnapshotBlockNumber + 1, numBlockFiles);
258-
259-
blockPaths.forEach((blockPath) => {
260-
const block = Block.parse(FileUtil.readCompressedJson(blockPath));
261-
newChain.push(block);
262-
});
255+
getNumBlockFiles() {
256+
return FileUtil.getNumBlockFiles(this.blockchainPath);
257+
}
263258

264-
if (Blockchain.isValidChain(newChain, latestSnapshotBlockNumber)) {
265-
logger.info(`Valid chain of size ${newChain.length}`);
266-
return newChain;
259+
loadBlock(blockNumber) {
260+
const blockPath = FileUtil.getBlockPath(this.blockchainPath, blockNumber);
261+
if (!fs.existsSync(blockPath)) {
262+
return null;
267263
}
268-
logger.error(`Invalid chain`);
269-
rimraf.sync(chainPath + '/*');
270-
return null;
264+
return Block.parse(FileUtil.readCompressedJson(blockPath));
271265
}
272266

273267
/**
274268
* Returns a section of the chain up to a maximuim of length CHAIN_SEGMENT_LENGTH, starting from
275-
* the `from` block number up till `to` block number.
269+
* the `from` block number (included) up till `to` block number (excluded).
276270
*
277-
* @param {Number} from - The lowest block number to get
278-
* @param {Number} to - The highest block number to geet
271+
* @param {Number} from - The lowest block number to get (included)
272+
* @param {Number} to - The highest block number to geet (excluded)
279273
* @return {list} A list of Blocks, up to a maximuim length of CHAIN_SEGMENT_LENGTH
280274
*/
281275
getBlockList(from, to) {
@@ -298,7 +292,7 @@ class Blockchain {
298292
if (to - from > CHAIN_SEGMENT_LENGTH) { // NOTE: To prevent large query.
299293
to = from + CHAIN_SEGMENT_LENGTH;
300294
}
301-
const blockPaths = FileUtil.getBlockPaths(this.blockchainPath, from, to - from);
295+
const blockPaths = FileUtil.getBlockPathList(this.blockchainPath, from, to - from);
302296
blockPaths.forEach((blockPath) => {
303297
blockList.push(Block.parse(FileUtil.readCompressedJson(blockPath)));
304298
});

0 commit comments

Comments
 (0)