[beta] Cancel Transaction (#5656)
* option to disable persistent txqueue (#5544) * option to disable persistent txqueue * New option goes with kin * Remove transaction RPC (#4949) * Cancel tx JS (#4958) * Remove transaction RPC * Bumping multihash and libc * Updating nanomsg * bump nanomsg * cancel tx * cancel-tx-js * cancel-tx-js * cancel-tx-js * cancel-tx-hs * cancel-tx-js * cancel-tx-js * cancel-tx-js * small fixes * edit & time till submit * edit & time till submit * updates * updates * udpates * udpates * grumbles * step 1 * Wonderful updates * ready * small refact * small refact * grumbles 1 * ffx2 * good ol' fashioned updates * latest and greatest * removeHash * removeHash * spec * fix 1 * fix 1 * fix 2 * fix 2 * ff * ff * ff * updates * Updating documentation for RPCs (#5392) * Removing minBlocks occurrencies * Docs for new RPCs. * Fixing linting issues, updating *withToken documentatiojn. * Adding missing RPCs. Fixing tests. * Fixing lint issues.
This commit is contained in:
parent
8dfc10ede9
commit
1aea9caf6d
3
.gitignore
vendored
3
.gitignore
vendored
@ -19,6 +19,9 @@
|
|||||||
# mac stuff
|
# mac stuff
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# npm stuff
|
||||||
|
npm-debug.log
|
||||||
|
|
||||||
# gdb files
|
# gdb files
|
||||||
.gdb_history
|
.gdb_history
|
||||||
|
|
||||||
|
@ -351,7 +351,7 @@ impl Engine for AuthorityRound {
|
|||||||
if step == parent_step
|
if step == parent_step
|
||||||
|| (header.number() >= self.validate_step_transition && step <= parent_step) {
|
|| (header.number() >= self.validate_step_transition && step <= parent_step) {
|
||||||
trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step);
|
trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step);
|
||||||
self.validators.report_malicious(header.author(), header.number(), Default::default());
|
self.validators.report_malicious(header.author());
|
||||||
Err(EngineError::DoubleVote(header.author().clone()))?;
|
Err(EngineError::DoubleVote(header.author().clone()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1008,6 +1008,16 @@ impl MinerService for Miner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_pending_transaction(&self, chain: &MiningBlockChainClient, hash: &H256) -> Option<PendingTransaction> {
|
||||||
|
let mut queue = self.transaction_queue.lock();
|
||||||
|
let tx = queue.find(hash);
|
||||||
|
if tx.is_some() {
|
||||||
|
let fetch_nonce = |a: &Address| chain.latest_nonce(a);
|
||||||
|
queue.remove_invalid(hash, &fetch_nonce);
|
||||||
|
}
|
||||||
|
tx
|
||||||
|
}
|
||||||
|
|
||||||
fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option<RichReceipt> {
|
fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option<RichReceipt> {
|
||||||
self.from_pending_block(
|
self.from_pending_block(
|
||||||
best_block,
|
best_block,
|
||||||
|
@ -150,6 +150,10 @@ pub trait MinerService : Send + Sync {
|
|||||||
/// Query pending transactions for hash.
|
/// Query pending transactions for hash.
|
||||||
fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option<PendingTransaction>;
|
fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option<PendingTransaction>;
|
||||||
|
|
||||||
|
/// Removes transaction from the queue.
|
||||||
|
/// NOTE: The transaction is not removed from pending block if mining.
|
||||||
|
fn remove_pending_transaction(&self, chain: &MiningBlockChainClient, hash: &H256) -> Option<PendingTransaction>;
|
||||||
|
|
||||||
/// Get a list of all pending transactions in the queue.
|
/// Get a list of all pending transactions in the queue.
|
||||||
fn pending_transactions(&self) -> Vec<PendingTransaction>;
|
fn pending_transactions(&self) -> Vec<PendingTransaction>;
|
||||||
|
|
||||||
|
@ -150,6 +150,7 @@
|
|||||||
"blockies": "0.0.2",
|
"blockies": "0.0.2",
|
||||||
"brace": "0.9.0",
|
"brace": "0.9.0",
|
||||||
"bytes": "2.4.0",
|
"bytes": "2.4.0",
|
||||||
|
"date-difference": "1.0.0",
|
||||||
"debounce": "1.0.0",
|
"debounce": "1.0.0",
|
||||||
"es6-error": "4.0.0",
|
"es6-error": "4.0.0",
|
||||||
"es6-promise": "4.0.5",
|
"es6-promise": "4.0.5",
|
||||||
|
@ -37,7 +37,11 @@ Object.keys(rustMethods).forEach((group) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function printType (type) {
|
function printType (type, obj) {
|
||||||
|
if (!type) {
|
||||||
|
throw new Error(`Invalid type in ${JSON.stringify(obj)}`);
|
||||||
|
}
|
||||||
|
|
||||||
return type.print || `\`${type.name}\``;
|
return type.print || `\`${type.name}\``;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +49,7 @@ function formatDescription (obj, prefix = '', indent = '') {
|
|||||||
const optional = obj.optional ? '(optional) ' : '';
|
const optional = obj.optional ? '(optional) ' : '';
|
||||||
const defaults = obj.default ? `(default: \`${obj.default}\`) ` : '';
|
const defaults = obj.default ? `(default: \`${obj.default}\`) ` : '';
|
||||||
|
|
||||||
return `${indent}${prefix}${printType(obj.type)} - ${optional}${defaults}${obj.desc}`;
|
return `${indent}${prefix}${printType(obj.type, obj)} - ${optional}${defaults}${obj.desc}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatType (obj) {
|
function formatType (obj) {
|
||||||
|
@ -211,3 +211,36 @@ export function inTraceType (whatTrace) {
|
|||||||
|
|
||||||
return whatTrace;
|
return whatTrace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function inDeriveType (derive) {
|
||||||
|
return derive && derive.type === 'hard' ? 'hard' : 'soft';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inDeriveHash (derive) {
|
||||||
|
const hash = derive && derive.hash ? derive.hash : derive;
|
||||||
|
const type = inDeriveType(derive);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hash: inHex(hash),
|
||||||
|
type
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inDeriveIndex (derive) {
|
||||||
|
if (!derive) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isArray(derive)) {
|
||||||
|
derive = [derive];
|
||||||
|
}
|
||||||
|
|
||||||
|
return derive.map(item => {
|
||||||
|
const index = inNumber10(item && item.index ? item.index : item);
|
||||||
|
|
||||||
|
return {
|
||||||
|
index,
|
||||||
|
type: inDeriveType(item)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -16,7 +16,11 @@
|
|||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
import { inAddress, inBlockNumber, inData, inFilter, inHex, inNumber10, inNumber16, inOptions, inTraceType } from './input';
|
import {
|
||||||
|
inAddress, inBlockNumber, inData, inFilter, inHex,
|
||||||
|
inNumber10, inNumber16, inOptions, inTraceType,
|
||||||
|
inDeriveHash, inDeriveIndex
|
||||||
|
} from './input';
|
||||||
import { isAddress } from '../../../test/types';
|
import { isAddress } from '../../../test/types';
|
||||||
|
|
||||||
describe('api/format/input', () => {
|
describe('api/format/input', () => {
|
||||||
@ -215,7 +219,7 @@ describe('api/format/input', () => {
|
|||||||
expect(formatted.to).to.equal('');
|
expect(formatted.to).to.equal('');
|
||||||
});
|
});
|
||||||
|
|
||||||
['gas', 'gasPrice', 'value', 'minBlock', 'nonce'].forEach((input) => {
|
['gas', 'gasPrice', 'value', 'nonce'].forEach((input) => {
|
||||||
it(`formats ${input} number as hexnumber`, () => {
|
it(`formats ${input} number as hexnumber`, () => {
|
||||||
const block = {};
|
const block = {};
|
||||||
|
|
||||||
@ -226,8 +230,8 @@ describe('api/format/input', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes minBlock as null when specified as such', () => {
|
it('passes condition as null when specified as such', () => {
|
||||||
expect(inOptions({ minBlock: null })).to.deep.equal({ minBlock: null });
|
expect(inOptions({ condition: null })).to.deep.equal({ condition: null });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores and passes through unknown keys', () => {
|
it('ignores and passes through unknown keys', () => {
|
||||||
@ -272,4 +276,66 @@ describe('api/format/input', () => {
|
|||||||
expect(inTraceType(type)).to.deep.equal([type]);
|
expect(inTraceType(type)).to.deep.equal([type]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('inDeriveHash', () => {
|
||||||
|
it('returns derive hash', () => {
|
||||||
|
expect(inDeriveHash(1)).to.deep.equal({
|
||||||
|
hash: '0x1',
|
||||||
|
type: 'soft'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(inDeriveHash(null)).to.deep.equal({
|
||||||
|
hash: '0x',
|
||||||
|
type: 'soft'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(inDeriveHash({
|
||||||
|
hash: 5
|
||||||
|
})).to.deep.equal({
|
||||||
|
hash: '0x5',
|
||||||
|
type: 'soft'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(inDeriveHash({
|
||||||
|
hash: 5,
|
||||||
|
type: 'hard'
|
||||||
|
})).to.deep.equal({
|
||||||
|
hash: '0x5',
|
||||||
|
type: 'hard'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('inDeriveIndex', () => {
|
||||||
|
it('returns derive hash', () => {
|
||||||
|
expect(inDeriveIndex(null)).to.deep.equal([]);
|
||||||
|
expect(inDeriveIndex([])).to.deep.equal([]);
|
||||||
|
|
||||||
|
expect(inDeriveIndex([1])).to.deep.equal([{
|
||||||
|
index: 1,
|
||||||
|
type: 'soft'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
expect(inDeriveIndex({
|
||||||
|
index: 1
|
||||||
|
})).to.deep.equal([{
|
||||||
|
index: 1,
|
||||||
|
type: 'soft'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
expect(inDeriveIndex([{
|
||||||
|
index: 1,
|
||||||
|
type: 'hard'
|
||||||
|
}, 5])).to.deep.equal([
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
type: 'hard'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 5,
|
||||||
|
type: 'soft'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -280,12 +280,6 @@ export function outTransaction (tx) {
|
|||||||
tx[key] = outTransactionCondition(tx[key]);
|
tx[key] = outTransactionCondition(tx[key]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'minBlock':
|
|
||||||
tx[key] = tx[key]
|
|
||||||
? outNumber(tx[key])
|
|
||||||
: null;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'creates':
|
case 'creates':
|
||||||
case 'from':
|
case 'from':
|
||||||
case 'to':
|
case 'to':
|
||||||
|
@ -384,7 +384,7 @@ describe('api/format/output', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
['blockNumber', 'gasPrice', 'gas', 'minBlock', 'nonce', 'transactionIndex', 'value'].forEach((input) => {
|
['blockNumber', 'gasPrice', 'gas', 'nonce', 'transactionIndex', 'value'].forEach((input) => {
|
||||||
it(`formats ${input} number as hexnumber`, () => {
|
it(`formats ${input} number as hexnumber`, () => {
|
||||||
const block = {};
|
const block = {};
|
||||||
|
|
||||||
@ -396,8 +396,8 @@ describe('api/format/output', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes minBlock as null when null', () => {
|
it('passes condition as null when null', () => {
|
||||||
expect(outTransaction({ minBlock: null })).to.deep.equal({ minBlock: null });
|
expect(outTransaction({ condition: null })).to.deep.equal({ condition: null });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores and passes through unknown keys', () => {
|
it('ignores and passes through unknown keys', () => {
|
||||||
|
@ -14,7 +14,9 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input';
|
import {
|
||||||
|
inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber, inDeriveHash, inDeriveIndex
|
||||||
|
} from '../../format/input';
|
||||||
import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outHwAccountInfo, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output';
|
import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outHwAccountInfo, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output';
|
||||||
|
|
||||||
export default class Parity {
|
export default class Parity {
|
||||||
@ -117,6 +119,18 @@ export default class Parity {
|
|||||||
.execute('parity_devLogsLevels');
|
.execute('parity_devLogsLevels');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deriveAddressHash (address, password, hash, shouldSave) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_deriveAddressHash', inAddress(address), password, inDeriveHash(hash), !!shouldSave)
|
||||||
|
.then(outAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
deriveAddressIndex (address, password, index, shouldSave) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_deriveAddressIndex', inAddress(address), password, inDeriveIndex(index), !!shouldSave)
|
||||||
|
.then(outAddress);
|
||||||
|
}
|
||||||
|
|
||||||
dropNonReservedPeers () {
|
dropNonReservedPeers () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_dropNonReservedPeers');
|
.execute('parity_dropNonReservedPeers');
|
||||||
@ -137,6 +151,11 @@ export default class Parity {
|
|||||||
.execute('parity_executeUpgrade');
|
.execute('parity_executeUpgrade');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportAccount (address, password) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_exportAccount', inAddress(address), password);
|
||||||
|
}
|
||||||
|
|
||||||
extraData () {
|
extraData () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_extraData');
|
.execute('parity_extraData');
|
||||||
@ -389,6 +408,12 @@ export default class Parity {
|
|||||||
.execute('parity_removeReservedPeer', encode);
|
.execute('parity_removeReservedPeer', encode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeTransaction (hash) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_removeTransaction', inHex(hash))
|
||||||
|
.then(outTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
rpcSettings () {
|
rpcSettings () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_rpcSettings');
|
.execute('parity_rpcSettings');
|
||||||
|
@ -379,7 +379,9 @@ export default {
|
|||||||
gasPrice: '0x2d20cff33',
|
gasPrice: '0x2d20cff33',
|
||||||
hash: '0x09e64eb1ae32bb9ac415ce4ddb3dbad860af72d9377bb5f073c9628ab413c532',
|
hash: '0x09e64eb1ae32bb9ac415ce4ddb3dbad860af72d9377bb5f073c9628ab413c532',
|
||||||
input: '0x',
|
input: '0x',
|
||||||
minBlock: null,
|
condition: {
|
||||||
|
block: 1
|
||||||
|
},
|
||||||
networkId: null,
|
networkId: null,
|
||||||
nonce: '0x0',
|
nonce: '0x0',
|
||||||
publicKey: '0x3fa8c08c65a83f6b4ea3e04e1cc70cbe3cd391499e3e05ab7dedf28aff9afc538200ff93e3f2b2cb5029f03c7ebee820d63a4c5a9541c83acebe293f54cacf0e',
|
publicKey: '0x3fa8c08c65a83f6b4ea3e04e1cc70cbe3cd391499e3e05ab7dedf28aff9afc538200ff93e3f2b2cb5029f03c7ebee820d63a4c5a9541c83acebe293f54cacf0e',
|
||||||
@ -421,7 +423,7 @@ export default {
|
|||||||
|
|
||||||
netChain: {
|
netChain: {
|
||||||
section: SECTION_NET,
|
section: SECTION_NET,
|
||||||
desc: 'Returns the name of the connected chain.',
|
desc: 'Returns the name of the connected chain. DEPRECATED use `parity_chain` instead.',
|
||||||
params: [],
|
params: [],
|
||||||
returns: {
|
returns: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -565,7 +567,9 @@ export default {
|
|||||||
gasPrice: '0xba43b7400',
|
gasPrice: '0xba43b7400',
|
||||||
hash: '0x160b3c30ab1cf5871083f97ee1cee3901cfba3b0a2258eb337dd20a7e816b36e',
|
hash: '0x160b3c30ab1cf5871083f97ee1cee3901cfba3b0a2258eb337dd20a7e816b36e',
|
||||||
input: '0x095ea7b3000000000000000000000000bf4ed7b27f1d666546e30d74d50d173d20bca75400000000000000000000000000002643c948210b4bd99244ccd64d5555555555',
|
input: '0x095ea7b3000000000000000000000000bf4ed7b27f1d666546e30d74d50d173d20bca75400000000000000000000000000002643c948210b4bd99244ccd64d5555555555',
|
||||||
minBlock: null,
|
condition: {
|
||||||
|
block: 1
|
||||||
|
},
|
||||||
networkId: 1,
|
networkId: 1,
|
||||||
nonce: '0x5',
|
nonce: '0x5',
|
||||||
publicKey: '0x96157302dade55a1178581333e57d60ffe6fdf5a99607890456a578b4e6b60e335037d61ed58aa4180f9fd747dc50d44a7924aa026acbfb988b5062b629d6c36',
|
publicKey: '0x96157302dade55a1178581333e57d60ffe6fdf5a99607890456a578b4e6b60e335037d61ed58aa4180f9fd747dc50d44a7924aa026acbfb988b5062b629d6c36',
|
||||||
@ -585,6 +589,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
pendingTransactionsStats: {
|
pendingTransactionsStats: {
|
||||||
|
section: SECTION_NET,
|
||||||
desc: 'Returns propagation stats for transactions in the queue.',
|
desc: 'Returns propagation stats for transactions in the queue.',
|
||||||
params: [],
|
params: [],
|
||||||
returns: {
|
returns: {
|
||||||
@ -602,6 +607,49 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
removeTransaction: {
|
||||||
|
section: SECTION_NET,
|
||||||
|
desc: 'Removes transaction from local transaction pool. Scheduled transactions and not-propagated transactions are safe to remove, removal of other transactions may have no effect though.',
|
||||||
|
params: [{
|
||||||
|
type: Hash,
|
||||||
|
desc: 'Hash of transaction to remove.',
|
||||||
|
example: '0x2547ea3382099c7c76d33dd468063b32d41016aacb02cbd51ebc14ff5d2b6a43'
|
||||||
|
}],
|
||||||
|
returns: {
|
||||||
|
type: Object,
|
||||||
|
desc: 'Removed transaction or `null`.',
|
||||||
|
details: TransactionResponse.details,
|
||||||
|
example: [
|
||||||
|
{
|
||||||
|
blockHash: null,
|
||||||
|
blockNumber: null,
|
||||||
|
creates: null,
|
||||||
|
from: '0xee3ea02840129123d5397f91be0391283a25bc7d',
|
||||||
|
gas: '0x23b58',
|
||||||
|
gasPrice: '0xba43b7400',
|
||||||
|
hash: '0x160b3c30ab1cf5871083f97ee1cee3901cfba3b0a2258eb337dd20a7e816b36e',
|
||||||
|
input: '0x095ea7b3000000000000000000000000bf4ed7b27f1d666546e30d74d50d173d20bca75400000000000000000000000000002643c948210b4bd99244ccd64d5555555555',
|
||||||
|
condition: {
|
||||||
|
block: 1
|
||||||
|
},
|
||||||
|
networkId: 1,
|
||||||
|
nonce: '0x5',
|
||||||
|
publicKey: '0x96157302dade55a1178581333e57d60ffe6fdf5a99607890456a578b4e6b60e335037d61ed58aa4180f9fd747dc50d44a7924aa026acbfb988b5062b629d6c36',
|
||||||
|
r: '0x92e8beb19af2bad0511d516a86e77fa73004c0811b2173657a55797bdf8558e1',
|
||||||
|
raw: '0xf8aa05850ba43b740083023b5894bb9bc244d798123fde783fcc1c72d3bb8c18941380b844095ea7b3000000000000000000000000bf4ed7b27f1d666546e30d74d50d173d20bca75400000000000000000000000000002643c948210b4bd99244ccd64d555555555526a092e8beb19af2bad0511d516a86e77fa73004c0811b2173657a55797bdf8558e1a062b4d4d125bbcb9c162453bc36ca156537543bb4414d59d1805d37fb63b351b8',
|
||||||
|
s: '0x62b4d4d125bbcb9c162453bc36ca156537543bb4414d59d1805d37fb63b351b8',
|
||||||
|
standardV: '0x1',
|
||||||
|
to: '0xbb9bc244d798123fde783fcc1c72d3bb8c189413',
|
||||||
|
transactionIndex: null,
|
||||||
|
v: '0x26',
|
||||||
|
value: '0x0'
|
||||||
|
},
|
||||||
|
new Dummy('{ ... }'),
|
||||||
|
new Dummy('{ ... }')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
phraseToAddress: {
|
phraseToAddress: {
|
||||||
section: SECTION_ACCOUNTS,
|
section: SECTION_ACCOUNTS,
|
||||||
desc: 'Converts a secret phrase into the corresponding address.',
|
desc: 'Converts a secret phrase into the corresponding address.',
|
||||||
@ -892,7 +940,9 @@ export default {
|
|||||||
v: '0x25',
|
v: '0x25',
|
||||||
r: '0xb40c6967a7e8bbdfd99a25fd306b9ef23b80e719514aeb7ddd19e2303d6fc139',
|
r: '0xb40c6967a7e8bbdfd99a25fd306b9ef23b80e719514aeb7ddd19e2303d6fc139',
|
||||||
s: '0x6bf770ab08119e67dc29817e1412a0e3086f43da308c314db1b3bca9fb6d32bd',
|
s: '0x6bf770ab08119e67dc29817e1412a0e3086f43da308c314db1b3bca9fb6d32bd',
|
||||||
minBlock: null
|
condition: {
|
||||||
|
block: 1
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new Dummy('{ ... }, { ... }, ...')
|
new Dummy('{ ... }, { ... }, ...')
|
||||||
]
|
]
|
||||||
@ -1283,12 +1333,14 @@ export default {
|
|||||||
params: [
|
params: [
|
||||||
{
|
{
|
||||||
type: Array,
|
type: Array,
|
||||||
desc: 'List of the Geth addresses to import.'
|
desc: 'List of the Geth addresses to import.',
|
||||||
|
example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: Array,
|
type: Array,
|
||||||
desc: 'Array of the imported addresses.'
|
desc: 'Array of the imported addresses.',
|
||||||
|
example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1298,7 +1350,114 @@ export default {
|
|||||||
params: [],
|
params: [],
|
||||||
returns: {
|
returns: {
|
||||||
type: Array,
|
type: Array,
|
||||||
desc: '20 Bytes addresses owned by the client.'
|
desc: '20 Bytes addresses owned by the client.',
|
||||||
|
example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deriveAddressHash: {
|
||||||
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
|
desc: 'Derive new address from given account address using specific hash.',
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
type: Address,
|
||||||
|
desc: 'Account address to derive from.',
|
||||||
|
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: String,
|
||||||
|
desc: 'Password to the account.',
|
||||||
|
example: 'hunter2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: Object,
|
||||||
|
desc: 'Derivation hash and type (`soft` or `hard`). E.g. `{ hash: "0x123..123", type: "hard" }`.',
|
||||||
|
example: {
|
||||||
|
hash: '0x2547ea3382099c7c76d33dd468063b32d41016aacb02cbd51ebc14ff5d2b6a43',
|
||||||
|
type: 'hard'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: Boolean,
|
||||||
|
desc: 'Flag indicating if the account should be saved.',
|
||||||
|
example: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: Address,
|
||||||
|
desc: '20 Bytes new derived address.',
|
||||||
|
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deriveAddressIndex: {
|
||||||
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
|
desc: 'Derive new address from given account address using hierarchical derivation (sequence of 32-bit integer indices).',
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
type: Address,
|
||||||
|
desc: 'Account address to export.',
|
||||||
|
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: String,
|
||||||
|
desc: 'Password to the account.',
|
||||||
|
example: 'hunter2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: Array,
|
||||||
|
desc: 'Hierarchical derivation sequence of index and type (`soft` or `hard`). E.g. `[{index:1,type:"hard"},{index:2,type:"soft"}]`.',
|
||||||
|
example: [
|
||||||
|
{ index: 1, type: 'hard' },
|
||||||
|
{ index: 2, type: 'soft' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: Boolean,
|
||||||
|
desc: 'Flag indicating if the account should be saved.',
|
||||||
|
example: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: Address,
|
||||||
|
desc: '20 Bytes new derived address.',
|
||||||
|
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
exportAccount: {
|
||||||
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
|
desc: 'Returns a standard wallet file for given account if password matches.',
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
type: Address,
|
||||||
|
desc: 'Account address to export.',
|
||||||
|
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: String,
|
||||||
|
desc: 'Password to the account.',
|
||||||
|
example: 'hunter2'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: Object,
|
||||||
|
desc: 'Standard wallet JSON.',
|
||||||
|
example: {
|
||||||
|
'address': '0042e5d2a662eeaca8a7e828c174f98f35d8925b',
|
||||||
|
'crypto': {
|
||||||
|
'cipher': 'aes-128-ctr',
|
||||||
|
'cipherparams': { 'iv': 'a1c6ff99070f8032ca1c4e8add006373' },
|
||||||
|
'ciphertext': 'df27e3db64aa18d984b6439443f73660643c2d119a6f0fa2fa9a6456fc802d75',
|
||||||
|
'kdf': 'pbkdf2',
|
||||||
|
'kdfparams': { 'c': 10240, 'dklen': 32, 'prf': 'hmac-sha256', 'salt': 'ddc325335cda5567a1719313e73b4842511f3e4a837c9658eeb78e51ebe8c815' },
|
||||||
|
'mac': '3dc888ae79cbb226ff9c455669f6cf2d79be72120f2298f6cb0d444fddc0aa3d'
|
||||||
|
},
|
||||||
|
'id': '6a186c80-7797-cff2-bc2e-7c1d6a6cc76e',
|
||||||
|
'meta': '{"passwordHint":"parity-export-test","timestamp":1490017814987}',
|
||||||
|
'name': 'parity-export-test',
|
||||||
|
'version': 3
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1622,7 +1781,14 @@ export default {
|
|||||||
example: {
|
example: {
|
||||||
from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
|
from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
|
||||||
to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567',
|
to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567',
|
||||||
value: fromDecimal(2441406250)
|
gas: fromDecimal(30400),
|
||||||
|
gasPrice: fromDecimal(10000000000000),
|
||||||
|
value: fromDecimal(2441406250),
|
||||||
|
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
|
||||||
|
condition: {
|
||||||
|
block: 354221,
|
||||||
|
time: new Date()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { Quantity, Data, BlockNumber } from '../types';
|
import { Quantity, Data } from '../types';
|
||||||
import { fromDecimal, Dummy } from '../helpers';
|
import { fromDecimal, Dummy } from '../helpers';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -71,9 +71,9 @@ export default {
|
|||||||
desc: 'Gas provided by the sender in Wei.',
|
desc: 'Gas provided by the sender in Wei.',
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
minBlock: {
|
condition: {
|
||||||
type: BlockNumber,
|
type: Object,
|
||||||
desc: 'Integer block number, or the string `\'latest\'`, `\'earliest\'` or `\'pending\'`. Request will not be propagated till the given block is reached.',
|
desc: 'Condition for scheduled transaction. Can be either an integer block number `{ block: 1 }` or UTC timestamp (in seconds) `{ timestamp: 1491290692 }`.',
|
||||||
optional: true
|
optional: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -114,7 +114,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
confirmRequestWithToken: {
|
confirmRequestWithToken: {
|
||||||
desc: 'Confirm specific request with token.',
|
desc: 'Confirm specific request with rolling token.',
|
||||||
params: [
|
params: [
|
||||||
{
|
{
|
||||||
type: Quantity,
|
type: Quantity,
|
||||||
@ -135,9 +135,9 @@ export default {
|
|||||||
desc: 'Gas provided by the sender in Wei.',
|
desc: 'Gas provided by the sender in Wei.',
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
minBlock: {
|
condition: {
|
||||||
type: BlockNumber,
|
type: Object,
|
||||||
desc: 'Integer block number, or the string `\'latest\'`, `\'earliest\'` or `\'pending\'`. Request will not be propagated till the given block is reached.',
|
desc: 'Conditional submission of the transaction. Can be either an integer block number `{ block: 1 }` or UTC timestamp (in seconds) `{ time: 1491290692 }` or `null`.',
|
||||||
optional: true
|
optional: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -145,7 +145,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: String,
|
type: String,
|
||||||
desc: 'Password.',
|
desc: 'Password (initially) or a token returned by the previous call.',
|
||||||
example: 'hunter2'
|
example: 'hunter2'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -159,7 +159,7 @@ export default {
|
|||||||
},
|
},
|
||||||
token: {
|
token: {
|
||||||
type: String,
|
type: String,
|
||||||
desc: 'Token used to authenticate the request.'
|
desc: 'Token used to authenticate the next request.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
example: {
|
example: {
|
||||||
|
@ -102,9 +102,9 @@ export class TransactionRequest {
|
|||||||
desc: 'Integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce.',
|
desc: 'Integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce.',
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
minBlock: {
|
condition: {
|
||||||
type: BlockNumber,
|
type: Object,
|
||||||
desc: 'Delay until this block if specified.',
|
desc: 'Conditional submission of the transaction. Can be either an integer block number `{ block: 1 }` or UTC timestamp (in seconds) `{ time: 1491290692 }` or `null`.',
|
||||||
optional: true
|
optional: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import dateDifference from 'date-difference';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
@ -24,6 +26,7 @@ import { txLink } from '~/3rdparty/etherscan/links';
|
|||||||
import IdentityIcon from '../../IdentityIcon';
|
import IdentityIcon from '../../IdentityIcon';
|
||||||
import IdentityName from '../../IdentityName';
|
import IdentityName from '../../IdentityName';
|
||||||
import MethodDecoding from '../../MethodDecoding';
|
import MethodDecoding from '../../MethodDecoding';
|
||||||
|
import MethodDecodingStore from '~/ui/MethodDecoding/methodDecodingStore';
|
||||||
|
|
||||||
import styles from '../txList.css';
|
import styles from '../txList.css';
|
||||||
|
|
||||||
@ -35,11 +38,15 @@ class TxRow extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
accountAddresses: PropTypes.array.isRequired,
|
accountAddresses: PropTypes.array.isRequired,
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
|
blockNumber: PropTypes.object,
|
||||||
|
contractAddresses: PropTypes.array.isRequired,
|
||||||
netVersion: PropTypes.string.isRequired,
|
netVersion: PropTypes.string.isRequired,
|
||||||
tx: PropTypes.object.isRequired,
|
tx: PropTypes.object.isRequired,
|
||||||
|
|
||||||
block: PropTypes.object,
|
block: PropTypes.object,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
cancelTransaction: PropTypes.func,
|
||||||
|
editTransaction: PropTypes.func,
|
||||||
historic: PropTypes.bool
|
historic: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,6 +54,33 @@ class TxRow extends Component {
|
|||||||
historic: true
|
historic: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
isCancelOpen: false,
|
||||||
|
isEditOpen: false,
|
||||||
|
canceled: false,
|
||||||
|
editing: false,
|
||||||
|
isContract: false,
|
||||||
|
isDeploy: false
|
||||||
|
};
|
||||||
|
|
||||||
|
methodDecodingStore = MethodDecodingStore.get(this.context.api);
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
const { address, tx } = this.props;
|
||||||
|
|
||||||
|
this
|
||||||
|
.methodDecodingStore
|
||||||
|
.lookup(address, tx)
|
||||||
|
.then((lookup) => {
|
||||||
|
const newState = {
|
||||||
|
isContract: lookup.contract,
|
||||||
|
isDeploy: lookup.deploy
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState(newState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { address, className, historic, netVersion, tx } = this.props;
|
const { address, className, historic, netVersion, tx } = this.props;
|
||||||
|
|
||||||
@ -135,11 +169,132 @@ class TxRow extends Component {
|
|||||||
return (
|
return (
|
||||||
<td className={ styles.timestamp }>
|
<td className={ styles.timestamp }>
|
||||||
<div>{ blockNumber && block ? moment(block.timestamp).fromNow() : null }</div>
|
<div>{ blockNumber && block ? moment(block.timestamp).fromNow() : null }</div>
|
||||||
<div>{ blockNumber ? _blockNumber.toFormat() : 'Pending' }</div>
|
<div>{ blockNumber ? _blockNumber.toFormat() : this.renderCancelToggle() }</div>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderCancelToggle () {
|
||||||
|
const { canceled, editing, isCancelOpen, isEditOpen } = this.state;
|
||||||
|
|
||||||
|
if (canceled) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.pending }>
|
||||||
|
<FormattedMessage
|
||||||
|
lassName={ styles.uppercase }
|
||||||
|
id='ui.txList.txRow.canceled'
|
||||||
|
defaultMessage='Canceled'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.pending }>
|
||||||
|
<div className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.editing'
|
||||||
|
defaultMessage='Editing'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCancelOpen && !isEditOpen) {
|
||||||
|
const pendingStatus = this.getCondition();
|
||||||
|
|
||||||
|
if (pendingStatus === 'submitting') {
|
||||||
|
return (
|
||||||
|
<div className={ styles.pending }>
|
||||||
|
<div />
|
||||||
|
<div className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.submitting'
|
||||||
|
defaultMessage='Submitting'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={ styles.pending }>
|
||||||
|
<span>
|
||||||
|
{ pendingStatus }
|
||||||
|
</span>
|
||||||
|
<div className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.scheduled'
|
||||||
|
defaultMessage='Scheduled'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<a onClick={ this.setEdit } className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.edit'
|
||||||
|
defaultMessage='Edit'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<span>{' | '}</span>
|
||||||
|
<a onClick={ this.setCancel } className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.cancel'
|
||||||
|
defaultMessage='Cancel'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let which;
|
||||||
|
|
||||||
|
if (isCancelOpen) {
|
||||||
|
which = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.verify.cancelEditCancel'
|
||||||
|
defaultMessage='Cancel'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
which = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.verify.cancelEditEdit'
|
||||||
|
defaultMessage='Edit'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.pending }>
|
||||||
|
<div />
|
||||||
|
<div className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.verify'
|
||||||
|
defaultMessage='Are you sure?'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<a onClick={ (isCancelOpen) ? this.cancelTx : this.editTx }>
|
||||||
|
{ which }
|
||||||
|
</a>
|
||||||
|
<span>{' | '}</span>
|
||||||
|
<a onClick={ this.revertEditCancel }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.verify.nevermind'
|
||||||
|
defaultMessage='Nevermind'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getIsKnownContract (address) {
|
||||||
|
const { contractAddresses } = this.props;
|
||||||
|
|
||||||
|
return contractAddresses
|
||||||
|
.map((a) => a.toLowerCase())
|
||||||
|
.includes(address.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
addressLink (address) {
|
addressLink (address) {
|
||||||
const { accountAddresses } = this.props;
|
const { accountAddresses } = this.props;
|
||||||
const isAccount = accountAddresses.includes(address);
|
const isAccount = accountAddresses.includes(address);
|
||||||
@ -150,6 +305,70 @@ class TxRow extends Component {
|
|||||||
|
|
||||||
return `/addresses/${address}`;
|
return `/addresses/${address}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCondition = () => {
|
||||||
|
const { blockNumber, tx } = this.props;
|
||||||
|
let { time, block } = tx.condition;
|
||||||
|
|
||||||
|
if (time) {
|
||||||
|
if ((time.getTime() - Date.now()) >= 0) {
|
||||||
|
// return `${dateDifference(new Date(), time, { compact: true })} left`;
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.pendingStatus.time'
|
||||||
|
defaultMessage='{time} left'
|
||||||
|
values={ {
|
||||||
|
time: dateDifference(new Date(), time, { compact: true })
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return 'submitting';
|
||||||
|
}
|
||||||
|
} else if (blockNumber) {
|
||||||
|
block = blockNumber.minus(block);
|
||||||
|
// return (block.toNumber() < 0)
|
||||||
|
// ? block.abs().toFormat(0) + ' blocks left'
|
||||||
|
// : 'submitting';
|
||||||
|
if (block.toNumber() < 0) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.pendingStatus.blocksLeft'
|
||||||
|
defaultMessage='{blockNumber} blocks left'
|
||||||
|
values={ {
|
||||||
|
blockNumber: block.abs().toFormat(0)
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return 'submitting';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelTx = () => {
|
||||||
|
const { cancelTransaction, tx } = this.props;
|
||||||
|
|
||||||
|
cancelTransaction(this, tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
editTx = () => {
|
||||||
|
const { editTransaction, tx } = this.props;
|
||||||
|
|
||||||
|
editTransaction(this, tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCancel = () => {
|
||||||
|
this.setState({ isCancelOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
setEdit = () => {
|
||||||
|
this.setState({ isEditOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
revertEditCancel = () => {
|
||||||
|
this.setState({ isCancelOpen: false, isEditOpen: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (initState) {
|
function mapStateToProps (initState) {
|
||||||
|
@ -14,35 +14,51 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { action, observable, transaction } from 'mobx';
|
import { action, observable } from 'mobx';
|
||||||
import { uniq } from 'lodash';
|
|
||||||
|
|
||||||
export default class Store {
|
export default class Store {
|
||||||
@observable blocks = {};
|
@observable blocks = {};
|
||||||
@observable sortedHashes = [];
|
@observable sortedHashes = [];
|
||||||
@observable transactions = {};
|
@observable transactions = {};
|
||||||
|
|
||||||
constructor (api) {
|
constructor (api, onNewError, hashes) {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
this._subscriptionId = 0;
|
this._onNewError = onNewError;
|
||||||
this._pendingHashes = [];
|
this.loadTransactions(hashes);
|
||||||
|
|
||||||
this.subscribe();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action addBlocks = (blocks) => {
|
@action addHash = (hash) => {
|
||||||
this.blocks = Object.assign({}, this.blocks, blocks);
|
if (!this.sortedHashes.includes(hash)) {
|
||||||
|
this.sortedHashes.push(hash);
|
||||||
|
this.sortHashes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action addTransactions = (transactions) => {
|
@action removeHash = (hash) => {
|
||||||
transaction(() => {
|
this.sortedHashes.remove(hash);
|
||||||
this.transactions = Object.assign({}, this.transactions, transactions);
|
let tx = this.transactions[hash];
|
||||||
this.sortedHashes = Object
|
|
||||||
.keys(this.transactions)
|
|
||||||
.sort((ahash, bhash) => {
|
|
||||||
const bnA = this.transactions[ahash].blockNumber;
|
|
||||||
const bnB = this.transactions[bhash].blockNumber;
|
|
||||||
|
|
||||||
|
if (tx) {
|
||||||
|
delete this.transactions[hash];
|
||||||
|
delete this.blocks[tx.blockNumber];
|
||||||
|
}
|
||||||
|
this.sortHashes();
|
||||||
|
}
|
||||||
|
|
||||||
|
containsAll = (arr1, arr2) => {
|
||||||
|
return arr2.every((arr2Item) => arr1.includes(arr2Item));
|
||||||
|
}
|
||||||
|
|
||||||
|
sameHashList = (transactions) => {
|
||||||
|
return this.containsAll(transactions, this.sortedHashes) && this.containsAll(this.sortedHashes, transactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
sortHashes = () => {
|
||||||
|
this.sortedHashes = this.sortedHashes.sort((hashA, hashB) => {
|
||||||
|
const bnA = this.transactions[hashA].blockNumber;
|
||||||
|
const bnB = this.transactions[hashB].blockNumber;
|
||||||
|
|
||||||
|
// 0 is a special case (has not been added to the blockchain yet)
|
||||||
if (bnB.eq(0)) {
|
if (bnB.eq(0)) {
|
||||||
return bnB.eq(bnA) ? 0 : 1;
|
return bnB.eq(bnA) ? 0 : 1;
|
||||||
} else if (bnA.eq(0)) {
|
} else if (bnA.eq(0)) {
|
||||||
@ -51,113 +67,82 @@ export default class Store {
|
|||||||
|
|
||||||
return bnB.comparedTo(bnA);
|
return bnB.comparedTo(bnA);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action clearPending () {
|
loadTransactions (_txhashes) {
|
||||||
this._pendingHashes = [];
|
const { eth } = this._api;
|
||||||
}
|
|
||||||
|
|
||||||
subscribe () {
|
// Ignore special cases and if the contents of _txhashes && this.sortedHashes are the same
|
||||||
this._api
|
if (Array.isArray(_txhashes) || this.sameHashList(_txhashes)) {
|
||||||
.subscribe('eth_blockNumber', (error, blockNumber) => {
|
|
||||||
if (error) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._pendingHashes.length) {
|
// Remove any tx that are edited/cancelled
|
||||||
this.loadTransactions(this._pendingHashes);
|
this.sortedHashes
|
||||||
this.clearPending();
|
.forEach((hash) => {
|
||||||
|
if (!_txhashes.includes(hash)) {
|
||||||
|
this.removeHash(hash);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add any new tx
|
||||||
|
_txhashes
|
||||||
|
.forEach((txhash) => {
|
||||||
|
if (this.sortedHashes.includes(txhash)) { return; }
|
||||||
|
eth.getTransactionByHash(txhash)
|
||||||
|
.then((tx) => {
|
||||||
|
if (!tx) { return; }
|
||||||
|
this.transactions[txhash] = tx;
|
||||||
|
// If the tx has a blockHash, let's get the blockNumber, otherwise it's ready to be added
|
||||||
|
if (tx.blockHash) {
|
||||||
|
eth.getBlockByNumber(tx.blockNumber)
|
||||||
|
.then((block) => {
|
||||||
|
this.blocks[tx.blockNumber] = block;
|
||||||
|
this.addHash(txhash);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.addHash(txhash);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelTransaction = (txComponent, tx) => {
|
||||||
|
const { parity } = this._api;
|
||||||
|
const { hash } = tx;
|
||||||
|
|
||||||
|
parity
|
||||||
|
.removeTransaction(hash)
|
||||||
|
.then(() => {
|
||||||
|
txComponent.setState({ canceled: true });
|
||||||
})
|
})
|
||||||
.then((subscriptionId) => {
|
.catch((err) => {
|
||||||
this._subscriptionId = subscriptionId;
|
this._onNewError({ message: err });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribe () {
|
editTransaction = (txComponent, tx) => {
|
||||||
if (!this._subscriptionId) {
|
const { parity } = this._api;
|
||||||
return;
|
const { hash, gas, gasPrice, to, from, value, input, condition } = tx;
|
||||||
}
|
|
||||||
|
|
||||||
this._api.unsubscribe(this._subscriptionId);
|
parity
|
||||||
this._subscriptionId = 0;
|
.removeTransaction(hash)
|
||||||
}
|
.then(() => {
|
||||||
|
parity.postTransaction({
|
||||||
loadTransactions (_txhashes = []) {
|
from,
|
||||||
const promises = _txhashes
|
to,
|
||||||
.filter((txhash) => !this.transactions[txhash] || this._pendingHashes.includes(txhash))
|
gas,
|
||||||
.map((txhash) => {
|
gasPrice,
|
||||||
return Promise
|
value,
|
||||||
.all([
|
condition,
|
||||||
this._api.eth.getTransactionByHash(txhash),
|
data: input
|
||||||
this._api.eth.getTransactionReceipt(txhash)
|
|
||||||
])
|
|
||||||
.then(([
|
|
||||||
transaction = {},
|
|
||||||
transactionReceipt = {}
|
|
||||||
]) => {
|
|
||||||
return {
|
|
||||||
...transactionReceipt,
|
|
||||||
...transaction
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (!promises.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise
|
|
||||||
.all(promises)
|
|
||||||
.then((_transactions) => {
|
|
||||||
const blockNumbers = [];
|
|
||||||
const transactions = _transactions
|
|
||||||
.filter((tx) => tx && tx.hash)
|
|
||||||
.reduce((txs, tx) => {
|
|
||||||
txs[tx.hash] = tx;
|
|
||||||
|
|
||||||
if (tx.blockNumber && tx.blockNumber.gt(0)) {
|
|
||||||
blockNumbers.push(tx.blockNumber.toNumber());
|
|
||||||
}
|
|
||||||
|
|
||||||
return txs;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
// No need to add transactions if there are none
|
|
||||||
if (Object.keys(transactions).length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addTransactions(transactions);
|
|
||||||
this.loadBlocks(blockNumbers);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.then(() => {
|
||||||
console.warn('loadTransactions', error);
|
txComponent.setState({ editing: true });
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadBlocks (_blockNumbers) {
|
|
||||||
const blockNumbers = uniq(_blockNumbers).filter((bn) => !this.blocks[bn]);
|
|
||||||
|
|
||||||
if (!blockNumbers || !blockNumbers.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise
|
|
||||||
.all(blockNumbers.map((blockNumber) => this._api.eth.getBlockByNumber(blockNumber)))
|
|
||||||
.then((blocks) => {
|
|
||||||
this.addBlocks(
|
|
||||||
blocks.reduce((blocks, block, index) => {
|
|
||||||
blocks[blockNumbers[index]] = block;
|
|
||||||
return blocks;
|
|
||||||
}, {})
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((err) => {
|
||||||
console.warn('loadBlocks', error);
|
this._onNewError({ message: err });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ describe('ui/TxList/store', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
store = new Store(api);
|
store = new Store(api, null, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
@ -53,16 +53,14 @@ describe('ui/TxList/store', () => {
|
|||||||
expect(store.sortedHashes.peek()).to.deep.equal([]);
|
expect(store.sortedHashes.peek()).to.deep.equal([]);
|
||||||
expect(store.transactions).to.deep.equal({});
|
expect(store.transactions).to.deep.equal({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('subscribes to eth_blockNumber', () => {
|
|
||||||
expect(api.subscribe).to.have.been.calledWith('eth_blockNumber');
|
|
||||||
expect(store._subscriptionId).to.equal(SUBID);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('addBlocks', () => {
|
describe('addBlocks', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.addBlocks(BLOCKS);
|
Object.keys(BLOCKS)
|
||||||
|
.forEach((blockNumber) => {
|
||||||
|
store.blocks[blockNumber] = BLOCKS[blockNumber];
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds the blocks to the list', () => {
|
it('adds the blocks to the list', () => {
|
||||||
@ -72,7 +70,12 @@ describe('ui/TxList/store', () => {
|
|||||||
|
|
||||||
describe('addTransactions', () => {
|
describe('addTransactions', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.addTransactions(TRANSACTIONS);
|
Object.keys(TRANSACTIONS)
|
||||||
|
.forEach((hash) => {
|
||||||
|
store.transactions[hash] = TRANSACTIONS[hash];
|
||||||
|
store.addHash(hash);
|
||||||
|
});
|
||||||
|
store.sortHashes();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds all transactions to the list', () => {
|
it('adds all transactions to the list', () => {
|
||||||
@ -82,9 +85,5 @@ describe('ui/TxList/store', () => {
|
|||||||
it('sorts transactions based on blockNumber', () => {
|
it('sorts transactions based on blockNumber', () => {
|
||||||
expect(store.sortedHashes.peek()).to.deep.equal(['0x234', '0x456', '0x345', '0x123']);
|
expect(store.sortedHashes.peek()).to.deep.equal(['0x234', '0x456', '0x345', '0x123']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds pending transactions to the pending queue', () => {
|
|
||||||
expect(store._pendingHashes).to.deep.equal(['0x234', '0x456']);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -42,10 +42,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.timestamp {
|
&.timestamp {
|
||||||
padding-top: 1.5em;
|
max-width: 5em;
|
||||||
text-align: right;
|
padding-top: 0.75em;
|
||||||
|
text-align: center;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
opacity: 0.5;
|
color: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.transaction {
|
&.transaction {
|
||||||
@ -83,4 +84,16 @@
|
|||||||
.left {
|
.left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pending {
|
||||||
|
padding: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending div {
|
||||||
|
padding-bottom: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uppercase {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,17 +35,13 @@ class TxList extends Component {
|
|||||||
PropTypes.array,
|
PropTypes.array,
|
||||||
PropTypes.object
|
PropTypes.object
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
netVersion: PropTypes.string.isRequired
|
blockNumber: PropTypes.object,
|
||||||
|
netVersion: PropTypes.string.isRequired,
|
||||||
|
onNewError: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
store = new Store(this.context.api);
|
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
this.store.loadTransactions(this.props.hashes);
|
this.store = new Store(this.context.api, this.props.onNewError, this.props.hashes);
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
this.store.unsubscribe();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (newProps) {
|
componentWillReceiveProps (newProps) {
|
||||||
@ -63,20 +59,24 @@ class TxList extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRows () {
|
renderRows () {
|
||||||
const { address, netVersion } = this.props;
|
const { address, netVersion, blockNumber } = this.props;
|
||||||
|
const { editTransaction, cancelTransaction } = this.store;
|
||||||
|
|
||||||
return this.store.sortedHashes.map((txhash) => {
|
return this.store.sortedHashes.map((txhash) => {
|
||||||
const tx = this.store.transactions[txhash];
|
const tx = this.store.transactions[txhash];
|
||||||
const blockNumber = tx.blockNumber.toNumber();
|
const txBlockNumber = tx.blockNumber.toNumber();
|
||||||
const block = this.store.blocks[blockNumber];
|
const block = this.store.blocks[txBlockNumber];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TxRow
|
<TxRow
|
||||||
key={ tx.hash }
|
key={ tx.hash }
|
||||||
tx={ tx }
|
tx={ tx }
|
||||||
block={ block }
|
block={ block }
|
||||||
|
blockNumber={ blockNumber }
|
||||||
address={ address }
|
address={ address }
|
||||||
netVersion={ netVersion }
|
netVersion={ netVersion }
|
||||||
|
editTransaction={ editTransaction }
|
||||||
|
cancelTransaction={ cancelTransaction }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -21,7 +21,8 @@ import { connect } from 'react-redux';
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import Store from '../../store';
|
import Store from '../../store';
|
||||||
import * as RequestsActions from '~/redux/providers/signerActions';
|
import { newError } from '~/redux/actions';
|
||||||
|
import { startConfirmRequest, startRejectRequest } from '~/redux/providers/signerActions';
|
||||||
import { Container, Page, TxList } from '~/ui';
|
import { Container, Page, TxList } from '~/ui';
|
||||||
|
|
||||||
import RequestPending from '../../components/RequestPending';
|
import RequestPending from '../../components/RequestPending';
|
||||||
@ -35,12 +36,13 @@ class RequestsPage extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
actions: PropTypes.shape({
|
|
||||||
startConfirmRequest: PropTypes.func.isRequired,
|
|
||||||
startRejectRequest: PropTypes.func.isRequired
|
|
||||||
}).isRequired,
|
|
||||||
gasLimit: PropTypes.object.isRequired,
|
gasLimit: PropTypes.object.isRequired,
|
||||||
netVersion: PropTypes.string.isRequired,
|
netVersion: PropTypes.string.isRequired,
|
||||||
|
startConfirmRequest: PropTypes.func.isRequired,
|
||||||
|
startRejectRequest: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
blockNumber: PropTypes.object,
|
||||||
|
newError: PropTypes.func,
|
||||||
signer: PropTypes.shape({
|
signer: PropTypes.shape({
|
||||||
pending: PropTypes.array.isRequired,
|
pending: PropTypes.array.isRequired,
|
||||||
finished: PropTypes.array.isRequired
|
finished: PropTypes.array.isRequired
|
||||||
@ -68,6 +70,7 @@ class RequestsPage extends Component {
|
|||||||
|
|
||||||
renderLocalQueue () {
|
renderLocalQueue () {
|
||||||
const { localHashes } = this.store;
|
const { localHashes } = this.store;
|
||||||
|
const { blockNumber, newError } = this.props;
|
||||||
|
|
||||||
if (!localHashes.length) {
|
if (!localHashes.length) {
|
||||||
return null;
|
return null;
|
||||||
@ -77,7 +80,9 @@ class RequestsPage extends Component {
|
|||||||
<Container title='Local Transactions'>
|
<Container title='Local Transactions'>
|
||||||
<TxList
|
<TxList
|
||||||
address=''
|
address=''
|
||||||
|
blockNumber={ blockNumber }
|
||||||
hashes={ localHashes }
|
hashes={ localHashes }
|
||||||
|
onNewError={ newError }
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
@ -106,7 +111,7 @@ class RequestsPage extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderPending = (data, index) => {
|
renderPending = (data, index) => {
|
||||||
const { actions, gasLimit, netVersion } = this.props;
|
const { startConfirmRequest, startRejectRequest, gasLimit, netVersion } = this.props;
|
||||||
const { date, id, isSending, payload, origin } = data;
|
const { date, id, isSending, payload, origin } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -119,8 +124,8 @@ class RequestsPage extends Component {
|
|||||||
isSending={ isSending }
|
isSending={ isSending }
|
||||||
netVersion={ netVersion }
|
netVersion={ netVersion }
|
||||||
key={ id }
|
key={ id }
|
||||||
onConfirm={ actions.startConfirmRequest }
|
onConfirm={ startConfirmRequest }
|
||||||
onReject={ actions.startRejectRequest }
|
onReject={ startRejectRequest }
|
||||||
origin={ origin }
|
origin={ origin }
|
||||||
payload={ payload }
|
payload={ payload }
|
||||||
signerstore={ this.store }
|
signerstore={ this.store }
|
||||||
@ -130,11 +135,11 @@ class RequestsPage extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { gasLimit, netVersion } = state.nodeStatus;
|
const { gasLimit, netVersion, blockNumber } = state.nodeStatus;
|
||||||
const { actions, signer } = state;
|
const { signer } = state;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actions,
|
blockNumber,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
netVersion,
|
netVersion,
|
||||||
signer
|
signer
|
||||||
@ -142,9 +147,11 @@ function mapStateToProps (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return {
|
return bindActionCreators({
|
||||||
actions: bindActionCreators(RequestsActions, dispatch)
|
newError,
|
||||||
};
|
startConfirmRequest,
|
||||||
|
startRejectRequest
|
||||||
|
}, dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
@ -95,7 +95,11 @@ export default class SignerStore {
|
|||||||
this._api.parity
|
this._api.parity
|
||||||
.localTransactions()
|
.localTransactions()
|
||||||
.then((localTransactions) => {
|
.then((localTransactions) => {
|
||||||
this.setLocalHashes(Object.keys(localTransactions));
|
const keys = Object
|
||||||
|
.keys(localTransactions)
|
||||||
|
.filter((key) => localTransactions[key].status !== 'canceled');
|
||||||
|
|
||||||
|
this.setLocalHashes(keys);
|
||||||
})
|
})
|
||||||
.then(nextTimeout)
|
.then(nextTimeout)
|
||||||
.catch(nextTimeout);
|
.catch(nextTimeout);
|
||||||
|
@ -173,14 +173,26 @@ impl<T: NodeInfo> LocalDataStore<T> {
|
|||||||
pub fn update(&self) -> Result<(), Error> {
|
pub fn update(&self) -> Result<(), Error> {
|
||||||
trace!(target: "local_store", "Updating local store entries.");
|
trace!(target: "local_store", "Updating local store entries.");
|
||||||
|
|
||||||
let mut batch = self.db.transaction();
|
|
||||||
|
|
||||||
let local_entries: Vec<TransactionEntry> = self.node.pending_transactions()
|
let local_entries: Vec<TransactionEntry> = self.node.pending_transactions()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let local_json = ::serde_json::to_value(&local_entries).map_err(Error::Json)?;
|
self.write_txs(&local_entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear data in this column.
|
||||||
|
pub fn clear(&self) -> Result<(), Error> {
|
||||||
|
trace!(target: "local_store", "Clearing local store entries.");
|
||||||
|
|
||||||
|
self.write_txs(&[])
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper for writing a vector of transaction entries to disk.
|
||||||
|
fn write_txs(&self, txs: &[TransactionEntry]) -> Result<(), Error> {
|
||||||
|
let mut batch = self.db.transaction();
|
||||||
|
|
||||||
|
let local_json = ::serde_json::to_value(txs).map_err(Error::Json)?;
|
||||||
let json_str = format!("{}", local_json);
|
let json_str = format!("{}", local_json);
|
||||||
|
|
||||||
batch.put_vec(self.col, LOCAL_TRANSACTIONS_KEY, json_str.into_bytes());
|
batch.put_vec(self.col, LOCAL_TRANSACTIONS_KEY, json_str.into_bytes());
|
||||||
|
@ -6,6 +6,7 @@ auto_update = "none"
|
|||||||
release_track = "current"
|
release_track = "current"
|
||||||
no_download = false
|
no_download = false
|
||||||
no_consensus = false
|
no_consensus = false
|
||||||
|
no_persistent_txqueue = false
|
||||||
|
|
||||||
chain = "homestead"
|
chain = "homestead"
|
||||||
base_path = "$HOME/.parity"
|
base_path = "$HOME/.parity"
|
||||||
|
@ -93,6 +93,8 @@ usage! {
|
|||||||
flag_chain: String = "homestead", or |c: &Config| otry!(c.parity).chain.clone(),
|
flag_chain: String = "homestead", or |c: &Config| otry!(c.parity).chain.clone(),
|
||||||
flag_keys_path: String = "$BASE/keys", or |c: &Config| otry!(c.parity).keys_path.clone(),
|
flag_keys_path: String = "$BASE/keys", or |c: &Config| otry!(c.parity).keys_path.clone(),
|
||||||
flag_identity: String = "", or |c: &Config| otry!(c.parity).identity.clone(),
|
flag_identity: String = "", or |c: &Config| otry!(c.parity).identity.clone(),
|
||||||
|
flag_no_persistent_txqueue: bool = false,
|
||||||
|
or |c: &Config| otry!(c.parity).no_persistent_txqueue,
|
||||||
|
|
||||||
// -- Account Options
|
// -- Account Options
|
||||||
flag_unlock: Option<String> = None,
|
flag_unlock: Option<String> = None,
|
||||||
@ -370,6 +372,7 @@ struct Operating {
|
|||||||
db_path: Option<String>,
|
db_path: Option<String>,
|
||||||
keys_path: Option<String>,
|
keys_path: Option<String>,
|
||||||
identity: Option<String>,
|
identity: Option<String>,
|
||||||
|
no_persistent_txqueue: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, PartialEq, RustcDecodable)]
|
#[derive(Default, Debug, PartialEq, RustcDecodable)]
|
||||||
@ -627,6 +630,7 @@ mod tests {
|
|||||||
flag_db_path: Some("$HOME/.parity/chains".into()),
|
flag_db_path: Some("$HOME/.parity/chains".into()),
|
||||||
flag_keys_path: "$HOME/.parity/keys".into(),
|
flag_keys_path: "$HOME/.parity/keys".into(),
|
||||||
flag_identity: "".into(),
|
flag_identity: "".into(),
|
||||||
|
flag_no_persistent_txqueue: false,
|
||||||
|
|
||||||
// -- Account Options
|
// -- Account Options
|
||||||
flag_unlock: Some("0xdeadbeefcafe0000000000000000000000000000".into()),
|
flag_unlock: Some("0xdeadbeefcafe0000000000000000000000000000".into()),
|
||||||
@ -828,6 +832,7 @@ mod tests {
|
|||||||
db_path: None,
|
db_path: None,
|
||||||
keys_path: None,
|
keys_path: None,
|
||||||
identity: None,
|
identity: None,
|
||||||
|
no_persistent_txqueue: None,
|
||||||
}),
|
}),
|
||||||
account: Some(Account {
|
account: Some(Account {
|
||||||
unlock: Some(vec!["0x1".into(), "0x2".into(), "0x3".into()]),
|
unlock: Some(vec!["0x1".into(), "0x2".into(), "0x3".into()]),
|
||||||
|
@ -280,6 +280,9 @@ Sealing/Mining Options:
|
|||||||
execution time limit. Also number of offending actions
|
execution time limit. Also number of offending actions
|
||||||
have to reach the threshold within that time.
|
have to reach the threshold within that time.
|
||||||
(default: {flag_tx_queue_ban_time} seconds)
|
(default: {flag_tx_queue_ban_time} seconds)
|
||||||
|
--no-persistent-txqueue Don't save pending local transactions to disk to be
|
||||||
|
restored whenever the node restarts.
|
||||||
|
(default: {flag_no_persistent_txqueue}).
|
||||||
--remove-solved Move solved blocks from the work package queue
|
--remove-solved Move solved blocks from the work package queue
|
||||||
instead of cloning them. This gives a slightly
|
instead of cloning them. This gives a slightly
|
||||||
faster import speed, but means that extra solutions
|
faster import speed, but means that extra solutions
|
||||||
|
@ -370,6 +370,7 @@ impl Configuration {
|
|||||||
check_seal: !self.args.flag_no_seal_check,
|
check_seal: !self.args.flag_no_seal_check,
|
||||||
download_old_blocks: !self.args.flag_no_ancient_blocks,
|
download_old_blocks: !self.args.flag_no_ancient_blocks,
|
||||||
verifier_settings: verifier_settings,
|
verifier_settings: verifier_settings,
|
||||||
|
no_persistent_txqueue: self.args.flag_no_persistent_txqueue,
|
||||||
};
|
};
|
||||||
Cmd::Run(run_cmd)
|
Cmd::Run(run_cmd)
|
||||||
};
|
};
|
||||||
@ -1184,6 +1185,7 @@ mod tests {
|
|||||||
check_seal: true,
|
check_seal: true,
|
||||||
download_old_blocks: true,
|
download_old_blocks: true,
|
||||||
verifier_settings: Default::default(),
|
verifier_settings: Default::default(),
|
||||||
|
no_persistent_txqueue: false,
|
||||||
};
|
};
|
||||||
expected.secretstore_conf.enabled = cfg!(feature = "secretstore");
|
expected.secretstore_conf.enabled = cfg!(feature = "secretstore");
|
||||||
assert_eq!(conf.into_command().unwrap().cmd, Cmd::Run(expected));
|
assert_eq!(conf.into_command().unwrap().cmd, Cmd::Run(expected));
|
||||||
|
@ -107,6 +107,7 @@ pub struct RunCmd {
|
|||||||
pub check_seal: bool,
|
pub check_seal: bool,
|
||||||
pub download_old_blocks: bool,
|
pub download_old_blocks: bool,
|
||||||
pub verifier_settings: VerifierSettings,
|
pub verifier_settings: VerifierSettings,
|
||||||
|
pub no_persistent_txqueue: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_ui(dapps_conf: &dapps::Configuration, signer_conf: &signer::Configuration) -> Result<(), String> {
|
pub fn open_ui(dapps_conf: &dapps::Configuration, signer_conf: &signer::Configuration) -> Result<(), String> {
|
||||||
@ -138,15 +139,20 @@ pub fn open_dapp(dapps_conf: &dapps::Configuration, dapp: &str) -> Result<(), St
|
|||||||
|
|
||||||
// node info fetcher for the local store.
|
// node info fetcher for the local store.
|
||||||
struct FullNodeInfo {
|
struct FullNodeInfo {
|
||||||
miner: Arc<Miner>, // TODO: only TXQ needed, just use that after decoupling.
|
miner: Option<Arc<Miner>>, // TODO: only TXQ needed, just use that after decoupling.
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::local_store::NodeInfo for FullNodeInfo {
|
impl ::local_store::NodeInfo for FullNodeInfo {
|
||||||
fn pending_transactions(&self) -> Vec<::ethcore::transaction::PendingTransaction> {
|
fn pending_transactions(&self) -> Vec<::ethcore::transaction::PendingTransaction> {
|
||||||
let local_txs = self.miner.local_transactions();
|
let miner = match self.miner.as_ref() {
|
||||||
self.miner.pending_transactions()
|
Some(m) => m,
|
||||||
|
None => return Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let local_txs = miner.local_transactions();
|
||||||
|
miner.pending_transactions()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(self.miner.future_transactions())
|
.chain(miner.future_transactions())
|
||||||
.filter(|tx| local_txs.contains_key(&tx.hash()))
|
.filter(|tx| local_txs.contains_key(&tx.hash()))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@ -337,11 +343,22 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
let store = {
|
let store = {
|
||||||
let db = service.db();
|
let db = service.db();
|
||||||
let node_info = FullNodeInfo {
|
let node_info = FullNodeInfo {
|
||||||
miner: miner.clone(),
|
miner: match cmd.no_persistent_txqueue {
|
||||||
|
true => None,
|
||||||
|
false => Some(miner.clone()),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let store = ::local_store::create(db, ::ethcore::db::COL_NODE_INFO, node_info);
|
let store = ::local_store::create(db, ::ethcore::db::COL_NODE_INFO, node_info);
|
||||||
|
|
||||||
|
if cmd.no_persistent_txqueue {
|
||||||
|
info!("Running without a persistent transaction queue.");
|
||||||
|
|
||||||
|
if let Err(e) = store.clear() {
|
||||||
|
warn!("Error clearing persistent transaction queue: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// re-queue pending transactions.
|
// re-queue pending transactions.
|
||||||
match store.pending_transactions() {
|
match store.pending_transactions() {
|
||||||
Ok(pending) => {
|
Ok(pending) => {
|
||||||
|
@ -28,7 +28,7 @@ use util::sha3;
|
|||||||
use jsonrpc_core::Error;
|
use jsonrpc_core::Error;
|
||||||
use v1::helpers::errors;
|
use v1::helpers::errors;
|
||||||
use v1::traits::ParitySet;
|
use v1::traits::ParitySet;
|
||||||
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo};
|
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction};
|
||||||
|
|
||||||
/// Parity-specific rpc interface for operations altering the settings.
|
/// Parity-specific rpc interface for operations altering the settings.
|
||||||
pub struct ParitySetClient<F> {
|
pub struct ParitySetClient<F> {
|
||||||
@ -135,4 +135,8 @@ impl<F: Fetch> ParitySet for ParitySetClient<F> {
|
|||||||
fn execute_upgrade(&self) -> Result<bool, Error> {
|
fn execute_upgrade(&self) -> Result<bool, Error> {
|
||||||
Err(errors::light_unimplemented(None))
|
Err(errors::light_unimplemented(None))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_transaction(&self, _hash: H256) -> Result<Option<Transaction>, Error> {
|
||||||
|
Err(errors::light_unimplemented(None))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,15 +30,10 @@ use updater::{Service as UpdateService};
|
|||||||
use jsonrpc_core::Error;
|
use jsonrpc_core::Error;
|
||||||
use v1::helpers::errors;
|
use v1::helpers::errors;
|
||||||
use v1::traits::ParitySet;
|
use v1::traits::ParitySet;
|
||||||
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo};
|
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction};
|
||||||
|
|
||||||
/// Parity-specific rpc interface for operations altering the settings.
|
/// Parity-specific rpc interface for operations altering the settings.
|
||||||
pub struct ParitySetClient<C, M, U, F=fetch::Client> where
|
pub struct ParitySetClient<C, M, U, F = fetch::Client> {
|
||||||
C: MiningBlockChainClient,
|
|
||||||
M: MinerService,
|
|
||||||
U: UpdateService,
|
|
||||||
F: Fetch,
|
|
||||||
{
|
|
||||||
client: Weak<C>,
|
client: Weak<C>,
|
||||||
miner: Weak<M>,
|
miner: Weak<M>,
|
||||||
updater: Weak<U>,
|
updater: Weak<U>,
|
||||||
@ -46,12 +41,7 @@ pub struct ParitySetClient<C, M, U, F=fetch::Client> where
|
|||||||
fetch: F,
|
fetch: F,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, M, U, F> ParitySetClient<C, M, U, F> where
|
impl<C, M, U, F> ParitySetClient<C, M, U, F> {
|
||||||
C: MiningBlockChainClient,
|
|
||||||
M: MinerService,
|
|
||||||
U: UpdateService,
|
|
||||||
F: Fetch,
|
|
||||||
{
|
|
||||||
/// Creates new `ParitySetClient` with given `Fetch`.
|
/// Creates new `ParitySetClient` with given `Fetch`.
|
||||||
pub fn new(client: &Arc<C>, miner: &Arc<M>, updater: &Arc<U>, net: &Arc<ManageNetwork>, fetch: F) -> Self {
|
pub fn new(client: &Arc<C>, miner: &Arc<M>, updater: &Arc<U>, net: &Arc<ManageNetwork>, fetch: F) -> Self {
|
||||||
ParitySetClient {
|
ParitySetClient {
|
||||||
@ -176,4 +166,12 @@ impl<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
|
|||||||
let updater = take_weak!(self.updater);
|
let updater = take_weak!(self.updater);
|
||||||
Ok(updater.execute_upgrade())
|
Ok(updater.execute_upgrade())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_transaction(&self, hash: H256) -> Result<Option<Transaction>, Error> {
|
||||||
|
let miner = take_weak!(self.miner);
|
||||||
|
let client = take_weak!(self.client);
|
||||||
|
let hash = hash.into();
|
||||||
|
|
||||||
|
Ok(miner.remove_pending_transaction(&*client, &hash).map(Into::into))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,6 +204,10 @@ impl MinerService for TestMinerService {
|
|||||||
self.pending_transactions.lock().get(hash).cloned().map(Into::into)
|
self.pending_transactions.lock().get(hash).cloned().map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_pending_transaction(&self, _chain: &MiningBlockChainClient, hash: &H256) -> Option<PendingTransaction> {
|
||||||
|
self.pending_transactions.lock().remove(hash).map(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
fn pending_transactions(&self) -> Vec<PendingTransaction> {
|
fn pending_transactions(&self) -> Vec<PendingTransaction> {
|
||||||
self.pending_transactions.lock().values().cloned().map(Into::into).collect()
|
self.pending_transactions.lock().values().cloned().map(Into::into).collect()
|
||||||
}
|
}
|
||||||
|
@ -204,3 +204,31 @@ fn rpc_parity_set_hash_content() {
|
|||||||
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
|
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpc_parity_remove_transaction() {
|
||||||
|
use ethcore::transaction::{Transaction, Action};
|
||||||
|
|
||||||
|
let miner = miner_service();
|
||||||
|
let client = client_service();
|
||||||
|
let network = network_service();
|
||||||
|
let updater = updater_service();
|
||||||
|
let mut io = IoHandler::new();
|
||||||
|
io.extend_with(parity_set_client(&client, &miner, &updater, &network).to_delegate());
|
||||||
|
|
||||||
|
let tx = Transaction {
|
||||||
|
nonce: 1.into(),
|
||||||
|
gas_price: 0x9184e72a000u64.into(),
|
||||||
|
gas: 0x76c0.into(),
|
||||||
|
action: Action::Call(5.into()),
|
||||||
|
value: 0x9184e72au64.into(),
|
||||||
|
data: vec![]
|
||||||
|
};
|
||||||
|
let signed = tx.fake_sign(2.into());
|
||||||
|
let hash = signed.hash();
|
||||||
|
|
||||||
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_removeTransaction", "params":[""#.to_owned() + &format!("0x{:?}", hash) + r#""], "id": 1}"#;
|
||||||
|
let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"condition":null,"creates":null,"from":"0x0000000000000000000000000000000000000002","gas":"0x76c0","gasPrice":"0x9184e72a000","hash":"0x0072c69d780cdfbfc02fed5c7d184151f9a166971d045e55e27695aaa5bcb55e","input":"0x","networkId":null,"nonce":"0x1","publicKey":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","r":"0x0","raw":"0xe9018609184e72a0008276c0940000000000000000000000000000000000000005849184e72a80808080","s":"0x0","standardV":"0x4","to":"0x0000000000000000000000000000000000000005","transactionIndex":null,"v":"0x0","value":"0x9184e72a"},"id":1}"#;
|
||||||
|
|
||||||
|
miner.pending_transactions.lock().insert(hash, signed);
|
||||||
|
assert_eq!(io.handle_request_sync(&request), Some(response.to_owned()));
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
use jsonrpc_core::Error;
|
use jsonrpc_core::Error;
|
||||||
use futures::BoxFuture;
|
use futures::BoxFuture;
|
||||||
|
|
||||||
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo};
|
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction};
|
||||||
|
|
||||||
build_rpc_trait! {
|
build_rpc_trait! {
|
||||||
/// Parity-specific rpc interface for operations altering the settings.
|
/// Parity-specific rpc interface for operations altering the settings.
|
||||||
@ -99,5 +99,14 @@ build_rpc_trait! {
|
|||||||
/// Execute a release which is ready according to upgrade_ready().
|
/// Execute a release which is ready according to upgrade_ready().
|
||||||
#[rpc(name = "parity_executeUpgrade")]
|
#[rpc(name = "parity_executeUpgrade")]
|
||||||
fn execute_upgrade(&self) -> Result<bool, Error>;
|
fn execute_upgrade(&self) -> Result<bool, Error>;
|
||||||
|
|
||||||
|
/// Removes transaction from transaction queue.
|
||||||
|
/// Makes sense only for transactions that were not propagated to other peers yet
|
||||||
|
/// like scheduled transactions or transactions in future.
|
||||||
|
/// It might also work for some local transactions with to low gas price
|
||||||
|
/// or excessive gas limit that are not accepted by other peers whp.
|
||||||
|
/// Returns `true` when transaction was removed, `false` if it was not found.
|
||||||
|
#[rpc(name = "parity_removeTransaction")]
|
||||||
|
fn remove_transaction(&self, H256) -> Result<Option<Transaction>, Error>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user