Skip to content
Permalink
Browse files

docs(api): add basic HTTP api docs

* fix: rename amount_delivered to delivered_amount
  • Loading branch information...
emschwartz committed May 22, 2019
1 parent 31e8bcf commit e9a285c97e499eb1a9b0db01378315621fe1aa42
@@ -12,7 +12,8 @@
[![Docker Image](https://img.shields.io/docker/pulls/emschwartz/interledger-rs.svg?maxAge=2592000)](https://hub.docker.com/r/emschwartz/interledger-rs/)

## Understanding Interledger.rs
- [API Docs](https://docs.rs/interledger)
- [HTTP API](./docs/api.md)
- [Rust API](https://docs.rs/interledger)
- [Interledger.rs Architecture](./docs/architecture.md)
- [Interledger Forum](https://forum.interledger.org) for general questions about the Interledger Protocol and Project

@@ -20,7 +20,7 @@ struct SpspPayRequest {
#[derive(Response, Debug)]
#[web(status = "200")]
struct SpspPayResponse {
amount_delivered: u64,
delivered_amount: u64,
}

#[derive(Response, Debug)]
@@ -67,10 +67,10 @@ impl_web! {
.map_err(|_| Response::builder().status(401).body("Unauthorized".to_string()).unwrap())
.and_then(move |account| {
pay(service, account, &body.receiver, body.source_amount)
.and_then(|amount_delivered| {
debug!("Sent SPSP payment and delivered: {} of the receiver's units", amount_delivered);
.and_then(|delivered_amount| {
debug!("Sent SPSP payment and delivered: {} of the receiver's units", delivered_amount);
Ok(SpspPayResponse {
amount_delivered,
delivered_amount,
})
})
.map_err(|err| {
@@ -45,12 +45,12 @@ where
&spsp.shared_secret,
source_amount,
)
.map(move |(amount_delivered, _plugin)| {
.map(move |(delivered_amount, _plugin)| {
debug!(
"Sent SPSP payment of {} and delivered {} of the receiver's units",
source_amount, amount_delivered
source_amount, delivered_amount
);
amount_delivered
delivered_amount
})
.map_err(move |err| {
error!("Error sending payment: {:?}", err);
@@ -47,7 +47,7 @@ where
source_amount,
congestion_controller: CongestionController::default(),
pending_requests: Cell::new(Vec::new()),
amount_delivered: 0,
delivered_amount: 0,
should_send_source_account: true,
sequence: 1,
rejected_packets: 0,
@@ -65,7 +65,7 @@ struct SendMoneyFuture<S: IncomingService<A>, A: Account> {
source_amount: u64,
congestion_controller: CongestionController,
pending_requests: Cell<Vec<PendingRequest>>,
amount_delivered: u64,
delivered_amount: u64,
should_send_source_account: bool,
sequence: u64,
rejected_packets: u64,
@@ -239,7 +239,7 @@ where
if let Ok(packet) = StreamPacket::from_encrypted(&self.shared_secret, fulfill.into_data()) {
if packet.ilp_packet_type() == IlpPacketType::Fulfill {
// TODO check that the sequence matches our outgoing packet
self.amount_delivered += packet.prepare_amount();
self.delivered_amount += packet.prepare_amount();
}
} else {
warn!(
@@ -311,10 +311,10 @@ where
} else {
self.state = SendMoneyFutureState::Closed;
debug!(
"Send money future finished. Delivered: {} ({} packets fulfilled, {} packets rejected)", self.amount_delivered, self.sequence - 1, self.rejected_packets,
"Send money future finished. Delivered: {} ({} packets fulfilled, {} packets rejected)", self.delivered_amount, self.sequence - 1, self.rejected_packets,
);
return Ok(Async::Ready((
self.amount_delivered,
self.delivered_amount,
self.next.take().unwrap(),
)));
}
@@ -141,8 +141,8 @@ mod send_money_to_receiver {
&shared_secret[..],
100,
)
.and_then(|(amount_delivered, _service)| {
assert_eq!(amount_delivered, 100);
.and_then(|(delivered_amount, _service)| {
assert_eq!(delivered_amount, 100);
Ok(())
})
.map_err(|err| panic!(err));
@@ -246,7 +246,7 @@ fn three_nodes() {
.and_then(|res| res.error_for_status())
.and_then(|res| res.into_body().concat2())
.and_then(|body| {
assert_eq!(str::from_utf8(body.as_ref()).unwrap(), "{\"amount_delivered\":2}");
assert_eq!(str::from_utf8(body.as_ref()).unwrap(), "{\"delivered_amount\":2}");
Ok(())
});

@@ -261,7 +261,7 @@ fn three_nodes() {
.and_then(|res| res.error_for_status())
.and_then(|res| res.into_body().concat2())
.and_then(|body| {
assert_eq!(str::from_utf8(body.as_ref()).unwrap(), "{\"amount_delivered\":500000}");
assert_eq!(str::from_utf8(body.as_ref()).unwrap(), "{\"delivered_amount\":500000}");
Ok(())
});

@@ -0,0 +1,175 @@
# ILP Node HTTP API

For instructions on running the ILP Node, see the [Readme](../README.md).

## Authentication

The ILP Node uses HTTP Bearer Token authorization. Most requests must either be authenticated with the admin token configured on the node or the token configured for a particular account.

## Account-Related Routes

By default, the API is available on port `7770`.

### POST /accounts

#### Request

The request must include:

```json
{
"ilp_address": "example.other-node",
"asset_code": "ABC",
"asset_scale": 9
}
```

Optional fields include:

```json
{
"ilp_address": "example.other-node",
"asset_code": "ABC",
"asset_scale": 9,
"max_packet_amount": 100000000000,
"min_balance": 0,
"http_incoming_token": "http bearer token they will use to authenticate with us",
"http_endpoint": "https://peer-ilp-over-http-endpoint.example/ilp",
"http_outgoing_token": "http bearer token we will use to authenticate with them",
"btp_uri": "btp+wss://:auth-token@peer-btp-endpoint",
"btp_incoming_token": "btp auth token they will use to authenticate with us",
"settle_threshold": 1000000000,
"settle_to": 0,
"send_routes": true,
"receive_routes": false,
"routing_relation": "Peer",
"round_trip_time": 500,
"amount_per_minute_limit": 1000000000,
"packets_per_minute_limit": 10
}
```

### GET /accounts

Admin only.

### GET /accounts/:id

Admin or account-holder only.

### GET /accounts/:id/balance

Admin or account-holder only.

#### Response

```json
{
"balance": 1000
}
```

## SPSP (Sending Payments)

### POST /pay

Account-holder only.

#### Request

```json
{
"receiver": "$payment-pointer.example",
"source_amount": 1000000
}
```

#### Response

```json
{
"delivered_amount": 2000000
}
```

### GET /spsp/:id

No authentication required.

This is the SPSP receiver endpoing that others will use to pay accounts on this node.

See the [Simple Payment Setup Protocol (SPSP) RFC](https://interledger.org/rfcs/0009-simple-payment-setup-protocol/) for more details about how this protocol works.

#### Response

```json
{
"destination_account":"test.21bae727127bd22d4d61f3e68eef80bc7d5a6edc.rH4jcsu2wcjMXS0-GhCRL0ZLwqssruLRspVsSJDMRcM","shared_secret":"5k/SCde7gR2QwN8a/vF2LneFt7EUt3WgzC3U6ym28aI="
}
```

### GET /.well-known/pay

No authentication required.

This is the "default" SPSP receiver account on this node. This endpoint is only enabled if the node is run with the configuration option `ILP_DEFAULT_SPSP_ACCOUNT={account id}`.

Same response as above.

## Node Settings

### GET /

Health check.

#### Response

```json
{
"status": "Ready"
}
```

### PUT /rates

Admin only.

Sets the exchange rates for the node.

### Request

```json
{
"ABC": 1.0,
"XYZ": 2.517
}
```

### PUT /routes/static

Admin only.

Configure static routes for the node. These will override routes received by CCP broadcast from other nodes.

### Request

```json
{
"example.some-prefix": 0,
"example.other.more-specific.prefix": 4
}
```

### PUT /routes/:prefix

Admin only.

Configure a single route.

### Request

```
"4"
```

### GET /routes
@@ -10,7 +10,6 @@ Note that this document assumes some familiarity with the Interledger Protocol (
- All details related to an account or peer are bundled in an [`Account`](#accounts) object, which is loaded from the `Store` and passed through the `Services`
- Nothing is instantiated for each packet or for each account; services that behave differently depending on account-specific details or configuration use methods on the `Account` object to get those details and behave accordingly
- Multiple identical nodes / connectors can be run and pointed at the same underlying database to horizontally scale a deployment for increased throughput
- Settlement is handled by [Settlement Engines](#settlement-engines) that run in separate processes but read from and write to the same database as the Interledger.rs components

## Services - Core Internal Abstraction

@@ -54,30 +53,4 @@ Each service that needs access to the database can define traits that the `Store

This approach to abstracting over different databases allows Interledger.rs to be used on top of multiple types of databases while leveraging the specific capabilities of each one. Instead of having one database abstraction that is the least common denominator of what each service requires, such as a simple key-value store, each service defines exactly what it needs and it is up to the `Store` implementation to provide it. For example, a `Store` implementation on top of a database that provides atomic transactions can use those for balance updates without the balance service needing to implement its own locking mechanism on top.

A further benefit of this approach is that each `Store` implementation can decide how to save and format the data in the underlying database and those choices are completely abstracted away from the services. The `Store` can organize all account-related details into sensible tables and indexes and multiple services can access the same underlying data using the service-specific methods from the traits the `Store` implements.

## Settlement Engines

### Separate Clearing and Settlement

Interledger.rs separates the "clearing" of ILP packets from the "settlement" of balances between directly connected nodes.

The logic for forwarding and processing ILP packets and updating account balances for them is implemented in Rust. External [settlement engines](./settlement-engines) are provided for specific ledgers and "Layer 2" technologies such as payment channels. The settlement engines can be written in any programming language -- ideally whichever has the best SDK for the underlying settlement mechanism.

Settlement engines are run as separate processes from the core Interledger node / connector. This means that even slower settlement engine implementations should not affect the latency or throughput of the Interledger.rs node.

### Common Database

The core Interledger.rs components and external settlement engines talk directly to the same databases. For example, the settlement engines will use the same balances tables as the Interledger node so that they can read the accrued account balance and update it to reflect incoming and outgoing settlements. Settlement engines can also define additional tables or indices, for example to map ledger addresses to `Account` records or to store the latest payment channel claims.

Note that this design means that each settlement engine must separately implement its interactions with different databases. Settlement engines _should_ support all databases for which there are Interledger.rs `Store` implementations.

### Polling Over PubSub

It is recommended for performance reasons that settlement engines poll the underlying database on some configured interval, rather than using a publish / subscribe method to receive real-time balance updates. The Interledger.rs node / connector may process orders of magnitude more ILP packets than the settlement engine can handle. The settlement engine can poll the database at any frequency that makes sense for that settlement method (every couple seconds or minutes for on-ledger transfer-based methods or more often for payment channel-based methods).

### Bilateral Messaging

If a settlement engine requires bilateral messaging, for example to exchange payment channel claims or updates, it is recommended to have a component written in Rust and a separate settlement engine. The Rust service will be added to service chain to handle incoming messages and write relevant details to the underlying database (through a `Store` abstraction). The separate settlement engine will connect to the underlying ledger or blockchain, listen for notifications, and submit transactions such as payment channel closes when applicable.

This design is preferable to having the Interledger.rs node / connector forward ILP Prepare packets with a specific ILP Address prefix to the external settlement engine. Forwarding all packets with a certain prefix to the settlement engine would make it easy to DoS by overwhelming the (not necessarily high-throughput) settlement engine with too many packets.
A further benefit of this approach is that each `Store` implementation can decide how to save and format the data in the underlying database and those choices are completely abstracted away from the services. The `Store` can organize all account-related details into sensible tables and indexes and multiple services can access the same underlying data using the service-specific methods from the traits the `Store` implements.
@@ -131,7 +131,7 @@ async function loadConfig() {
async function generateTestnetCredentials() {
console.log('Generating testnet credentials...')
const adminToken = randomBytes(32).toString('hex')
const nodeId = randomBytes(20).toString('hex')
const nodeId = randomBytes(12).toString('hex')
const localTunnelSubdomain = `ilp-node-${nodeId}`
const ilpAddress = `test.${nodeId}`
const secretSeed = randomBytes(32).toString('hex')

0 comments on commit e9a285c

Please sign in to comment.
You can’t perform that action at this time.