Skip to content

Commit

Permalink
Merge pull request #37 from ainblockchain/feature/lia/streamlet-conse…
Browse files Browse the repository at this point in the history
…nsus

Feature/lia/streamlet consensus
  • Loading branch information
liayoo authored Aug 4, 2020
2 parents f9e20c1 + b678ac9 commit 19ebf7b
Show file tree
Hide file tree
Showing 33 changed files with 1,920 additions and 482 deletions.
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ GET http://<ip_address>:5000/peer_nodes

## Node

Operates a single peer node instance of the AIN blockchain. A single blockchain node instance processes incoming transaction requests and maintains a local copy of the entire blockchain. The blockchain node first queries the tracker-server for ip addresses of other peers, and then syncs its local blockchain to the network consensus blockchain. If the blockchain specifies a "STAKE" argument on startup, it will then begin to take part in the forging/validating process for new blocks.
Operates a single peer node instance of the AIN blockchain. A single blockchain node instance processes incoming transaction requests and maintains a local copy of the entire blockchain. The blockchain node first queries the tracker-server for ip addresses of other peers, and then syncs its local blockchain to the network consensus blockchain. If a node is included in the whitelist and has staked appropriate amount of AIN, it will then take part in the consensus protocol.

### Running without Docker

Expand All @@ -90,16 +90,25 @@ yarn install
```
- Run blockchain nodes
```
STAKE=250 ACCOUNT_INDEX=0 HOSTING_ENV=local DEBUG=false node client/index.js
STAKE=250 ACCOUNT_INDEX=1 HOSTING_ENV=local DEBUG=false node client/index.js
STAKE=250 ACCOUNT_INDEX=2 HOSTING_ENV=local DEBUG=false node client/index.js
STAKE=250 ACCOUNT_INDEX=3 HOSTING_ENV=local DEBUG=false node client/index.js
STAKE=250 ACCOUNT_INDEX=4 HOSTING_ENV=local DEBUG=false node client/index.js
ACCOUNT_INDEX=0 HOSTING_ENV=local DEBUG=false node client/index.js
ACCOUNT_INDEX=1 HOSTING_ENV=local DEBUG=false node client/index.js
ACCOUNT_INDEX=2 HOSTING_ENV=local DEBUG=false node client/index.js
ACCOUNT_INDEX=3 HOSTING_ENV=local DEBUG=false node client/index.js
ACCOUNT_INDEX=4 HOSTING_ENV=local DEBUG=false node client/index.js
```
Before starting node jobs, remove existing blockchain files and logs if necessary:
```
rm -rf blockchain/blockchains logger/logs
```
The default size of the validator whitelist is 5. Set NUM_VALIDATORS environment variable when running the first node if you'd like to run different number of validator nodes than 5.

### How to run tests

```
npm run test_unit
npm run test_smoke
npm run test_integration
```

#### On Google Coud Platform (GCP)

Expand Down Expand Up @@ -134,7 +143,7 @@ docker pull ainblockchain/blockchain-database
```
- Run with Docker image
```
docker run -e STAKE=250 -e ACCOUNT_INDEX=0 -e HOSTING_ENV="gcp" -e TRACKER_WS_ADDR="ws://<ip_address_of_tracker_server>:5000" --network="host" -d ainblockchain/ain-blockchain:latest
docker run -e ACCOUNT_INDEX=0 -e HOSTING_ENV="gcp" -e TRACKER_WS_ADDR="ws://<ip_address_of_tracker_server>:5000" --network="host" -d ainblockchain/ain-blockchain:latest
```
#### Enter Docker container and inspect blockchain files
Expand Down
75 changes: 52 additions & 23 deletions blockchain/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,25 @@ const ChainUtil = require('../chain-util');
const Transaction = require('../tx-pool/transaction');
const {
GENESIS_OWNERS, ADDITIONAL_OWNERS, GENESIS_RULES, ADDITIONAL_RULES, GENESIS_FUNCTIONS,
ADDITIONAL_FUNCTIONS, PredefinedDbPaths, GenesisToken, GenesisAccounts
ADDITIONAL_FUNCTIONS, PredefinedDbPaths, GenesisToken, GenesisAccounts, GenesisWhitelist
} = require('../constants');
const { ConsensusDbPaths, ConsensusConsts } = require('../consensus/constants');
const BlockFilePatterns = require('./block-file-patterns');
const zipper = require('zip-local');
const sizeof = require('object-sizeof');

