diff --git a/PENDING.md b/PENDING.md index aa40903b52..2975d18c07 100644 --- a/PENDING.md +++ b/PENDING.md @@ -1,3 +1,8 @@ ### Added -- [\#2149](https://github.com/cosmos/voyager/issues/2149) display multi-message transactions @fedekunze \ No newline at end of file +- [\#2149](https://github.com/cosmos/voyager/issues/2149) display multi-message transactions @fedekunze + +### Fixed + +- [\#2330](https://github.com/cosmos/voyager/pull/2330) Fixed rewards not updating as expected @faboweb +- [\#2330](https://github.com/cosmos/voyager/pull/2330) Fixed transactions not loading when refreshing on PageTransactions @faboweb \ No newline at end of file diff --git a/app/src/renderer/components/common/TmBalance.vue b/app/src/renderer/components/common/TmBalance.vue index 07613afedb..1ee16bf3ea 100644 --- a/app/src/renderer/components/common/TmBalance.vue +++ b/app/src/renderer/components/common/TmBalance.vue @@ -45,7 +45,8 @@ export default { }, data() { return { - num + num, + lastUpdate: 0 } }, computed: { @@ -85,8 +86,14 @@ export default { lastHeader: { immediate: true, handler(newHeader) { - const waitTenBlocks = Number(newHeader.height) % 10 === 0 - if (this.session.signedIn && waitTenBlocks) { + const height = Number(newHeader.height) + // run the update queries the first time and after every 10 blocks + const waitedTenBlocks = height - this.lastUpdate >= 10 + if ( + this.session.signedIn && + (this.lastUpdate === 0 || waitedTenBlocks) + ) { + this.lastUpdate = height this.$store.dispatch(`getTotalRewards`) this.$store.dispatch(`queryWalletBalances`) } diff --git a/app/src/renderer/components/staking/TabMyDelegations.vue b/app/src/renderer/components/staking/TabMyDelegations.vue index ee3078bda4..618f4f5c4c 100644 --- a/app/src/renderer/components/staking/TabMyDelegations.vue +++ b/app/src/renderer/components/staking/TabMyDelegations.vue @@ -73,7 +73,8 @@ export default { unbondTransactions: `Transactions currently in the undelegation period`, validatorURL: `/staking/validators`, time, - num + num, + lastUpdate: 0 }), computed: { ...mapGetters([ @@ -130,18 +131,8 @@ export default { }, lastHeader: { immediate: true, - handler(newHeader) { - const waitTwentyBlocks = Number(newHeader.height) % 20 === 0 - if ( - waitTwentyBlocks && - this.yourValidators && - this.yourValidators.length > 0 - ) { - this.$store.dispatch( - `getRewardsFromAllValidators`, - this.yourValidators - ) - } + handler() { + this.$store.dispatch(`getRewardsFromMyValidators`) } } }, diff --git a/app/src/renderer/components/staking/TabValidators.vue b/app/src/renderer/components/staking/TabValidators.vue index 5df836c5d3..e268a8da1b 100644 --- a/app/src/renderer/components/staking/TabValidators.vue +++ b/app/src/renderer/components/staking/TabValidators.vue @@ -24,28 +24,18 @@ export default { TmDataLoading, TmDataConnecting }, + data: () => ({ + lastUpdate: 0 + }), computed: { ...mapGetters([ `lastHeader`, `delegates`, `committedDelegations`, `connected`, - `session` - ]), - yourValidators( - { - committedDelegations, - delegates: { delegates }, - session: { signedIn } - } = this - ) { - return ( - signedIn && - delegates.filter( - ({ operator_address }) => operator_address in committedDelegations - ) - ) - } + `session`, + `yourValidators` + ]) }, watch: { "session.signedIn": function(signedIn) { @@ -53,25 +43,15 @@ export default { }, lastHeader: { immediate: true, - handler(newHeader) { - const waitTwentyBlocks = Number(newHeader.height) % 20 === 0 - if ( - waitTwentyBlocks && - this.yourValidators && - this.yourValidators.length > 0 - ) { - this.$store.dispatch( - `getRewardsFromAllValidators`, - this.yourValidators - ) - } + handler() { + this.$store.dispatch(`getRewardsFromMyValidators`) } } }, mounted() { this.$store.dispatch(`updateDelegates`) if (this.yourValidators) { - this.$store.dispatch(`getRewardsFromAllValidators`, this.yourValidators) + this.$store.dispatch(`getRewardsFromMyValidators`, this.yourValidators) } } } diff --git a/app/src/renderer/components/wallet/PageTransactions.vue b/app/src/renderer/components/wallet/PageTransactions.vue index 6ab7fb89c9..f9616f6a4e 100644 --- a/app/src/renderer/components/wallet/PageTransactions.vue +++ b/app/src/renderer/components/wallet/PageTransactions.vue @@ -78,8 +78,13 @@ export default { return this.orderedTransactions.length === 0 } }, - mounted() { - this.refreshTransactions() + watch: { + "session.signedIn": { + immediate: true, + handler() { + this.refreshTransactions() + } + } }, methods: { async refreshTransactions({ $store, session } = this) { diff --git a/app/src/renderer/vuex/getters.js b/app/src/renderer/vuex/getters.js index 7ee5007c4a..727e41edba 100644 --- a/app/src/renderer/vuex/getters.js +++ b/app/src/renderer/vuex/getters.js @@ -26,6 +26,11 @@ export const wallet = state => state.wallet // fee distribution export const distribution = state => state.distribution +export const yourValidators = (state, getters) => + state.session.signedIn ? + getters.delegates.delegates.filter( + ({ operator_address }) => operator_address in getters.committedDelegations + ) : [] // staking export const liquidAtoms = state => @@ -101,4 +106,4 @@ export const nodeUrl = state => state.connection.connected ? state.connection.nodeUrl : undefined export const blocks = state => (state.blocks ? state.blocks.blocks : []) -export const block = state => (state.blocks ? state.blocks.block : []) \ No newline at end of file +export const block = state => (state.blocks ? state.blocks.block : []) diff --git a/app/src/renderer/vuex/modules/delegates.js b/app/src/renderer/vuex/modules/delegates.js index be5dc7222b..d001bc510a 100644 --- a/app/src/renderer/vuex/modules/delegates.js +++ b/app/src/renderer/vuex/modules/delegates.js @@ -11,7 +11,8 @@ export default ({ node }) => { globalPower: null, loading: false, loaded: false, - error: null + error: null, + lastValidatorsUpdate: 0 } const state = JSON.parse(JSON.stringify(emptyState)) @@ -58,16 +59,28 @@ export default ({ node }) => { resetSessionData({ rootState }) { rootState.delegates = JSON.parse(JSON.stringify(emptyState)) }, - async updateSigningInfo({ commit }, validators) { - for (const validator of validators) { - if (validator.consensus_pubkey) { - const signing_info = await node.getValidatorSigningInfo( - validator.consensus_pubkey - ) - if (!isEmpty(signing_info)) validator.signing_info = signing_info + async updateSigningInfo( + { + commit, + getters: { lastHeader } + }, + validators + ) { + // throttle the update for validators for every 10 blocks + const waited10Blocks = + Number(lastHeader.height) - state.lastDelegatesUpdate >= 10 + if (state.lastValidatorsUpdate === 0 || waited10Blocks) { + state.lastValidatorsUpdate = Number(lastHeader.height) + for (const validator of validators) { + if (validator.consensus_pubkey) { + const signing_info = await node.getValidatorSigningInfo( + validator.consensus_pubkey + ) + if (!isEmpty(signing_info)) validator.signing_info = signing_info + } } + commit(`setDelegates`, validators) } - commit(`setDelegates`, validators) }, async getDelegates({ state, commit, dispatch, rootState }) { commit(`setDelegateLoading`, true) diff --git a/app/src/renderer/vuex/modules/distribution.js b/app/src/renderer/vuex/modules/distribution.js index 4d08bb7fa2..41cf0776c6 100644 --- a/app/src/renderer/vuex/modules/distribution.js +++ b/app/src/renderer/vuex/modules/distribution.js @@ -7,6 +7,7 @@ export default ({ node }) => { loading: false, loaded: false, error: null, + lastValidatorRewardsUpdate: 0, // keep track of last update so we can throttle the interval /* totalRewards use the following format: { denom1: amount1, @@ -69,9 +70,7 @@ export default ({ node }) => { resetSessionData({ rootState }) { rootState.distribution = JSON.parse(JSON.stringify(emptyState)) }, - async getTotalRewards( - { state, rootState: { session }, commit } - ) { + async getTotalRewards({ state, rootState: { session }, commit }) { state.loading = true try { const rewardsArray = await node.getDelegatorRewards(session.address) @@ -86,7 +85,10 @@ export default ({ node }) => { state.loading = false }, async withdrawAllRewards( - { rootState: { wallet }, dispatch }, + { + rootState: { wallet }, + dispatch + }, { password, submitType } ) { await dispatch(`sendTx`, { @@ -99,16 +101,38 @@ export default ({ node }) => { await dispatch(`queryWalletBalances`) await dispatch(`getAllTxs`) }, - async getRewardsFromAllValidators({ state, dispatch }, validators) { - state.loading = true - await Promise.all(validators.map(validator => - dispatch(`getRewardsFromValidator`, validator.operator_address) - )) - state.loading = false - state.loaded = true + async getRewardsFromMyValidators( + { + state, + dispatch, + getters: { lastHeader, yourValidators } + } + ) { + // throttle the update of validator rewards to every 20 blocks + const waitedTwentyBlocks = + Number(lastHeader.height) - state.lastValidatorRewardsUpdate >= 20 + if ( + (state.lastValidatorRewardsUpdate === 0 || waitedTwentyBlocks) && + yourValidators && + yourValidators.length > 0 + ) { + state.lastValidatorRewardsUpdate = Number(lastHeader.height) + state.loading = true + await Promise.all( + yourValidators.map(validator => + dispatch(`getRewardsFromValidator`, validator.operator_address) + ) + ) + state.loading = false + state.loaded = true + } }, async getRewardsFromValidator( - { state, rootState: { session }, commit }, + { + state, + rootState: { session }, + commit + }, validatorAddr ) { state.loading = true diff --git a/test/unit/specs/components/common/TmBalance.spec.js b/test/unit/specs/components/common/TmBalance.spec.js index e73c1ab433..5a9b55284c 100644 --- a/test/unit/specs/components/common/TmBalance.spec.js +++ b/test/unit/specs/components/common/TmBalance.spec.js @@ -100,12 +100,27 @@ describe(`TmBalance`, () => { }) describe(`should update balance and rewards `, () => { - it(`if user is signed in and wait for 10 blocks`, () => { + it(`if user is signed in initially`, () => { const $store = { dispatch: jest.fn() } const session = { signedIn: true } const newHeader = { height: `10` } TmBalance.watch.lastHeader.handler.call( - { session, $store }, + { session, $store, lastUpdate: 0 }, + newHeader) + expect($store.dispatch).toHaveBeenCalledWith(`getTotalRewards`) + expect($store.dispatch).toHaveBeenCalledWith(`queryWalletBalances`) + }) + + it(`if user is signed in and wait for 10 blocks`, () => { + const $store = { dispatch: jest.fn() } + const session = { signedIn: true } + const newHeader = { height: `20` } + TmBalance.watch.lastHeader.handler.call( + { session, $store, lastUpdate: 15 }, + newHeader) + expect($store.dispatch).not.toHaveBeenCalled() + TmBalance.watch.lastHeader.handler.call( + { session, $store, lastUpdate: 5 }, newHeader) expect($store.dispatch).toHaveBeenCalledWith(`getTotalRewards`) expect($store.dispatch).toHaveBeenCalledWith(`queryWalletBalances`) diff --git a/test/unit/specs/components/staking/TabMyDelegations.spec.js b/test/unit/specs/components/staking/TabMyDelegations.spec.js index 0a8b35c4e0..5a7787c265 100644 --- a/test/unit/specs/components/staking/TabMyDelegations.spec.js +++ b/test/unit/specs/components/staking/TabMyDelegations.spec.js @@ -168,7 +168,7 @@ describe(`Component: TabMyDelegations`, () => { { $store, yourValidators }, newHeader) expect($store.dispatch).not.toHaveBeenCalledWith( - `getRewardsFromAllValidators`, + `getRewardsFromMyValidators`, yourValidators ) }) @@ -181,25 +181,10 @@ describe(`Component: TabMyDelegations`, () => { { $store, yourValidators }, newHeader) expect($store.dispatch).not.toHaveBeenCalledWith( - `getRewardsFromAllValidators`, + `getRewardsFromMyValidators`, yourValidators ) }) - - describe(`should update rewards `, () => { - it(`if has waited for 20 blocks and has delegations`, () => { - const $store = { dispatch: jest.fn() } - const yourValidators = [{}] - const newHeader = { height: `40` } - TabMyDelegations.watch.lastHeader.handler.call( - { $store, yourValidators }, - newHeader) - expect($store.dispatch).toHaveBeenCalledWith( - `getRewardsFromAllValidators`, - yourValidators - ) - }) - }) }) }) }) diff --git a/test/unit/specs/components/staking/TabValidators.spec.js b/test/unit/specs/components/staking/TabValidators.spec.js index 7cc6fa6849..a644a310b6 100644 --- a/test/unit/specs/components/staking/TabValidators.spec.js +++ b/test/unit/specs/components/staking/TabValidators.spec.js @@ -18,7 +18,8 @@ describe(`TabValidators`, () => { signedIn: true }, connected: true, - lastHeader: { height: 20 } + lastHeader: { height: 20 }, + yourValidators: validators } beforeEach(async () => { @@ -54,7 +55,8 @@ describe(`TabValidators`, () => { signedIn: true }, connected: false, - lastHeader: { height: 20 } + lastHeader: { height: 20 }, + yourValidators: validators } } @@ -83,7 +85,8 @@ describe(`TabValidators`, () => { signedIn: true }, connected: true, - lastHeader: { height: 20 } + lastHeader: { height: 20 }, + yourValidators: validators } } @@ -112,7 +115,8 @@ describe(`TabValidators`, () => { signedIn: true }, connected: true, - lastHeader: { height: 20 } + lastHeader: { height: 20 }, + yourValidators: validators } } @@ -145,76 +149,14 @@ describe(`TabValidators`, () => { expect(dispatch).toHaveBeenCalledWith(`updateDelegates`) }) - describe(`yourValidators`, () => { - it(`should return validators if signed in`, () => { - expect( - TabValidators.computed.yourValidators({ - committedDelegations: { - [validators[0].operator_address]: 1, - [validators[2].operator_address]: 2 - }, - delegates: { delegates: validators }, - session: { signedIn: true } - }) - ).toEqual([validators[0], validators[2]]) - }) - - it(`should return false if not signed in`, () => { - expect( - TabValidators.computed.yourValidators({ - committedDelegations: { - [validators[0].operator_address]: 1, - [validators[2].operator_address]: 2 - }, - delegates: { delegates: validators }, - session: { signedIn: false } - }) - ).toBe(false) - }) - }) - - describe(`update rewards on new blocks`, () => { - describe(`shouldn't update`, () => { - it(`if hasn't waited for 20 blocks `, () => { - const $store = { dispatch: jest.fn() } - const yourValidators = [{}] - const newHeader = { height: `30` } - TabValidators.watch.lastHeader.handler.call( - { $store, yourValidators }, - newHeader) - expect($store.dispatch).not.toHaveBeenCalledWith( - `getRewardsFromAllValidators`, - yourValidators - ) - }) - - it(`if user doesn't have any delegations `, () => { - const $store = { dispatch: jest.fn() } - const yourValidators = [] - const newHeader = { height: `40` } - TabValidators.watch.lastHeader.handler.call( - { $store, yourValidators }, - newHeader) - expect($store.dispatch).not.toHaveBeenCalledWith( - `getRewardsFromAllValidators`, - yourValidators - ) - }) - - describe(`should update rewards `, () => { - it(`if has waited for 20 blocks and has delegations`, () => { - const $store = { dispatch: jest.fn() } - const yourValidators = [{}] - const newHeader = { height: `40` } - TabValidators.watch.lastHeader.handler.call( - { $store, yourValidators }, - newHeader) - expect($store.dispatch).toHaveBeenCalledWith( - `getRewardsFromAllValidators`, - yourValidators - ) - }) - }) - }) + it(`should trigger reward updates on every block `, () => { + const $store = { dispatch: jest.fn() } + const newHeader = { height: `40` } + TabValidators.watch.lastHeader.handler.call( + { $store }, + newHeader) + expect($store.dispatch).toHaveBeenCalledWith( + `getRewardsFromMyValidators` + ) }) }) diff --git a/test/unit/specs/components/wallet/PageTransactions.spec.js b/test/unit/specs/components/wallet/PageTransactions.spec.js index 5d95db0e6a..d7a31efefa 100644 --- a/test/unit/specs/components/wallet/PageTransactions.spec.js +++ b/test/unit/specs/components/wallet/PageTransactions.spec.js @@ -302,6 +302,14 @@ describe(`PageTransactions`, () => { expect($store.dispatch).not.toHaveBeenCalledWith(`getAllTxs`) }) + it(`should load transactions when signing in`, () => { + const refreshTransactions = jest.fn() + PageTransactions.watch[`session.signedIn`].handler.call({ + refreshTransactions + }) + expect(refreshTransactions).toHaveBeenCalled() + }) + it(`should show transactions`, async () => { expect(wrapper.findAll(`li-any-transaction-stub`).length).toBe(6) }) diff --git a/test/unit/specs/store/delegates.spec.js b/test/unit/specs/store/delegates.spec.js index 4192c0437e..6165d7caaa 100644 --- a/test/unit/specs/store/delegates.spec.js +++ b/test/unit/specs/store/delegates.spec.js @@ -138,7 +138,6 @@ describe(`Module: Delegates`, () => { it(`fetches the signing information from all delegates`, async () => { const { actions, mutations, state } = instance const commit = jest.fn() - const dispatch = jest.fn() mutations.setDelegates(state, [ { operator_address: `foo`, @@ -150,7 +149,7 @@ describe(`Module: Delegates`, () => { expect.objectContaining({ signing_info: expect.anything() }) ) await actions.updateSigningInfo( - { state, commit, dispatch }, + { state, commit, getters: { lastHeader: { height: `43` } } }, state.delegates ) @@ -159,6 +158,43 @@ describe(`Module: Delegates`, () => { ) }) + it(`throttles validator fetching to every 20 blocks`, async () => { + node = Object.assign({}, nodeMock, { + getValidatorSigningInfo: jest.fn() + }) + instance = delegatesModule({ node }) + const { actions, state } = instance + const commit = jest.fn() + state.lastValidatorsUpdate = 0 + await actions.updateSigningInfo( + { + state, commit, getters: { lastHeader: { height: `43` } } + }, + [ + { + operator_address: `foo`, + consensus_pubkey: `bar`, + tokens: `10` + } + ] + ) + expect(state.lastValidatorsUpdate).toBe(43) + node.getValidatorSigningInfo.mockClear() + await actions.updateSigningInfo( + { + state, commit, getters: { lastHeader: { height: `44` } } + }, + [ + { + operator_address: `foo`, + consensus_pubkey: `bar`, + tokens: `10` + } + ] + ) + expect(node.getValidatorSigningInfo).not.toHaveBeenCalled() + }) + it(`should query for delegates on reconnection if was loading before`, async () => { const { actions } = delegatesModule({}) const instance = { diff --git a/test/unit/specs/store/distribution.spec.js b/test/unit/specs/store/distribution.spec.js index 81c90a9b62..ad8e8b6e7f 100644 --- a/test/unit/specs/store/distribution.spec.js +++ b/test/unit/specs/store/distribution.spec.js @@ -139,15 +139,14 @@ describe(`Module: Fee Distribution`, () => { }) }) - describe(`getRewardsFromAllValidators`, () => { + describe(`getRewardsFromMyValidators`, () => { it(`success`, async () => { const validators = [ { operator_address: `cosmosvaloper1address1` }, { operator_address: `cosmosvaloper1address2` }, ] - await actions.getRewardsFromAllValidators( - { state, dispatch }, - validators + await actions.getRewardsFromMyValidators( + { state, dispatch, getters: { lastHeader: { height: `44` }, yourValidators: validators } } ) expect(dispatch).toBeCalledTimes(2) expect(dispatch).toBeCalledWith( @@ -166,12 +165,28 @@ describe(`Module: Fee Distribution`, () => { { operator_address: `cosmosvaloper1address2` }, ] dispatch = jest.fn(async () => Promise.reject(Error(`invalid address`))) - await expect(actions.getRewardsFromAllValidators( - { state, dispatch }, - validators) + await expect(actions.getRewardsFromMyValidators( + { state, dispatch, getters: { lastHeader: { height: `44` }, yourValidators: validators } } + ) ).rejects.toThrowError(`invalid address`) }) + it(`throttle to every 20 blocks`, async () => { + const validators = [ + { operator_address: `cosmosvaloper1address1` }, + { operator_address: `cosmosvaloper1address2` }, + ] + state.lastValidatorRewardsUpdate = 0 + await actions.getRewardsFromMyValidators( + { state, dispatch, getters: { lastHeader: { height: `43` }, yourValidators: validators } } + ) + expect(state.lastValidatorRewardsUpdate).toBe(43) + dispatch.mockClear() + await actions.getRewardsFromMyValidators( + { state, dispatch, getters: { lastHeader: { height: `44` }, yourValidators: validators } } + ) + expect(dispatch).not.toHaveBeenCalled() + }) }) describe(`getRewardsFromValidator`, () => { diff --git a/test/unit/specs/store/getters.spec.js b/test/unit/specs/store/getters.spec.js index 68f5cf7793..fb4a96ec2e 100644 --- a/test/unit/specs/store/getters.spec.js +++ b/test/unit/specs/store/getters.spec.js @@ -1,13 +1,16 @@ -import { oldBondedAtoms, liquidAtoms, totalAtoms, oldUnbondingAtoms } from "renderer/vuex/getters.js" +import { oldBondedAtoms, liquidAtoms, totalAtoms, oldUnbondingAtoms, yourValidators } from "renderer/vuex/getters.js" +import validators from "./json/validators.js" describe(`Store: getters`, () => { it(`liquidAtoms`, () => { const result = liquidAtoms({ stakingParameters: { parameters: { bond_denom: `stake` } }, - wallet: { balances: [{ - denom: `stake`, - amount: 42 - }] } + wallet: { + balances: [{ + denom: `stake`, + amount: 42 + }] + } }) expect(result).toBe(42) @@ -59,9 +62,40 @@ describe(`Store: getters`, () => { }, { balance: `12` }] - } } + } + } }) expect(result.toNumber()).toBe(63) }) + + describe(`yourValidators`, () => { + it(`should return validators if signed in`, () => { + expect( + yourValidators({ + session: { signedIn: true } + }, { + committedDelegations: { + [validators[0].operator_address]: 1, + [validators[2].operator_address]: 2 + }, + delegates: { delegates: validators } + }) + ).toEqual([validators[0], validators[2]]) + }) + + it(`should return false if not signed in`, () => { + expect( + yourValidators({ + session: { signedIn: false } + }, { + committedDelegations: { + [validators[0].operator_address]: 1, + [validators[2].operator_address]: 2 + }, + delegates: { delegates: validators } + }) + ).toEqual([]) + }) + }) }) \ No newline at end of file