Merge branch 'master' into ui-2

# Conflicts:
#
js/src/shell/Signer/components/TransactionPendingFormConfirm/transaction
PendingFormConfirm.js
#	js/src/ui/Container/Title/title.js
#	js/src/views/Application/Snackbar/snackbar.js
#	js/src/views/Status/Peers/peers.js
This commit is contained in:
Jaco Greeff 2017-05-16 13:30:19 +02:00
commit 31be9d25f3
18 changed files with 528 additions and 110 deletions

View File

@ -25,7 +25,8 @@
] ]
}, },
"validateScoreTransition": 1000000, "validateScoreTransition": 1000000,
"eip155Transition": 1000000 "eip155Transition": 1000000,
"validateStepTransition": 1500000
} }
} }
}, },

View File

@ -57,6 +57,8 @@ pub struct AuthorityRoundParams {
pub validate_score_transition: u64, pub validate_score_transition: u64,
/// Number of first block where EIP-155 rules are validated. /// Number of first block where EIP-155 rules are validated.
pub eip155_transition: u64, pub eip155_transition: u64,
/// Monotonic step validation transition block.
pub validate_step_transition: u64,
} }
impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams { impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
@ -70,6 +72,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
start_step: p.start_step.map(Into::into), start_step: p.start_step.map(Into::into),
validate_score_transition: p.validate_score_transition.map_or(0, Into::into), validate_score_transition: p.validate_score_transition.map_or(0, Into::into),
eip155_transition: p.eip155_transition.map_or(0, Into::into), eip155_transition: p.eip155_transition.map_or(0, Into::into),
validate_step_transition: p.validate_step_transition.map_or(0, Into::into),
} }
} }
} }
@ -128,6 +131,7 @@ pub struct AuthorityRound {
validators: Box<ValidatorSet>, validators: Box<ValidatorSet>,
validate_score_transition: u64, validate_score_transition: u64,
eip155_transition: u64, eip155_transition: u64,
validate_step_transition: u64,
} }
// header-chain validator. // header-chain validator.
@ -208,6 +212,7 @@ impl AuthorityRound {
validators: new_validator_set(our_params.validators), validators: new_validator_set(our_params.validators),
validate_score_transition: our_params.validate_score_transition, validate_score_transition: our_params.validate_score_transition,
eip155_transition: our_params.eip155_transition, eip155_transition: our_params.eip155_transition,
validate_step_transition: our_params.validate_step_transition,
}); });
// Do not initialize timeouts for tests. // Do not initialize timeouts for tests.
if should_timeout { if should_timeout {
@ -379,7 +384,8 @@ impl Engine for AuthorityRound {
// Ensure header is from the step after parent. // Ensure header is from the step after parent.
let parent_step = header_step(parent)?; let parent_step = header_step(parent)?;
if step <= parent_step { if 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(), header.number(), Default::default());
Err(EngineError::DoubleVote(header.author().clone()))?; Err(EngineError::DoubleVote(header.author().clone()))?;

View File

@ -39,9 +39,9 @@ export default class Parity {
.then(outAccountInfo); .then(outAccountInfo);
} }
addReservedPeer (encode) { addReservedPeer (enode) {
return this._transport return this._transport
.execute('parity_addReservedPeer', encode); .execute('parity_addReservedPeer', enode);
} }
chainStatus () { chainStatus () {
@ -429,9 +429,9 @@ export default class Parity {
.execute('parity_releasesInfo'); .execute('parity_releasesInfo');
} }
removeReservedPeer (encode) { removeReservedPeer (enode) {
return this._transport return this._transport
.execute('parity_removeReservedPeer', encode); .execute('parity_removeReservedPeer', enode);
} }
removeTransaction (hash) { removeTransaction (hash) {

View File

@ -104,6 +104,12 @@ export default class SignerMiddleware {
return this.confirmRawRequest(store, id, signature); return this.confirmRawRequest(store, id, signature);
} }
confirmDecryptedMsg (store, id, decrypted) {
const { msg } = decrypted;
return this.confirmRawRequest(store, id, msg);
}
confirmSignedTransaction (store, id, txSigned) { confirmSignedTransaction (store, id, txSigned) {
const { netVersion } = store.getState().nodeStatus; const { netVersion } = store.getState().nodeStatus;
const { signature, tx } = txSigned; const { signature, tx } = txSigned;
@ -154,7 +160,7 @@ export default class SignerMiddleware {
} }
onConfirmStart = (store, action) => { onConfirmStart = (store, action) => {
const { condition, gas = 0, gasPrice = 0, id, password, payload, txSigned, dataSigned, wallet } = action.payload; const { condition, gas = 0, gasPrice = 0, id, password, payload, txSigned, dataSigned, decrypted, wallet } = action.payload;
const handlePromise = this._createConfirmPromiseHandler(store, id); const handlePromise = this._createConfirmPromiseHandler(store, id);
const transaction = payload.sendTransaction || payload.signTransaction; const transaction = payload.sendTransaction || payload.signTransaction;
@ -177,10 +183,14 @@ export default class SignerMiddleware {
} }
} }
// TODO [ToDr] Support eth_sign for external wallet (wallet && !transction) // TODO [ToDr] Support eth_sign for external wallet (wallet && dataSigned)
if (dataSigned) { if (dataSigned) {
return this.confirmSignedData(store, id, dataSigned); return this.confirmSignedData(store, id, dataSigned);
} }
// TODO [ToDr] Support parity_decrypt for external wallet (wallet && decrypted)
if (decrypted) {
return this.confirmDecryptedMsg(store, id, decrypted);
}
return handlePromise(this._api.signer.confirmRequest(id, { gas, gasPrice, condition }, password)); return handlePromise(this._api.signer.confirmRequest(id, { gas, gasPrice, condition }, password));
} }

View File

@ -114,7 +114,7 @@ class DecryptRequest extends Component {
} }
renderActions () { renderActions () {
const { accounts, address, focus, isFinished, status } = this.props; const { accounts, address, focus, isFinished, status, data } = this.props;
const account = accounts[address]; const account = accounts[address];
if (isFinished) { if (isFinished) {
@ -153,15 +153,16 @@ class DecryptRequest extends Component {
onConfirm={ this.onConfirm } onConfirm={ this.onConfirm }
onReject={ this.onReject } onReject={ this.onReject }
className={ styles.actions } className={ styles.actions }
dataToSign={ { decrypt: data } }
/> />
); );
} }
onConfirm = (data) => { onConfirm = (data) => {
const { id } = this.props; const { id } = this.props;
const { password } = data; const { password, decrypted, wallet } = data;
this.props.onConfirm({ id, password }); this.props.onConfirm({ id, password, decrypted, wallet });
} }
onReject = () => { onReject = () => {

View File

@ -40,6 +40,12 @@ const PAYLOAD_SIGN = {
const PAYLOAD_SIGNTX = { const PAYLOAD_SIGNTX = {
signTransaction: TRANSACTION signTransaction: TRANSACTION
}; };
const PAYLOAD_DECRYPT = {
decrypt: {
address: ADDRESS,
msg: 'testing'
}
};
let component; let component;
let onConfirm; let onConfirm;
@ -109,4 +115,18 @@ describe('views/Signer/RequestPending', () => {
expect(component.find('Connect(TransactionPending)')).to.have.length(1); expect(component.find('Connect(TransactionPending)')).to.have.length(1);
}); });
}); });
describe('decrypt', () => {
beforeEach(() => {
render(PAYLOAD_DECRYPT);
});
it('renders defaults', () => {
expect(component).to.be.ok;
});
it('renders DecryptRequest component', () => {
expect(component.find('Connect(DecryptRequest)')).to.have.length(1);
});
});
}); });

