Skip to content

Commit acc48f5

Browse files
committed
Merge branch 'dev' into issue-260-preserve-original-error-message
2 parents fb0ec9b + 0c216c9 commit acc48f5

File tree

18 files changed

+403
-116
lines changed

18 files changed

+403
-116
lines changed

Dockerfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ ENV SINGLE_TICK=
3434

3535
WORKDIR /usr/local/bin
3636

37-
RUN apt update && apt-get install -y libssl-dev
37+
RUN apt update && apt-get install -y libssl-dev ca-certificates
38+
39+
COPY docs/config/cloudflare_origin.crt /usr/local/share/ca-certificates/
40+
41+
RUN update-ca-certificates
3842

3943
COPY --from=builder /usr/local/bin/validator_worker .
4044

adapter/src/ethereum.rs

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ impl Adapter for EthereumAdapter {
115115

116116
fn sign(&self, state_root: &str) -> AdapterResult<String> {
117117
if let Some(wallet) = &self.wallet {
118-
let message = Message::from_slice(&hash_message(state_root));
118+
let state_root = hex::decode(state_root)
119+
.map_err(|_| AdapterError::Signature("invalid state_root".to_string()))?;
120+
let message = Message::from_slice(&hash_message(&state_root));
119121
let wallet_sign = wallet
120122
.sign(&self.keystore_pwd, &message)
121123
.map_err(|_| map_error("failed to sign messages"))?;
@@ -130,11 +132,16 @@ impl Adapter for EthereumAdapter {
130132
}
131133

132134
fn verify(&self, signer: &ValidatorId, state_root: &str, sig: &str) -> AdapterResult<bool> {
133-
let decoded_signature = hex::decode(sig)
135+
if !sig.starts_with("0x") {
136+
return Err(AdapterError::Signature("not 0x prefixed hex".to_string()));
137+
}
138+
let decoded_signature = hex::decode(&sig[2..])
134139
.map_err(|_| AdapterError::Signature("invalid signature".to_string()))?;
135140
let address = Address::from_slice(signer.inner());
136141
let signature = Signature::from_electrum(&decoded_signature);
137-
let message = Message::from_slice(&hash_message(state_root));
142+
let state_root = hex::decode(state_root)
143+
.map_err(|_| AdapterError::Signature("invalid state_root".to_string()))?;
144+
let message = Message::from_slice(&hash_message(&state_root));
138145

139146
verify_address(&address, &signature, &message).or_else(|_| Ok(false))
140147
}
@@ -314,14 +321,13 @@ impl RelayerClient {
314321
}
315322
}
316323

317-
fn hash_message(message: &str) -> [u8; 32] {
324+
fn hash_message(message: &[u8]) -> [u8; 32] {
318325
let eth = "\x19Ethereum Signed Message:\n";
319326
let message_length = message.len();
320327

321-
let encoded = format!("{}{}{}", eth, message_length, message);
322-
323328
let mut result = Keccak::new_keccak256();
324-
result.update(&encoded.as_bytes());
329+
result.update(&format!("{}{}", eth, message_length).as_bytes());
330+
result.update(&message);
325331

326332
let mut res: [u8; 32] = [0; 32];
327333
result.finalize(&mut res);
@@ -371,10 +377,9 @@ pub fn ewt_sign(
371377

372378
let payload_encoded =
373379
base64::encode_config(&serde_json::to_string(payload)?, base64::URL_SAFE_NO_PAD);
374-
let message = Message::from_slice(&hash_message(&format!(
375-
"{}.{}",
376-
header_encoded, payload_encoded
377-
)));
380+
let message = Message::from_slice(&hash_message(
381+
&format!("{}.{}", header_encoded, payload_encoded).as_bytes(),
382+
));
378383
let signature: Signature = signer
379384
.sign(password, &message)
380385
.map_err(|_| map_error("sign message"))?
@@ -394,10 +399,9 @@ pub fn ewt_verify(
394399
payload_encoded: &str,
395400
token: &str,
396401
) -> Result<VerifyPayload, Box<dyn Error>> {
397-
let message = Message::from_slice(&hash_message(&format!(
398-
"{}.{}",
399-
header_encoded, payload_encoded
400-
)));
402+
let message = Message::from_slice(&hash_message(
403+
&format!("{}.{}", header_encoded, payload_encoded).as_bytes(),
404+
));
401405

402406
let decoded_signature = base64::decode_config(&token, base64::URL_SAFE_NO_PAD)?;
403407
let signature = Signature::from_electrum(&decoded_signature);
@@ -466,23 +470,35 @@ mod test {
466470

467471
// Sign
468472
let expected_response =
469-
"0xce654de0b3d14d63e1cb3181eee7a7a37ef4a06c9fabc204faf96f26357441b625b1be460fbe8f5278cc02aa88a5d0ac2f238e9e3b8e4893760d33bccf77e47f1b";
473+
"0x625fd46f82c4cfd135ea6a8534e85dbf50beb157046dce59d2e97aacdf4e38381d1513c0e6f002b2f05c05458038b187754ff38cc0658dfc9ba854cccfb6e13e1b";
470474
let message = "2bdeafae53940669daa6f519373f686c";
471-
let response = eth_adapter.sign(message).expect("failed to sign message");
472-
assert_eq!(expected_response, response, "invalid signature");
475+
let signature = eth_adapter.sign(message).expect("failed to sign message");
476+
assert_eq!(expected_response, signature, "invalid signature");
473477

474478
// Verify
475479
let signature =
476-
"ce654de0b3d14d63e1cb3181eee7a7a37ef4a06c9fabc204faf96f26357441b625b1be460fbe8f5278cc02aa88a5d0ac2f238e9e3b8e4893760d33bccf77e47f1b";
480+
"0x9e07f12958ce7c5eb1362eb9461e4745dd9d74a42b921391393caea700bfbd6e1ad876a7d8f9202ef1fe6110dbfe87840c5676ca5c4fda9f3330694a1ac2a1fc1b";
477481
let verify = eth_adapter
478482
.verify(
479-
&ValidatorId::try_from("2bDeAFAE53940669DaA6F519373f686c1f3d3393")
483+
&ValidatorId::try_from("2892f6C41E0718eeeDd49D98D648C789668cA67d")
480484
.expect("Failed to parse id"),
481-
"2bdeafae53940669daa6f519373f686c",
485+
"8bc45d8eb27f4c98cab35d17b0baecc2a263d6831ef0800f4c190cbfac6d20a3",
482486
&signature,
483487
)
484488
.expect("Failed to verify signatures");
485489

490+
let sig1 = "0x9fa5852041b9818021323aff8260624fd6998c52c95d9ad5036e0db6f2bf2b2d48a188ec1d638581ff56b0a2ecceca6d3880fc65030558bd8f68b154e7ebf80f1b";
491+
let msg = "1648231285e69677531ffe70719f67a07f3d4393b8425a5a1c84b0c72434c77b";
492+
493+
let verify2 = eth_adapter
494+
.verify(
495+
&ValidatorId::try_from("ce07CbB7e054514D590a0262C93070D838bFBA2e")
496+
.expect("Failed to parse id"),
497+
msg,
498+
&sig1,
499+
)
500+
.expect("Failed to verify signatures");
501+
486502
assert_eq!(verify, true, "invalid signature verification");
487503
}
488504

docs/config/cloudflare_origin.crt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICiTCCAi6gAwIBAgIUXZP3MWb8MKwBE1Qbawsp1sfA/Y4wCgYIKoZIzj0EAwIw
3+
gY8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T
4+
YW4gRnJhbmNpc2NvMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYDVQQL
5+
Ey9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhvcml0
6+
eTAeFw0xOTA4MjMyMTA4MDBaFw0yOTA4MTUxNzAwMDBaMIGPMQswCQYDVQQGEwJV
7+
UzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEZ
8+
MBcGA1UEChMQQ2xvdWRGbGFyZSwgSW5jLjE4MDYGA1UECxMvQ2xvdWRGbGFyZSBP
9+
cmlnaW4gU1NMIEVDQyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwWTATBgcqhkjOPQIB
10+
BggqhkjOPQMBBwNCAASR+sGALuaGshnUbcxKry+0LEXZ4NY6JUAtSeA6g87K3jaA
11+
xpIg9G50PokpfWkhbarLfpcZu0UAoYy2su0EhN7wo2YwZDAOBgNVHQ8BAf8EBAMC
12+
AQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUhTBdOypw1O3VkmcH/es5
13+
tBoOOKcwHwYDVR0jBBgwFoAUhTBdOypw1O3VkmcH/es5tBoOOKcwCgYIKoZIzj0E
14+
AwIDSQAwRgIhAKilfntP2ILGZjwajktkBtXE1pB4Y/fjAfLkIRUzrI15AiEA5UCL
15+
XYZZ9m2c3fKwIenMMojL1eqydsgqj/wK4p5kagQ=
16+
-----END CERTIFICATE-----
17+
-----BEGIN CERTIFICATE-----
18+
MIIEADCCAuigAwIBAgIID+rOSdTGfGcwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNV
19+
BAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTQwMgYDVQQLEytDbG91
20+
ZEZsYXJlIE9yaWdpbiBTU0wgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRYwFAYDVQQH
21+
Ew1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMB4XDTE5MDgyMzIx
22+
MDgwMFoXDTI5MDgxNTE3MDAwMFowgYsxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBD
23+
bG91ZEZsYXJlLCBJbmMuMTQwMgYDVQQLEytDbG91ZEZsYXJlIE9yaWdpbiBTU0wg
24+
Q2VydGlmaWNhdGUgQXV0aG9yaXR5MRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMw
25+
EQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
26+
AQEAwEiVZ/UoQpHmFsHvk5isBxRehukP8DG9JhFev3WZtG76WoTthvLJFRKFCHXm
27+
V6Z5/66Z4S09mgsUuFwvJzMnE6Ej6yIsYNCb9r9QORa8BdhrkNn6kdTly3mdnykb
28+
OomnwbUfLlExVgNdlP0XoRoeMwbQ4598foiHblO2B/LKuNfJzAMfS7oZe34b+vLB
29+
yrP/1bgCSLdc1AxQc1AC0EsQQhgcyTJNgnG4va1c7ogPlwKyhbDyZ4e59N5lbYPJ
30+
SmXI/cAe3jXj1FBLJZkwnoDKe0v13xeF+nF32smSH0qB7aJX2tBMW4TWtFPmzs5I
31+
lwrFSySWAdwYdgxw180yKU0dvwIDAQABo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYD
32+
VR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUJOhTV118NECHqeuU27rhFnj8KaQw
33+
HwYDVR0jBBgwFoAUJOhTV118NECHqeuU27rhFnj8KaQwDQYJKoZIhvcNAQELBQAD
34+
ggEBAHwOf9Ur1l0Ar5vFE6PNrZWrDfQIMyEfdgSKofCdTckbqXNTiXdgbHs+TWoQ
35+
wAB0pfJDAHJDXOTCWRyTeXOseeOi5Btj5CnEuw3P0oXqdqevM1/+uWp0CM35zgZ8
36+
VD4aITxity0djzE6Qnx3Syzz+ZkoBgTnNum7d9A66/V636x4vTeqbZFBr9erJzgz
37+
hhurjcoacvRNhnjtDRM0dPeiCJ50CP3wEYuvUzDHUaowOsnLCjQIkWbR7Ni6KEIk
38+
MOz2U0OBSif3FTkhCgZWQKOOLo1P42jHC3ssUZAtVNXrCk3fw9/E15k8NPkBazZ6
39+
0iykLhH1trywrKRMVw67F44IE8Y=
40+
-----END CERTIFICATE-----
41+

primitives/src/adapter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ impl fmt::Display for AdapterError {
3131
AdapterError::Authorization(error) => write!(f, "Authorization error: {}", error),
3232
AdapterError::Configuration(error) => write!(f, "Configuration error: {}", error),
3333
AdapterError::Signature(error) => write!(f, "Signature error: {}", error),
34-
AdapterError::InvalidChannel(error) => write!(f, "Invalid Channel error: {}", error),
34+
AdapterError::InvalidChannel(error) => write!(f, "{}", error),
3535
AdapterError::Failed(error) => write!(f, "error: {}", error),
3636
}
3737
}

primitives/src/channel.rs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::fmt;
33

44
use chrono::serde::{ts_milliseconds, ts_milliseconds_option, ts_seconds};
55
use chrono::{DateTime, Utc};
6-
use serde::{Deserialize, Serialize};
6+
use serde::{Deserialize, Deserializer, Serialize};
77
use serde_hex::{SerHex, StrictPfx};
88

99
use crate::big_num::BigNum;
@@ -13,7 +13,25 @@ use std::ops::Deref;
1313

1414
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone, Hash)]
1515
#[serde(transparent)]
16-
pub struct ChannelId(#[serde(with = "SerHex::<StrictPfx>")] [u8; 32]);
16+
pub struct ChannelId(
17+
#[serde(
18+
deserialize_with = "channel_id_from_str",
19+
serialize_with = "SerHex::<StrictPfx>::serialize"
20+
)]
21+
[u8; 32],
22+
);
23+
24+
fn channel_id_from_str<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
25+
where
26+
D: Deserializer<'de>,
27+
{
28+
let channel_id = String::deserialize(deserializer)?;
29+
if channel_id.is_empty() || channel_id.len() != 66 {
30+
return Err(serde::de::Error::custom("invalid channel id".to_string()));
31+
}
32+
33+
<[u8; 32] as FromHex>::from_hex(&channel_id[2..]).map_err(serde::de::Error::custom)
34+
}
1735

1836
impl Deref for ChannelId {
1937
type Target = [u8];
@@ -72,7 +90,9 @@ pub struct Pricing {
7290
#[derive(Serialize, Deserialize, Debug, Clone)]
7391
#[serde(rename_all = "UPPERCASE")]
7492
pub struct PricingBounds {
93+
#[serde(default, skip_serializing_if = "Option::is_none")]
7594
pub impression: Option<Pricing>,
95+
#[serde(default, skip_serializing_if = "Option::is_none")]
7696
pub click: Option<Pricing>,
7797
}
7898

@@ -86,7 +106,8 @@ pub struct ChannelSpec {
86106
pub max_per_impression: BigNum,
87107
/// Minimum payment offered per impression
88108
pub min_per_impression: BigNum,
89-
// Event pricing bounds
109+
/// Event pricing bounds
110+
#[serde(default, skip_serializing_if = "Option::is_none")]
90111
pub pricing_bounds: Option<PricingBounds>,
91112
/// An array of TargetingTag (optional)
92113
#[serde(default, skip_serializing_if = "Vec::is_empty")]
@@ -95,6 +116,7 @@ pub struct ChannelSpec {
95116
#[serde(default, skip_serializing_if = "Option::is_none")]
96117
pub min_targeting_score: Option<f64>,
97118
/// EventSubmission object, applies to event submission (POST /channel/:id/events)
119+
#[serde(default, skip_serializing_if = "Option::is_none")]
98120
pub event_submission: Option<EventSubmission>,
99121
/// A millisecond timestamp of when the campaign was created
100122
#[serde(
@@ -112,6 +134,7 @@ pub struct ChannelSpec {
112134
)]
113135
pub active_from: Option<DateTime<Utc>>,
114136
/// A random number to ensure the campaignSpec hash is unique
137+
#[serde(default, skip_serializing_if = "Option::is_none")]
115138
pub nonce: Option<BigNum>,
116139
/// A millisecond timestamp of when the campaign should enter a withdraw period
117140
/// (no longer accept any events other than CHANNEL_CLOSE)
@@ -232,17 +255,34 @@ pub enum ChannelError {
232255
/// which in terms means, that the adapter shouldn't handle this Channel
233256
AdapterNotIncluded,
234257
/// when `channel.valid_until` has passed (< now), the channel should be handled
235-
PassedValidUntil,
258+
InvalidValidUntil(String),
236259
UnlistedValidator,
237260
UnlistedCreator,
238261
UnlistedAsset,
239262
MinimumDepositNotMet,
240263
MinimumValidatorFeeNotMet,
264+
FeeConstraintViolated,
241265
}
242266

243267
impl fmt::Display for ChannelError {
244268
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245-
write!(f, "Channel error",)
269+
match self {
270+
ChannelError::InvalidArgument(error) => write!(f, "{}", error),
271+
ChannelError::AdapterNotIncluded => write!(f, "channel is not validated by us"),
272+
ChannelError::InvalidValidUntil(error) => write!(f, "{}", error),
273+
ChannelError::UnlistedValidator => write!(f, "validators are not in the whitelist"),
274+
ChannelError::UnlistedCreator => write!(f, "channel.creator is not whitelisted"),
275+
ChannelError::UnlistedAsset => write!(f, "channel.depositAsset is not whitelisted"),
276+
ChannelError::MinimumDepositNotMet => {
277+
write!(f, "channel.depositAmount is less than MINIMAL_DEPOSIT")
278+
}
279+
ChannelError::MinimumValidatorFeeNotMet => {
280+
write!(f, "channel validator fee is less than MINIMAL_FEE")
281+
}
282+
ChannelError::FeeConstraintViolated => {
283+
write!(f, "total fees <= deposit: fee constraint violated")
284+
}
285+
}
246286
}
247287
}
248288

primitives/src/channel_validator.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use crate::channel::{Channel, ChannelError, SpecValidator, SpecValidators};
22
use crate::config::Config;
3+
use crate::BigNum;
34
use crate::ValidatorId;
45
use chrono::Utc;
56
use std::cmp::PartialEq;
7+
use time::Duration;
68

79
pub trait ChannelValidator {
810
fn is_channel_valid(
@@ -17,7 +19,21 @@ pub trait ChannelValidator {
1719
};
1820

1921
if channel.valid_until < Utc::now() {
20-
return Err(ChannelError::PassedValidUntil);
22+
return Err(ChannelError::InvalidValidUntil(
23+
"channel.validUntil has passed".to_string(),
24+
));
25+
}
26+
27+
if channel.valid_until > (Utc::now() + Duration::days(365)) {
28+
return Err(ChannelError::InvalidValidUntil(
29+
"channel.validUntil should not be greater than one year".to_string(),
30+
));
31+
}
32+
33+
if channel.spec.withdraw_period_start > channel.valid_until {
34+
return Err(ChannelError::InvalidValidUntil(
35+
"channel withdrawPeriodStart is invalid".to_string(),
36+
));
2137
}
2238

2339
if !all_validators_listed(&channel.spec.validators, &config.validators_whitelist) {
@@ -40,6 +56,17 @@ pub trait ChannelValidator {
4056
return Err(ChannelError::MinimumValidatorFeeNotMet);
4157
}
4258

59+
let total_validator_fee: BigNum = channel
60+
.spec
61+
.validators
62+
.iter()
63+
.map(|v| v.fee.clone())
64+
.fold(BigNum::from(0), |acc, x| acc + x);
65+
66+
if total_validator_fee >= channel.deposit_amount {
67+
return Err(ChannelError::FeeConstraintViolated);
68+
}
69+
4370
Ok(())
4471
}
4572
}

primitives/src/sentry.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ pub struct EventAggregateResponse {
160160
pub events: Vec<EventAggregate>,
161161
}
162162

163+
#[derive(Serialize, Deserialize, Debug)]
164+
#[serde(rename_all = "camelCase")]
165+
pub struct ValidationErrorResponse {
166+
pub status_code: u64,
167+
pub message: String,
168+
pub validation: Vec<String>,
169+
}
170+
163171
#[derive(Serialize, Deserialize)]
164172
#[serde(rename_all = "camelCase")]
165173
pub struct AdvancedAnalyticsResponse {

primitives/src/util/tests/prep_db.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::{
2+
channel::{Pricing, PricingBounds},
23
BigNum, Channel, ChannelId, ChannelSpec, EventSubmission, SpecValidators, ValidatorDesc,
34
ValidatorId,
45
};
@@ -35,7 +36,7 @@ lazy_static! {
3536
auth.insert("user".into(), "x8c9v1b2".into());
3637
auth.insert("publisher".into(), "testing".into());
3738
auth.insert("publisher2".into(), "testing2".into());
38-
auth.insert("creator".into(), "0x033ed90e0fec3f3ea1c9b005c724d704501e0196".into());
39+
auth.insert("creator".into(), "0x033Ed90e0FeC3F3ea1C9b005C724D704501e0196".into());
3940
auth.insert("tester".into(), "AUTH_awesomeTester".into());
4041

4142
auth
@@ -69,7 +70,7 @@ lazy_static! {
6970
title: None,
7071
validators: SpecValidators::new(DUMMY_VALIDATOR_LEADER.clone(), DUMMY_VALIDATOR_FOLLOWER.clone()),
7172
max_per_impression: 10.into(),
72-
min_per_impression: 10.into(),
73+
min_per_impression: 1.into(),
7374
targeting: vec![],
7475
min_targeting_score: None,
7576
event_submission: Some(EventSubmission { allow: vec![] }),
@@ -79,7 +80,7 @@ lazy_static! {
7980
nonce: Some(nonce),
8081
withdraw_period_start: Utc.timestamp_millis(4_073_414_400_000),
8182
ad_units: vec![],
82-
pricing_bounds: None,
83+
pricing_bounds: Some(PricingBounds {impression: None, click: Some(Pricing { max: 0.into(), min: 0.into()})}),
8384
},
8485
}
8586
};

0 commit comments

Comments
 (0)