-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conditions are NOT a ledger layer concern #359
Comments
(Note that there is a ton of stuff that I left out of the ticket description, for example how balances are handled under this new view. But the length of the text was already getting ridiculous, so please feel free to ask questions.) |
Well there goes ILPv2. And the name ILPv3 is already taken. So I guess we're landing on ILPv4 😉 |
😂 |
I actually think that when we "go live", we should use 😉 |
Famous last words: "there's no way we'll need more than 1023 characters in the address." (Speaking of which...) I would bet on the hash function being broken as the motivation for going to the next version once things settle down in the short term. |
/me is so happy he decides to take a 2 week holiday |
I sure am glad that I didn't update every plugin to LPI 2. I think this makes a lot of sense though, because now the architecture can be described as: "You ask your peer for the fulfillment to a packet; if they give you the fulfillment, you pay them the money. The payment for each packet could be defaulted upon, so amounts are kept low. In order to make a payment, you split it into many packets of money." One thing I notice is that this obsoletes BTP; Now it's a message followed by an unconditional transfer. Additionally, it's not immediately clear how a completely unconditional trustline should work. Under this architecture it might look like:
But if the balance is unsecured, this is redundant. We may just say that's ok, and eat the efficiency loss in that case (BTP already contains this extra message, so it would really only affect protocols like ILP-over-HTTP). But it still seems like intuitively that there are a few things the ledger layer knows how to do right. One other thing is that modification of the ILP packet doesn't sit so well with me. Although it does seem more elegant than the current version of things, which is to pass all the non-ILP fields separately. Also congratulations @adrianhopebailie for your vision finally being realized :) |
Woot! I’m liking this idea. /me decides to take Adrian’s vacation as well. :-) |
Data is Money if the Medium is a Ledger. This proposal changes what we call the ledger. We used to say the trustline of PluginVirtual, with its Prepare and Fulfill messages, was a ledger. That confused everybody. Now we can say the mediums interconnected by Interledger are Data, and the mediums that back their liquidity are ledgers. |
There are a number of things I really like about this proposal, but there's one thing that isn't sitting right with me. This change is very close to the one proposed in ILP3. I proposed merging the fields from the ILP packet into the transfer, this merges the transfer fields into the ILP packet. The substantive differences between these approaches are that having a standard packet encoding:
I don't think the packet encoding is super difficult to implement (as long as you write only the functions for encoding/decoding a variable-length octet string, rather than a full OER parser), so I think these are good enough reasons to have a standard format. It feels like we're on one of these: We're getting closer to the end and things are moving/changing faster but the differences are getting smaller and smaller. (For example, updating the PSK2 implementation to use this format would be super quick because it would be using all the same fields but pulling them from a different place) The thing that's not sitting quite right with me is thinking about how we would actually implement different HTLAs under this new architecture -- specifically the part about when we pay.
But we could set up the agreement in a number of different ways. We could send money with every prepare, after every fulfill, after the balance hits a certain limit, or at a pre-defined time (and of course with escrow, but you spoke to that above). The argument for keeping the HTLAs in the "layer" below ILP, like what's proposed in #358, is that the important part is not just the hashlock but the agreement around what action the hashlock means you should take, and that varies. Would we build the connector such that you could configure when it should send money for each of its peers? Or would it make more sense for that type of logic to live in plugins, each of which would have a predefined agreement/protocol? If you wanted to implement another HTLA, wouldn't you just end up writing a ledger plugin that looks at the ILP packet it's being passed to know what it should do (which would break the layering)? As @justmoon mentioned the other day in #356 (comment) though, it does seem like getting rid of the separation between the IP and ethernet layers would be a good idea... |
Isn't that decision out of scope now, sort of? Two peers need to agree on a minimum and maximum balance, and then the sending connector can have its own strategy for making sure they stay within those limits, and this strategy doesn't necessarily need to be announced or agreed on, right? In a way, the specific ledger used to settle the balance of the transfer layer is even irrelevant if you just look at the transfer layer, except maybe that the transfer layer needs to pick a unit of value, and you would usually pick the one in which you plan to settle. For instance, if settlement happens on xrp paychan, then it makes sense if amounts on the transfer layer are expressed in XRP.
Yes, I liked how one-to-one escrow can be modeled as one pre-funded transfer to Ellie and one post-funded transfer from Ellie. And one-to-many escrow like e.g. five-bells-ledger, will now be temporarily deprecated by strong enlightenment, so that's also a nice simplification. |
I’m not sure I agree. While point number 2 seems valid, the new packet design forces each hop to re-encode a new packet with a new amount and expiry for each next hop, no? Personally, I'm "on the fence" about the current ILPv4 packet changes: Primarily, I don’t like the idea of each Connector having to modify the ILP packet at each hop (though maybe I should just embrace that). However, I do like the idea of a timeless lLP-Layer-2 interface (a.k.a. LPI2), and the #356 comment is giving me pause about what I'm about to write. But, setting aside the #356 comment for a second...would it not make sense to break the ILPv4 packet into 2 separate payloads (one for Layer3 Interledger data and one for Layer2 LPI data), like this: // Not mutated at each hop...
InterledgerPrepare ::= SEQUENCE {
-- Execution condition
executionCondition Uint256,
— Destination ILP Address.
destination Address,
-- Information for recipient (Layer4 transport layer information)
data OCTET STRING (SIZE (0..32767))
}
//different at each hop
LocalLedgerPrepare ::= SEQUENCE {
-- Local amount
amount UInt64,
-- Expiry date
expiresAt Timestamp,
-- Extra info for local ledger.
data OCTET STRING (SIZE (0..32767))
} If the above were the structure of the payload at each layer, then the LPI could look like this: async sendData(ilpData:Buffer, localLedgerData:Buffer):Buffer
async sendMoney(amount: string): void If we were to clearly separate the two packets according to Interledger Layer3/Layer2 , then it becomes obvious what parts of the payloads change and what parts do not change at each hop. Also, if two peers want to use a different I suppose the outstanding question in all of this still relates to the #353 comment - is it actually better to conflate the layer 2 and 3 data?? |
Who said anything about getting rid of the separation between the IP and ethernet layers? (I'm guessing you mean internetwork and network layers.) That would be a bad idea. What I'm talking about in that comment is to make sure only the internetwork layer does routing. The key insight here is that an Ethernet switch is basically doing the same thing an IP router does. So you can get rid of Ethernet switches and turn them into routers. And suddenly you don't need Ethernet addresses anymore, so you don't need address translation, etc. You can make the network layer a pure link layer, i.e. physical (bilateral) connections only. The same thing applies to ILP. Once we realize that a ledger is just another connector, we can get rid of everything in the ledger layer that deals with multiparty (i.e. more than bilateral) functionality and handle that on the Interledger layer where we already have to handle it. The realization in this ticket is that escrow falls into the category of multi-lateral functionality and therefore should be on the interledger layer, not the ledger layer. (Note that the ledger should really be called the "account" layer if it's only bilateral, but I don't think it's a good idea to try such a massive rename at this point, we'd never quite get rid of the old name.)
Let's actually categorize HTLAs into a useful framework. I would say that there are only three cases:
Note that in case 3 you still have to pay the intermediary and they have to pay the recipient, so case 3 can be transformed into case 1/2 by making the intermediary a connector. In the escrow case, you pay-first the escrow provider, then they pay-later the recipient. Looking at the HTLA types:
With payment channels, pay first or pay later doesn't make much of a difference. If you looked on the wire, you could barely tell which mode a set of connectors are using:
Is this pay first? Pay later? You can't tell and it doesn't much matter. I'd say we should add configuration to the connector that lets us select:
The max exposure setting decides whether to use pay-first or pay-later. You'll always want to use pay-later. It's better for your liquidity and doesn't require complex refunds in case of a rejection. The only exception is if the other side doesn't trust you, but you trust them. In that case, you have to use pay-first. How often you settle depends on the cost/speed of the underlying settlement system and how much trust you have in your counterparty. |
No, you can update the packet in-place, you don't have to copy it. The amount is fixed-length, so that's easy. The expiry date is variable-length (although it only varies if the milliseconds have different amounts of trailing zeros.) So we should think about changing the expiry date to a fixed length format even if it means not using OER's GeneralizedTime, which is otherwise a perfect fit for our needs. If the connector's minMessageWindow is a multiple of 1000 ms (which it currently is in ilp-connector), then this is not an issue, the new expiry date will always have the same length as the previous one. If connectors in the future use minMessageWindow values that aren't divisible by 1000ms, perhaps a convention might emerge to add 1ms to your expiry if the last digit is zero. Not super-elegant, but whatever. You can always fall back to re-encoding the packet. In other words, sticking with the variable-length encoding is a defensible choice imo. Anybody have an opinion on whether expiresAt should be changed to a fixed length layout and if so, what it should be? Here are some options:
I kinda like option 2. Probably easiest to parse. Otherwise, option 4. If ILP is successful, those saved bytes might add up to trillions of dollars or whatever. Or option 1, which is sticking with "proper" OER... choices, choices. |
Ok, seems like nobody cares. Chatted with @sharafian and we settled on option no 4 as the most defensible. No cruft bytes and even though it's not the most "OER-y" option (that would be option 1), it can still be serialized/parsed as a string by a standard OER implementation. |
I wonder why the authors of OER decided that being fixed-length was not a design goal for the GeneralizedTime type. Does it explicitly prohibit trailing zeroes? If not, then we could just say we use the subset of OER GeneralizedTime where all trailing zeroes are present? And what is it that makes our use case incompatible with what the authors of OER had expected people to need? I guess a PrintableString is not an incorrect use of OER, so I'm fine with option 4, too. |
Yeah; Currently having the BTP channel also available to send claims is useful, which is why the plugin bundles the money connection together with the data connection. If we do find in the future that it's useful to have them as two separate plugins, it would be trivial to implement in our current architecture:
|
And which one of the two would you call the ledger plugin? |
I mean, would you agree with my point that this proposal would change what we call the ledger? |
Are you saying that the sendData function isn't part of the ledger layer because it doesn't have bearing on the ledger itself? |
Old view: the trustline ledger with the conditional transfers of PluginVirtual is the real interledgery ledger to which the L in LPI refers, the PayPal ledger may also be a ledger, but it's out-of-scope for interledger. New view: the data sent for prepare and fulfill is just data, not money. The unconditional transfers that rebalance the liquidity afterwards, are now the real interledgery ledger to which the L in LPI refers. |
This definitely feels like a big change to how we talk about Interledger, and specifically what it requires from ledgers. But it isn't a super substantive change from how we were already using ILP with trustlines and payment channels.
I don't think it changes what entity we call the ledger, only what functionality we expect from ledgers. The old view was that Interledger required ledgers to be fast, cheap, and support conditional transfers or we'd need to add an extra layer on top of them. The new view is that ledgers just do simple transfers of value and the Interledger layer implements the conditional part. |
yes, so in the example from the screenshot, we will now say Paypal is the ledger, and PluginVirtual's trustline no longer is. |
My intuition tells me that having something that keeps constant during the payment process can be a good thing. That would represent the initial contract between the sender and receiver, the "intention to make a payment" that both parties have agreed, for example during the SPSP phase. In the InterledgerPrepare packet that you mention it could also be useful to add the intended initial amount. In case of trouble this could really help in tracing the source of the problem and taking sensible decisions. It adds a few extra bytes but this is not really a concern in terms of performance. A wrong network setup forcing for example continuous tcp re-connections will have a much bigger impact in terms of slowdowns. Also the payment related data would go into the InterledgerPrepare (constant) packet while any data in the LocalLedgerPrepare could be used at will by peers's plugins. (private data within peers). |
It's taken me a while to catch up on everything but my general feeling is that:
I agree with @sappenin (I think). An ILP prepare packet should contain an ILP Address, a Condition and Data (only expected to be consumed by the entity identified by the address). The response packet should contain either an Error plus Data or a Fulfillment plus Data. In this way the ILP layer is a clean inter-networking layer that is purely for the purpose of routing a condition and request data in one direction and routing the fulfillment and response data in the other direction. The understanding between the payer and payee is that the payee will only respond with a valid fulfillment if they get paid to do so. Since the payer has no direct relationship with the payee they make a request to a local peer, committing to pay them if they are able to get the fulfillment from the payee. This propagates all the way to the payee who ultimately gets paid for the fulfillment and response data which are passed all the way back to the payer. How much is paid (if anything) is out of scope of the Interledger layer. In fact a payer may not pay their local peer anything if they think the ILP Packet will still be delivered. The Condition, Fulfillment and Data exchanged may be valuable enough in their own right. @justmoon said:
I'm not sure I agree with that. Sure, it's possible, but why force implementations to do this if we can design the protocol to avoid it? Instead we are using a non-standard fixed-length date format just to make this easier. I think we should discuss this further. In #363 @emschwartz asked the same question and @justmoon made the point:
Again, I don't agree completely. I think forcing all layers below the ledger layer to standardize on UInt64 with no currency for amounts adds complexity rather than reducing it. IMO the interface from the ledger layer up to the interledger layer should look as follows:
i.e. The ledger layer has dealt with preparing for an incoming transfer which will be made in exchange for routing the attached ILP Packet. Based on the amount it is being paid and the time remaining before the offer expires the ILP layer routes the packet to an outgoing ledger layer route. The current proposal makes the interface:
I'm not convinced that makes this simpler since the connector now has to decode and recode the ILP packet but I need to spend some time looking at the implementations to be sure. I do like @emschwartz characterization of the layers below this being split further and, despite my gut telling me we'll encounter a lot of debate around the "true" meaning of these terms, also agree with calling them the clearing and settlement layers. The clearing layer is responsible for the transfer of value between peers on the route of a payment. The ILP layer is unaffected by the semantics of this relationship BUT by having a well-defined standard for the condition and fulfillment the participants at the clearing layer can leverage this and use the condition and fulfillment in the rules they enforce for transfers between the nodes. Where two peers don't trust one-another they can insert an intermediary between then that they mutually trust. This could be a mutually trusted "ledger" (as described by @justmoon above) where the system is just responsible for same-currency transfers between two accounts. This system could also be a decentralized system like XRP Ledger so the two connecting parties don't need to trust a third-party but just trust the decentralized system. So perhaps a good way to start thinking about ledgers is simply as a specialization of a connector where all accounts are the same currency. |
An observation that I wonder if everyone agrees with: The interface from interledger -> ledger component in ILPv4 is:
This can be executed either as:
OR
It's possible that the protocol used between two peers supports passing the ilpPacket with the In this case I'd expect the plugin for that protocol to only support the latter sequence (pay first) but to queue the
On the receiving side the plugin will parse the incoming @justmoon @emschwartz @sharafian @michielbdejong @dappelt does that sound right? |
Follow up question: who initiates the reversal of the Similarly, how does the receiver on the account request a i.e. Who is responsible for reconciling |
With ILP v1, the plugin (even if it was a virtual / BTP plugin) was expected to keep that balance. Now we no longer call it a balance that needs to be reconciled if it gets too low, but an " |
You probably wouldn't reverse it. Instead, the sender's account would just have a small positive balance. (Don't think of
They don't, they just wouldn't forward any more ILP payments for that sender if they don't pay up.
Not really, because the whole point of this change was to remove conditional transfers from the ledger and you're talking about how to support putting them back in... |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. \n\n If this issue is important, please feel free to bring it up on the next Interledger Community Group Call or in the Gitter chat. |
Brought to you from our sponsors at holy ****, there are still ways to simplify ILP...
This is a follow-on to @adrianhopebailie's famous ticket #270. He was the first to recognize that executionConditions awkwardly straddle two layers: Interledger and ledger. This dissonance stuck with me until it lead me to the realization described in this ticket, which finally resolves it.
We recently started seriously implementing an earlier realization we dubbed the Interledger enlightenment. Before the enlightenment, we thought that ledgers were the nodes and connectors were the edges of the financial graph. But we realized that a simpler and more general model is that the nodes in the Interledger graph are senders, connectors and receivers and the edges are accounts they have with one another. A ledger then is just another type of connector.
Interestingly, there aren't a lot of different types of account you can have. You can have a credit relationship, which we sometimes call a trustline. Or you can have a payment channel, which essentially the crypto-equivalent of a check: One party signs something called a claim that the other party can take to someone else (such as a blockchain) to get paid. Checks reduce the trust needed between the parties because the settlement is enforceable.
In ILP, we use unsecured payment channels. What we do is we transfer a claim after receiving a fulfillment. In other words, for us, a payment channel is just a very efficient way to send some money. Note that there is no condition involved.
We believe that such unsecured payment channels are fine, because each payment is very small, so it is not worth worrying about losing a payment, as long as the cumulative loss is limited.
If we use unconditional transfers between each pair of nodes, then how come we haven't gotten rid of conditions entirely? (We call this "optimistic mode" in ILP speak.)
The reason we still have conditions is to isolate risk to a specific hop. I.e. if someone along the chain fails, we want to be able to assign blame correctly. That's why the two phase execution still makes sense.
But if conditions are only evaluated by connectors and ledger plugins only do unconditional transfers, then conditions are an Interledger concern, not a ledger layer concern.
In other words, the LPI
sendTransfer
should be changed from:to
If your head just exploded, please do not be concerned. This is normal.
The best way I can explain the above is that this is exactly what
ilp-plugin-xrp-paychan
already does. It first sends a prepare which is basically just sending a bunch of data to the next connector with no money attached at all. If it gets back a fulfillment, it now owes the connector, so before it can send more payments, it needs to send some money by sending a claim against the underlying payment channel.The important thing to understand is that the sendData call is triggering both the next sendData call as well as the next sendMoney call at each hop. This is what creates the Interledger two-phase semantics, i.e. as the receiver I only need to trust connector 2 to send the money, not the sender and the whole chain in between.
The sendData call can also throw an InterledgerRejectionError just as before, in which case the sendMoney call is never reached.
So where did the
executionCondition
go? It moved inside the ILP payment packet. So did the expiry. The fulfillment moved inside the ILP fulfillment packet. Note that that does mean that the ILP packet changes at each hop, but that's no different than the IP hop limit changing at each hop.Certain logic that needed to be reimplemented in every plugin before, such as verifying the fulfillment etc. is now part of the connector / sender.
If the hashing function we use needed to be updated in a future version of ILP, we would no longer need to also change all of the ledger protocols. In fact, the ledger plugin interface is now arguably timeless - it can send integers of information (bytes) and it can send integers of value.
It makes sense that the escrow would go away under the enlightenment. Escrow is just transferring money from one account to another. It is still possible to implement the escrow pattern using this new LPI. Ledgers are connectors now, so the escrowing ledger is a connector. Let's call her Ellie.
If Bob hadn't responded with the fulfillment in time, Ellie would have responded to Alice's request with a rejection packet and sent the money back to Alice.
As with routing, escrow functionality can also be implemented in the ledger plugin if there is a ledger that is fast and permissive enough and can do escrow, but doesn't understand ILP. Since no such ledgers exist (at least none that are fast enough for actually doing ILP on them) this is a theoretical exercise. Any future ledgers that may be built specifically for ILP that wish to implement the escrow pattern should think of themselves as connectors.
The text was updated successfully, but these errors were encountered: