From fbc5c7126edc2f9a48ead980a5d7d731d68d7576 Mon Sep 17 00:00:00 2001 From: Mario Pino Date: Wed, 26 Feb 2020 22:25:00 +0100 Subject: [PATCH] WIP: Improve TransactionV2 implementation (#349) * Add from field to SendTx type * add amount to claim rewards transactions * convert to units in string coin reducer * fix typo * add multidenom string coinreducer for claim amount * Add UnknownTx to schema * Add BlockV2 type which returns TransactionV2 txs * Add blockV2 query that returns TransactionV2 txs * kill handling claim rewards amount * Update lib/reducers/cosmosV0-reducers.js Co-Authored-By: Fabian * Update lib/reducers/cosmosV0-reducers.js Co-Authored-By: Fabian * Cleanup * lint * Support claim rewards from multiple validators * Fix, cleanup Co-authored-by: Ana G. <40721795+Bitcoinera@users.noreply.github.com> Co-authored-by: Fabian --- lib/message-types.js | 2 +- lib/reducers/cosmosV0-reducers.js | 36 +++++++++++++++++++++---------- lib/resolvers.js | 5 +++++ lib/schema.js | 20 ++++++++++++++++- lib/source/cosmosV0-source.js | 33 ++++++++++++++++++++++++++-- lib/source/cosmosV2-source.js | 3 ++- 6 files changed, 83 insertions(+), 16 deletions(-) diff --git a/lib/message-types.js b/lib/message-types.js index 0265445da1..05f16e52e7 100644 --- a/lib/message-types.js +++ b/lib/message-types.js @@ -34,7 +34,7 @@ const lunieMessageTypes = { UNSTAKE: `UnstakeTx`, VOTE: `VoteTx`, DEPOSIT: `DepositTx`, - WITHDRAW_DELEGATION_REWARDS: `ClaimRewardsTx`, + CLAIM_REWARDS: `ClaimRewardsTx`, SUBMIT_PROPOSAL: `SubmitProposalTx`, UNKNOWN: `UnknownTx` } diff --git a/lib/reducers/cosmosV0-reducers.js b/lib/reducers/cosmosV0-reducers.js index bacb3f0617..738c62b799 100644 --- a/lib/reducers/cosmosV0-reducers.js +++ b/lib/reducers/cosmosV0-reducers.js @@ -435,7 +435,7 @@ function formatTransactionsReducer(txs, reducers) { return reversedTxs.map(tx => transactionReducer(tx, reducers)) } -function transactionReducerV2(transaction, reducers) { +function transactionReducerV2(transaction, reducers, stakingDenom) { // TODO check if this is anywhere not an array let fees if (Array.isArray(transaction.tx.value.fee.amount)) { @@ -451,7 +451,13 @@ function transactionReducerV2(transaction, reducers) { type: getMessageType(type), hash: transaction.txhash, height: transaction.height, - details: transactionDetailsReducer(getMessageType(type), value, reducers), + details: transactionDetailsReducer( + getMessageType(type), + value, + reducers, + transaction, + stakingDenom + ), timestamp: transaction.timestamp, memo: transaction.tx.value.memo, fees, @@ -460,13 +466,15 @@ function transactionReducerV2(transaction, reducers) { return returnedMessages } -function transactionsReducerV2(txs, reducers) { +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)) + return collection.concat( + transactionReducerV2(transaction, reducers, stakingDenom) + ) }, []) } @@ -537,7 +545,7 @@ function getMessageType(type) { case 'MsgUndelegate': return lunieMessageTypes.UNSTAKE case 'MsgWithdrawDelegationReward': - return lunieMessageTypes.WITHDRAW_DELEGATION_REWARDS + return lunieMessageTypes.CLAIM_REWARDS case 'MsgSubmitProposal': return lunieMessageTypes.SUBMIT_PROPOSAL case 'MsgVote': @@ -550,7 +558,7 @@ function getMessageType(type) { } // function to map cosmos messages to our details format -function transactionDetailsReducer(type, message, reducers) { +function transactionDetailsReducer(type, message, reducers, transaction) { let details switch (type) { case lunieMessageTypes.SEND: @@ -565,8 +573,8 @@ function transactionDetailsReducer(type, message, reducers) { case lunieMessageTypes.UNSTAKE: details = unstakeDetailsReducer(message, reducers) break - case lunieMessageTypes.WITHDRAW_DELEGATION_REWARDS: - details = claimRewardsDetailsReducer(message, reducers) + case lunieMessageTypes.CLAIM_REWARDS: + details = claimRewardsDetailsReducer(transaction.tx.value.msg) break case lunieMessageTypes.SUBMIT_PROPOSAL: details = submitProposalDetailsReducer(message, reducers) @@ -589,6 +597,7 @@ function transactionDetailsReducer(type, message, reducers) { function sendDetailsReducer(message, reducers) { return { + from: [message.from_address], to: [message.to_address], amount: reducers.coinReducer(message.amount[0]) } @@ -616,10 +625,15 @@ function unstakeDetailsReducer(message, reducers) { } } -function claimRewardsDetailsReducer(message, reducers) { +function claimRewardsDetailsReducer(messages) { return { - from: [message.validator_address], - amount: reducers.coinReducer(message.amount) + from: messages + .filter(msg => msg.type.split(`/`)[1] === `MsgWithdrawDelegationReward`) + .map(msg => msg.value.validator_address), + amount: { + amount: 0, + denom: `` + } } } diff --git a/lib/resolvers.js b/lib/resolvers.js index 512e05eaec..e79b3a37f0 100644 --- a/lib/resolvers.js +++ b/lib/resolvers.js @@ -221,6 +221,11 @@ const resolvers = { cacheControl.setCacheHint({ maxAge }) return remoteFetch(dataSources, networkId).getBlockByHeight(height) }, + blockV2: (_, { networkId, height }, { dataSources }, { cacheControl }) => { + const maxAge = height ? 60 : 10 + cacheControl.setCacheHint({ maxAge }) + return remoteFetch(dataSources, networkId).getBlockByHeightV2(height) + }, network: (_, { id }) => { const network = networkMap[id] if (network.id === 'local-cosmos-hub-testnet') { diff --git a/lib/schema.js b/lib/schema.js index 35e52040e9..b4e656b2c1 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -85,6 +85,16 @@ const typeDefs = gql` proposer_address: String } + type BlockV2 @cacheControl(maxAge: 10) { + networkId: String! + height: Int + hash: String + chainId: String + time: String + transactions: [TransactionV2] + proposer_address: String + } + type Maintenance { id: Int! message: String @@ -166,12 +176,13 @@ const typeDefs = gql` | SubmitProposalTx | VoteTx | DepositTx + | UnknownTx type TransactionV2 { type: String! hash: String! height: Int! - details: TransactionDetails + details: TransactionDetails! timestamp: String! memo: String fees: [Coin]! @@ -180,6 +191,7 @@ const typeDefs = gql` type SendTx { amount: Coin! + from: [String]! to: [String]! } @@ -197,6 +209,7 @@ const typeDefs = gql` type UnstakeTx { amount: Coin! from: [String]! + liquidDate: String } type ClaimRewardsTx { @@ -221,6 +234,10 @@ const typeDefs = gql` amount: Coin! } + type UnknownTx { + blockExplorerLink: String + } + type GovernanceParameters { depositDenom: String votingThreshold: Float @@ -270,6 +287,7 @@ const typeDefs = gql` type Query { block(networkId: String!, height: Int): Block + blockV2(networkId: String!, height: Int): BlockV2 proposal(networkId: String!, id: Int!): Proposal proposals(networkId: String!): [Proposal] validators( diff --git a/lib/source/cosmosV0-source.js b/lib/source/cosmosV0-source.js index 2d9f0f8514..eb248f2a9c 100644 --- a/lib/source/cosmosV0-source.js +++ b/lib/source/cosmosV0-source.js @@ -81,7 +81,7 @@ class CosmosV0API extends RESTDataSource { return this.getRetry(url) } - async getStakinDenom() { + async getStakingDenom() { const stakingParameters = await this.query('/staking/parameters') return stakingParameters.bond_denom } @@ -125,6 +125,17 @@ class CosmosV0API extends RESTDataSource { : [] } + async getTransactionsV2ByHeight(height) { + const txs = await this.loadPaginatedTxs(`txs?tx.height=${height}`) + return Array.isArray(txs) + ? this.reducers.transactionsReducerV2( + txs, + this.reducers, + this.getStakingDenom() + ) + : [] + } + async getValidatorSigningInfos(validators) { const signingInfos = await Promise.all( validators.map(({ consensus_pubkey }) => @@ -337,6 +348,24 @@ class CosmosV0API extends RESTDataSource { return this.reducers.blockReducer(this.networkId, block, transactions) } + async getBlockByHeightV2(blockHeight) { + let block, transactions + if (blockHeight) { + const response = await Promise.all([ + this.getRetry(`blocks/${blockHeight}`), + this.getTransactionsV2ByHeight(blockHeight) + ]) + block = response[0] + transactions = response[1] + } else { + block = await this.getRetry(`blocks/latest`) + transactions = await this.getTransactionsV2ByHeight( + block.block_meta.header.height + ) + } + return this.reducers.blockReducer(this.networkId, block, transactions) + } + async getBalancesFromAddress(address) { this.checkAddress(address) const response = await this.query(`bank/balances/${address}`) @@ -476,7 +505,7 @@ class CosmosV0API extends RESTDataSource { delegatorAddress, validatorsDictionary ), - this.getStakinDenom() + this.getStakingDenom() ]) const rewards = await this.getRewards( delegatorAddress, diff --git a/lib/source/cosmosV2-source.js b/lib/source/cosmosV2-source.js index 958b9231cf..4c355a05bf 100644 --- a/lib/source/cosmosV2-source.js +++ b/lib/source/cosmosV2-source.js @@ -79,7 +79,8 @@ class CosmosV2API extends CosmosV0API { this.loadPaginatedTxs(`/txs?transfer.recipient=${address}`) ]).then(([senderTxs, recipientTxs]) => [].concat(senderTxs, recipientTxs)) - return this.reducers.transactionsReducerV2(txs, this.reducers) + const stakingDenom = await this.getStakingDenom() + return this.reducers.transactionsReducerV2(txs, this.reducers, stakingDenom) } extractInvolvedAddresses(transaction) {