Skip to content

Commit f86937f

Browse files
authored
Settlement Engine API interop updates (#183)
* fix(engines): make CreateAccount endpoint accept the id as a json body parameter * fix(settlement): Make Connector's receive_money endpoint accept a String instead of u64 for amount. The number is only ever converted to a u64 before scale conversion and input to the store. The client also converts it to a string before submitting to the engine * fix(engines): use the Quantity struct exposed from the Settlements API When sending money we convert the received string to a U256 When receiving money we convert the received U256 to a string * feat(eth-se): configure asset_scale via CLI
1 parent ed85ca4 commit f86937f

File tree

15 files changed

+151
-144
lines changed

15 files changed

+151
-144
lines changed

crates/interledger-api/src/routes/accounts.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,15 @@ impl_web! {
8585
se_url
8686
.path_segments_mut()
8787
.expect("Invalid settlement engine URL")
88-
.push("accounts")
89-
.push(&id.to_string());
88+
.push("accounts");
9089
trace!(
9190
"Sending account {} creation request to settlement engine: {:?}",
9291
id,
9392
se_url.clone()
9493
);
9594
let action = move || {
9695
Client::new().post(se_url.clone())
96+
.json(&json!({"id" : id.to_string()}))
9797
.send()
9898
.map_err(move |err| {
9999
error!("Error sending account creation command to the settlement engine: {:?}", err)

crates/interledger-settlement-engines/src/api.rs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
use crate::ApiResponse;
2-
use crate::Quantity;
3-
use crate::SettlementEngine;
1+
use crate::{ApiResponse, CreateAccount, SettlementEngine};
42
use bytes::Bytes;
53
use futures::{
64
future::{err, ok, Either},
75
Future,
86
};
97
use hyper::{Response, StatusCode};
8+
use interledger_settlement::Quantity;
109
use interledger_settlement::{IdempotentData, IdempotentStore};
1110
use log::error;
1211
use ring::digest::{digest, SHA256};
@@ -60,13 +59,14 @@ impl_web! {
6059
self.make_idempotent_call(f, input_hash, idempotency_key)
6160
}
6261

63-
#[post("/accounts/:account_id")]
62+
#[post("/accounts")]
6463
/// Forwards the data to the API engine's `create_account` function.
65-
/// Endpoint: POST /accounts/:id/
66-
fn create_account(&self, account_id: String, idempotency_key: Option<String>) -> impl Future<Item = Response<String>, Error = Response<String>> {
67-
let input_hash = get_hash_of(account_id.as_ref());
64+
/// Endpoint: POST /accounts/
65+
fn create_account(&self, body: CreateAccount, idempotency_key: Option<String>) -> impl Future<Item = Response<String>, Error = Response<String>> {
66+
let input = format!("{:?}", body);
67+
let input_hash = get_hash_of(input.as_ref());
6868
let engine = self.engine.clone();
69-
let f = move || engine.create_account(account_id);
69+
let f = move || engine.create_account(body);
7070
self.make_idempotent_call(f, input_hash, idempotency_key)
7171
}
7272

@@ -214,7 +214,7 @@ mod tests {
214214

215215
fn create_account(
216216
&self,
217-
_account_id: String,
217+
_account_id: CreateAccount,
218218
) -> Box<dyn Future<Item = ApiResponse, Error = ApiResponse> + Send> {
219219
Box::new(ok((
220220
StatusCode::from_u16(201).unwrap(),
@@ -234,7 +234,7 @@ mod tests {
234234

235235
let ret: Response<_> = block_on(api.execute_settlement(
236236
"1".to_owned(),
237-
Quantity { amount: 100 },
237+
Quantity::new(100, 6),
238238
Some(IDEMPOTENCY.clone()),
239239
))
240240
.unwrap();
@@ -244,7 +244,7 @@ mod tests {
244244
// is idempotent
245245
let ret: Response<_> = block_on(api.execute_settlement(
246246
"1".to_owned(),
247-
Quantity { amount: 100 },
247+
Quantity::new(100, 6),
248248
Some(IDEMPOTENCY.clone()),
249249
))
250250
.unwrap();
@@ -254,7 +254,7 @@ mod tests {
254254
// // fails with different id and same data
255255
let ret: Response<_> = block_on(api.execute_settlement(
256256
"42".to_owned(),
257-
Quantity { amount: 100 },
257+
Quantity::new(100, 6),
258258
Some(IDEMPOTENCY.clone()),
259259
))
260260
.unwrap_err();
@@ -267,7 +267,7 @@ mod tests {
267267
// fails with same id and different data
268268
let ret: Response<_> = block_on(api.execute_settlement(
269269
"1".to_string(),
270-
Quantity { amount: 42 },
270+
Quantity::new(42, 6),
271271
Some(IDEMPOTENCY.clone()),
272272
))
273273
.unwrap_err();
@@ -280,7 +280,7 @@ mod tests {
280280
// fails with different id and different data
281281
let ret: Response<_> = block_on(api.execute_settlement(
282282
"42".to_string(),
283-
Quantity { amount: 42 },
283+
Quantity::new(42, 6),
284284
Some(IDEMPOTENCY.clone()),
285285
))
286286
.unwrap_err();
@@ -370,19 +370,22 @@ mod tests {
370370
};
371371

372372
let ret: Response<_> =
373-
block_on(api.create_account("1".to_owned(), Some(IDEMPOTENCY.clone()))).unwrap();
373+
block_on(api.create_account(CreateAccount::new("1"), Some(IDEMPOTENCY.clone())))
374+
.unwrap();
374375
assert_eq!(ret.status().as_u16(), 201);
375376
assert_eq!(ret.body(), "CREATED");
376377

377378
// is idempotent
378379
let ret: Response<_> =
379-
block_on(api.create_account("1".to_owned(), Some(IDEMPOTENCY.clone()))).unwrap();
380+
block_on(api.create_account(CreateAccount::new("1"), Some(IDEMPOTENCY.clone())))
381+
.unwrap();
380382
assert_eq!(ret.status().as_u16(), 201);
381383
assert_eq!(ret.body(), "CREATED");
382384

383385
// fails with different id
384386
let ret: Response<_> =
385-
block_on(api.create_account("42".to_owned(), Some(IDEMPOTENCY.clone()))).unwrap_err();
387+
block_on(api.create_account(CreateAccount::new("42"), Some(IDEMPOTENCY.clone())))
388+
.unwrap_err();
386389
assert_eq!(ret.status().as_u16(), 409);
387390
assert_eq!(
388391
ret.body(),

crates/interledger-settlement-engines/src/engines/ethereum_ledger/eth_engine.rs

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ use url::Url;
3434
use uuid::Uuid;
3535

3636
use crate::stores::redis_ethereum_ledger::*;
37-
38-
use crate::{ApiResponse, Quantity, SettlementEngine, SettlementEngineApi};
37+
use crate::{ApiResponse, CreateAccount, SettlementEngine, SettlementEngineApi};
38+
use interledger_settlement::Quantity;
3939

4040
const MAX_RETRIES: usize = 10;
4141
const ETH_CREATE_ACCOUNT_PREFIX: &[u8] = b"ilp-ethl-create-account-message";
@@ -78,6 +78,7 @@ pub struct EthereumLedgerSettlementEngine<S, Si, A> {
7878
confirmations: u8,
7979
poll_frequency: Duration,
8080
connector_url: Url,
81+
asset_scale: u8,
8182
}
8283

8384
pub struct EthereumLedgerSettlementEngineBuilder<'a, S, Si, A> {
@@ -91,6 +92,7 @@ pub struct EthereumLedgerSettlementEngineBuilder<'a, S, Si, A> {
9192
poll_frequency: Option<Duration>,
9293
connector_url: Option<Url>,
9394
token_address: Option<Address>,
95+
asset_scale: Option<u8>,
9496
watch_incoming: bool,
9597
account_type: PhantomData<A>,
9698
}
@@ -111,6 +113,7 @@ where
111113
poll_frequency: None,
112114
connector_url: None,
113115
token_address: None,
116+
asset_scale: None,
114117
watch_incoming: false,
115118
account_type: PhantomData,
116119
}
@@ -126,6 +129,11 @@ where
126129
self
127130
}
128131

132+
pub fn asset_scale(&mut self, asset_scale: u8) -> &mut Self {
133+
self.asset_scale = Some(asset_scale);
134+
self
135+
}
136+
129137
pub fn chain_id(&mut self, chain_id: u8) -> &mut Self {
130138
self.chain_id = Some(chain_id);
131139
self
@@ -178,6 +186,11 @@ where
178186
} else {
179187
Duration::from_secs(5)
180188
};
189+
let asset_scale = if let Some(asset_scale) = self.asset_scale {
190+
asset_scale
191+
} else {
192+
18
193+
};
181194

182195
let (eloop, transport) = Http::new(ethereum_endpoint).unwrap();
183196
eloop.into_remote();
@@ -196,6 +209,7 @@ where
196209
confirmations,
197210
poll_frequency,
198211
connector_url,
212+
asset_scale,
199213
account_type: PhantomData,
200214
};
201215
if self.watch_incoming {
@@ -355,11 +369,7 @@ where
355369
store
356370
.load_account_id_from_address(addr)
357371
.and_then(move |id| {
358-
self_clone.notify_connector(
359-
id.to_string(),
360-
amount.low_u64(),
361-
tx_hash,
362-
)
372+
self_clone.notify_connector(id.to_string(), amount, tx_hash)
363373
})
364374
.and_then(move |_| {
365375
// only save the transaction hash if the connector
@@ -442,7 +452,7 @@ where
442452
Either::A(
443453
store.load_account_id_from_address(addr)
444454
.and_then(move |id| {
445-
self_clone.notify_connector(id.to_string(), amount.low_u64(), tx_hash)
455+
self_clone.notify_connector(id.to_string(), amount, tx_hash)
446456
})
447457
.and_then(move |_| {
448458
// only save the transaction hash if the connector
@@ -463,11 +473,12 @@ where
463473
fn notify_connector(
464474
&self,
465475
account_id: String,
466-
amount: u64,
476+
amount: U256,
467477
tx_hash: H256,
468478
) -> impl Future<Item = (), Error = ()> {
469479
let mut url = self.connector_url.clone();
470480
let account_id_clone = account_id.clone();
481+
let asset_scale = self.asset_scale;
471482
url.path_segments_mut()
472483
.expect("Invalid connector URL")
473484
.push("accounts")
@@ -480,7 +491,7 @@ where
480491
client
481492
.post(url.clone())
482493
.header("Idempotency-Key", tx_hash.to_string())
483-
.json(&json!({ "amount": amount }))
494+
.json(&json!({ "amount": amount.to_string(), "scale" : asset_scale }))
484495
.send()
485496
.map_err(move |err| {
486497
error!(
@@ -611,10 +622,11 @@ where
611622
/// the store.
612623
fn create_account(
613624
&self,
614-
account_id: String,
625+
account_id: CreateAccount,
615626
) -> Box<dyn Future<Item = ApiResponse, Error = ApiResponse> + Send> {
616627
let self_clone = self.clone();
617628
let store: S = self.store.clone();
629+
let account_id = account_id.id;
618630

619631
Box::new(
620632
result(A::AccountId::from_str(&account_id).map_err({
@@ -741,27 +753,32 @@ where
741753
account_id: String,
742754
body: Quantity,
743755
) -> Box<dyn Future<Item = ApiResponse, Error = ApiResponse> + Send> {
744-
let amount = U256::from(body.amount);
745756
let self_clone = self.clone();
746-
747757
Box::new(
748-
self_clone
749-
.load_account(account_id)
750-
.map_err(move |err| {
751-
let error_msg = format!("Error loading account {:?}", err);
752-
error!("{}", error_msg);
753-
(StatusCode::from_u16(400).unwrap(), error_msg)
754-
})
755-
.and_then(move |(_account_id, addresses)| {
756-
self_clone
757-
.settle_to(addresses.own_address, amount, addresses.token_address)
758-
.map_err(move |_| {
759-
let error_msg = "Error connecting to the blockchain.".to_string();
760-
error!("{}", error_msg);
761-
(StatusCode::from_u16(502).unwrap(), error_msg)
762-
})
763-
})
764-
.and_then(move |_| Ok((StatusCode::OK, "OK".to_string()))),
758+
result(U256::from_dec_str(&body.amount).map_err(move |err| {
759+
let error_msg = format!("Error converting to U256 {:?}", err);
760+
error!("{:?}", error_msg);
761+
(StatusCode::from_u16(400).unwrap(), error_msg)
762+
}))
763+
.and_then(move |amount| {
764+
self_clone
765+
.load_account(account_id)
766+
.map_err(move |err| {
767+
let error_msg = format!("Error loading account {:?}", err);
768+
error!("{}", error_msg);
769+
(StatusCode::from_u16(400).unwrap(), error_msg)
770+
})
771+
.and_then(move |(_account_id, addresses)| {
772+
self_clone
773+
.settle_to(addresses.own_address, amount, addresses.token_address)
774+
.map_err(move |_| {
775+
let error_msg = "Error connecting to the blockchain.".to_string();
776+
error!("{}", error_msg);
777+
(StatusCode::from_u16(502).unwrap(), error_msg)
778+
})
779+
})
780+
.and_then(move |_| Ok((StatusCode::OK, "OK".to_string())))
781+
}),
765782
)
766783
}
767784
}
@@ -804,6 +821,7 @@ pub fn run_ethereum_engine<R, Si>(
804821
private_key: Si,
805822
chain_id: u8,
806823
confirmations: u8,
824+
asset_scale: u8,
807825
poll_frequency: u64,
808826
connector_url: String,
809827
token_address: Option<Address>,
@@ -824,6 +842,7 @@ where
824842
.chain_id(chain_id)
825843
.connector_url(&connector_url)
826844
.confirmations(confirmations)
845+
.asset_scale(asset_scale)
827846
.poll_frequency(poll_frequency)
828847
.watch_incoming(watch_incoming)
829848
.token_address(token_address)
@@ -907,7 +926,7 @@ mod tests {
907926

908927
let bob_mock = mockito::mock("POST", "/accounts/42/settlements")
909928
.match_body(mockito::Matcher::JsonString(
910-
"{\"amount\": 100 }".to_string(),
929+
"{\"amount\": \"100\", \"scale\": 18 }".to_string(),
911930
))
912931
.with_status(200)
913932
.with_body("OK".to_string())
@@ -930,8 +949,8 @@ mod tests {
930949
false, // alice sends the transaction to bob (set it up so that she doesn't listen for inc txs)
931950
);
932951

933-
let ret = block_on(alice_engine.send_money(bob.id.to_string(), Quantity { amount: 100 }))
934-
.unwrap();
952+
let ret =
953+
block_on(alice_engine.send_money(bob.id.to_string(), Quantity::new(100, 6))).unwrap();
935954
assert_eq!(ret.0.as_u16(), 200);
936955
assert_eq!(ret.1, "OK");
937956

@@ -974,7 +993,7 @@ mod tests {
974993

975994
// the signed message does not match. We are not able to make Mockito
976995
// capture the challenge and return a signature on it.
977-
let ret = block_on(engine.create_account(bob.id.to_string())).unwrap_err();
996+
let ret = block_on(engine.create_account(CreateAccount::new(bob.id))).unwrap_err();
978997
assert_eq!(ret.0.as_u16(), 502);
979998
// assert_eq!(ret.1, "CREATED");
980999

crates/interledger-settlement-engines/src/lib.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,19 @@ pub mod engines;
2020
pub mod stores;
2121
pub use self::api::SettlementEngineApi;
2222

23-
#[derive(Extract, Debug, Clone, Copy)]
24-
pub struct Quantity {
25-
amount: u64,
23+
#[derive(Extract, Debug, Clone, Hash)]
24+
pub struct CreateAccount {
25+
id: String,
26+
}
27+
28+
impl CreateAccount {
29+
pub fn new<T: ToString>(id: T) -> Self {
30+
CreateAccount { id: id.to_string() }
31+
}
2632
}
2733

2834
use http::StatusCode;
35+
use interledger_settlement::Quantity;
2936

3037
pub type ApiResponse = (StatusCode, String);
3138

@@ -46,6 +53,6 @@ pub trait SettlementEngine {
4653

4754
fn create_account(
4855
&self,
49-
account_id: String,
56+
account_id: CreateAccount,
5057
) -> Box<dyn Future<Item = ApiResponse, Error = ApiResponse> + Send>;
5158
}

0 commit comments

Comments
 (0)