Skip to content

Commit

Permalink
Michiel/make clear separation between v0 and v2 reducers (#530)
Browse files Browse the repository at this point in the history
* Replace transactionReducer with v2 for pollTransactionSuccess

* Replace transactionReducer with v2 for pollTransactionSuccess

* Fix structure

* Make clear seperation between v0 and v2 reducers

* Fix problem publishing transaction

* Fix pollTransactionSuccess

* Fix pollTransactionSuccess
  • Loading branch information
michielmulders authored Mar 26, 2020
1 parent 8428967 commit 14d3c3b
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 286 deletions.
47 changes: 28 additions & 19 deletions lib/controller/transaction/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// const { getMessage } = require('./messageConstructor')
const { networkMap } = require('../../networks')
const Sentry = require('@sentry/node')
const { publishUserTransactionAdded } = require('../../subscriptions')
const reducers = require('../../reducers/cosmosV0-reducers') // TODO the whole transaction service only works for cosmos rn
const { publishUserTransactionAddedV2 } = require('../../subscriptions')
const reducers = require('../../reducers/cosmosV2-reducers') // TODO the whole transaction service only works for cosmos rn
const {
prestore,
storePrestored,
Expand Down Expand Up @@ -271,31 +271,40 @@ async function pollTransactionSuccess(
// publishUserTransactionAdded is also done in the block subscription
// but also here as a fallback
// TODO the client might now update twice as it receives the success twice, could be fine though
const transaction = reducers.transactionReducer(res, reducers)
const transactions = reducers.transactionReducerV2(res, reducers)
// store in db
storePrestored(hash)
// we need to call
publishUserTransactionAdded(networkId, senderAddress, transaction)
transactions.forEach(transaction =>
publishUserTransactionAddedV2(networkId, senderAddress, transaction)
)
} catch (error) {
console.error('TX failed:', hash, error)
let transaction

let transactions
if (res.tx) {
transaction = reducers.transactionReducer(res, reducers)
transactions = reducers.transactionReducerV2(res, reducers)
} else {
// on timeout we don't get a transaction back
transaction = {
type: '',
hash,
height: -1,
group: '',
timestamp: '',
signature: '',
value: '',
success: false,
log: error.message
}
// on timeout we don't get a transactionV2 back
transactions = [
{
type: '',
hash,
key: '',
height: -1,
details: {},
timestamp: '',
memo: '',
success: false,
log: error.message
}
]
}
publishUserTransactionAdded(networkId, senderAddress, transaction)

transactions.forEach(transaction =>
publishUserTransactionAddedV2(networkId, senderAddress, transaction)
)

Sentry.withScope(scope => {
scope.setExtra('api_url', url)
scope.setExtra('hash', hash)
Expand Down
277 changes: 13 additions & 264 deletions lib/reducers/cosmosV0-reducers.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
const { uniqWith, sortBy, reverse } = require('lodash')
const { uniqWith, sortBy, reverse, flatten } = require('lodash')
const { cosmosMessageType } = require('../message-types')
const {
cosmosWhitelistedMessageTypes,
lunieMessageTypes
} = require('../../lib/message-types')
const { cosmosWhitelistedMessageTypes } = require('../../lib/message-types')
const BigNumber = require('bignumber.js')
const _ = require('lodash')
const Sentry = require('@sentry/node')

/**
* Modify the following reducers with care as they are used for ./cosmosV2-reducer.js as well
* [proposalBeginTime, proposalEndTime, getDeposit, tallyReducer, atoms, getValidatorStatus, coinReducer]
*/

function proposalBeginTime(proposal) {
switch (proposal.proposal_status.toLowerCase()) {
case 'depositperiod':
Expand Down Expand Up @@ -223,7 +224,11 @@ function validatorReducer(networkId, signedBlocksWindow, validator) {
: undefined,
uptimePercentage:
1 -
Number(validator.signing_info.missed_blocks_counter) /
Number(
validator.signing_info
? validator.signing_info.missed_blocks_counter
: 0
) /
Number(signedBlocksWindow),
tokens: atoms(validator.tokens),
commissionUpdateTime: validator.commission.update_time,
Expand Down Expand Up @@ -381,7 +386,7 @@ async function overviewReducer(
) {
stakingDenom = denomLookup(stakingDenom)

const totalRewards = _.flatten(rewards)
const totalRewards = flatten(rewards)
// this filter is here for multidenom networks. If there is the field denoms inside rewards, we filter
// only the staking denom rewards for 'totalRewards'
.filter(reward =>
Expand Down Expand Up @@ -512,89 +517,6 @@ function extractInvolvedAddresses(transaction) {
return involvedAddresses
}

function transactionReducerV2(transaction, reducers, stakingDenom) {
try {
// TODO check if this is anywhere not an array
let fees
if (Array.isArray(transaction.tx.value.fee.amount)) {
fees = transaction.tx.value.fee.amount.map(coinReducer)
} else {
fees = [coinReducer(transaction.tx.value.fee.amount)]
}
// We do display only the transactions we support in Lunie
const filteredMessages = transaction.tx.value.msg.filter(
({ type }) => getMessageType(type) !== 'Unknown'
)
const { claimMessages, otherMessages } = filteredMessages.reduce(
({ claimMessages, otherMessages }, message) => {
// we need to aggregate all withdraws as we display them together in one transaction
if (getMessageType(message.type) === lunieMessageTypes.CLAIM_REWARDS) {
claimMessages.push(message)
} else {
otherMessages.push(message)
}
return { claimMessages, otherMessages }
},
{ claimMessages: [], otherMessages: [] }
)

// we need to aggregate claim rewards messages in one single one to avoid transaction repetition
const claimMessage =
claimMessages.length > 0
? claimRewardsMessagesAggregator(claimMessages)
: undefined
const allMessages = claimMessage
? otherMessages.concat(claimMessage) // add aggregated claim message
: otherMessages
const returnedMessages = allMessages.map(({ value, type }, index) => ({
type: getMessageType(type),
hash: transaction.txhash,
key: `${transaction.txhash}_${index}`,
height: transaction.height,
details: transactionDetailsReducer(
getMessageType(type),
value,
reducers,
transaction,
stakingDenom
),
timestamp: transaction.timestamp,
memo: transaction.tx.value.memo,
fees,
success:
transaction.logs && transaction.logs[index]
? transaction.logs[index].success || false
: false,
log:
transaction.logs && transaction.logs[index]
? transaction.logs[index].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
} catch (error) {
Sentry.withScope(function(scope) {
scope.setExtra('transaction', transaction)
Sentry.captureException(error)
})
return [] // must return something differ from undefined
}
}

function transactionsReducerV2(txs, reducers, stakingDenom) {
const duplicateFreeTxs = uniqWith(txs, (a, b) => a.txhash === b.txhash)
const sortedTxs = sortBy(duplicateFreeTxs, ['timestamp'])
const reversedTxs = reverse(sortedTxs)
// here we filter out all transactions related to validators
return reversedTxs.reduce((collection, transaction) => {
return collection.concat(
transactionReducerV2(transaction, reducers, stakingDenom)
)
}, [])
}

// to be able to catch all validators from a multi-claim reward tx, we need to capture
// more than just the first value message.
function txMultiClaimRewardReducer(txMessages) {
Expand Down Expand Up @@ -651,177 +573,6 @@ function transactionReducer(transaction, reducers) {
}
}

// map Cosmos SDK message types to Lunie message types
function getMessageType(type) {
// different networks use different prefixes for the transaction types like cosmos/MsgSend vs core/MsgSend in Terra
const transactionTypeSuffix = type.split('/')[1]
switch (transactionTypeSuffix) {
case 'MsgSend':
return lunieMessageTypes.SEND
case 'MsgDelegate':
return lunieMessageTypes.STAKE
case 'MsgBeginRedelegate':
return lunieMessageTypes.RESTAKE
case 'MsgUndelegate':
return lunieMessageTypes.UNSTAKE
case 'MsgWithdrawDelegationReward':
return lunieMessageTypes.CLAIM_REWARDS
case 'MsgSubmitProposal':
return lunieMessageTypes.SUBMIT_PROPOSAL
case 'MsgVote':
return lunieMessageTypes.VOTE
case 'MsgDeposit':
return lunieMessageTypes.DEPOSIT
default:
return lunieMessageTypes.UNKNOWN
}
}

// function to map cosmos messages to our details format
function transactionDetailsReducer(
type,
message,
reducers,
transaction,
stakingDenom
) {
let details
switch (type) {
case lunieMessageTypes.SEND:
details = sendDetailsReducer(message, reducers)
break
case lunieMessageTypes.STAKE:
details = stakeDetailsReducer(message, reducers)
break
case lunieMessageTypes.RESTAKE:
details = restakeDetailsReducer(message, reducers)
break
case lunieMessageTypes.UNSTAKE:
details = unstakeDetailsReducer(message, reducers)
break
case lunieMessageTypes.CLAIM_REWARDS:
details = claimRewardsDetailsReducer(
message,
reducers,
transaction,
stakingDenom
)
break
case lunieMessageTypes.SUBMIT_PROPOSAL:
details = submitProposalDetailsReducer(message, reducers)
break
case lunieMessageTypes.VOTE:
details = voteProposalDetailsReducer(message, reducers)
break
case lunieMessageTypes.DEPOSIT:
details = depositDetailsReducer(message, reducers)
break
default:
details = {}
}

return {
type,
...details
}
}

function claimRewardsMessagesAggregator(claimMessages) {
// reduce all withdraw messages to one one collecting the validators from all the messages
const onlyValidatorsAddressesArray = claimMessages.map(
msg => msg.value.validator_address
)
return {
type: `type/MsgWithdrawDelegationReward`,
value: {
validators: onlyValidatorsAddressesArray
}
}
}

function sendDetailsReducer(message, reducers) {
return {
from: [message.from_address],
to: [message.to_address],
amount: reducers.coinReducer(message.amount[0])
}
}

function stakeDetailsReducer(message, reducers) {
return {
to: [message.validator_address],
amount: reducers.coinReducer(message.amount)
}
}

function restakeDetailsReducer(message, reducers) {
return {
from: [message.validator_src_address],
to: [message.validator_dst_address],
amount: reducers.coinReducer(message.amount)
}
}

function unstakeDetailsReducer(message, reducers) {
return {
from: [message.validator_address],
amount: reducers.coinReducer(message.amount)
}
}

function claimRewardsDetailsReducer(
message,
reducers,
transaction,
stakingDenom
) {
return {
from: message.validators,
amounts: claimRewardsAmountReducer(transaction, reducers, stakingDenom)
}
}

function claimRewardsAmountReducer(transaction, reducers, stakingDenom) {
if (!transaction.success) {
return [
{
amoun: 0,
denom: stakingDenom
}
]
}
return reducers.rewardCoinReducer(
transaction.events
.find(event => event.type === `transfer`)
.attributes.find(attribute => attribute.key === `amount`).value,
stakingDenom
)
}

function submitProposalDetailsReducer(message, reducers) {
return {
proposalType: message.content.type,
proposalTitle: message.content.value.title,
proposalDescription: message.content.value.description,
initialDeposit: reducers.coinReducer(message.initial_deposit[0])
}
}

function voteProposalDetailsReducer(message) {
return {
proposalId: message.proposal_id,
voteOption: message.option
}
}

// TO TEST!
function depositDetailsReducer(message, reducers) {
return {
proposalId: message.proposal_id,
amount: reducers.coinReducer(message.amount)
}
}

module.exports = {
proposalReducer,
governanceParameterReducer,
Expand All @@ -841,8 +592,6 @@ module.exports = {
calculateTokens,
undelegationEndTimeReducer,
formatTransactionsReducer,
transactionsReducerV2,
transactionReducerV2,

atoms,
proposalBeginTime,
Expand Down
Loading

0 comments on commit 14d3c3b

Please sign in to comment.