Skip to content
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

Extended API for fine-grained payment tracking #867

Closed
wants to merge 9 commits into from
Closed

Extended API for fine-grained payment tracking #867

wants to merge 9 commits into from

Conversation

akumaigorodski
Copy link
Contributor

@akumaigorodski akumaigorodski commented Feb 14, 2019

This is (yet another) attempt to sneak in an extended API which tries to improve on current limitations:

  • send method has a hard timeout so can't be relied upon when payment takes a long time.
  • Irregular cases, such as on-chain refunds and lost dusty payments, are not accounted for.

Above limitations are tolerable when Eclair is run by user directly, but are problematic when API is used by a service which sends/receives payments on behalf of it's users since a service has to be able to tell users what is going on with their payments as well as reliably track these payments at all times.

New API strategy is this:

  • send becomes a fire-and-forget method.
  • Two new Payment events are added: PaymentSettlingOnChain and PaymentLostOnChain.
  • Outgoing payment is considered PENDING until websoket sends PaymentSent | PaymentLostOnChain | PaymentSettlingOnChain(isDone = true), alternatively user may call sentinfo API endpoint which would return the same objects.
  • Incoming payment is considered PENDING until it is expired or websocket sends PaymentReceived | PaymentLostOnChain | PaymentSettlingOnChain(isDone = true), alternatively user may call receivedinfo API endpoint which would return the same objects.

One serious change this requires is https://github.com/btcontract/eclair/commit/a76ec92e3c1d09608c3db37f819de416fc0cbbc1#diff-aeb98076f262543b70a86753bd3719adR53 which adds paymentHash to HtlcTimeoutTx. This is needed for PaymentSettlingOnChain event which has paymentHash and txid fields. txid there belongs to a final CSV-delayed refunding transaction and allows API user to link off-chain failed payment to on-chain refunding transaction.

val inFlightHtlcs = commitments.localCommit.spec.htlcs.map(htlc => htlc.add.paymentHash -> htlc.add.amountMsat).toMap
var onChainHashes = Set.empty[BinaryData] // this is needed to collect hashes of payments which can be resolved on-chain

val successAndDelayedTxs = for {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An advantage of using for-sequences here:

  • if either success or delayed fails then no tx is published as opposed to current approach where success may be published but delayed won't be (because applying on-chain fees would make it dusty for example).
  • if either success or delayed fails then no PaymentSettlingOnChain will be sent, but PaymentLostOnChain will be sent for this payment instead.

Further, if LocalCommitPublished can be modified to store (success, delayed) and (timeout, delayed) tuples directly then UI and API may inform how much blocks is left for each delayed payment: CLTV(timeout) + CSV(delayed) as well as total on-chain fees paid: timeout.input.amount - delayed.output.amount.

@akumaigorodski
Copy link
Contributor Author

IntegrationSpec seems to be failing, I'm not sure what's going on here, any advice?

@araspitzu
Copy link
Contributor

araspitzu commented Feb 14, 2019

IntegrationSpec seems to be failing, I'm not sure what's going on here, any advice?

Looks like integration-test ecalir nodes couldn't find a config key, maybe because of this addition? https://github.com/ACINQ/eclair/pull/867/files#diff-8f6c9262b4235c9580874fdd69f54b7cR234 Notice that config key has been moved in the router block, and has become router.broadcast-interval

@pm47 pm47 self-assigned this Mar 5, 2019
Copy link
Member

@pm47 pm47 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept OK, but I have several problems with the implementation:

  • Please submit a separate PR for the formatting changes. I'm not convinced by most of them to be honest, and they make the review more difficult;

  • The audit db isn't supposed to be used as a random access db but more like an append-only log for audit purpose. I know that there is a demand for a real payment database and I think we should probably add one;

  • I understand the need to index payments by payment_hash (although it is not a real index!) but this could be inferred from the local commitment.

  • Just because we call claimCurrentLocalCommitTxOutputs/claimRemoteCommitTxOutputs doesn't mean that this will be confirmed. If an htlc is fulfilled in the local tx, and if local commit wins the race despite remote publishing its local commit, then the code will produce an invalid event AFAICT.

Let me think about this a bit more and I may come up with an alternate proposal.

@@ -202,7 +204,7 @@ object NodeParams {
NodeParams(
keyManager = keyManager,
alias = nodeAlias,
color = Color(color.data(0), color.data(1), color.data(2)),
color = Color(color.data.head, color.data(1), color.data(2)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above. For this particular change it hurts readibility more than anything, as previous code was more consistent.

@@ -179,7 +181,7 @@ object NodeParams {
val p = PublicKey(e.getString("nodeid"))
val gf = BinaryData(e.getString("global-features"))
val lf = BinaryData(e.getString("local-features"))
(p -> (gf, lf))
(p, (gf, lf))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you following intellij recommendations or some automated code modification suggestions? This makes review more difficult are there are lot of unrelated changes.

Can you revert and submit a separate PR for those?

Also by convention I Iike using -> for defining tuples in the context of a Map.

}

def receive: Receive = {
case message: PaymentFailed => flowInput.offer(Serialization write message)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The json serialization should probably be part of the flow instead of duplicating Serialization.write(...)


val forwarder = context.actorOf(Props(new Forwarder(nodeParams)), "forwarder")

// this will be used to detect htlc timeouts
context.system.eventStream.subscribe(self, classOf[CurrentBlockCount])
implicitStream.subscribe(self, classOf[CurrentBlockCount])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should refer to the implicit variable here. Same for logging: we use an implicit that is passed to the Helper functions for context, but we call directly log from within the actor. Does that make sense?

@@ -50,15 +50,15 @@ class LocalPaymentHandler(nodeParams: NodeParams) extends Actor with ActorLoggin
case PurgeExpiredRequests =>
context.become(run(hash2preimage.filterNot { case (_, pr) => hasExpired(pr) }))

case ReceivePayment(amount_opt, desc, expirySeconds_opt, extraHops) =>
case ReceivePayment(amount_opt, desc, expirySeconds_opt, extraHops, fallbackAddressOpt) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Options should be suffixed with _opt by convention

@@ -230,10 +230,10 @@ object PaymentRequest {
f.version match {
case 17 if prefix == "lnbc" => Base58Check.encode(Base58.Prefix.PubkeyAddress, data)
case 18 if prefix == "lnbc" => Base58Check.encode(Base58.Prefix.ScriptAddress, data)
case 17 if prefix == "lntb" => Base58Check.encode(Base58.Prefix.PubkeyAddressTestnet, data)
case 18 if prefix == "lntb" => Base58Check.encode(Base58.Prefix.ScriptAddressTestnet, data)
case 17 if prefix == "lntb" || prefix == "lnbcrt" => Base58Check.encode(Base58.Prefix.PubkeyAddressTestnet, data)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch

# Conflicts:
#	eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
#	eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala
pm47 added a commit that referenced this pull request Mar 7, 2019
This is a simpler alternative to #867, with the following limitations:
- no `OnChainRefundsDb` and associated API calls
- `PaymentSettlingOnChain` event will be sent exactly once per payment
and have less information
- we don't touch `HtlcTimeoutTx`
- use json4s type hints instead of manual attributes to case classes
@pm47
Copy link
Member

pm47 commented Apr 15, 2019

This is has been superseded by #884 and #885.

@pm47 pm47 closed this Apr 15, 2019
@akumaigorodski akumaigorodski deleted the extended-api-2 branch November 3, 2019 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants