Skip to content

Commit

Permalink
Polkadot send transaction extraction (#500)
Browse files Browse the repository at this point in the history
* tx logic draft

* fix lint

* wip

* lint

* working for send tx type

* should work publishing send tx!

* correct data is being passed to publishers

* cleanup

* working with extrinsics

* implement coin reducer

* Update lib/reducers/polkadotV0-reducers.js

Co-Authored-By: Fabian <[email protected]>

* Update lib/reducers/polkadotV0-reducers.js

Co-Authored-By: Fabian <[email protected]>

Co-authored-by: Fabian <[email protected]>
  • Loading branch information
mariopino and faboweb authored Mar 24, 2020
1 parent 5362805 commit 4aafab8
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 70 deletions.
33 changes: 14 additions & 19 deletions lib/block-listeners/polkadot-node-subscription.js
Original file line number Diff line number Diff line change
@@ -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')
Expand Down Expand Up @@ -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)
Expand Down
141 changes: 95 additions & 46 deletions lib/reducers/polkadotV0-reducers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const _ = require('lodash')
const BigNumber = require('bignumber.js')

const { lunieMessageTypes } = require('../../lib/message-types')

function blockReducer(
networkId,
blockHeight,
Expand Down Expand Up @@ -83,66 +86,112 @@ 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 = {
blockReducer,
validatorReducer,
balanceReducer,
delegationReducer,
extractInvolvedAddresses
extractInvolvedAddresses,
transactionsReducerV2,
transactionDetailsReducer,
sendDetailsReducer,
coinReducer
}
26 changes: 21 additions & 5 deletions lib/source/polkadotV0-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`)

Expand Down

0 comments on commit 4aafab8

Please sign in to comment.