-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First steps to integrate Polkadot (#376)
* Add polkadot-testnet in networks.js * Install polkadot-api, polkadot-node-sub. draft * lint * lint * lint * add polkadotV0 source * add address creator * Update data/networks.js Co-Authored-By: Jordan Bibla <[email protected]> * Update data/networks.js Co-Authored-By: Jordan Bibla <[email protected]> * blocks, events * Cleanup * polling instead subscription * husky * updateDBValidatorProfiles * no wait for block data fetching, kind of stable * Add reducers file * block subscription works! * Cleanup, fix memory leak * lint, node * comment * Handle polkadot chain reorgs * Cleanup * Optimization, cleanup * Optimization * fixes, validators query working, cleanup * comment * lint * fix block time * handle polkadot chain hangups * cleanup * validator reducer * lint * wip * calculate and include a bunch of validator fields * add bech32_prefix address_prefix to networks.js * add 1 space so we dont break tests * remove chain reorg handling, more stable Co-authored-by: Ana G. <[email protected]> Co-authored-by: Fabian <[email protected]> Co-authored-by: Jordan Bibla <[email protected]>
- Loading branch information
1 parent
cded999
commit 4cc08d9
Showing
7 changed files
with
10,403 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
const _ = require('lodash') | ||
const { | ||
publishBlockAdded | ||
// publishUserTransactionAdded, | ||
// publishEvent: publishEvent | ||
} = require('../subscriptions') | ||
const Sentry = require('@sentry/node') | ||
const database = require('../database') | ||
const config = require('../../config.js') | ||
|
||
// This class polls for new blocks | ||
// Used for listening to events, such as new blocks. | ||
class PolkadotNodeSubscription { | ||
constructor(network, PolkadotApiClass, store) { | ||
this.network = network | ||
this.polkadotAPI = new PolkadotApiClass(network) | ||
this.store = store | ||
this.validators = [] | ||
this.sessionValidators = [] | ||
const networkSchemaName = this.network.id.replace(/-/g, '_') | ||
this.db = new database(config)(networkSchemaName) | ||
this.height = 0 | ||
this.currentSessionIndex = 0 | ||
this.blockQueue = [] | ||
this.subscribeForNewBlock() | ||
} | ||
|
||
async subscribeForNewBlock() { | ||
const api = await this.polkadotAPI.getAPIPromise() | ||
|
||
// Subscribe to new block headers | ||
await api.rpc.chain.subscribeNewHeads(async blockHeader => { | ||
const blockHeight = blockHeader.number.toNumber() | ||
if (this.height < blockHeight) { | ||
this.height = blockHeight | ||
console.log(`\x1b[36mNew kusama block #${blockHeight}\x1b[0m`) | ||
this.newBlockHandler(blockHeight) | ||
} | ||
}) | ||
} | ||
|
||
// Sometimes blocks get published unordered so we need to enqueue | ||
// them before publish to ensure correct order. This adds 3 blocks delay. | ||
enqueueAndPublishBlockAdded(newBlock) { | ||
this.blockQueue.push(newBlock) | ||
if (this.blockQueue.length > 2) { | ||
this.blockQueue.sort((a, b) => | ||
a.height > b.height ? 1 : b.height > a.height ? -1 : 0 | ||
) | ||
console.log( | ||
`\x1b[36mPublishing new kusama block #${newBlock.height}\x1b[0m` | ||
) | ||
publishBlockAdded(this.network.id, this.blockQueue.shift()) | ||
} | ||
} | ||
|
||
// For each block event, we fetch the block information and publish a message. | ||
// A GraphQL resolver is listening for these messages and sends the block to | ||
// each subscribed user. | ||
async newBlockHandler(blockHeight) { | ||
try { | ||
Sentry.configureScope(function(scope) { | ||
scope.setExtra('height', blockHeight) | ||
}) | ||
|
||
const block = await this.polkadotAPI.getBlockByHeight(blockHeight) | ||
// publishBlockAdded(this.network.id, block) | ||
this.enqueueAndPublishBlockAdded(block) | ||
|
||
// We dont need to fetch validators on every new block. | ||
// Validator list only changes on new sessions | ||
if ( | ||
this.currentSessionIndex < block.sessionIndex || | ||
this.currentSessionIndex === 0 | ||
) { | ||
console.log( | ||
`\x1b[36mCurrent session index is ${block.sessionIndex}, fetching validators!\x1b[0m` | ||
) | ||
this.currentSessionIndex = block.sessionIndex | ||
this.sessionValidators = await this.polkadotAPI.getAllValidators() | ||
} | ||
|
||
this.updateDBValidatorProfiles(this.sessionValidators) | ||
this.store.update({ | ||
height: blockHeight, | ||
block, | ||
validators: this.sessionValidators | ||
}) | ||
|
||
// For each transaction listed in a block we extract the relevant addresses. This is published to the network. | ||
// A GraphQL resolver is listening for these messages and sends the | ||
// transaction to each subscribed user. | ||
|
||
// let addresses = [] | ||
// addresses = this.polkadotAPI.extractInvolvedAddresses(block.transactions) | ||
// addresses = _.uniq(addresses) | ||
|
||
// if (addresses.length > 0) { | ||
// console.log( | ||
// `\x1b[36mAddresses included in tx for block #${blockHeight}: ${addresses}\x1b[0m` | ||
// ) | ||
// } | ||
|
||
// addresses.forEach(address => { | ||
// publishUserTransactionAdded(this.network.id, address, tx) | ||
// publishEvent(this.network.id, 'transaction', address, tx) | ||
// }) | ||
} catch (error) { | ||
console.error(`newBlockHandler failed: ${error}`) | ||
Sentry.captureException(error) | ||
} | ||
} | ||
|
||
async getValidatorMap(validators) { | ||
const validatorMap = _.keyBy(validators, 'operatorAddress') | ||
return validatorMap | ||
} | ||
|
||
// this adds all the validator addresses to the database so we can easily check in the database which ones have an image and which ones don't | ||
async updateDBValidatorProfiles(validators) { | ||
// filter only new validators | ||
let newValidators = validators.filter( | ||
validator => | ||
!this.validators.find( | ||
v => | ||
v.address == validator.operatorAddress && v.name == validator.name // in case if validator name was changed | ||
) | ||
) | ||
// save all new validators to an array | ||
this.validators = [ | ||
...this.validators.filter( | ||
({ operatorAddress }) => | ||
!newValidators.find( | ||
({ operatorAddress: newValidatorOperatorAddress }) => | ||
newValidatorOperatorAddress === operatorAddress | ||
) | ||
), | ||
...newValidators.map(v => ({ | ||
address: v.operatorAddress, | ||
name: v.name | ||
})) | ||
] | ||
// update only new onces | ||
const validatorRows = newValidators.map( | ||
({ operatorAddress, name, chainId }) => ({ | ||
operator_address: operatorAddress, | ||
name, | ||
chain_id: chainId | ||
}) | ||
) | ||
return this.db.upsert('validatorprofiles', validatorRows) | ||
} | ||
} | ||
|
||
module.exports = PolkadotNodeSubscription |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
function blockReducer( | ||
networkId, | ||
blockHeight, | ||
blockHash, | ||
sessionIndex, | ||
blockAuthor, | ||
blockEvents | ||
) { | ||
return { | ||
networkId, | ||
height: blockHeight, | ||
chainId: `kusama-cc3`, | ||
hash: blockHash, | ||
sessionIndex, | ||
time: new Date().toISOString(), // TODO: Get from blockchain state | ||
transactions: blockEvents, // TODO: IMPROVE! | ||
proposer_address: blockAuthor | ||
} | ||
} | ||
|
||
function validatorReducer(network, validator) { | ||
return { | ||
networkId: network.id, | ||
chainId: network.chain_id, | ||
operatorAddress: validator.accountId, | ||
website: | ||
validator.identity.web && validator.identity.web !== `` | ||
? validator.identity.web | ||
: ``, | ||
identity: | ||
validator.identity.display && validator.identity.display !== `` | ||
? validator.identity.display | ||
: validator.accountId, | ||
name: | ||
validator.identity.display && validator.identity.display !== `` | ||
? validator.identity.display | ||
: validator.accountId, | ||
votingPower: validator.votingPower.toFixed(9), | ||
startHeight: undefined, | ||
uptimePercentage: undefined, | ||
tokens: validator.tokens, | ||
commissionUpdateTime: undefined, | ||
commission: validator.validatorPrefs.commission / 100000000, | ||
maxCommission: undefined, | ||
maxChangeCommission: undefined, | ||
status: `ACTIVE`, // We are fetching current session active validators only (not intentions) | ||
statusDetailed: ``, // TODO: Include validator heartbeat messages | ||
delegatorShares: undefined | ||
} | ||
} | ||
|
||
module.exports = { | ||
blockReducer, | ||
validatorReducer | ||
} |
Oops, something went wrong.