Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Billy/454 tx info #770

Merged
merged 25 commits into from
Jun 5, 2018
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
* Added COSMOS_MOCKED env variable to allow overwriting mocked mode from the command line @faboweb
* User will now be logged out if switching between mocked and live connector @faboweb
* recovery seed form validation & tests @okwme
* added tx view to block explorer @okwme

### Changed

Expand Down
34 changes: 30 additions & 4 deletions app/src/renderer/components/monitor/PageBlock.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template lang="pug">
page(:title="pageBlockTitle")
data-loading(v-if="blockchain.blockLoading")
data-empty(v-else-if="!block.header || !blockMeta")
data-empty(v-else-if="!block || !block.header || !blockMeta")
template(v-else)
div(slot="menu"): tool-bar
router-link(:to="{ name: 'block', params: { block: block.header.height - 1 }}"
Expand Down Expand Up @@ -44,13 +44,23 @@ page(:title="pageBlockTitle")
part(title='Transactions')
data-loading(v-if="blockchain.blockLoading")
data-empty(v-else-if="block.header.num_txs === 0" title="Empty Block" subtitle="There were no transactions in this block.")
list-item(v-else v-for="tx in block.data.txs" :key="tx.id" dt="Transaction" :dd="TODO")
template(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jolesbi this is what you were looking for, right?

v-else-if="txs.length"
)
li-transaction(
:key="tkey + '-tx'"
v-for="(tx, tkey) in txs"
v-if="isObj(tx)"
:transaction-value="transactionValueify(tx)"
:address="tx.tx.msg.inputs[0].address"
:devMode="config.devMode")
</template>

<script>
import { mapGetters } from "vuex"
import moment from "moment"
import num from "scripts/num"
import LiTransaction from "wallet/LiTransaction"
import DataLoading from "common/NiDataLoading"
import DataEmpty from "common/NiDataEmpty"
import ToolBar from "common/NiToolBar"
Expand All @@ -60,6 +70,7 @@ import Page from "common/NiPage"
export default {
name: "page-block",
components: {
LiTransaction,
DataLoading,
DataEmpty,
ToolBar,
Expand All @@ -68,12 +79,15 @@ export default {
Page
},
computed: {
...mapGetters(["blockchain"]),
...mapGetters(["blockchain", "blockTxInfo", "config"]),
blockchainHeight() {
return this.blockchain.blocks.length > 0
? this.blockchain.blocks[0].header.height
: 0
},
txs() {
return this.blockTxInfo || (this.block.data && this.block.data.txs)
},
block() {
return this.blockchain.block
},
Expand All @@ -96,13 +110,25 @@ export default {
},
nextBlockAvailable() {
return (
this.block.header && this.block.header.height < this.blockchainHeight
this.block &&
this.block.header &&
this.block.header.height < this.blockchainHeight
)
}
},
methods: {
isObj(thing) {
return typeof thing === "object"
},
fetchBlock() {
this.$store.dispatch("getBlock", parseInt(this.$route.params.block))
},
transactionValueify(tv) {
tv = JSON.parse(JSON.stringify(tv))
tv.tx.inputs = tv.tx.msg.inputs
tv.tx.outputs = tv.tx.msg.outputs
tv.time = this.block && this.block.blockHeaderTime
return tv
}
},
mounted() {
Expand Down
2 changes: 2 additions & 0 deletions app/src/renderer/connectors/lcdClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ Object.assign(Client.prototype, {
},
coinTxs: argReq("GET", "/tx/coin"),

txs: argReq("GET", "/txs"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥇


// staking
candidate: argReq("GET", "/query/stake/candidate"),
candidates: req("GET", "/query/stake/candidates"),
Expand Down
14 changes: 14 additions & 0 deletions app/src/renderer/scripts/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import createHash from "create-hash"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we just get this from the Node crypto module to avoid adding a dependency?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, didn't realize it was also that ez

import varint from "varint"
import b64 from "base64-js"

export default function(txstring) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script is called utils and exports a function that converts tx strings into json? I think this is not really understandable.
How about a script called tx-utils.js that exports a function named parseTx?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it should be named, could be something like getTxHash

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, i think getTxHash is an accurate name

let txbytes = b64.toByteArray(txstring)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add a dependency instead of doing Buffer.from(base64string, 'base64')?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't realize it was that ez!

let varintlen = new Uint8Array(varint.encode(txbytes.length))
let tmp = new Uint8Array(varintlen.byteLength + txbytes.byteLength)
tmp.set(new Uint8Array(varintlen), 0)
tmp.set(new Uint8Array(txbytes), varintlen.byteLength)
return createHash("ripemd160")
.update(Buffer.from(tmp))
.digest("hex")
}
10 changes: 10 additions & 0 deletions app/src/renderer/vuex/getters.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,13 @@ export const connected = state => state.node.connected
export const lastHeader = state => state.node.lastHeader
export const nodeIP = state => state.node.nodeIP
export const mockedConnector = state => state.node.mocked

//blockchain
export const blockTxInfo = state => {
return (
state.blockchain.block &&
state.blockchain.blockTxs.find(
b => b.length && b[0].height === state.blockchain.block.header.height
)
)
}
106 changes: 89 additions & 17 deletions app/src/renderer/vuex/modules/blockchain.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import convertTx from "../../scripts/utils.js"

export default ({ commit, node }) => {
const state = {
blocks: [],
Expand All @@ -7,7 +9,8 @@ export default ({ commit, node }) => {
blockLoading: false,
subscription: false,
syncing: true,
blockMetas: []
blockMetas: [],
blockTxs: []
}

const mutations = {
Expand All @@ -19,6 +22,9 @@ export default ({ commit, node }) => {
},
setBlockMetaInfo(state, blockMetaInfo) {
state.blockMetaInfo = blockMetaInfo
},
setBlockTxInfo(state, blockTxInfo) {
state.blockTxInfo = blockTxInfo
}
}

Expand All @@ -30,21 +36,33 @@ export default ({ commit, node }) => {
dispatch("subscribeToBlocks")
},
async getBlock({ state, commit, dispatch }, height) {
state.blockLoading = true
state.blockHeight = height
return Promise.all([
dispatch("queryBlock", height).then(block => commit("setBlock", block)),
dispatch("queryBlockInfo", height).then(blockMetaInfo =>
commit("setBlockMetaInfo", blockMetaInfo)
)
]).then(
() => {
state.blockLoading = false
},
() => {
state.blockLoading = false
}
)
return new Promise((resolve, reject) => {
state.blockLoading = true
state.blockHeight = height
return Promise.all([
dispatch("queryBlock", height).then(block =>
commit("setBlock", block)
),
dispatch("queryBlockInfo", height).then(blockMetaInfo =>
commit("setBlockMetaInfo", blockMetaInfo)
)
])
.then(() => {
state.blockLoading = false
dispatch("queryTxInfo", height)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you return this, I think you save yourself the inner .catch

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored all the promises to be more concise

.then(blockTxInfo => {
commit("setBlockTxInfo", blockTxInfo)
resolve()
})
.catch(error => {
return reject(error)
})
})
.catch(error => {
state.blockLoading = false
return reject(error)
})
})
},
async queryBlock({ state, commit }, height) {
return new Promise(resolve => {
Expand All @@ -61,6 +79,60 @@ export default ({ commit, node }) => {
})
})
},
async queryTxInfo({ state, dispatch }, height) {
let blockTxInfo = state.blockTxs.find(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not store them per height?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was following method used by queryBlockInfo() but you're right keying the height is smart i'll update both of em

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually just updating the blockchain one for sake of PR scope

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually i think both of these need to stay as arrays as they are used in for loops

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you point me to where they are used in loops? What I have found, there is always a selection by height happening when blockTxs appear in code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops i totally misinterpreted the unit test error relating to should disable the next block button if last block. thought that info was counting an array length for looping and failed when it became an obj (as per a similar error in blockchain.spec.js). i've gone back and updated to a keyed storage method for blockTxs as well as blockMetas

b => b.length && b[0].height === height
)
if (blockTxInfo) {
return blockTxInfo
}
try {
blockTxInfo = await dispatch("getTxs", {
key: 0,
len:
state.block && state.block.data && state.block.data.txs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of these chains of &&'s? Seems like it makes things harder to debug because if the format changes or data is missing, it will silently pass this call and have undefined behavior inside getTxs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was to allow the tests that had very minimal dummy blocks to pass. but you're right if the block is malformed it should throw an error. i've updated the other tests to use a well formed block dummy

? state.block.data.txs.length
: 0,
txs:
state.block && state.block.data && state.block.data.txs
? state.block.data.txs.slice(0)
: []
})
blockTxInfo && state.blockTxs.push(blockTxInfo)
return blockTxInfo
} catch (error) {
return Promise.reject(error)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't that the default for async functions rejecting on error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was having trouble getting the error reporting come out correctly in the tests so went through and made each error very explicit until i found the culprit, i'll walk it back

}
},
getTxs({ state, commit, dispatch }, { key, len, txs }) {
// this function is recursice promie used as an async loop in order to query all tx
// found in a block. it's made similarly to queryBlockInfo only there
// is more than one async call to make. the txstring is included but might not
// actually be useful. etherscan.io includes something similar but it's seldom helpful
return new Promise(async (resolve, reject) => {
if (key >= len) return resolve(txs)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment stating what this statement accomplishes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

let txstring = atob(txs[key])
let hash = await convertTx(txs[key])
node
.txs(hash)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why mix await and .then syntax?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored all the promises to be more concise

.then(data => {
data.string = txstring
txs[key] = data
dispatch("getTxs", { key: key + 1, len, txs })
.then(txs => {
resolve(txs)
})
.catch(reject)
})
.catch(err => {
commit("notifyError", {
title: `Couldn't query block`,
body: err.message
})
reject(err)
})
})
},
async queryBlockInfo({ state, commit }, height) {
let blockMetaInfo = state.blockMetas.find(b => b.header.height === height)
if (blockMetaInfo) {
Expand All @@ -77,7 +149,7 @@ export default ({ commit, node }) => {
})
resolve(null)
} else {
resolve(data.block_metas[0])
resolve(data.block_metas.length ? data.block_metas[0] : null)
}
}
)
Expand Down
7 changes: 6 additions & 1 deletion app/src/renderer/vuex/modules/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export default ({ commit, node }) => {
setTransactionTime(state, { blockHeight, blockMetaInfo }) {
state.history = state.history.map(t => {
if (t.height === blockHeight) {
t.time = blockMetaInfo.header.time
// console.log("blockMetaInfo", blockMetaInfo)
t.time = blockMetaInfo && blockMetaInfo.header.time
}
return t
})
Expand Down Expand Up @@ -103,6 +104,10 @@ export default ({ commit, node }) => {
},
async queryTransactionTime({ commit, dispatch }, blockHeight) {
let blockMetaInfo = await dispatch("queryBlockInfo", blockHeight)
// console.log(
// "received blockMetaInfo at height " + blockHeight,
// blockMetaInfo
// )
commit("setTransactionTime", { blockHeight, blockMetaInfo })
},
async loadDenoms({ state, commit }) {
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,10 @@
"@vue/test-utils": "1.0.0-beta.11",
"autosize": "4.0.2",
"axios": "0.18.0",
"base64-js": "1.3.0",
"casual": "1.5.19",
"chart.js": "2.7.2",
"create-hash": "1.2.0",
"electron": "1.8.6",
"electron-chromedriver": "1.8.0",
"electron-ga": "1.0.6",
Expand All @@ -128,6 +130,7 @@
"tendermint-crypto": "github:mappum/js-crypto",
"toml": "2.3.3",
"user-home": "2.0.0",
"varint": "5.0.0",
"vue": "2.5.16",
"vue-directive-tooltip": "1.4.5",
"vue-electron": "1.0.6",
Expand Down
12 changes: 11 additions & 1 deletion test/unit/specs/components/common/NiModalSearch.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Vuelidate from "vuelidate"

describe("NiModalSearch", () => {
let wrapper, store
let { mount, shallow, localVue } = setup()
let { mount, localVue } = setup()

beforeEach(() => {
let instance = mount(NiModalSearch, { propsData: { type: "transactions" } })
Expand Down Expand Up @@ -55,4 +55,14 @@ describe("NiModalSearch", () => {
.trim()
).toBe("Find")
})

it("should go to block", () => {
wrapper.vm.$router.go("balances")
wrapper.setProps({ type: "blocks" })
store.commit("setSearchVisible", ["blocks", true])
expect(wrapper.vm.$route.name).toBe("balances")
wrapper.vm.query = "1"
wrapper.vm.gotoBlock()
expect(wrapper.vm.$route.name).toBe("block")
})
})
Loading