W3C home > Mailing lists > Public > public-interledger@w3.org > August 2017

Re: [Ledger] An attempt to clean up the protocol architecture

From: Ben Sharafian <sharafian@ripple.com>
Date: Fri, 18 Aug 2017 16:09:30 +0200
Message-ID: <CAEcG=+01C+Wpx1n4Lpz=N4=eD=j3ZiqWMyOVofbyE8eGPpYrfw@mail.gmail.com>
To: Adrian Hope-Bailie <adrian@hopebailie.com>
Cc: Evan Schwartz <evan@ripple.com>, Interledger Community Group <public-interledger@w3.org>, Interledger Mailing List - IETF <ledger@ietf.org>
>
> What you're describing is an implementation of a local transfer service
> that doesn't (or can't) take advantage of Interledger. In fact, this will
> be the norm for most existing payment networks that we want to add to the
> Interledger and this is where your work on payment channels and HTLAs comes
> in to play.



>
> If you have a local transfer channel that doesn't support conditions
> natively or supports them in a way that requires some adaption you can
> create an overlay network that does what's needed. Example: Bitcoin is not
> a suitable network to use for ILP so we use a payment channel between the
> peers that is underwritten by Bitcoin.


Exactly, when I say you need a to handle the condition and fulfillment via
a clearing layer, this "overlay network" is what I'm referring to. The
whole point of an HTLA is that from ILP's point of view it's just a
lower-level protocol that can commit conditional transfers and fulfill
them. ILP doesn't care how the ledger/HTLA works under the hood, whether
it's a clearing/overlay layer or a "real" ledger.

You're equating this "overlay network," which is a type of ledger, with the
ILP layer. If there is an overlay network that handles the condition and
fulfillment, that means you _are_ using a ledger, because the overlay
network _is_ a ledger. The ILP layer is a layer above this.

If we assume the ILP packet is well defined with all the ILP-required data
> elements then when a lower layer wants to use that data it can but should
> do it in a way that doesn't break the ILP layer for everyone else. How that
> is implemented in practice will depend on the lower layer protocol.


That's one way to see it, but what I'm wondering is why that's a better
model than saying there's a clearly defined interface that the ledger-layer
exposes, and then building the ILP layer on top of that.

Unless the next hop is backwards


There's no reason to send a backwards hop. If you don't want to forward a
payment you reject it.

Not at all. You're assuming that there is no expiry on the local transfer.
> As a receiving node I have an incoming transfer with an expiry and it
> carries an ILP packet that also has an expiry.


If that's what you're talking about, the ILP packet already has an expiry.
It's implemented in the PSK details so the receiver knows whether the
packet it issued is still valid. But that's an application layer concern,
because it's only the receiver who looks at the ILP packet's expiry. The
connectors only need to know the expiries of the local transfers.



On Fri, Aug 18, 2017 at 3:21 PM, Adrian Hope-Bailie <adrian@hopebailie.com>
wrote:

