Simple GUI for local transactions
This commit is contained in:
parent
cd686b5d68
commit
ff27bbcb4f
@ -15,7 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { inAddress, inData, inHex, inNumber16, inOptions } from '../../format/input';
|
import { inAddress, inData, inHex, inNumber16, inOptions } from '../../format/input';
|
||||||
import { outAccountInfo, outAddress, outHistogram, outNumber, outPeers } from '../../format/output';
|
import { outAccountInfo, outAddress, outHistogram, outNumber, outPeers, outTransaction } from '../../format/output';
|
||||||
|
|
||||||
export default class Parity {
|
export default class Parity {
|
||||||
constructor (transport) {
|
constructor (transport) {
|
||||||
@ -117,16 +117,27 @@ export default class Parity {
|
|||||||
.execute('parity_hashContent', url);
|
.execute('parity_hashContent', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
importGethAccounts (accounts) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_importGethAccounts', (accounts || []).map(inAddress))
|
||||||
|
.then((accounts) => (accounts || []).map(outAddress));
|
||||||
|
}
|
||||||
|
|
||||||
listGethAccounts () {
|
listGethAccounts () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_listGethAccounts')
|
.execute('parity_listGethAccounts')
|
||||||
.then((accounts) => (accounts || []).map(outAddress));
|
.then((accounts) => (accounts || []).map(outAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
importGethAccounts (accounts) {
|
localTransactions () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_importGethAccounts', (accounts || []).map(inAddress))
|
.execute('parity_localTransactions')
|
||||||
.then((accounts) => (accounts || []).map(outAddress));
|
.then(transactions => {
|
||||||
|
Object.values(transactions)
|
||||||
|
.filter(tx => tx.transaction)
|
||||||
|
.map(tx => tx.transaction = outTransaction(tx.transaction));
|
||||||
|
return transactions;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
minGasPrice () {
|
minGasPrice () {
|
||||||
@ -192,6 +203,17 @@ export default class Parity {
|
|||||||
.execute('parity_nodeName');
|
.execute('parity_nodeName');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pendingTransactions () {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_pendingTransactions')
|
||||||
|
.then(data => data.map(outTransaction));
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingTransactionsStats () {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_pendingTransactionsStats');
|
||||||
|
}
|
||||||
|
|
||||||
phraseToAddress (phrase) {
|
phraseToAddress (phrase) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_phraseToAddress', phrase)
|
.execute('parity_phraseToAddress', phrase)
|
||||||
|
17
js/src/dapps/localtx.html
Normal file
17
js/src/dapps/localtx.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
|
||||||
|
<title>Local transactions Viewer</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="container"></div>
|
||||||
|
<script src="vendor.js"></script>
|
||||||
|
<script src="commons.js"></script>
|
||||||
|
<script src="/parity-utils/parity.js"></script>
|
||||||
|
<script src="localtx.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
33
js/src/dapps/localtx.js
Normal file
33
js/src/dapps/localtx.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||||
|
injectTapEventPlugin();
|
||||||
|
|
||||||
|
import Application from './localtx/Application';
|
||||||
|
|
||||||
|
import '../../assets/fonts/Roboto/font.css';
|
||||||
|
import '../../assets/fonts/RobotoMono/font.css';
|
||||||
|
import './style.css';
|
||||||
|
import './localtx.html';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Application />,
|
||||||
|
document.querySelector('#container')
|
||||||
|
);
|
197
js/src/dapps/localtx/Application/application.js
Normal file
197
js/src/dapps/localtx/Application/application.js
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { api } from '../parity';
|
||||||
|
|
||||||
|
import { Transaction, LocalTransaction } from '../Transaction';
|
||||||
|
|
||||||
|
export default class Application extends Component {
|
||||||
|
state = {
|
||||||
|
loading: true,
|
||||||
|
transactions: [],
|
||||||
|
localTransactions: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const poll = () => this.fetchTransactionData().then(poll);
|
||||||
|
this._timeout = setTimeout(poll, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
clearTimeout(this._timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchTransactionData () {
|
||||||
|
return Promise.all([
|
||||||
|
api.parity.pendingTransactions(),
|
||||||
|
api.parity.pendingTransactionsStats(),
|
||||||
|
api.parity.localTransactions(),
|
||||||
|
]).then(([pending, stats, local]) => {
|
||||||
|
// Combine results together
|
||||||
|
const transactions = pending.map(tx => {
|
||||||
|
return {
|
||||||
|
transaction: tx,
|
||||||
|
stats: stats[tx.hash],
|
||||||
|
isLocal: !!local[tx.hash]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add transaction data to locals
|
||||||
|
transactions
|
||||||
|
.filter(tx => tx.isLocal)
|
||||||
|
.map(data => {
|
||||||
|
const tx = data.transaction;
|
||||||
|
local[tx.hash].transaction = tx;
|
||||||
|
local[tx.hash].stats = data.stats;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert local transactions to array
|
||||||
|
const localTransactions = Object.keys(local).map(hash => {
|
||||||
|
const data = local[hash];
|
||||||
|
data.txHash = hash;
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort local transactions by nonce (move future to the end)
|
||||||
|
localTransactions.sort((a, b) => {
|
||||||
|
a = a.transaction || {};
|
||||||
|
b = b.transaction || {};
|
||||||
|
|
||||||
|
if (a.from && b.from && a.from !== b.from) {
|
||||||
|
return a.from < b.from;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a.nonce || !b.nonce) {
|
||||||
|
return !a.nonce ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BigNumber(a.nonce).comparedTo(new BigNumber(b.nonce));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
transactions,
|
||||||
|
localTransactions
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { loading } = this.state;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div>Loading...</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={ { padding: '1rem 2rem'}}>
|
||||||
|
<h1>Your past local transactions</h1>
|
||||||
|
{ this.renderLocals() }
|
||||||
|
<h1>Transactions in the queue</h1>
|
||||||
|
{ this.renderQueueSummary() }
|
||||||
|
{ this.renderQueue() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderQueueSummary () {
|
||||||
|
const { transactions } = this.state;
|
||||||
|
if (!transactions.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = transactions.length;
|
||||||
|
const locals = transactions.filter(tx => tx.isLocal).length;
|
||||||
|
const fee = transactions
|
||||||
|
.map(tx => tx.transaction)
|
||||||
|
.map(tx => tx.gasPrice.mul(tx.gas))
|
||||||
|
.reduce((sum, fee) => sum.add(fee), new BigNumber(0));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<h3>
|
||||||
|
Count: <strong>{ locals ? `${count} (${locals})` : count }</strong>
|
||||||
|
|
||||||
|
Total Fee: <strong>{ api.util.fromWei(fee).toFixed(3) } ETH</strong>
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderQueue () {
|
||||||
|
const { transactions } = this.state;
|
||||||
|
if (!transactions.length) {
|
||||||
|
return (
|
||||||
|
<h3>The queue seems is empty.</h3>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table cellSpacing='0'>
|
||||||
|
<thead>
|
||||||
|
{ Transaction.renderHeader() }
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
transactions.map((tx, idx) => (
|
||||||
|
<Transaction
|
||||||
|
key={ tx.transaction.hash }
|
||||||
|
idx={ idx + 1}
|
||||||
|
isLocal={ tx.isLocal }
|
||||||
|
transaction={ tx.transaction }
|
||||||
|
stats={ tx.stats }
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLocals () {
|
||||||
|
const { localTransactions } = this.state;
|
||||||
|
if (!localTransactions.length) {
|
||||||
|
return (
|
||||||
|
<h3>You haven't sent any transactions yet.</h3>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table cellSpacing='0'>
|
||||||
|
<thead>
|
||||||
|
{ LocalTransaction.renderHeader() }
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
localTransactions.map(tx => (
|
||||||
|
<LocalTransaction
|
||||||
|
key={ tx.txHash }
|
||||||
|
hash={ tx.txHash }
|
||||||
|
transaction={ tx.transaction }
|
||||||
|
status={ tx.status }
|
||||||
|
stats={ tx.stats }
|
||||||
|
details={ tx }
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
32
js/src/dapps/localtx/Application/application.spec.js
Normal file
32
js/src/dapps/localtx/Application/application.spec.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import '../../../environment/tests';
|
||||||
|
|
||||||
|
import Application from './application';
|
||||||
|
|
||||||
|
describe('localtx/Application', () => {
|
||||||
|
describe('rendering', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const rendered = shallow(<Application />);
|
||||||
|
|
||||||
|
expect(rendered).to.be.defined;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
17
js/src/dapps/localtx/Application/index.js
Normal file
17
js/src/dapps/localtx/Application/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './application';
|
17
js/src/dapps/localtx/Transaction/index.js
Normal file
17
js/src/dapps/localtx/Transaction/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export { Transaction, LocalTransaction } from './transaction';
|
31
js/src/dapps/localtx/Transaction/transaction.css
Normal file
31
js/src/dapps/localtx/Transaction/transaction.css
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
.from {
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction {
|
||||||
|
td {
|
||||||
|
padding: 7px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:first-child {
|
||||||
|
padding: 7px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.local {
|
||||||
|
background: #8bc34a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nowrap {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit {
|
||||||
|
label, input {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
359
js/src/dapps/localtx/Transaction/transaction.js
Normal file
359
js/src/dapps/localtx/Transaction/transaction.js
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
import { api } from '../parity';
|
||||||
|
|
||||||
|
import styles from './transaction.css';
|
||||||
|
|
||||||
|
import IdentityIcon from '../../githubhint/IdentityIcon';
|
||||||
|
|
||||||
|
class BaseTransaction extends Component {
|
||||||
|
|
||||||
|
shortHash (hash) {
|
||||||
|
return `${hash.substr(0, 6)}..${hash.substr(hash.length - 4)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHash (hash) {
|
||||||
|
return (
|
||||||
|
<code title={ hash }>
|
||||||
|
{ this.shortHash(hash) }
|
||||||
|
</code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFrom (transaction) {
|
||||||
|
if (!transaction) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div title={ transaction.from } className={ styles.from }>
|
||||||
|
<IdentityIcon
|
||||||
|
address={ transaction.from }
|
||||||
|
/>
|
||||||
|
0x{ transaction.nonce.toString(16) }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderGasPrice (transaction) {
|
||||||
|
if (!transaction) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span title={ `${transaction.gasPrice.toFormat(0) } wei`}>
|
||||||
|
{ api.util.fromWei(transaction.gasPrice, 'shannon').toFormat(2) } shannon
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderGas (transaction) {
|
||||||
|
if (!transaction) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span title={ `${transaction.gas.toFormat(0)} Gas` }>
|
||||||
|
{ transaction.gas.div(10**6).toFormat(3) } MGas
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPropagation (stats) {
|
||||||
|
const noOfPeers = Object.keys(stats.propagatedTo).length;
|
||||||
|
const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={ styles.nowrap }>
|
||||||
|
{ noOfPropagations } ({ noOfPeers } peers)
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Transaction extends BaseTransaction {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
idx: PropTypes.number.isRequired,
|
||||||
|
transaction: PropTypes.object.isRequired,
|
||||||
|
isLocal: PropTypes.bool,
|
||||||
|
stats: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
isLocal: false,
|
||||||
|
stats: {
|
||||||
|
firstSeen: 0,
|
||||||
|
propagatedTo: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static renderHeader () {
|
||||||
|
return (
|
||||||
|
<tr className={ styles.header }>
|
||||||
|
<th></th>
|
||||||
|
<th>
|
||||||
|
Transaction
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
From
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Gas Price
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Gas
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
First seen
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
# Propagated
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { isLocal, stats, transaction, idx } = this.props;
|
||||||
|
|
||||||
|
const clazz = classnames(styles.transaction, {
|
||||||
|
[styles.local]: isLocal
|
||||||
|
});
|
||||||
|
const noOfPeers = Object.keys(stats.propagatedTo).length;
|
||||||
|
const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className={ clazz }>
|
||||||
|
<td>
|
||||||
|
{ idx }.
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ this.renderHash(transaction.hash) }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ this.renderFrom(transaction) }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ this.renderGasPrice(transaction) }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ this.renderGas(transaction) }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ stats.firstSeen }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ this.renderPropagation(stats) }
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LocalTransaction extends BaseTransaction {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
hash: PropTypes.string.isRequired,
|
||||||
|
status: PropTypes.string.isRequired,
|
||||||
|
transaction: PropTypes.object,
|
||||||
|
isLocal: PropTypes.bool,
|
||||||
|
stats: PropTypes.object,
|
||||||
|
details: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
stats: {
|
||||||
|
propagatedTo: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static renderHeader () {
|
||||||
|
return (
|
||||||
|
<tr className={ styles.header }>
|
||||||
|
<th></th>
|
||||||
|
<th>
|
||||||
|
Transaction
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
From
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Gas Price / Gas
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Propagated
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
isSending: false,
|
||||||
|
isResubmitting: false,
|
||||||
|
gasPrice: null,
|
||||||
|
gas: null
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleResubmit = () => {
|
||||||
|
const { transaction } = this.props;
|
||||||
|
const { isResubmitting, gasPrice } = this.state;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isResubmitting: !isResubmitting,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (gasPrice === null) {
|
||||||
|
this.setState({
|
||||||
|
gasPrice: `0x${ transaction.gasPrice.toString(16) }`,
|
||||||
|
gas: `0x${ transaction.gas.toString(16) }`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sendTransaction = () => {
|
||||||
|
const { transaction } = this.props;
|
||||||
|
const { gasPrice, gas } = this.state;
|
||||||
|
|
||||||
|
const newTransaction = {
|
||||||
|
from: transaction.from,
|
||||||
|
to: transaction.to,
|
||||||
|
nonce: transaction.nonce,
|
||||||
|
value: transaction.value,
|
||||||
|
data: transaction.data,
|
||||||
|
gasPrice, gas
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isResubmitting: false,
|
||||||
|
isSending: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeSending = () => this.setState({
|
||||||
|
isSending: false
|
||||||
|
});
|
||||||
|
|
||||||
|
api.eth.sendTransaction(newTransaction)
|
||||||
|
.then(closeSending)
|
||||||
|
.catch(closeSending);
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
if (this.state.isResubmitting) {
|
||||||
|
return this.renderResubmit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { stats, transaction, hash, status } = this.props;
|
||||||
|
const { isSending } = this.state;
|
||||||
|
|
||||||
|
const resubmit = isSending ? (
|
||||||
|
'sending...'
|
||||||
|
) : (
|
||||||
|
<a href='javascript:void' onClick={ this.toggleResubmit }>
|
||||||
|
resubmit
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className={ styles.transaction }>
|
||||||
|
<td>
|
||||||
|
{ !transaction ? null : resubmit }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ this.renderHash(hash) }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ this.renderFrom(transaction) }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ this.renderGasPrice(transaction) }
|
||||||
|
<br />
|
||||||
|
{ this.renderGas(transaction) }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ status === 'pending' ? this.renderPropagation(stats) : '-' }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ this.renderStatus() }
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStatus () {
|
||||||
|
const { details } = this.props;
|
||||||
|
|
||||||
|
let state = {
|
||||||
|
'pending': () => `In queue: Pending`,
|
||||||
|
'future': () => `In queue: Future`,
|
||||||
|
'mined': () => `Mined`,
|
||||||
|
'dropped': () => `Dropped because of queue limit`,
|
||||||
|
'invalid': () => `Transaction is invalid`,
|
||||||
|
'rejected': () => `Rejected: ${ details.error }`,
|
||||||
|
'replaced': () => `Replaced by ${ this.shortHash(details.hash) }`,
|
||||||
|
}[this.props.status];
|
||||||
|
|
||||||
|
return state ? state() : 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
renderResubmit () {
|
||||||
|
const { transaction } = this.props;
|
||||||
|
const { gasPrice, gas } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className={ styles.transaction }>
|
||||||
|
<td>
|
||||||
|
<a href='javascript:void' onClick={ this.toggleResubmit }>
|
||||||
|
cancel
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ this.renderHash(transaction.hash) }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{ this.renderFrom(transaction) }
|
||||||
|
</td>
|
||||||
|
<td className={ styles.edit }>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
value={ gasPrice }
|
||||||
|
onChange={ el => this.setState({ gasPrice: el.target.value }) }
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
value={ gas }
|
||||||
|
onChange={ el => this.setState({ gas: el.target.value }) }
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td colSpan='2'>
|
||||||
|
<a href='javascript:void' onClick={ this.sendTransaction }>
|
||||||
|
Send
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
js/src/dapps/localtx/Transaction/transaction.spec.js
Normal file
37
js/src/dapps/localtx/Transaction/transaction.spec.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import '../../../environment/tests';
|
||||||
|
|
||||||
|
import Transaction from './transaction';
|
||||||
|
|
||||||
|
describe('localtx/Transaction', () => {
|
||||||
|
describe('rendering', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const rendered = shallow(
|
||||||
|
<Transaction
|
||||||
|
isLocal={ false }
|
||||||
|
transaction={ {} }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(rendered).to.be.defined;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
21
js/src/dapps/localtx/parity.js
Normal file
21
js/src/dapps/localtx/parity.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const api = window.parent.secureApi;
|
||||||
|
|
||||||
|
export {
|
||||||
|
api
|
||||||
|
};
|
@ -224,15 +224,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
listGethAccounts: {
|
|
||||||
desc: 'Returns a list of the accounts available from Geth',
|
|
||||||
params: [],
|
|
||||||
returns: {
|
|
||||||
type: Array,
|
|
||||||
desc: '20 Bytes addresses owned by the client.'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
importGethAccounts: {
|
importGethAccounts: {
|
||||||
desc: 'Imports a list of accounts from geth',
|
desc: 'Imports a list of accounts from geth',
|
||||||
params: [
|
params: [
|
||||||
@ -247,6 +238,24 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
listGethAccounts: {
|
||||||
|
desc: 'Returns a list of the accounts available from Geth',
|
||||||
|
params: [],
|
||||||
|
returns: {
|
||||||
|
type: Array,
|
||||||
|
desc: '20 Bytes addresses owned by the client.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
localTransactions: {
|
||||||
|
desc: 'Returns an object of current and past local transactions.',
|
||||||
|
params: [],
|
||||||
|
returns: {
|
||||||
|
type: Object,
|
||||||
|
desc: 'Mapping of `tx hash` into status object.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
minGasPrice: {
|
minGasPrice: {
|
||||||
desc: 'Returns currently set minimal gas price',
|
desc: 'Returns currently set minimal gas price',
|
||||||
params: [],
|
params: [],
|
||||||
@ -379,6 +388,24 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pendingTransactions: {
|
||||||
|
desc: 'Returns a list of transactions currently in the queue.',
|
||||||
|
params: [],
|
||||||
|
returns: {
|
||||||
|
type: Array,
|
||||||
|
desc: 'Transactions ordered by priority'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
pendingTransactionsStats: {
|
||||||
|
desc: 'Returns propagation stats for transactions in the queue',
|
||||||
|
params: [],
|
||||||
|
returns: {
|
||||||
|
type: Object,
|
||||||
|
desc: 'mapping of `tx hash` into `stats`'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
phraseToAddress: {
|
phraseToAddress: {
|
||||||
desc: 'Converts a secret phrase into the corresponting address',
|
desc: 'Converts a secret phrase into the corresponting address',
|
||||||
params: [
|
params: [
|
||||||
|
@ -39,5 +39,15 @@
|
|||||||
"author": "Parity Team <admin@ethcore.io>",
|
"author": "Parity Team <admin@ethcore.io>",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"secure": true
|
"secure": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d",
|
||||||
|
"url": "localtx",
|
||||||
|
"name": "TxQueue Viewer",
|
||||||
|
"description": "Have a peak on internals of transaction queue of your node.",
|
||||||
|
"author": "Parity Team <admin@ethcore.io>",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"secure": true
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -40,6 +40,7 @@ module.exports = {
|
|||||||
'githubhint': ['./dapps/githubhint.js'],
|
'githubhint': ['./dapps/githubhint.js'],
|
||||||
'registry': ['./dapps/registry.js'],
|
'registry': ['./dapps/registry.js'],
|
||||||
'signaturereg': ['./dapps/signaturereg.js'],
|
'signaturereg': ['./dapps/signaturereg.js'],
|
||||||
|
'localtx': ['./dapps/localtx.js'],
|
||||||
'tokenreg': ['./dapps/tokenreg.js'],
|
'tokenreg': ['./dapps/tokenreg.js'],
|
||||||
// app
|
// app
|
||||||
'index': ['./index.js']
|
'index': ['./index.js']
|
||||||
|
Loading…
Reference in New Issue
Block a user