diff --git a/lib/block-listeners/polkadot-node-subscription.js b/lib/block-listeners/polkadot-node-subscription.js index 1cdc2e73e8..34e434c023 100644 --- a/lib/block-listeners/polkadot-node-subscription.js +++ b/lib/block-listeners/polkadot-node-subscription.js @@ -1,9 +1,9 @@ const _ = require('lodash') const { ApiPromise, WsProvider } = require('@polkadot/api') const { - publishBlockAdded - // publishUserTransactionAdded, - // publishEvent: publishEvent + publishBlockAdded, + publishUserTransactionAddedV2, + publishEvent: publishEvent } = require('../subscriptions') const Sentry = require('@sentry/node') const database = require('../database') @@ -133,25 +133,20 @@ class PolkadotNodeSubscription { validators: this.getValidatorMap(this.sessionValidators) }) + // TODO remove, only for demo purposes + // publishEvent(this.network.id, 'block', '', block) + // 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. - - // TODO move address extraction to transaction reducer - // let addresses = [] - // addresses = this.polkadotAPI.reducers.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) - // }) + block.transactions.forEach(tx => { + tx.involvedAddresses.forEach(address => { + console.log(`publishUserTransactionAddedV2`, address, tx) + publishUserTransactionAddedV2(this.network.id, address, tx) + console.log(`publishEvent`, address, tx) + publishEvent(this.network.id, 'transaction', address, tx) + }) + }) } catch (error) { console.error(`newBlockHandler failed`, JSON.stringify(error, null, 2)) Sentry.captureException(error) diff --git a/lib/reducers/cosmosV0-reducers.js b/lib/reducers/cosmosV0-reducers.js index 81c82fb59e..d9fd36c1ad 100644 --- a/lib/reducers/cosmosV0-reducers.js +++ b/lib/reducers/cosmosV0-reducers.js @@ -565,9 +565,9 @@ function transactionReducerV2(transaction, reducers, stakingDenom) { log: transaction.logs && transaction.logs[index] ? transaction.logs[index].log - : transaction.logs[0] // failing txs show the first logs - ? transaction.logs[0].log - : '', + ? transaction.logs[index].log || transaction.logs[0] // failing txs show the first logs + : transaction.logs[0].log || '' + : JSON.parse(transaction.raw_log).message, involvedAddresses: _.uniq(reducers.extractInvolvedAddresses(transaction)) })) return returnedMessages @@ -576,6 +576,7 @@ function transactionReducerV2(transaction, reducers, stakingDenom) { scope.setExtra('transaction', transaction) Sentry.captureException(error) }) + return [] // must return something differ from undefined } } @@ -635,7 +636,10 @@ function transactionReducer(transaction, reducers) { return result } catch (err) { Sentry.withScope(function(scope) { - scope.setExtra('transaction', transaction) + scope.setExtra('transaction', { + ...transaction, + raw: JSON.stringify(transaction) + }) Sentry.captureException(err) }) return { @@ -775,6 +779,12 @@ function claimRewardsDetailsReducer( } function claimRewardsAmountReducer(transaction, reducers, stakingDenom) { + if (!transaction.success) { + return { + amoun: 0, + denom: stakingDenom + } + } return reducers.rewardCoinReducer( transaction.events .find(event => event.type === `transfer`) diff --git a/lib/reducers/polkadotV0-reducers.js b/lib/reducers/polkadotV0-reducers.js index ec5c23a1af..b0139eedd6 100644 --- a/lib/reducers/polkadotV0-reducers.js +++ b/lib/reducers/polkadotV0-reducers.js @@ -1,5 +1,8 @@ +const _ = require('lodash') const BigNumber = require('bignumber.js') +const { lunieMessageTypes } = require('../../lib/message-types') + function blockReducer( networkId, blockHeight, @@ -83,60 +86,102 @@ function delegationReducer(network, delegation, validator) { } } -function extractInvolvedAddresses(blockEvents) { - let involvedAddresses = [] - blockEvents.forEach(async record => { - const { event } = record - - // event.section balances - // event.method NewAccount - // event.data ["FNtKsMaSWe2UAatJjnCkikEJsiQYDqTcB4ujUebs51KRE1V",100000000000] - - if (event.section === `balances` && event.method === `NewAccount`) { - console.log(`Involved address:`, event.data[0]) - involvedAddresses.push(event.data[0]) - } +function transactionsReducerV2(network, extrinsics, blockHeight, reducers) { + // Filter Polkadot tx to Lunie supported types + return extrinsics.reduce((collection, extrinsic) => { + return collection.concat( + transactionReducerV2(network, extrinsic, blockHeight, reducers) + ) + }, []) +} - // event.section balances - // event.method Deposit - // event.data ["D948vxMSA6u7G5gqPGQdAUDMJbiR7wgqZ1La8XeBiXr9FTF",2000000000] +// Map Polkadot event method to Lunie message types +function getMessageType(type) { + switch (type) { + case 'transfer': + return lunieMessageTypes.SEND + default: + return lunieMessageTypes.UNKNOWN + } +} - if (event.section === `balances` && event.method === `Deposit`) { - console.log(`Involved address:`, event.data[0]) - involvedAddresses.push(event.data[0]) +function transactionReducerV2(network, extrinsic, blockHeight, reducers) { + if (extrinsic.method.meta.name.toString() === `transfer`) { + const tx = { + type: getMessageType(extrinsic.method.meta.name.toString()), + hash: extrinsic.hash.toHex(), + height: blockHeight, + details: transactionDetailsReducer( + network, + getMessageType(extrinsic.method.meta.name.toString()), + reducers, + extrinsic + ), + timestamp: new Date().getTime(), // FIXME!: pass it from block, we should get current timestamp from blockchain for new blocks + memo: ``, + fees: { + amount: `0`, + denom: `KSM` + }, // FIXME! + success: true, + log: ``, + involvedAddresses: _.uniq(reducers.extractInvolvedAddresses(extrinsic)) } + return [tx] + } + return [] +} - // event.section balances - // event.method ReapedAccount - // event.data ["GksmapjLhJBS4vA7JBT6oMbc98YLGheR9qmTHDkeo4F9koh",0] +// Map polkadot messages to our details format +function transactionDetailsReducer(network, type, reducers, extrinsic) { + let details + switch (type) { + case lunieMessageTypes.SEND: + details = sendDetailsReducer(network, extrinsic, reducers) + break + default: + details = {} + } + return { + type, + ...details + } +} - if (event.section === `balances` && event.method === `ReapedAccount`) { - console.log(`Involved address:`, event.data[0]) - involvedAddresses.push(event.data[0]) +function coinReducer(network, amount) { + if (!amount) { + return { + amount: 0, + denom: '' } + } - // event.section balances - // event.method Transfer - // event.data ["GksmapjLhJBS4vA7JBT6oMbc98YLGheR9qmTHDkeo4F9koh","GwoksmaSpMdDwxxRYV9P4BwCcF1agXNWjdxSF2oEWwEc1iy",60000000000,10000000000] + return { + denom: network.coinLookup[0].viewDenom, + amount: BigNumber(amount) + .times(network.coinLookup[0].chainToViewConversionFactor) + .toFixed(9) + } +} - if (event.section === `balances` && event.method === `ReapedAccount`) { - console.log(`Involved address:`, event.data[0]) - involvedAddresses.push(event.data[0]) - console.log(`Involved address:`, event.data[1]) - involvedAddresses.push(event.data[1]) - } +function sendDetailsReducer(network, extrinsic, reducers) { + return { + from: [extrinsic.signer.toString()], + to: [extrinsic.args[0].toString()], + amount: reducers.coinReducer(network, extrinsic.args[1]) + } +} - // console.log( - // `\x1b[36mNew kusama extrinsic for block #${blockHeight} index: ${index} section: ${ - // event.section - // } method: ${ - // event.method - // } phase: ${phase.toString()} data: ${JSON.stringify( - // event.data - // )}\x1b[0m` - // ) - }) - return involvedAddresses +// TO IMPROVE: duplicate logic in reducer and address extraction +function extractInvolvedAddresses(extrinsic) { + if (extrinsic.method.meta.name.toString() === `transfer`) { + const involvedAddresses = [ + extrinsic.signer.toString(), + extrinsic.args[0].toString() + ] + return involvedAddresses + } + return [] } module.exports = { @@ -144,5 +189,9 @@ module.exports = { validatorReducer, balanceReducer, delegationReducer, - extractInvolvedAddresses + extractInvolvedAddresses, + transactionsReducerV2, + transactionDetailsReducer, + sendDetailsReducer, + coinReducer } diff --git a/lib/resolvers.js b/lib/resolvers.js index 2abbc332b2..37ddb8da12 100644 --- a/lib/resolvers.js +++ b/lib/resolvers.js @@ -356,7 +356,25 @@ const resolvers = { transactions: (_, { networkId, address, pageNumber }, { dataSources }) => remoteFetch(dataSources, networkId).getTransactions(address, pageNumber), transactionsV2: (_, { networkId, address, pageNumber }, { dataSources }) => - remoteFetch(dataSources, networkId).getTransactionsV2(address, pageNumber) + remoteFetch(dataSources, networkId).getTransactionsV2( + address, + pageNumber + ), + estimate: () => { + try { + const gasEstimate = 550000 + + return { + gasEstimate, + success: true + } + } catch (e) { + return { + error: e.message, + success: false + } + } + } }, Subscription: { blockAdded: { diff --git a/lib/schema.js b/lib/schema.js index 9a1ba53b3e..0fa167f1ef 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -310,6 +310,12 @@ const typeDefs = gql` event(networkId: String!, eventType: String!, ressourceId: String): Event } + type EstimateResult { + gasEstimate: Int + error: String + success: Boolean! + } + type Query { block(networkId: String!, height: Int): Block blockV2(networkId: String!, height: Int): BlockV2 @@ -367,6 +373,7 @@ const typeDefs = gql` address: String! pageNumber: Int ): [TransactionV2] + estimate: EstimateResult! } ` diff --git a/lib/source/polkadotV0-source.js b/lib/source/polkadotV0-source.js index 13e1686572..e2cb80d735 100644 --- a/lib/source/polkadotV0-source.js +++ b/lib/source/polkadotV0-source.js @@ -29,30 +29,46 @@ class polkadotAPI { // heavy nesting to provide optimal parallelization here const [ - [{ author }, blockEvents, blockHash], + [{ author }, { block }, blockHash], sessionIndex ] = await Promise.all([ api.rpc.chain.getBlockHash(blockHeight).then(async blockHash => { - const [{ author }, blockEvents] = await Promise.all([ + const [{ author }, { block }] = await Promise.all([ api.derive.chain.getHeader(blockHash), - api.query.system.events.at(blockHash) + api.rpc.chain.getBlock(blockHash) ]) - return [{ author }, blockEvents, blockHash] + return [{ author }, { block }, blockHash] }), api.query.babe.epochIndex() ]) + const transactions = await this.getTransactionsV2( + block.extrinsics, + parseInt(blockHeight) + ) + return this.reducers.blockReducer( this.networkId, blockHeight, blockHash, sessionIndex.toNumber(), author, - blockEvents + transactions ) } + async getTransactionsV2(extrinsics, blockHeight) { + return Array.isArray(extrinsics) + ? this.reducers.transactionsReducerV2( + this.network, + extrinsics, + blockHeight, + this.reducers + ) + : [] + } + async getAllValidators() { console.time(`getAllValidators`)