>
>
> On 18 August 2017 at 10:38, Ben Sharafian <sharafian@ripple.com> wrote:
>
>> Two connectors exchanging a transfer only care about the data that is
>>> relevant to them for that transfer. It's quite possible for two connectors
>>> to perform a transfer that has no conditions or fulfillments or a transfer
>>> that has a different condition and fulfillment (such as an atomic mode
>>> transfer where the condition is a compound one that has multiple
>>> sub-conditions).
>>
>>
>> Yes, two connectors could absolutely send an unconditional transfer on an
>> underlying ledger in order to settle an Interledger payment. But in order
>> to benefit from any of Interledger's guarantees (trustless connectors,
>> retry-ability, etc.), they MUST keep a conditional local transfer, at least
>> for clearing. If the local transfer cannot time out, it cannot be safely
>> retried. If the local transfer clears without a fulfillment, it loses the
>> "receipt or your money back" property. Yes, two parties in the chain could
>> eschew these benefits but that is no longer a correct implementation of
>> Interledger.
>>
>
> Aha! That is where I disagree! Interledger is not implemented at the local
> transfer layer, it is implemented at the Interledger layer. The whole point
> is that it an Interledger payment can travel over payment networks that
> know nothing about ILP but the greatest value comes from using ones that do.
>
> What you're describing is an implementation of a local transfer service
> that doesn't (or can't) take advantage of Interledger. In fact, this will
> be the norm for most existing payment networks that we want to add to the
> Interledger and this is where your work on payment channels and HTLAs comes
> in to play.
>
> If you have a local transfer channel that doesn't support conditions
> natively or supports them in a way that requires some adaption you can
> create an overlay network that does what's needed. Example: Bitcoin is not
> a suitable network to use for ILP so we use a payment channel between the
> peers that is underwritten by Bitcoin.
>
>
>>
>> If a protocol at a lower layer wants to use that data then it must
>>> replicate it. That seems inefficient but it's the correct way to do it.
>>
>>
>> What's the actual reasoning behind this, and why is it considered the
>> correct approach? If there's some IETF document that says this I'd like to
>> see it, because it intuitively feels wrong to have lower level abstractions
>> looking into higher levels.
>>
>
> The data in the ILP layer is there for a specific reason. It's part of an
> end-to-end exchange between the sender and receiver.
>
>
> *Side note: It's unusual to be designing the whole stack at once so we
> keep being tempted to move things around but in reality we should look at
> the ILP layer in isolation and make sure it is complete. Alternatively we
> should try to test our design against a number of lower and upper layer
> protocols to make sure our thinking is sound.*
> If we assume the ILP packet is well defined with all the ILP-required data
> elements then when a lower layer wants to use that data it can but should
> do it in a way that doesn't break the ILP layer for everyone else. How that
> is implemented in practice will depend on the lower layer protocol.
>
>
>>
>> Routing requires looking at the condition, expiry and amount. A
>>> connector's routing logic shouldn't forward a packet if the expiry is too
>>> low or if the condition is obviously corrupted.
>>
>>
>> Those are validity checks and you're right that they are performed in the
>> connector, but the destination account, destination amount, and data are
>> enough to figure out what the best next hop is.
>>
>
> Unless the next hop is backwards
>
>
>>
>> The ILP Packet's purpose is to describe where a payment is going.
>>
>
> Not only that. For comparison, an IP packet does a lot more than just
> describe where a packet is going. All end-to-end concerns need to be in
> there too.
>
>
>> The data it carries is only for the purpose of making sure the receiver
>> can identify that payment.
>>
>
> No, the condition is there so the receiver can ensure it is sending back
> the correct fulfillment and the expiry to give the receiver some assurance
> that the packet is still worth processing.
>
>
>> In that context, the expiry has no bearing on where it will be routed nor
>> on how much to route, only on whether or not an individual connector will
>> take the risk of forwarding it.
>>
>> The ILP packet just contains the end-to-end condition (always a SHA-256
>>> hash) and then the local transfer can have a different condition that is
>>> derived from the condition in the ILP packet.
>>
>>
>> Fair enough; in your case you can transmit the condition twice and verify
>> the structure of the complex local condition, and in the
>> no-condition-in-ILP version you can verify the structure of the local
>> condition and extract the SHA256 condition to pass on.
>>
>> I think the expiry should always be the expiry set by the sender. It
>>> won't be changed.
>>
>>
>> That increases connector risk enormously, allowing anybody to cause a
>> connector to lose money by submitting a fulfillment at the last moment.
>>
>
> Not at all. You're assuming that there is no expiry on the local transfer.
> As a receiving node I have an incoming transfer with an expiry and it
> carries an ILP packet that also has an expiry.
>
> My routing logic should look at both and decide a) if this is safe to
> route on (i.e. put some of my own capital at risk) and b) what expiry to
> set on the next hop.
>
> There is no incentive to change the expiry in the packet as this can only
> be targeted at upstream connectors and the result will simply be that the
> payment is declined and the upstream local transfer rolls back.
>
>
>> If an attacker knows enough about latency in the chain, they could even
>> target this attack at somebody many hops away. Staggering expiries (giving
>> you a minimum amount of time to fulfill a source transfer) is an easy
>> mitigation against this attack; we shouldn't take it out.
>>
>
> I agree. We would still have the local expiry.
>
>
>>
>> Comparing the condition in the local transfer and the one in the ILP
>>> packet should be part of the routing logic.
>>
>>
>> Sure, it can be done as an extra validity check, but it's just more code
>> and more attack surface. I still don't see any tangible benefit from
>> doing the layering in this way, aside from your assertion that it's the
>> correct way to do it. If you've read any documents that explain why this is
>> the correct way to do layering, I think I'd understand you better.
>>
>>
>> On Thu, Aug 17, 2017 at 7:32 PM, Adrian Hope-Bailie <
>> adrian@hopebailie.com> wrote:
>>
>>>
>>>
>>> On 16 August 2017 at 10:22, Ben Sharafian <sharafian@ripple.com> wrote:
>>>
>>>> OK, I think in that case we're mostly disagreeing over where the
>>>> condition/fulfillment/expiry actually go in the data.
>>>>
>>>
>>> That's one way to look at it but that's ultimately what the architecting
>>> the layering is. Deciding at which layer (and therefor encapsulated in what
>>> packet) certain data should be.
>>>
>>>
>>>> The reason I don't agree with your position is based on which parties I
>>>> think should be aware of ILP.
>>>>
>>>
>>> I don't think that's the right way to look at it. The connector needs to
>>> be able to understand at least the ILP layer data AND the lower layer data.
>>> Normally the way the processing stack is implemented is that there is a
>>> module for each layer that processes the data from that layer and then
>>> passes the payload and any other important information up to the next layer.
>>>
>>>
>>>> In order to track the balance between each other accurately, the two
>>>> connectors have to keep track of conditions, fulfillments, and expiries on
>>>> each of the transfers.
>>>>
>>>
>>> This is where I disagree with you. Two connectors exchanging a transfer
>>> only care about the data that is relevant to them for that transfer. It's
>>> quite possible for two connectors to perform a transfer that has no
>>> conditions or fulfillments or a transfer that has a different condition and
>>> fulfillment (such as an atomic mode transfer where the condition is a
>>> compound one that has multiple sub-conditions).
>>>
>>>
>>>> That means the connectors' accounting logic that handles the
>>>> conditions, fulfillments, and expiries is going to be using some
>>>> information inside the ILP packet and some information outside of it in
>>>> order to perform these transfers.
>>>>
>>>
>>> It will only use info inside the packet if it uses conditional transfers
>>> that use that same condition. This is the most likely scenario but that is
>>> not a protocol requirement.
>>>
>>>
>>>>
>>>> I think it's cleaner to say everything required to make these local
>>>> transfers should go in one protocol, so the accounting logic of these
>>>> connectors doesn't have to deal with ILP directly.
>>>>
>>>
>>> I strongly disagree with that. That's entirely the wrong reason to put
>>> data into a specific layer. The data in the ILP layer is there because it's
>>> "end-to-end" data.
>>>
>>> If a protocol at a lower layer wants to use that data then it must
>>> replicate it. That seems inefficient but it's the correct way to do it.
>>>
>>> One could define a lower layer protocol that doesn't replicate the data
>>> but the rules of the protocol are "Get the condition from the ILP packet".
>>> In that case, that specific lower level protocol is forcing implementations
>>> to understand the ILP packet format, that's an implementation detail.
>>>
>>> Another lower layer protocol might take the condition from the ILP
>>> packet and re-encode it in a different form (like a base64ulr string or NI:
>>> uri)
>>>
>>>
>>>> Then the connectors' ILP-packet-related behavior can all be routing
>>>> related.
>>>>
>>>
>>> Routing requires looking at the condition, expiry and amount. A
>>> connector's routing logic shouldn't forward a packet if the expiry is too
>>> low or if the condition is obviously corrupted.
>>>
>>> If the protocols were designed correctly as I am proposing then another
>>> routing decision would be, is the condition that was used in the incoming
>>> transfer the same as the one used in the ILP packet?
>>>
>>>
>>>
>>>> This would add a few benefits; two connectors could perform non-ILP
>>>> conditional transfers between one another (which would be useful for
>>>> reconciliation and settlement), and could also allow two connectors to use
>>>> more complex condition types (i.e. signatures for atomic mode) without
>>>> forcing us to support that in the ILP packet.
>>>>
>>>
>>> You seem to have this backwards. Both of these things are supported if
>>> the condition and expiry ARE in the ILP packet. Atomic mode is not
>>> supported in the ILP protocol it is supported in the lower layer transfer
>>> protocol. The ILP packet just contains the end-to-end condition (always a
>>> SHA-256 hash) and then the local transfer can have a different condition
>>> that is derived from the condition in the ILP packet.
>>>
>>>
>>>> It also keeps the integrity of the ILP packet as something lower levels
>>>> don't modify; the connector has to modify the expiry in order to pass along
>>>> an ILP payment (they may not modify the expiry if they're using something
>>>> like atomic mode, but then we have the issue with advanced condition types
>>>> not being supported in the ILP packet).
>>>>
>>>
>>> I think the expiry should always be the expiry set by the sender. It
>>> won't be changed.
>>>
>>>>
>>>> In the case where the ledger _does_ care about the condition and
>>>> fulfillment, the argument in favor of separating
>>>> condition/fulfillment/expiry from the rest of the packet is similar.
>>>> Because we don't control the features of all ledgers, we'll need our
>>>> plugins or ledger adapters to be aware of ILP. This makes it hard to
>>>> interact with any events that don't involve ILP packets, and impossible to
>>>> handle features that extend beyond what we support in the ILP packet. We
>>>> could pass details about non-ILP ledger features (like a crypto condition)
>>>> in the side data, but in the event of any redundancy we have to check the
>>>> ledger-supplied info, not the info in the ILP packet.
>>>>
>>>
>>> Comparing the condition in the local transfer and the one in the ILP
>>> packet should be part of the routing logic.
>>>
>>>
>>>>
>>>> Basically, condition/fulfillment/expiry are used for accounting local
>>>> transfers (even if they aren't being "ledger" enforced) in addition to
>>>> their role as every-hop information. By putting them in the ILP packet, we
>>>> limit the special features that ledgers can support and make our software
>>>> abstractions harder to separate cleanly. By putting them in the local
>>>> transfer alongside the ILP packet but not inside it, we do separate the
>>>> data a little more, but we have more freedom in what the underlying
>>>> accounting and ledger logic can do, and our software modules will have more
>>>> clearly separated domains.
>>>>
>>>
>>> They should be in both the local transfer AND the ILP packet if they are
>>> needed by the local transfer protocol. This allows the flexibility you
>>> desire because the local transfer protocol can do ANYTHING including using
>>> the condition from the ILP packet as-is, not at all or enhanced for
>>> something like atomic mode.
>>>
>>>
>>>
>>>>
>>>> On Tue, Aug 15, 2017 at 10:24 AM, Adrian Hope-Bailie <
>>>> adrian@hopebailie.com> wrote:
>>>>
>>>>> Exactly 👍
>>>>>
>>>>> On Tue, Aug 15, 2017 at 6:52 PM Ben Sharafian <sharafian@ripple.com>
>>>>> wrote:
>>>>>
>>>>>> Ok, I think I have a better idea of what you're saying.
>>>>>>
>>>>>> It sounds like you're saying the ILP layer contains all information
>>>>>> that is common between every hop (destination, destination amount, opaque
>>>>>> data, condition, fulfillment, expiry). The lower level would then be used
>>>>>> for all the transfer-local details (source amount, next connector account,
>>>>>> custom/local data).
>>>>>>
>>>>>> If the lower level wanted to do anything related to the every-hop
>>>>>> payment, i.e. escrow funds until the receipt has been produced, it would
>>>>>> look into the ILP layer for that information. If the lower level didn't do
>>>>>> any escrow or expiries that require every-hop details, it would simply
>>>>>> function as a communication method.
>>>>>>
>>>>>> Is that right?
>>>>>>
>>>>>> On Tue, Aug 15, 2017 at 6:35 PM, Adrian Hope-Bailie <
>>>>>> adrian@hopebailie.com> wrote:
>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> On 15 August 2017 at 16:00, Ben Sharafian <sharafian@ripple.com>
>>>>>>> wrote:
>>>>>>>
>>>>>>>> In that case, the plugin or whatever is doing the accounting is the
>>>>>>>>>>> ledger. Digital value is always tracked in ledgers, so I think it does make
>>>>>>>>>>> sense to think of this as the base layer. The reason to abstract the
>>>>>>>>>>> functionality you expect from the ledger layer is precisely so you can
>>>>>>>>>>> handle it in different ways, depending on what the underlying systems
>>>>>>>>>>> provide.
>>>>>>>>>>
>>>>>>>>>> I see 3 ways to think of the layer(s) underpinning ILP:
>>>>>>>>>>
>>>>>>>>>>    1. The "Ledger Layer" provides both messaging capabilities
>>>>>>>>>>    and some type of HTLA
>>>>>>>>>>    <https://github.com/interledger/rfcs/blob/master/0022-hashed-timelock-agreements/0022-hashed-timelock-agreements.md>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>    1. There are separate plugins for messaging and for transfers
>>>>>>>>>>    and when you peer with someone you have to agree on a plugin for each
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>    1. We standardize the messaging part and say that all goes
>>>>>>>>>>    over IP and then just have more minimal plugins for the on-ledger
>>>>>>>>>>    settlements
>>>>>>>>>>
>>>>>>>>>> Number 1 is what we have and I think that still makes the most
>>>>>>>>>> sense.
>>>>>>>>>
>>>>>>>>>
>>>>>>>> I think you're confusing implementation details and defining of
>>>>>>>>> interfaces with definition of a protocol stack. The only differences
>>>>>>>>> between the tree examples you have above is in implementation.
>>>>>>>>
>>>>>>>>
>>>>>>>> I had to scroll up after reading this to make sure it was @adrian
>>>>>>>> talking, because that seems like the opposite of what you were arguing for.
>>>>>>>>
>>>>>>>
>>>>>>> I don't think so. But I think that is part of the problem. We are
>>>>>>> not all focused on the same thing. I am actually not very interested in CLP
>>>>>>> at all. It is one of potentially many protocols that may exist below the
>>>>>>> ILP layer. All we're doing by defining CLP is bootstrapping the network by
>>>>>>> defining a protocol for everyone to use to get started.
>>>>>>>
>>>>>>> In designing the ILP layer properly we should try and forget
>>>>>>> everything we know about the lower layers other than what features we
>>>>>>> require of them.
>>>>>>>
>>>>>>> There is a misconception that ILP requires the lower layers to
>>>>>>> support conditional transfers, that is not true.
>>>>>>>
>>>>>>> All we actually need from a lower layer protocol is to transfer data
>>>>>>> back and forth and provide a way to reliably map requests to responses.
>>>>>>>
>>>>>>> What ILP provides lower layers is a way to reward your peer for
>>>>>>> passing on the packet. The Internetworking layer defines a condition, a
>>>>>>> reward that must be paid to the receiver for the fulfillment and the time
>>>>>>> allowed to claim this reward.
>>>>>>>
>>>>>>> Because of this, within lower-layer protocols that offer the basic
>>>>>>> request/response features we need, we could add conditional payment
>>>>>>> semantics that use the condition, expiry and fulfillment provided by ILP.
>>>>>>> This would allow a node to offer a reward to  their next peer to for
>>>>>>> delivering the packet they send them and to make the local financial
>>>>>>> transaction contingent on the end-to-end transaction.
>>>>>>>
>>>>>>> But crucially, adding that semantic to the lower layer protocol
>>>>>>> provides nothing extra to the ILP layer. The value is purely derived from
>>>>>>> the two peers who use that protocol and can now use conditional payments to
>>>>>>> protect themselves from their peers.
>>>>>>>
>>>>>>>
>>>>>>>> The proposal that you're arguing for is basically asserting that
>>>>>>>> we're going to be using CLP, because it makes the assumption that the
>>>>>>>> connectors (who understand ILP) are managing the HTLA logic.
>>>>>>>>
>>>>>>>
>>>>>>> Not at all. I am asserting that it doesn't matter what protocol you
>>>>>>> use below the ILP layer because it shouldn't matter. All of this talk about
>>>>>>> ILP being different because money is more important than data is nonsense.
>>>>>>>
>>>>>>> The difference between ILP and IP that makes ILP suitable for value
>>>>>>> transfer and IP not is at the internetworking layer. ILP requires that all
>>>>>>> packets are either a request or response and that the responses follow the
>>>>>>> same path as the requests. Further ILP defines a signature scheme that
>>>>>>> gives the sender a way to be certain the request was received by the
>>>>>>> receiver.
>>>>>>>
>>>>>>> *This could be done entirely without money* but then there would be
>>>>>>> little incentive to sign the receipt or deliver the signature back to the
>>>>>>> original sender.
>>>>>>>
>>>>>>> So, if you add money then you add economic incentives to the mix. At
>>>>>>> each hop the sender promises the next upstream peer a payment if they can
>>>>>>> return the receipt. The net effect is that this can be used to transfer
>>>>>>> money from the sender to the receiver by simply defining up front the
>>>>>>> amount that the receiver must get to produce the signature.
>>>>>>>
>>>>>>>
>>>>>>>>
>>>>>>>> In the current model, the CLP/trustline model and the direct ledger
>>>>>>>> model both work without having to treat anything on the ILP layer
>>>>>>>> differently. We're favoring on-ledger messaging because of our
>>>>>>>> implementation, yes, but we've been able to switch most all of our plugins
>>>>>>>> from on-ledger messaging to RPC-based messaging without changing the ILP
>>>>>>>> layer at all.
>>>>>>>>
>>>>>>>> With the ledger-level abstraction, we were able to switch from our
>>>>>>>> ledger-based mode of thinking to the CLP/trustline based way without
>>>>>>>> changing anything other than the plugin. Your argument comes from an
>>>>>>>> assumption of a CLP-style ledger protocol with some underlying ledger,
>>>>>>>> which we can't assume is always the case.
>>>>>>>>
>>>>>>>
>>>>>>> I'm not sure what any of that proves tbh. These are all
>>>>>>> implementation concerns. They don't change the fact that the condition,
>>>>>>> expiry and fulfillment are part of ILP not the lower layer protocols.
>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> From the perspective of the Interledger Protocol the implementation
>>>>>>>>> of the lower layers is not important, that's the whole point of layering.
>>>>>>>>> By forcing important aspects of ILP like the condition, fulfillment and
>>>>>>>>> expiry down into those layers you muddy the waters and we now have to
>>>>>>>>> standardize those protocols too. Instead we should just be defining the
>>>>>>>>> functions they must provide and then leave it up to implementations to
>>>>>>>>> provide those functions.
>>>>>>>>
>>>>>>>>
>>>>>>>> I don't agree with this point; the condition and fulfillment have
>>>>>>>> actual meaning to the ledger layer.
>>>>>>>>
>>>>>>>
>>>>>>> NO THEY DON'T! They have meaning to SOME ledgers that implement SOME
>>>>>>> lower layer protocols, IF they choose to use them.
>>>>>>>
>>>>>>> Excuse the shouting but this is the crux of the issue. We need to
>>>>>>> all agree that it is entirely possible for a transfer to be done that
>>>>>>> doesn't use the condition and fulfillment and that if this was in the
>>>>>>> middle of a 10-hop ILP payment it would have no effect on the sender and
>>>>>>> receiver.
>>>>>>>
>>>>>>>>
>>>>>>>> You've said that the ledger often doesn't care about fulfillment
>>>>>>>> and condition, but the ledger _layer_'s (where transfers are done) role is
>>>>>>>> to take in condition and fulfillment and make a transfer which satisfies
>>>>>>>> its HTLA.
>>>>>>>>
>>>>>>>
>>>>>>> No, the ledger's role is to keep a tab of net financial positions
>>>>>>> between two peers. It MAY use conditions and fulfillments that it pulls
>>>>>>> from the ILP layer to help it do that in a way both peers agree on.
>>>>>>>
>>>>>>> Note that a "layer" doesn't have a role. I think there is some
>>>>>>> confusion about the difference between layering the protocol and
>>>>>>> abstracting functionality into different components.
>>>>>>>
>>>>>>>
>>>>>>>> If the ledger layer has to look into the ILP packet to do so, that
>>>>>>>> is a blatant breaking of layering.
>>>>>>>>
>>>>>>>
>>>>>>> Not at all! The module acting at the layers *below* the
>>>>>>> internetworking layer shouldn't modify anything inside the packets of the
>>>>>>> higher layers but they can definitely inspect them and adjust their
>>>>>>> behavior based on what they to find.
>>>>>>>
>>>>>>> In fact the prevalence of this is the subject of a lot of debate at
>>>>>>> the IETF currently because endpoints are often encrypting their payloads
>>>>>>> and in some cases this makes it difficult for middle-boxes to be effective
>>>>>>> at their jobs.
>>>>>>>
>>>>>>> By putting the condition, fulfillment, and expiry on the ledger
>>>>>>>> layer, we leave it open for any ledger type to work, rather than forcing
>>>>>>>> all ledger-layer software to understand ILP.
>>>>>>>>
>>>>>>>
>>>>>>> Actually you do the opposite. You make it a requirement of every
>>>>>>> protocol below the ILP layer to define a way to carry these data elements
>>>>>>> and encode and decode them, even if they don't use them
>>>>>>>
>>>>>>> Ledger layer components don't have to understand ILP unless they
>>>>>>> choose to re-use the condition for their own local transfer. Ledgers
>>>>>>> themselves *never* have to understand ILP.
>>>>>>>
>>>>>>> Remember a ledger layer protocol could use a completely different
>>>>>>> conditional payments scheme, like atomic mode ILP, where it takes the
>>>>>>> end-to-end condition and creates a new compound condition that depends on
>>>>>>> the fulfillment and some notary signature.
>>>>>>>
>>>>>>> There will be a component in a connector's stack that must pass the
>>>>>>> ILP packet to the next peer. If it does this using a transfer protocol that
>>>>>>> uses conditional transfers and wants to use the same condition as the ILP
>>>>>>> packet then it must decode the packet.
>>>>>>>
>>>>>>> But, it will likely do something with that condition before sending
>>>>>>> the transfer to the ledger like encoding it differently or rehashing it
>>>>>>> (lightning?) so that it's in the form expected by the ledger.
>>>>>>>
>>>>>>> That's an implementation decision of the lower layer protocol used
>>>>>>> between those two peers.
>>>>>>>
>>>>>>>
>>>>>>>>
>>>>>>>> I agree that Interledger defines how conditions, fulfillments, and
>>>>>>>> expiries should be chained together, but it makes no assertions about their
>>>>>>>> data format.
>>>>>>>>
>>>>>>>
>>>>>>> ILP doesn't define how anything is chained together. From the
>>>>>>> perspective of ILP the condition and fulfillment are end-to-end data. They
>>>>>>> are agreed by the two endpoints who don't care how they get from Alice to
>>>>>>> Bob and back.
>>>>>>>
>>>>>>> The design of ILP is such that it facilitates the design of lower
>>>>>>> level protocols that can be used to carry the ILP packets across multiple
>>>>>>> hops (networks) using economic incentives such that the sender pays enough
>>>>>>> for the first hop to ensure that all nodes in between can extract the fee
>>>>>>> they want and the receiver will still get the amount they expected..
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>> ILP says you should send your outgoing transfer with the same
>>>>>>>> condition as the incoming one, and a lower expiry.
>>>>>>>>
>>>>>>>
>>>>>>> No it doesn't. An internetworking protocol can't prescribe that kind
>>>>>>> of thing to lower level protocols. An incoming and outgoing transfer could
>>>>>>> be sent using completely different protocols and the financial agreement
>>>>>>> with the peers on those two routes could be vastly different too.
>>>>>>>
>>>>>>> The only service ILP requires of lower level protocols is that they
>>>>>>> can map a response to an original request. This requirement is okay because
>>>>>>> it is isolated to a single route/link at a time not a requirement that
>>>>>>> crosses the inter-network boundary that ILP crosses.
>>>>>>>
>>>>>>>
>>>>>>>> But because ILP allows for many different types of ledgers, it
>>>>>>>> doesn't make sense to assert how these are encoded.
>>>>>>>>
>>>>>>>
>>>>>>> By putting them in the ILP packet you do the opposite. You make no
>>>>>>> assertions about how they are encoded if they are used at lower layers, or
>>>>>>> how they may be combined with other conditions or even used to derive new
>>>>>>> conditions.
>>>>>>>
>>>>>>>>
>>>>>>>> IP doesn't tell you how to encode an ethernet packet. It doesn't
>>>>>>>> even know whether it's going over a computer or a hand-written letter
>>>>>>>> carried by a pigeon. IP takes for granted that you can send data over one
>>>>>>>> connection by putting it in a lower level.
>>>>>>>>
>>>>>>>
>>>>>>> Correct, but if a link layer protocol wanted to look into the IP
>>>>>>> packet headers of a packet it wants to transfer and use some data from
>>>>>>> there in its internal logic (or even reuse data in it's own frame) that
>>>>>>> would be totally fine.
>>>>>>>
>>>>>>>
>>>>>>>> Even though IP tells you how to chain these connections together,
>>>>>>>> it doesn't have to put the things it's chaining on the internetworking
>>>>>>>> level.
>>>>>>>>
>>>>>>>
>>>>>>> IP doesn't tell you how to chain things together. IP simply defines
>>>>>>> the end-to end data envelope and address space. Because of this nodes that
>>>>>>> implement the multiple lower layer protocols are able to push IP packets
>>>>>>> down a link and expect the node on the other side to understand the headers
>>>>>>> and route it onward on another link.
>>>>>>>
>>>>>>> Everything needed by the IP module to decide what to do with the
>>>>>>> packet is in the IP packet headers. i.e. Has it exceeded a TTL? Is there a
>>>>>>> route for this destination? Is it corrupted (checksum fails)? But also,
>>>>>>> everything that is needed by the endpoint (like the source address) is also
>>>>>>> in there.
>>>>>>>
>>>>>>> There is no dependency on nodes to be good citizens and always pass
>>>>>>> certain other data from the lower layers into the next link. That would be
>>>>>>> breaking the layering.
>>>>>>>
>>>>>>>
>>>>>>>> IP also assumes that if you get some incoming data on a connection
>>>>>>>> you can copy it and send it out on the next connection. Because you can
>>>>>>>> already send data over a connection, all IP adds is the missing piece: a
>>>>>>>> packet that tells you where to go.
>>>>>>>>
>>>>>>>> With ILP, we assume that there is a way to prepare a conditional
>>>>>>>> transfer, expire a conditional transfer, and fulfill a conditional
>>>>>>>> transfer.
>>>>>>>>
>>>>>>>
>>>>>>> No we don't! We assume that if we deliver the packet as intended
>>>>>>> we'll get back a response packet with a signature that matches the
>>>>>>> condition in the packet. So, if we have an agreement with someone that they
>>>>>>> will pay us when we present that signature then we are prepared to enter a
>>>>>>> similar agreement with the next peer because we expect that signature to
>>>>>>> come all the way back along the interledger layer route..
>>>>>>>
>>>>>>>
>>>>>>>> We also assume that if you get an incoming transfer you can create
>>>>>>>> an outgoing transfer with the same condition. The abstraction we made means
>>>>>>>> that conditions and fulfillments are already carried in the lower levels.
>>>>>>>>
>>>>>>>
>>>>>>> That is a bad assumption that comes from the broken layering. What
>>>>>>> if my outgoing link doesn't support conditional transfers? So now where do
>>>>>>> I put the condition?
>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> We could have assumed that no ledgers ever support conditional
>>>>>>>> transfers, and said the only thing ILP gets from lower levels is the
>>>>>>>> ability to send a transfer. But if we want to support the case where any of
>>>>>>>> them do, we have to keep the conditions and fulfillments in the layer where
>>>>>>>> they're actually used.
>>>>>>>>
>>>>>>>
>>>>>>> I don't follow that logic at all. If we want to support the case
>>>>>>> where any of them do then we must ensure the condition and expiry are
>>>>>>> always carried in a consistent place at the internetworking layer so that
>>>>>>> if they do want to use them they know where to find them.
>>>>>>>
>>>>>>>
>>>>>>>>
>>>>>>>> On Tue, Aug 15, 2017 at 12:04 PM, Adrian Hope-Bailie <
>>>>>>>> adrian@hopebailie.com> wrote:
>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> On 14 August 2017 at 22:03, Evan Schwartz <evan@ripple.com> wrote:
>>>>>>>>>
>>>>>>>>>> I think this thread is going to get extremely unwieldy but here
>>>>>>>>>> goes:
>>>>>>>>>>
>>>>>>>>>> > - All interledger layer messages should be ILP packets
>>>>>>>>>> (including fulfillments) and be capable of carrying higher layer protocol
>>>>>>>>>> payloads.
>>>>>>>>>>
>>>>>>>>>> Interledger has higher requirements than ILP for the lowest
>>>>>>>>>> layer, specifically because we are carrying money and not just data. One of
>>>>>>>>>> the requirements is being able to transmit a 32-byte fulfillment back along
>>>>>>>>>> the same path that carried the payment originally. If we expect this of the
>>>>>>>>>> lower layer, I don't see a point in putting the fulfillment into an ILP
>>>>>>>>>> packet and transmitting it as Interledger data along the same path. All
>>>>>>>>>> ledger-layer protocols will need to interpret the fulfillment passed in
>>>>>>>>>> their protocol, not the one passed through the Interledger layer.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> This is not correct. There is no requirement on ledger layer
>>>>>>>>> protocols to transmit or understand the fulfillment. You are looking at
>>>>>>>>> this through the lens of existing implementations from the bottom up
>>>>>>>>> instead of starting at the interledger layer.
>>>>>>>>>
>>>>>>>>> The primary function of the condition and fulfillment is as a
>>>>>>>>> signed end-to-end receipt. If the sender agrees a condition with a receiver
>>>>>>>>> and then gets back the valid fulfillment they don't care what happened in
>>>>>>>>> the middle. The receiver has signed a receipt saying they have their money.
>>>>>>>>>
>>>>>>>>> The value of using a standard for the receipt and signature is
>>>>>>>>> that each transfer along the way CAN re-use it. One the one hand you can
>>>>>>>>> have a transfer between two peers that have zero trust and the ledger they
>>>>>>>>> use supports conditional payments completely. On the other extreme you can
>>>>>>>>> have two peers that have a full trust and ignore the condition and
>>>>>>>>> fulfillment completely.
>>>>>>>>>
>>>>>>>>> The ledger layer protocols carry ILP packets. Payment requests and
>>>>>>>>> either fulfillment or error responses. If a ledger layer protocol wants to
>>>>>>>>> use the condition and fulfillment for their own operations they can extract
>>>>>>>>> these from the ILP packets and use them.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>> > - While it may make sense to split the interledger payment and
>>>>>>>>>> interledger quoting protocols into new higher level protocols that seems
>>>>>>>>>> like an unnecessary abstraction. Instead the packet definitions should just
>>>>>>>>>> have some consistency and probably a common base/header.
>>>>>>>>>>
>>>>>>>>>> The current protocols effectively have this header but it isn't
>>>>>>>>>> separated out. There are two fields in the request header: type and
>>>>>>>>>> destination address. There is one field in the response header: type. I
>>>>>>>>>> don't think it makes that much of a big difference to separate these fields
>>>>>>>>>> if all of the fields in that packet need to be interpreted together (for
>>>>>>>>>> example, you can't understand a quote request if you strip off the
>>>>>>>>>> destination address).
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> I agree that we don't HAVE to explicitly separate them out but I
>>>>>>>>> think ti would make it clearer how the stack is architected if there was a
>>>>>>>>> header that was consistent across all packets. Currently the only thing
>>>>>>>>> that is consitent across all ILP packets is that they are defined int he
>>>>>>>>> same file.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> > - We should define two base ILP packet types: request and
>>>>>>>>>> response.
>>>>>>>>>>
>>>>>>>>>> Unless this adds some substantive benefit or new fields I don't
>>>>>>>>>> think it's worth breaking all of the formats we have just to rearrange
>>>>>>>>>> things.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> The goal of this exercise is to tease out the best design and
>>>>>>>>> ignore the cost of change until we can compare the results with the current
>>>>>>>>> design.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> > - ILP is not about ledgers, it is about trustlines between
>>>>>>>>>> nodes/hosts.
>>>>>>>>>>
>>>>>>>>>> A ledger is any system that tracks accounts and balances. When
>>>>>>>>>> you use a trustline all of your messages still need to go through an
>>>>>>>>>> accounting system (such as the plugin in the JS implementation) and then on
>>>>>>>>>> to the other program logic.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> As above, this is incorrect. There is no requirement for "all
>>>>>>>>> messages to go through an accounting system".
>>>>>>>>>
>>>>>>>>> Since designing the first implementation of 5-bells ledger we have
>>>>>>>>> assumed that passing the ILP packet MUST be done by the ledger because that
>>>>>>>>> is how we implemented it. But that is not true. It is perfectly valid for
>>>>>>>>> the passing of an ILP packet from one peer to another to be simply an
>>>>>>>>> exchange of data.
>>>>>>>>>
>>>>>>>>> The receiving peer makes a decision about whether or not to
>>>>>>>>> forward the packet based on the current financial position they have with
>>>>>>>>> the sending peer.
>>>>>>>>>
>>>>>>>>> It is convenient if the ledger that records the net positions of
>>>>>>>>> the peers also forwards the messaging and even better if it natively
>>>>>>>>> supports conditional payments and can use the condition and the fulfillment
>>>>>>>>> from the ILP packet for those but that's all it is, convenient.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>> In that case, the plugin or whatever is doing the accounting is
>>>>>>>>>> the ledger. Digital value is always tracked in ledgers, so I think it does
>>>>>>>>>> make sense to think of this as the base layer. The reason to abstract the
>>>>>>>>>> functionality you expect from the ledger layer is precisely so you can
>>>>>>>>>> handle it in different ways, depending on what the underlying systems
>>>>>>>>>> provide.
>>>>>>>>>>
>>>>>>>>>> I see 3 ways to think of the layer(s) underpinning ILP:
>>>>>>>>>>
>>>>>>>>>>    1. The "Ledger Layer" provides both messaging capabilities
>>>>>>>>>>    and some type of HTLA
>>>>>>>>>>    <https://github.com/interledger/rfcs/blob/master/0022-hashed-timelock-agreements/0022-hashed-timelock-agreements.md>
>>>>>>>>>>    2. There are separate plugins for messaging and for transfers
>>>>>>>>>>    and when you peer with someone you have to agree on a plugin for each
>>>>>>>>>>    3. We standardize the messaging part and say that all goes
>>>>>>>>>>    over IP and then just have more minimal plugins for the on-ledger
>>>>>>>>>>    settlements
>>>>>>>>>>
>>>>>>>>>> Number 1 is what we have and I think that still makes the most
>>>>>>>>>> sense.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> I think you're confusing implementation details and defining of
>>>>>>>>> interfaces with definition of a protocol stack. The only differences
>>>>>>>>> between the tree examples you have above is in implementation.
>>>>>>>>>
>>>>>>>>> From the perspective of the Interledger Protocol the
>>>>>>>>> implementation of the lower layers is not important, that's the whole point
>>>>>>>>> of layering. By forcing important aspects of ILP like the condition,
>>>>>>>>> fulfillment and expiry down into those layers you muddy the waters and we
>>>>>>>>> now have to standardize those protocols too. Instead we should just be
>>>>>>>>> defining the functions they must provide and then leave it up to
>>>>>>>>> implementations to provide those functions.
>>>>>>>>>
>>>>>>>>> I know we want to define a standard to bootstrap the system (CLP)
>>>>>>>>> but that's misleading us into thinking it's an essential part of the stack.
>>>>>>>>> It's perfectly valid for two peers to not use CLP and still be part of the
>>>>>>>>> Interledger.
>>>>>>>>>
>>>>>>>>> That said, you raise an interesting consideration about the layers
>>>>>>>>> below ILP and actually I think it makes sense to split these.
>>>>>>>>>
>>>>>>>>> We keep trying to force messaging through the ledger layer and
>>>>>>>>> actually that's the wrong place to put it if we can split the ledger layer
>>>>>>>>> into a messaging layer and a ledger layer. That way we can stop trying to
>>>>>>>>> think of all HLTAs as ledgers.
>>>>>>>>>
>>>>>>>>> A thought, why not use sub-layers as is common in other stacks:
>>>>>>>>>
>>>>>>>>> 1. Link layer: Layer upon which two peers that have a direct link,
>>>>>>>>> or participate in the same payment network, communicate
>>>>>>>>> 2. Transfer/ ledger: Layer on which financial positions between
>>>>>>>>> two peers are recorded
>>>>>>>>>
>>>>>>>>> This reflects the already emerging HTLA model and many of our
>>>>>>>>> existing plugins and ledger integrations.
>>>>>>>>> Link Layer: XRP Paychan, Lightning
>>>>>>>>> Ledger Layer: XRP Ledger, Bitcoin
>>>>>>>>>
>>>>>>>>> This doesn't prevent us from defining a standard binary protocol
>>>>>>>>> that defines all of the operations for both layers (like CLP) but I see
>>>>>>>>> value in distinguishing between these two.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> > - The protocol should differentiate between the operation of
>>>>>>>>>> preparing a transfer on a ledger and the operation of passing an ILP packet
>>>>>>>>>> from one peer to another.
>>>>>>>>>>
>>>>>>>>>> The protocol assumes your conditional transfer is underpinned by
>>>>>>>>>> some HTLA
>>>>>>>>>> <https://github.com/interledger/rfcs/blob/master/0022-hashed-timelock-agreements/0022-hashed-timelock-agreements.md>.
>>>>>>>>>> It doesn't care whether that's on-ledger or not.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> What do you mean when you say "the protocol"? In my statement I am
>>>>>>>>> referring to ILP.
>>>>>>>>> My point above being that ILP expects ILP packets to be passed
>>>>>>>>> from peer to peer but has no expectations about transfers.
>>>>>>>>>
>>>>>>>>> It's perfectly legal (from an ILP perspective) for two peers to
>>>>>>>>> exchange ILP packets with no transfers. Clearly if a node routes a packet
>>>>>>>>> on and has no incoming transfer it's going to lose money but that's a
>>>>>>>>> consideration for that node. It doesn't affect anyone else in the chain.
>>>>>>>>>
>>>>>>>>> ILP doesn't assume anything about transfers at all, let alone
>>>>>>>>> conditional transfers. It provides useful semantics for conditional
>>>>>>>>> transfers to be used by two peers to transact as part of a larger ILP
>>>>>>>>> payment.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> > - The condition and timeout should be included in the ILP
>>>>>>>>>> payment packet.
>>>>>>>>>>
>>>>>>>>>> I strongly disagree with this. We had this debate a year ago and
>>>>>>>>>> I was on your side but was convinced that this is not a good idea.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Yes, I recall this and I'm sorry I didn't push harder on this
>>>>>>>>> point. Unfortunately I think the decision to pull it out of the packet is
>>>>>>>>> mostly driven by how our prototypes were implemented rather than good
>>>>>>>>> architecture.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> The layer below ILP must be capable of doing conditional
>>>>>>>>>> transfers based on sha256 hashlocks with 32-byte preimages.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> This is not true and I think it would be useful for us to agree on
>>>>>>>>> this as this seems to be the argument I am coming up against most often.
>>>>>>>>> The peers participating in a transfer that is part of an ILP payment may
>>>>>>>>> wish to use conditional transfers as a way to enforce their agreement but
>>>>>>>>> this is not a requirement of the protocol.
>>>>>>>>>
>>>>>>>>> The agreement between any two peers is: "I will pay you X if you
>>>>>>>>> can provide a receipt that Y was paid Z before T".
>>>>>>>>> ILP provides a standard for expressing this agreement so that
>>>>>>>>> these can be chained together BUT it is not a requirement that every
>>>>>>>>> agreement in the chain uses the condition, and fulfillment provided at the
>>>>>>>>> ILP layer.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> As a result, the original condition and the corresponding
>>>>>>>>>> preimage MUST be expressed in that layer.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> As I have shown above, this is not true.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>> Then the question is whether we should also include it in the
>>>>>>>>>> packet that is forwarded. What ultimately convinced me is the following:
>>>>>>>>>> All connectors MUST ignore the condition if it is in the packet, because
>>>>>>>>>> they are only guaranteed their money back if they use the same condition
>>>>>>>>>> from the incoming transfer they got.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Here is where the layering is being corrupted.
>>>>>>>>>
>>>>>>>>> All connectors MUST inspect the condition in the ILP packet as
>>>>>>>>> part of their decision to route the packet or not.
>>>>>>>>> When the local transfer module of the connectors stack passes the
>>>>>>>>> ILP packet up to the ILP module it should indicate the properties of the
>>>>>>>>> incoming transfer that carried the packet.
>>>>>>>>> This is essential firstly so that the routing logic in the ILP
>>>>>>>>> module can record the incoming transfer identifier so it is able to use the
>>>>>>>>> correct response id when it passes back the fulfillment or error.
>>>>>>>>> The other properties that the ILP module should look at are the
>>>>>>>>> condition and expiry on the incoming transfer.
>>>>>>>>>
>>>>>>>>> If the incoming route uses conditional transfers and these are
>>>>>>>>> supposed to match the condition and expiry in the ILP packet then the ILP
>>>>>>>>> module should compare them and reject the packet if:
>>>>>>>>> a) the conditions don't match OR
>>>>>>>>> b) the expiry is too short
>>>>>>>>>
>>>>>>>>> We should still discuss if the expiry should be set by the sender
>>>>>>>>> and left unchanged or used like a TTL and decremented by each node.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>> Also, the receiver will need to take out the condition in order
>>>>>>>>>> to hash the packet for PSK or IPR.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> This is completely normal. Zeroing a checksum field in a header
>>>>>>>>> before calculating the checksum is VERY common precisely because it's long
>>>>>>>>> been accepted that the right place to put that data is in the headers and
>>>>>>>>> the work of zero'ing it out to calculate the checksum (or signature in our
>>>>>>>>> case) is not material.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>> So basically, no one wants the condition in there. It feels like
>>>>>>>>>> it ought to be in there, but literally none of the parties want the extra
>>>>>>>>>> 32 bytes in there.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> "Nobody wants it there" is a terrible reason to abandon the
>>>>>>>>> correct design. The whole purpose of a good architecture is you accept that
>>>>>>>>> there may be cases in future that haven't been considered now so designing
>>>>>>>>> just for the known cases is a bad idea.
>>>>>>>>>
>>>>>>>>> Good architecture is not the same as optimization. Taking stuff
>>>>>>>>> out (even when it feels wrong) to save a few bytes is a good sign that it's
>>>>>>>>> a bad idea.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> The reason the timeout should not be in there is that there isn't
>>>>>>>>>> a single timeout for the payment. There are multiple separate timeouts for
>>>>>>>>>> each of the bilateral transfers. Those must go in the individual transfers
>>>>>>>>>> and there is no sensible value to put in the Interledger packet.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> As above, this is somewhat equivalent to the TTL in an IP packet.
>>>>>>>>> I'm open to discussing if it should be a fixed value set by the sender
>>>>>>>>> where each node uses their own value but has the sender-defined value as a
>>>>>>>>> reference or it is actually decremented at each hop.
>>>>>>>>>
>>>>>>>>> Either way, this is part of ILP not the ledger layer just like the
>>>>>>>>> condition and fulfillment. It may be used by the ledger layer but that's
>>>>>>>>> implementation specific. It belongs in the ILP packet.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>> --
>>>>> Sent from a mobile device, please excuse any typos
>>>>>
>>>>
>>>>
>>>
>>
>
Received on Friday, 18 August 2017 14:10:00 UTC

This archive was generated by hypermail 2.3.1 : Friday, 18 August 2017 14:10:01 UTC