480 lines
15 KiB
Rust
480 lines
15 KiB
Rust
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
|
// This file is part of OpenEthereum.
|
|
|
|
// OpenEthereum 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.
|
|
|
|
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//! Types used in Confirmations queue (Trusted Signer)
|
|
|
|
use ansi_term::Colour;
|
|
use bytes::ToPretty;
|
|
use serde::{Serialize, Serializer};
|
|
use std::fmt;
|
|
|
|
use ethereum_types::{H160, H256, H520, U256};
|
|
use ethkey::Password;
|
|
use v1::{
|
|
helpers,
|
|
types::{Bytes, Origin, RichRawTransaction, TransactionCondition, TransactionRequest},
|
|
};
|
|
|
|
/// Confirmation waiting in a queue
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct ConfirmationRequest {
|
|
/// Id of this confirmation
|
|
pub id: U256,
|
|
/// Payload
|
|
pub payload: ConfirmationPayload,
|
|
/// Request origin
|
|
pub origin: Origin,
|
|
}
|
|
|
|
impl From<helpers::ConfirmationRequest> for ConfirmationRequest {
|
|
fn from(c: helpers::ConfirmationRequest) -> Self {
|
|
ConfirmationRequest {
|
|
id: c.id,
|
|
payload: c.payload.into(),
|
|
origin: c.origin,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ConfirmationRequest {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"#{}: {} coming from {}",
|
|
self.id, self.payload, self.origin
|
|
)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ConfirmationPayload {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match *self {
|
|
ConfirmationPayload::SendTransaction(ref transaction) => write!(f, "{}", transaction),
|
|
ConfirmationPayload::SignTransaction(ref transaction) => {
|
|
write!(f, "(Sign only) {}", transaction)
|
|
}
|
|
ConfirmationPayload::EthSignMessage(ref sign) => write!(f, "{}", sign),
|
|
ConfirmationPayload::EIP191SignMessage(ref sign) => write!(f, "{}", sign),
|
|
ConfirmationPayload::Decrypt(ref decrypt) => write!(f, "{}", decrypt),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ethereum-prefixed Sign request
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct EthSignRequest {
|
|
/// Address
|
|
pub address: H160,
|
|
/// Hash to sign
|
|
pub data: Bytes,
|
|
}
|
|
|
|
/// EIP191 Sign request
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct EIP191SignRequest {
|
|
/// Address
|
|
pub address: H160,
|
|
/// Hash to sign
|
|
pub data: H256,
|
|
}
|
|
|
|
impl From<(H160, H256)> for EIP191SignRequest {
|
|
fn from(tuple: (H160, H256)) -> Self {
|
|
EIP191SignRequest {
|
|
address: tuple.0,
|
|
data: tuple.1,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for EIP191SignRequest {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"sign 0x{} with {}",
|
|
self.data.0.pretty(),
|
|
Colour::White.bold().paint(format!("0x{:?}", self.address)),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl From<(H160, Bytes)> for EthSignRequest {
|
|
fn from(tuple: (H160, Bytes)) -> Self {
|
|
EthSignRequest {
|
|
address: tuple.0,
|
|
data: tuple.1,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for EthSignRequest {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"sign 0x{} with {}",
|
|
self.data.0.pretty(),
|
|
Colour::White.bold().paint(format!("0x{:?}", self.address)),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Decrypt request
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct DecryptRequest {
|
|
/// Address
|
|
pub address: H160,
|
|
/// Message to decrypt
|
|
pub msg: Bytes,
|
|
}
|
|
|
|
impl From<(H160, Bytes)> for DecryptRequest {
|
|
fn from(tuple: (H160, Bytes)) -> Self {
|
|
DecryptRequest {
|
|
address: tuple.0,
|
|
msg: tuple.1,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for DecryptRequest {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"decrypt data with {}",
|
|
Colour::White.bold().paint(format!("0x{:?}", self.address)),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Confirmation response for particular payload
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum ConfirmationResponse {
|
|
/// Transaction Hash
|
|
SendTransaction(H256),
|
|
/// Transaction RLP
|
|
SignTransaction(RichRawTransaction),
|
|
/// Signature (encoded as VRS)
|
|
Signature(H520),
|
|
/// Decrypted data
|
|
Decrypt(Bytes),
|
|
}
|
|
|
|
impl Serialize for ConfirmationResponse {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
match *self {
|
|
ConfirmationResponse::SendTransaction(ref hash) => hash.serialize(serializer),
|
|
ConfirmationResponse::SignTransaction(ref rlp) => rlp.serialize(serializer),
|
|
ConfirmationResponse::Signature(ref signature) => signature.serialize(serializer),
|
|
ConfirmationResponse::Decrypt(ref data) => data.serialize(serializer),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Confirmation response with additional token for further requests
|
|
#[derive(Clone, PartialEq, Serialize)]
|
|
pub struct ConfirmationResponseWithToken {
|
|
/// Actual response
|
|
pub result: ConfirmationResponse,
|
|
/// New token
|
|
pub token: Password,
|
|
}
|
|
|
|
/// Confirmation payload, i.e. the thing to be confirmed
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub enum ConfirmationPayload {
|
|
/// Send Transaction
|
|
SendTransaction(TransactionRequest),
|
|
/// Sign Transaction
|
|
SignTransaction(TransactionRequest),
|
|
/// Signature
|
|
#[serde(rename = "sign")]
|
|
EthSignMessage(EthSignRequest),
|
|
/// signature without prefix
|
|
EIP191SignMessage(EIP191SignRequest),
|
|
/// Decryption
|
|
Decrypt(DecryptRequest),
|
|
}
|
|
|
|
impl From<helpers::ConfirmationPayload> for ConfirmationPayload {
|
|
fn from(c: helpers::ConfirmationPayload) -> Self {
|
|
match c {
|
|
helpers::ConfirmationPayload::SendTransaction(t) => {
|
|
ConfirmationPayload::SendTransaction(t.into())
|
|
}
|
|
helpers::ConfirmationPayload::SignTransaction(t) => {
|
|
ConfirmationPayload::SignTransaction(t.into())
|
|
}
|
|
helpers::ConfirmationPayload::EthSignMessage(address, data) => {
|
|
ConfirmationPayload::EthSignMessage(EthSignRequest {
|
|
address,
|
|
data: data.into(),
|
|
})
|
|
}
|
|
helpers::ConfirmationPayload::SignMessage(address, data) => {
|
|
ConfirmationPayload::EIP191SignMessage(EIP191SignRequest { address, data })
|
|
}
|
|
helpers::ConfirmationPayload::Decrypt(address, msg) => {
|
|
ConfirmationPayload::Decrypt(DecryptRequest {
|
|
address,
|
|
msg: msg.into(),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Possible modifications to the confirmed transaction sent by `Trusted Signer`
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct TransactionModification {
|
|
/// Modified transaction sender
|
|
pub sender: Option<H160>,
|
|
/// Modified gas price
|
|
pub gas_price: Option<U256>,
|
|
/// Modified gas
|
|
pub gas: Option<U256>,
|
|
/// Modified transaction condition.
|
|
pub condition: Option<Option<TransactionCondition>>,
|
|
}
|
|
|
|
/// Represents two possible return values.
|
|
#[derive(Debug, Clone)]
|
|
pub enum Either<A, B>
|
|
where
|
|
A: fmt::Debug + Clone,
|
|
B: fmt::Debug + Clone,
|
|
{
|
|
/// Primary value
|
|
Either(A),
|
|
/// Secondary value
|
|
Or(B),
|
|
}
|
|
|
|
impl<A, B> From<A> for Either<A, B>
|
|
where
|
|
A: fmt::Debug + Clone,
|
|
B: fmt::Debug + Clone,
|
|
{
|
|
fn from(a: A) -> Self {
|
|
Either::Either(a)
|
|
}
|
|
}
|
|
|
|
impl<A, B> Serialize for Either<A, B>
|
|
where
|
|
A: Serialize + fmt::Debug + Clone,
|
|
B: Serialize + fmt::Debug + Clone,
|
|
{
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
match *self {
|
|
Either::Either(ref a) => a.serialize(serializer),
|
|
Either::Or(ref b) => b.serialize(serializer),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use ethereum_types::{Address, H256, U256};
|
|
use serde_json;
|
|
use std::str::FromStr;
|
|
use v1::{helpers, types::TransactionCondition};
|
|
|
|
#[test]
|
|
fn should_serialize_sign_confirmation() {
|
|
// given
|
|
let request = helpers::ConfirmationRequest {
|
|
id: 15.into(),
|
|
payload: helpers::ConfirmationPayload::EthSignMessage(
|
|
Address::from_low_u64_be(1),
|
|
vec![5].into(),
|
|
),
|
|
origin: Origin::Rpc("test service".into()),
|
|
};
|
|
|
|
// when
|
|
let res = serde_json::to_string(&ConfirmationRequest::from(request));
|
|
let expected = r#"{"id":"0xf","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}},"origin":{"rpc":"test service"}}"#;
|
|
|
|
// then
|
|
assert_eq!(res.unwrap(), expected.to_owned());
|
|
}
|
|
|
|
#[test]
|
|
fn should_serialize_transaction_confirmation() {
|
|
// given
|
|
let request = helpers::ConfirmationRequest {
|
|
id: 15.into(),
|
|
payload: helpers::ConfirmationPayload::SendTransaction(
|
|
helpers::FilledTransactionRequest {
|
|
transaction_type: Default::default(),
|
|
from: Address::from_low_u64_be(0),
|
|
used_default_from: false,
|
|
to: None,
|
|
gas: 15_000.into(),
|
|
gas_price: Some(10_000.into()),
|
|
max_fee_per_gas: None,
|
|
value: 100_000.into(),
|
|
data: vec![1, 2, 3],
|
|
nonce: Some(1.into()),
|
|
condition: None,
|
|
access_list: None,
|
|
max_priority_fee_per_gas: None,
|
|
},
|
|
),
|
|
origin: Origin::Signer {
|
|
session: H256::from_low_u64_be(5),
|
|
},
|
|
};
|
|
|
|
// when
|
|
let res = serde_json::to_string(&ConfirmationRequest::from(request));
|
|
let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}},"origin":{"signer":{"session":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#;
|
|
|
|
// then
|
|
assert_eq!(res.unwrap(), expected.to_owned());
|
|
}
|
|
|
|
#[test]
|
|
fn should_serialize_sign_transaction_confirmation() {
|
|
// given
|
|
let request = helpers::ConfirmationRequest {
|
|
id: 15.into(),
|
|
payload: helpers::ConfirmationPayload::SignTransaction(
|
|
helpers::FilledTransactionRequest {
|
|
transaction_type: Default::default(),
|
|
from: Address::from_low_u64_be(0),
|
|
used_default_from: false,
|
|
to: None,
|
|
gas: 15_000.into(),
|
|
gas_price: Some(10_000.into()),
|
|
max_fee_per_gas: None,
|
|
value: 100_000.into(),
|
|
data: vec![1, 2, 3],
|
|
nonce: Some(1.into()),
|
|
condition: None,
|
|
access_list: None,
|
|
max_priority_fee_per_gas: None,
|
|
},
|
|
),
|
|
origin: Origin::Unknown,
|
|
};
|
|
|
|
// when
|
|
let res = serde_json::to_string(&ConfirmationRequest::from(request));
|
|
let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}},"origin":"unknown"}"#;
|
|
|
|
// then
|
|
assert_eq!(res.unwrap(), expected.to_owned());
|
|
}
|
|
|
|
#[test]
|
|
fn should_serialize_decrypt_confirmation() {
|
|
// given
|
|
let request = helpers::ConfirmationRequest {
|
|
id: 15.into(),
|
|
payload: helpers::ConfirmationPayload::Decrypt(
|
|
Address::from_low_u64_be(10),
|
|
vec![1, 2, 3].into(),
|
|
),
|
|
origin: Default::default(),
|
|
};
|
|
|
|
// when
|
|
let res = serde_json::to_string(&ConfirmationRequest::from(request));
|
|
let expected = r#"{"id":"0xf","payload":{"decrypt":{"address":"0x000000000000000000000000000000000000000a","msg":"0x010203"}},"origin":"unknown"}"#;
|
|
|
|
// then
|
|
assert_eq!(res.unwrap(), expected.to_owned());
|
|
}
|
|
|
|
#[test]
|
|
fn should_deserialize_modification() {
|
|
// given
|
|
let s1 = r#"{
|
|
"sender": "0x000000000000000000000000000000000000000a",
|
|
"gasPrice":"0xba43b7400",
|
|
"condition": { "block": 66 }
|
|
}"#;
|
|
let s2 = r#"{"gas": "0x1233"}"#;
|
|
let s3 = r#"{}"#;
|
|
|
|
// when
|
|
let res1: TransactionModification = serde_json::from_str(s1).unwrap();
|
|
let res2: TransactionModification = serde_json::from_str(s2).unwrap();
|
|
let res3: TransactionModification = serde_json::from_str(s3).unwrap();
|
|
|
|
// then
|
|
assert_eq!(
|
|
res1,
|
|
TransactionModification {
|
|
sender: Some(Address::from_low_u64_be(10)),
|
|
gas_price: Some(U256::from_str("0ba43b7400").unwrap()),
|
|
gas: None,
|
|
condition: Some(Some(TransactionCondition::Number(0x42))),
|
|
}
|
|
);
|
|
assert_eq!(
|
|
res2,
|
|
TransactionModification {
|
|
sender: None,
|
|
gas_price: None,
|
|
gas: Some(U256::from_str("1233").unwrap()),
|
|
condition: None,
|
|
}
|
|
);
|
|
assert_eq!(
|
|
res3,
|
|
TransactionModification {
|
|
sender: None,
|
|
gas_price: None,
|
|
gas: None,
|
|
condition: None,
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn should_serialize_confirmation_response_with_token() {
|
|
// given
|
|
let response = ConfirmationResponseWithToken {
|
|
result: ConfirmationResponse::SendTransaction(H256::default()),
|
|
token: "test-token".into(),
|
|
};
|
|
|
|
// when
|
|
let res = serde_json::to_string(&response);
|
|
let expected = r#"{"result":"0x0000000000000000000000000000000000000000000000000000000000000000","token":"test-token"}"#;
|
|
|
|
// then
|
|
assert_eq!(res.unwrap(), expected.to_owned());
|
|
}
|
|
}
|