Skip to content

Commit

Permalink
Merge pull request exonum#111 from boguslavsky/tx-loop
Browse files Browse the repository at this point in the history
wait for transaction acceptance; add missed spinner in cabinet
  • Loading branch information
boguslavsky committed Mar 15, 2018
2 parents 03caeee + 1023ee3 commit 1efb855
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 90 deletions.
2 changes: 1 addition & 1 deletion frontend/src/pages/AuthPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
this.isSpinnerVisible = true
this.$blockchain.createWallet(this.name).then(function(keyPair) {
this.$blockchain.createWallet(this.name).then(keyPair => {
self.name = ''
self.keyPair = keyPair
self.isSpinnerVisible = false
Expand Down
22 changes: 15 additions & 7 deletions frontend/src/pages/WalletPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@
<strong v-numeral="transaction.body.amount"/> received from <code>{{ transaction.body.from }}</code>
</div>
<div class="col-sm-3">
<span v-if="transaction.status" class="badge badge-success">executed</span>
<span v-else class="badge badge-danger">failed</span>
<span v-if="transaction.status" class="badge badge-success">accepted</span>
<span v-else class="badge badge-danger">rejected</span>
</div>
</div>
</li>
Expand Down Expand Up @@ -101,6 +101,8 @@
</div>
</div>
</modal>

<spinner :visible="isSpinnerVisible"/>
</div>
</template>

Expand Down Expand Up @@ -147,10 +149,13 @@
this.$storage.get().then(function(keyPair) {
self.isSpinnerVisible = true
self.$blockchain.addFunds(keyPair, self.amountToAdd).then(function() {
self.$blockchain.addFunds(keyPair, self.amountToAdd).then(data => {
self.isSpinnerVisible = false
self.isAddFundsModalVisible = false
self.$notify('success', 'Add funds transaction has been sent')
self.balance = data.wallet.balance
self.height = data.block.height
self.transactions = data.transactions
self.$notify('success', 'Add funds transaction has been written into the blockchain')
}).catch(function(error) {
self.isSpinnerVisible = false
self.$notify('error', error.toString())
Expand Down Expand Up @@ -184,10 +189,13 @@
self.isSpinnerVisible = true
self.$blockchain.transfer(keyPair, self.receiver, self.amountToTransfer).then(function() {
self.$blockchain.transfer(keyPair, self.receiver, self.amountToTransfer).then(data => {
self.isSpinnerVisible = false
self.isTransferModalVisible = false
self.$notify('success', 'Transfer transaction has been sent')
self.balance = data.wallet.balance
self.height = data.block.height
self.transactions = data.transactions
self.$notify('success', 'Transfer transaction has been written into the blockchain')
}).catch(function(error) {
self.isSpinnerVisible = false
self.$notify('error', error.toString())
Expand All @@ -211,7 +219,7 @@
this.$storage.get().then(function(keyPair) {
self.isSpinnerVisible = true
self.$blockchain.getWallet(keyPair).then(function(data) {
self.$blockchain.getWallet(keyPair).then(data => {
self.isSpinnerVisible = false
self.name = data.wallet.name
self.publicKey = keyPair.publicKey
Expand Down
181 changes: 102 additions & 79 deletions frontend/src/plugins/blockchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const TX_URL = '/api/services/cryptocurrency/v1/wallets/transaction'
const CONFIG_URL = '/api/services/configuration/v1/configs/actual'
const WALLET_URL = '/api/services/cryptocurrency/v1/wallets/info?pubkey='

const ATTEMPTS = 10
const ATTEMPT_TIMEOUT = 500
const NETWORK_ID = 0
const PROTOCOL_VERSION = 0
const SERVICE_ID = 128
Expand Down Expand Up @@ -96,8 +98,103 @@ function getPublicKeyOfTransaction(transactionId, transaction) {
}
}

function getWallet(keyPair) {
return axios.get(CONFIG_URL).then(response => {
// actual list of public keys of validators
const validators = response.data.config.validator_keys.map(validator => {
return validator.consensus_key
})

return axios.get(WALLET_URL + keyPair.publicKey).then(response => {
return response.data
}).then((data) => {
if (!Exonum.verifyBlock(data.block_info, validators, NETWORK_ID)) {
throw new Error('Block can not be verified')
}

// find root hash of table with wallets in the tree of all tables
const tableKey = TableKey.hash({
service_id: SERVICE_ID,
table_index: 0
})
const walletsHash = Exonum.merklePatriciaProof(data.block_info.block.state_hash, data.wallet.mpt_proof, tableKey)
if (walletsHash === null) {
throw new Error('Wallets table not found')
}

// find wallet in the tree of all wallets
const wallet = Exonum.merklePatriciaProof(walletsHash, data.wallet.value, keyPair.publicKey, Wallet)
if (wallet === null) {
throw new Error('Wallet not found')
}

// get transactions
const transactionsMetaData = Exonum.merkleProof(
wallet.history_hash,
wallet.history_len,
data.wallet_history.mt_proof,
[0, wallet.history_len],
TransactionMetaData
)

if (data.wallet_history.values.length !== transactionsMetaData.length) {
// number of transactions in wallet history is not equal
// to number of transactions in array with transactions meta data
throw new Error('Transactions can not be verified')
}

// validate each transaction
let transactions = []
for (let i = 0; i < data.wallet_history.values.length; i++) {
let Transaction = getTransaction(data.wallet_history.values[i].message_id)
const publicKey = getPublicKeyOfTransaction(data.wallet_history.values[i].message_id, data.wallet_history.values[i].body)

Transaction.signature = data.wallet_history.values[i].signature

if (Transaction.hash(data.wallet_history.values[i].body) !== transactionsMetaData[i].tx_hash) {
throw new Error('Invalid transaction hash has been found')
} else if (!Transaction.verifySignature(data.wallet_history.values[i].signature, publicKey, data.wallet_history.values[i].body)) {
throw new Error('Invalid transaction signature has been found')
}

transactions.push(Object.assign({
hash: transactionsMetaData[i].tx_hash,
status: transactionsMetaData[i].execution_status
}, data.wallet_history.values[i]))
}

return {
block: data.block_info.block,
wallet: wallet,
transactions: transactions
}
})
})
}

function waitForAcceptance(keyPair, hash) {
let attempt = ATTEMPTS

return (function makeAttempt() {
return getWallet(keyPair).then(data => {
// find transaction in a wallet proof
if (typeof data.transactions.find((transaction) => transaction.hash === hash) === 'undefined') {
if (--attempt > 0) {
return new Promise((resolve) => {
setTimeout(resolve, ATTEMPT_TIMEOUT)
}).then(makeAttempt)
} else {
throw new Error('Transaction has not been found')
}
} else {
return data
}
})
})();
}

module.exports = {
install: function(Vue) {
install(Vue) {
Vue.prototype.$blockchain = {
createWallet: name => {
const keyPair = Exonum.keyPair()
Expand All @@ -118,9 +215,7 @@ module.exports = {
message_id: TX_WALLET_ID,
signature: signature,
body: data
}).then(() => {
return keyPair
})
}).then(() => keyPair)
},

addFunds: (keyPair, amountToAdd) => {
Expand All @@ -141,7 +236,7 @@ module.exports = {
message_id: TX_ISSUE_ID,
signature: signature,
body: data
})
}).then(response => waitForAcceptance(keyPair, response.data.tx_hash))
},

transfer: (keyPair, receiver, amountToTransfer) => {
Expand All @@ -163,82 +258,10 @@ module.exports = {
message_id: TX_TRANSFER_ID,
signature: signature,
body: data
})
}).then(response => waitForAcceptance(keyPair, response.data.tx_hash))
},

getWallet: keyPair => {
return axios.get(CONFIG_URL).then(response => {
// actual list of public keys of validators
const validators = response.data.config.validator_keys.map(validator => {
return validator.consensus_key
})

return axios.get(WALLET_URL + keyPair.publicKey).then(response => {
return response.data
}).then((data) => {
if (!Exonum.verifyBlock(data.block_info, validators, NETWORK_ID)) {
throw new Error('Block can not be verified')
}

// find root hash of table with wallets in the tree of all tables
const tableKey = TableKey.hash({
service_id: SERVICE_ID,
table_index: 0
})
const walletsHash = Exonum.merklePatriciaProof(data.block_info.block.state_hash, data.wallet.mpt_proof, tableKey)
if (walletsHash === null) {
throw new Error('Wallets table not found')
}

// find wallet in the tree of all wallets
const wallet = Exonum.merklePatriciaProof(walletsHash, data.wallet.value, keyPair.publicKey, Wallet)
if (wallet === null) {
throw new Error('Wallet not found')
}

// get transactions
const transactionsMetaData = Exonum.merkleProof(
wallet.history_hash,
wallet.history_len,
data.wallet_history.mt_proof,
[0, wallet.history_len],
TransactionMetaData
)

if (data.wallet_history.values.length !== transactionsMetaData.length) {
// number of transactions in wallet history is not equal
// to number of transactions in array with transactions meta data
throw new Error('Transactions can not be verified')
}

// validate each transaction
let transactions = []
for (let i = 0; i < data.wallet_history.values.length; i++) {
let Transaction = getTransaction(data.wallet_history.values[i].message_id)
const publicKey = getPublicKeyOfTransaction(data.wallet_history.values[i].message_id, data.wallet_history.values[i].body)

Transaction.signature = data.wallet_history.values[i].signature

if (Transaction.hash(data.wallet_history.values[i].body) !== transactionsMetaData[i].tx_hash) {
throw new Error('Invalid transaction hash has been found')
} else if (!Transaction.verifySignature(data.wallet_history.values[i].signature, publicKey, data.wallet_history.values[i].body)) {
throw new Error('Invalid transaction signature has been found')
}

transactions.push(Object.assign({
hash: transactionsMetaData[i].tx_hash,
status: transactionsMetaData[i].execution_status
}, data.wallet_history.values[i]))
}

return {
block: data.block_info.block,
wallet: wallet,
transactions: transactions
}
})
})
}
getWallet: getWallet
}
}
}
2 changes: 1 addition & 1 deletion frontend/src/plugins/notify.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Noty from 'noty'

export default {
install: function(Vue) {
install(Vue) {
Vue.prototype.$notify = function(type = 'information', text) {
new Noty({
theme: 'bootstrap-v4',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/plugins/storage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default {
install: function(Vue) {
install(Vue) {
Vue.prototype.$storage = {
set: function(keyPair) {
localStorage.setItem('user', JSON.stringify(keyPair))
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/plugins/validate.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default {
install: function(Vue) {
install(Vue) {
Vue.prototype.$validateHex = function(hash, bytes) {
bytes = bytes || 32

Expand Down

0 comments on commit 1efb855

Please sign in to comment.