const LOG_PREFIX = 'BLOCK';

class Block {
constructor(lastHash, lastVotes, transactions, number, timestamp, proposer, validators) {
constructor(lastHash, lastVotes, transactions, number, epoch, timestamp, proposer, validators) {
this.last_votes = lastVotes;
this.transactions = transactions;
// Block's header
this.last_hash = lastHash;
this.last_votes_hash = ChainUtil.hashString(stringify(lastVotes));
this.transactions_hash = ChainUtil.hashString(stringify(transactions));
this.number = number;
this.epoch = epoch;
this.timestamp = timestamp;
this.proposer = proposer;
this.validators = validators;
Expand All @@ -37,6 +39,7 @@ class Block {
last_votes_hash: this.last_votes_hash,
transactions_hash: this.transactions_hash,
number: this.number,
epoch: this.epoch,
timestamp: this.timestamp,
proposer: this.proposer,
validators: this.validators,
Expand All @@ -51,6 +54,7 @@ class Block {
last_votes_hash: ${ChainUtil.shortenHash(this.last_votes_hash)}
transactions_hash: ${ChainUtil.shortenHash(this.transactions_hash)}
number: ${this.number}
epoch: ${this.epoch}
timestamp: ${this.timestamp}
proposer: ${this.proposer}
validators: ${this.validators}
Expand All @@ -66,8 +70,8 @@ class Block {
return ChainUtil.hashString(stringify(block.header));
}

static createBlock(lastHash, lastVotes, transactions, number, proposer, validators) {
return new Block(lastHash, lastVotes, transactions, number, Date.now(), proposer, validators);
static createBlock(lastHash, lastVotes, transactions, number, epoch, proposer, validators) {
return new Block(lastHash, lastVotes, transactions, number, epoch, Date.now(), proposer, validators);
}

static getFileName(block) {
Expand All @@ -84,15 +88,15 @@ class Block {
if (!Block.hasRequiredFields(blockInfo)) return null;
if (blockInfo instanceof Block) return blockInfo;
return new Block(blockInfo['last_hash'], blockInfo['last_votes'],
blockInfo['transactions'], blockInfo['number'], blockInfo['timestamp'],
blockInfo['proposer'], blockInfo['validators']);
blockInfo['transactions'], blockInfo['number'], blockInfo['epoch'],
blockInfo['timestamp'], blockInfo['proposer'], blockInfo['validators']);
}

static hasRequiredFields(block) {
return (block.last_hash !== undefined && block.last_votes !== undefined &&
block.transactions !== undefined && block.number !== undefined &&
block.timestamp !== undefined && block.proposer !== undefined &&
block.validators !== undefined);
block.epoch !== undefined && block.timestamp !== undefined &&
block.proposer !== undefined && block.validators !== undefined);
}

static validateHashes(block) {
Expand All @@ -112,18 +116,8 @@ class Block {
return true;
}

static validateProposedBlock(block, blockchain) {
if (!Block.validateHashes(block)) {
return false;
}
if (block.number !== (blockchain.lastBlockNumber() + 1)) {
logger.error(`[${LOG_PREFIX}] Number is not correct for block ${block.hash} ` +
`Expected: ${(blockchain.lastBlockNumber() + 1)} ` +
`Actual: ${block.number}`);
return false;
}
// TODO (lia): check the contents of block.last_votes if they indeed voted for
// the previous block.
static validateProposedBlock(block) {
if (!Block.validateHashes(block)) return false;
const nonceTracker = {};
let transaction;
for (let i=0; i<block.transactions.length; i++) {
Expand Down Expand Up @@ -166,6 +160,14 @@ class Block {
throw Error('Missing genesis rules file: ' + GENESIS_RULES);
}

// Consensus (whitelisting) operation
// TODO(lia): increase this list to 10
const whitelistValOp = {
type: 'SET_VALUE',
ref: `/${ConsensusDbPaths.CONSENSUS}/${ConsensusDbPaths.WHITELIST}`,
value: GenesisWhitelist
};

// Function configs operation
const functionConfigs = JSON.parse(fs.readFileSync(GENESIS_FUNCTIONS));
if (ADDITIONAL_FUNCTIONS) {
Expand Down Expand Up @@ -216,14 +218,37 @@ class Block {
ref: '/',
value: ownerConfigs
};
const whitelistRuleOp = {
type: 'SET_RULE',
ref: `/${ConsensusDbPaths.CONSENSUS}/${ConsensusDbPaths.WHITELIST}`,
value: `auth === '${ownerAccount.address}'`
}
const whitelistOwnerOp = {
type: 'SET_OWNER',
ref: `/${ConsensusDbPaths.CONSENSUS}/${ConsensusDbPaths.WHITELIST}`,
value: {
[ownerAccount.address]: {
"branch_owner": true,
"write_function": true,
"write_owner": true,
"write_rule": true
},
"*": {
"branch_owner": false,
"write_function": false,
"write_owner": false,
"write_rule": false
}
}
}

// Transaction
const firstTxData = {
nonce: -1,
timestamp,
operation: {
type: 'SET',
op_list: [ tokenOp, balanceOp, functionsOp, rulesOp, ownersOp ]
op_list: [ tokenOp, balanceOp, whitelistValOp, functionsOp, rulesOp, whitelistRuleOp, ownersOp, whitelistOwnerOp ]
}
};
const firstSig = ainUtil.ecSignTransaction(firstTxData, keyBuffer);
Expand Down Expand Up @@ -286,9 +311,13 @@ class Block {
const lastVotes = [];
const transactions = Block.getGenesisBlockData();
const number = 0;
const epoch = 0;
const proposer = ownerAccount.address;
const validators = [];
return new this(lastHash, lastVotes, transactions, number, timestamp,
const validators = {};
for (let i = 0; i < ConsensusConsts.INITIAL_NUM_VALIDATORS; i++) {
validators[GenesisAccounts.others[i].address] = ConsensusConsts.INITIAL_STAKE;
}
return new this(lastHash, lastVotes, transactions, number, epoch, timestamp,
proposer, validators);
}
}
Expand Down
14 changes: 14 additions & 0 deletions blockchain/genesis_owners.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,19 @@
}
}
}
},
"consensus": {
"whitelist": {
".owner": {
"owners": {
"*": {
"branch_owner": true,
"write_function": true,
"write_owner": true,
"write_rule": true
}
}
}
}
}
}
6 changes: 3 additions & 3 deletions blockchain/genesis_rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
"$number": {
".write": "newData === null && !!getValue('/consensus/number/' + (Number($number) + 1000))",
"propose": {
".write": "newData !== null && util.isDict(newData) && newData.proposer === auth && Number($number) === newData.number && (newData.number === 1 || getValue('/consensus/number/' + (newData.number - 1) + '/propose/validators/' + auth) > 0)"
".write": "newData !== null && util.isDict(newData) && newData.proposer === auth && Number($number) === newData.number && getValue('/consensus/whitelist/' + auth) > 0 && (lastBlockNumber < 1 || getValue('/deposit_accounts/consensus/' + auth + '/value') > 0)"
},
"register": {
"vote": {
"$user_addr": {
".write": "auth === $user_addr && util.isDict(newData) && util.isString(newData.block_hash) && util.isNumber(newData.stake) && newData.stake > 0 && getValue('/deposit_accounts/consensus/' + $user_addr + '/value') >= newData.stake && !getValue('/consensus/number/' + (Number($number) + 1) + '/propose')"
".write": "auth === $user_addr && util.isDict(newData) && util.isString(newData.block_hash) && util.isNumber(newData.stake) && newData.stake > 0 && getValue('/consensus/whitelist/' + auth) >= newData.stake && (lastBlockNumber < 1 || getValue('/deposit_accounts/consensus/' + auth + '/value') > 0)"
}
}
}
Expand Down
Loading

0 comments on commit 19ebf7b

Please sign in to comment.