View File

@ -41,6 +41,9 @@ export default class TransactionPendingForm extends Component {
}), }),
PropTypes.shape({ PropTypes.shape({
data: PropTypes.string.isRequired data: PropTypes.string.isRequired
}),
PropTypes.shape({
decrypt: PropTypes.string.isRequired
}) })
]).isRequired ]).isRequired
}; };

View File

@ -22,7 +22,7 @@ import ReactTooltip from 'react-tooltip';
import { Button, Form, Input, IdentityIcon, QrCode, QrScan } from '@parity/ui'; import { Button, Form, Input, IdentityIcon, QrCode, QrScan } from '@parity/ui';
import { generateTxQr, generateDataQr } from '~/shell/Signer/utils/qrscan'; import { generateTxQr, generateDecryptQr, generateDataQr } from '~/shell/Signer/utils/qrscan';
import styles from './transactionPendingFormConfirm.css'; import styles from './transactionPendingFormConfirm.css';
@ -344,7 +344,7 @@ export default class TransactionPendingFormConfirm extends Component {
return ( return (
<QrScan <QrScan
className={ styles.camera } className={ styles.camera }
onScan={ this.onScanTx } onScan={ this.onScan }
/> />
); );
} }
@ -396,8 +396,8 @@ export default class TransactionPendingFormConfirm extends Component {
); );
} }
onScanTx = (signature) => { onScan = (signature) => {
const { chainId, rlp, tx, data } = this.state.qr; const { chainId, rlp, tx, data, decrypt } = this.state.qr;
if (signature && signature.substr(0, 2) !== '0x') { if (signature && signature.substr(0, 2) !== '0x') {
signature = `0x${signature}`; signature = `0x${signature}`;
@ -417,6 +417,16 @@ export default class TransactionPendingFormConfirm extends Component {
return; return;
} }
if (decrypt) {
this.props.onConfirm({
decrypted: {
decrypt,
msg: signature
}
});
return;
}
this.props.onConfirm({ this.props.onConfirm({
dataSigned: { dataSigned: {
data, data,
@ -491,7 +501,7 @@ export default class TransactionPendingFormConfirm extends Component {
generateQr = () => { generateQr = () => {
const { api } = this.context; const { api } = this.context;
const { netVersion, dataToSign } = this.props; const { netVersion, dataToSign } = this.props;
const { transaction, data } = dataToSign; const { transaction, data, decrypt } = dataToSign;
const setState = qr => { const setState = qr => {
this.setState({ qr }); this.setState({ qr });
}; };
@ -501,6 +511,11 @@ export default class TransactionPendingFormConfirm extends Component {
return; return;
} }
if (decrypt) {
generateDecryptQr(decrypt).then(setState);
return;
}
generateDataQr(data).then(setState); generateDataQr(data).then(setState);
} }
@ -539,7 +554,7 @@ export default class TransactionPendingFormConfirm extends Component {
const { account, dataToSign } = this.props; const { account, dataToSign } = this.props;
const { qr } = this.state; const { qr } = this.state;
if (dataToSign.data && qr && !qr.value) { if ((dataToSign.data || dataToSign.decrypt) && qr && !qr.value) {
this.generateQr(); this.generateQr();
return; return;
} }

View File

@ -121,6 +121,16 @@ export function generateDataQr (data) {
}); });
} }
export function generateDecryptQr (data) {
return Promise.resolve({
decrypt: data,
value: JSON.stringify({
action: 'decrypt',
data
})
});
}
export function generateTxQr (api, netVersion, transaction) { export function generateTxQr (api, netVersion, transaction) {
return createUnsignedTx(api, netVersion, transaction) return createUnsignedTx(api, netVersion, transaction)
.then((qr) => { .then((qr) => {

View File

@ -0,0 +1,35 @@
// Copyright 2015-2017 Parity Technologies (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, { PropTypes } from 'react';
import styles from '../title.css';
export default function Actions ({ actions }) {
if (!actions || !actions.length) {
return null;
}
return (
<div className={ styles.actions }>
{ actions }
</div>
);
}
Actions.propTypes = {
actions: PropTypes.array
};

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (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 './actions';

View File

@ -21,6 +21,15 @@ $bylineMaxHeight: 2.4rem;
$titleLineHeight: 2rem; $titleLineHeight: 2rem;
$smallFontSize: 0.75rem; $smallFontSize: 0.75rem;
.container {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.actions {
}
.byline, .byline,
.description { .description {
color: $bylineColor; color: $bylineColor;

View File

@ -18,24 +18,29 @@ import React, { PropTypes } from 'react';
import { nodeOrStringProptype } from '@parity/shared/util/proptypes'; import { nodeOrStringProptype } from '@parity/shared/util/proptypes';
import Actions from './Actions';
import Byline from './Byline'; import Byline from './Byline';
import Description from './Description'; import Description from './Description';
import styles from './title.css'; import styles from './title.css';
export default function Title ({ byline, className, description, title }) { export default function Title ({ actions, byline, className, description, title }) {
return ( return (
<div className={ className }> <div className={ className }>
<h3 className={ styles.title }> <div>
{ title } <h3 className={ styles.title }>
</h3> { title }
<Byline byline={ byline } /> </h3>
<Description description={ description } /> <Byline byline={ byline } />
<Description description={ description } />
</div>
<Actions actions={ actions } />
</div> </div>
); );
} }
Title.propTypes = { Title.propTypes = {
actions: PropTypes.array,
byline: nodeOrStringProptype(), byline: nodeOrStringProptype(),
className: PropTypes.string, className: PropTypes.string,
description: nodeOrStringProptype(), description: nodeOrStringProptype(),

View File

@ -45,3 +45,16 @@
white-space: nowrap; white-space: nowrap;
} }
} }
.form {
align-items: center;
display: flex;
flex-direction: row;
float: right;
width: 40em;
.input {
flex: 1;
margin-right: 1em;
}
}

View File

@ -14,91 +14,327 @@
// 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 React, { PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Container } from '@parity/ui'; import { showSnackbar } from '@parity/shared/redux/providers/snackbarActions';
import { newError } from '@parity/shared/redux/actions';
import Peer from './Peer'; import { Button, Container, ContainerTitle, Input, ScrollableText, ShortenedHash } from '@parity/ui';
import styles from './peers.css'; import styles from './peers.css';
function Peers ({ peers }) { class Peers extends Component {
return ( static contextTypes = {
<Container api: PropTypes.object
title={ };
<FormattedMessage
id='status.peers.title'
defaultMessage='network peers'
/>
}
>
<div className={ styles.peers }>
<table>
<thead>
<tr>
<th />
<th>
<FormattedMessage
id='status.peers.table.header.id'
defaultMessage='ID'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.remoteAddress'
defaultMessage='Remote Address'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.name'
defaultMessage='Name'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.ethHeader'
defaultMessage='Header (ETH)'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.ethDiff'
defaultMessage='Difficulty (ETH)'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.caps'
defaultMessage='Capabilities'
/>
</th>
</tr>
</thead>
<tbody>
{
peers.map((peer, index) => {
return (
<Peer
index={ index }
key={ index }
peer={ peer }
/>
);
})
}
</tbody>
</table>
</div>
</Container>
);
}
Peers.propTypes = { static propTypes = {
peers: PropTypes.array.isRequired peers: PropTypes.array.isRequired,
}; newError: PropTypes.func,
showSnackbar: PropTypes.func
};
state = {
action: '',
formInput: '',
showForm: false
};
getActions () {
return [
<Button
key='btn_acceptNonReserved'
label={
<FormattedMessage
id='peers.acceptNonReserved.label'
defaultMessage='Accept non-reserved'
/>
}
onClick={ this.handleAcceptNonReserved }
/>,
<Button
key='btn_dropNonReserved'
label={
<FormattedMessage
id='peers.dropNonReserved.label'
defaultMessage='Drop non-reserved'
/>
}
onClick={ this.handleDropNonReserved }
/>,
<Button
key='btn_addReserved'
label={
<FormattedMessage
id='peers.addReserved.label'
defaultMessage='Add reserved'
/>
}
onClick={ this.handleAddReserved }
/>,
<Button
key='btn_removeReserved'
label={
<FormattedMessage
id='peers.removeReserved.label'
defaultMessage='Remove reserved'
/>
}
onClick={ this.handleRemoveReserved }
/>
];
}
render () {
const { peers } = this.props;
return (
<Container>
<ContainerTitle
actions={ this.getActions() }
title={
<FormattedMessage
id='status.peers.title'
defaultMessage='network peers'
/>
}
/>
{ this.renderForm() }
<div className={ styles.peers }>
<table>
<thead>
<tr>
<th />
<th>
<FormattedMessage
id='status.peers.table.header.id'
defaultMessage='ID'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.remoteAddress'
defaultMessage='Remote Address'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.name'
defaultMessage='Name'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.ethHeader'
defaultMessage='Header (ETH)'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.ethDiff'
defaultMessage='Difficulty (ETH)'
/>
</th>
<th>
<FormattedMessage
id='status.peers.table.header.caps'
defaultMessage='Capabilities'
/>
</th>
</tr>
</thead>
<tbody>
{ this.renderPeers(peers) }
</tbody>
</table>
</div>
</Container>
);
}
renderForm () {
const { action, showForm } = this.state;
if (!showForm) {
return null;
}
return (
<div className={ styles.form }>
<div className={ styles.input }>
<Input
label={
<FormattedMessage
id='peers.form.label'
defaultMessage='Peer enode URL'
/>
}
onChange={ this.handleInputChange }
/>
</div>
<Button
label={
<FormattedMessage
id='peers.form.action.label'
defaultMessage='{add, select, true {Add} false {}}{remove, select, true {Remove} false {}}'
values={ {
add: action === 'add',
remove: action === 'remove'
} }
/>
}
onClick={ this.handleConfirmForm }
/>
<Button
label={
<FormattedMessage
id='peers.form.cancel.label'
defaultMessage='Cancel'
/>
}
onClick={ this.handleCancelForm }
/>
</div>
);
}
renderPeers (peers) {
return peers.map((peer, index) => this.renderPeer(peer, index));
}
renderPeer (peer, index) {
const { caps, id, name, network, protocols } = peer;
return (
<tr
className={ styles.peer }
key={ id }
>
<td>
{ index + 1 }
</td>
<td>
<ScrollableText small text={ id } />
</td>
<td>
{ network.remoteAddress }
</td>
<td>
{ name }
</td>
<td>
{
protocols.eth
? <ShortenedHash data={ protocols.eth.head } />
: null
}
</td>
<td>
{
protocols.eth && protocols.eth.difficulty.gt(0)
? protocols.eth.difficulty.toExponential(16)
: null
}
</td>
<td>
{
caps && caps.length > 0
? caps.join(' - ')
: null
}
</td>
</tr>
);
}
handleAcceptNonReserved = () => {
return this.context.api.parity.acceptNonReservedPeers()
.then(() => {
const message = (
<FormattedMessage
id='peers.acceptNonReservedPeers.success'
defaultMessage='Accepting non-reserved peers'
/>
);
this.props.showSnackbar(message, 3000);
})
.catch((error) => {
this.props.newError(error);
});
};
handleDropNonReserved = () => {
return this.context.api.parity.dropNonReservedPeers()
.then(() => {
const message = (
<FormattedMessage
id='peers.dropNonReservedPeers.success'
defaultMessage='Dropping non-reserved peers'
/>
);
this.props.showSnackbar(message, 3000);
})
.catch((error) => {
this.props.newError(error);
});
};
handleAddReserved = () => {
this.setState({ showForm: true, action: 'add' });
};
handleRemoveReserved = () => {
this.setState({ showForm: true, action: 'remove' });
};
handleInputChange = (event, value) => {
this.setState({ formInput: value });
};
handleCancelForm = () => {
this.setState({ showForm: false, action: '', formInput: '' });
};
handleConfirmForm = () => {
const { action, formInput } = this.state;
let method;
if (action === 'add') {
method = 'addReservedPeer';
} else if (action === 'remove') {
method = 'removeReservedPeer';
}
this.setState({ showForm: false, action: '', formInput: '' });
if (!method) {
return;
}
this.context.api.parity[method](formInput)
.then(() => {
const message = (
<FormattedMessage
id='peers.form.action.success'
defaultMessage='Successfully {add, select, true {added} false {}}{remove, select, true {removed} false {}} a reserved peer'
values={ {
add: action === 'add',
remove: action === 'remove'
} }
/>
);
this.props.showSnackbar(message, 3000);
})
.catch((error) => {
this.props.newError(error);
});
};
}
function mapStateToProps (state) { function mapStateToProps (state) {
const handshakeRegex = /handshake/i; const handshakeRegex = /handshake/i;
@ -118,7 +354,11 @@ function mapStateToProps (state) {
return { peers: realPeers }; return { peers: realPeers };
} }
export default connect( function mapDispatchToProps (dispatch) {
mapStateToProps, return bindActionCreators({
null newError,
)(Peers); showSnackbar
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Peers);

View File

@ -46,6 +46,9 @@ pub struct AuthorityRoundParams {
/// See main AuthorityRoundParams docs. /// See main AuthorityRoundParams docs.
#[serde(rename="eip155Transition")] #[serde(rename="eip155Transition")]
pub eip155_transition: Option<Uint>, pub eip155_transition: Option<Uint>,
/// Block from which monotonic steps start.
#[serde(rename="validateStepTransition")]
pub validate_step_transition: Option<Uint>,
} }
/// Authority engine deserialization. /// Authority engine deserialization.
@ -71,7 +74,8 @@ mod tests {
}, },
"blockReward": "0x50", "blockReward": "0x50",
"startStep" : 24, "startStep" : 24,
"eip155Transition": "0x42" "eip155Transition": "0x42",
"validateStepTransition": 150
} }
}"#; }"#;

View File

@ -201,8 +201,10 @@ impl<D: Dispatcher + 'static> Signer for SignerClient<D> {
Err(err) => Err(errors::invalid_params("Invalid signature received.", err)), Err(err) => Err(errors::invalid_params("Invalid signature received.", err)),
} }
}, },
// TODO [ToDr]: Decrypt - pass through? ConfirmationPayload::Decrypt(_address, _data) => {
_ => Err(errors::unimplemented(Some("Non-transaction requests does not support RAW signing yet.".into()))), // TODO [ToDr]: Decrypt can we verify if the answer is correct?
Ok(ConfirmationResponse::Decrypt(bytes))
},
}; };
if let Ok(ref response) = result { if let Ok(ref response) = result {
signer.request_confirmed(id, Ok(response.clone())); signer.request_confirmed(id, Ok(response.clone()));

View File

@ -28,7 +28,7 @@ use jsonrpc_core::IoHandler;
use v1::{SignerClient, Signer, Origin}; use v1::{SignerClient, Signer, Origin};
use v1::metadata::Metadata; use v1::metadata::Metadata;
use v1::tests::helpers::TestMinerService; use v1::tests::helpers::TestMinerService;
use v1::types::H520; use v1::types::{Bytes as RpcBytes, H520};
use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload}; use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload};
use v1::helpers::dispatch::{FullDispatcher, eth_data_hash}; use v1::helpers::dispatch::{FullDispatcher, eth_data_hash};
@ -479,7 +479,6 @@ fn should_confirm_sign_transaction_with_rlp() {
assert_eq!(tester.miner.imported_transactions.lock().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 0);
} }
#[test] #[test]
fn should_confirm_data_sign_with_signature() { fn should_confirm_data_sign_with_signature() {
// given // given
@ -510,6 +509,34 @@ fn should_confirm_data_sign_with_signature() {
assert_eq!(tester.miner.imported_transactions.lock().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 0);
} }
#[test]
fn should_confirm_decrypt_with_phrase() {
// given
let tester = signer_tester();
let address = tester.accounts.new_account("test").unwrap();
tester.signer.add_request(ConfirmationPayload::Decrypt(
address,
vec![1, 2, 3, 4].into(),
), Origin::Unknown).unwrap();
assert_eq!(tester.signer.requests().len(), 1);
let decrypted = serde_json::to_string(&RpcBytes::new(b"phrase".to_vec())).unwrap();
// when
let request = r#"{
"jsonrpc":"2.0",
"method":"signer_confirmRequestRaw",
"params":["0x1", "#.to_owned() + &decrypted + r#"],
"id":1
}"#;
let response = r#"{"jsonrpc":"2.0","result":"#.to_owned() + &decrypted + r#","id":1}"#;
// then
assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned()));
assert_eq!(tester.signer.requests().len(), 0);
assert_eq!(tester.miner.imported_transactions.lock().len(), 0);
}
#[test] #[test]
fn should_generate_new_token() { fn should_generate_new_token() {
// given // given