From 6669e434f14cc6f8b7d38c3fe8a4628c611d98f5 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 11 Dec 2023 19:20:07 +0100 Subject: [PATCH] tapgarden: start courier only after confirmation This commit makes sure we only start the proof courier once the on-chain transaction has confirmed. Otherwise we'll run into backoffs for sure until we get the first confirmation. --- tapgarden/custodian.go | 29 ++++++++++---- tapgarden/custodian_test.go | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 7 deletions(-) diff --git a/tapgarden/custodian.go b/tapgarden/custodian.go index 199b7a8d20..51a8cea7aa 100644 --- a/tapgarden/custodian.go +++ b/tapgarden/custodian.go @@ -391,13 +391,21 @@ func (c *Custodian) inspectWalletTx(walletTx *lndclient.Transaction) error { } c.events[op] = event + + // Now that we've seen this output confirm on + // chain, we'll launch a goroutine to use the + // ProofCourier to import the proof into our + // local DB. + c.Wg.Add(1) + go c.receiveProof(event.Addr.Tap, op) } continue } // This is a new output, let's find out if it's for an address - // of ours. + // of ours. This step also creates a new event for the address + // if it doesn't exist yet. addr, err := c.mapToTapAddr(walletTx, uint32(idx), op) if err != nil { return err @@ -409,9 +417,17 @@ func (c *Custodian) inspectWalletTx(walletTx *lndclient.Transaction) error { continue } - // Now that we've seen this output on chain, we'll launch a - // goroutine to use the ProofCourier to import the proof into - // our local DB. + // We now need to wait for a confirmation, since proofs will + // be delivered once the anchor transaction is confirmed. If + // we skip it now, we'll receive another notification once the + // transaction is confirmed. + if walletTx.Confirmations == 0 { + continue + } + + // Now that we've seen this output confirm on chain, we'll + // launch a goroutine to use the ProofCourier to import the + // proof into our local DB. c.Wg.Add(1) go c.receiveProof(addr, op) } @@ -445,7 +461,7 @@ func (c *Custodian) receiveProof(addr *address.Tap, op wire.OutPoint) { &addr.ProofCourierAddr, recipient, ) if err != nil { - log.Errorf("unable to initiate proof courier service handle: "+ + log.Errorf("Unable to initiate proof courier service handle: "+ "%v", err) return } @@ -574,8 +590,7 @@ func (c *Custodian) importAddrToWallet(addr *address.AddrWithKeyInfo) error { log.Infof("Imported Taproot Asset address %v into wallet", addrStr) if p2trAddr != nil { - log.Infof("watching p2tr address %v on chain", - p2trAddr.String()) + log.Infof("Watching p2tr address %v on chain", p2trAddr) } return c.cfg.AddrBook.SetAddrManaged(ctxt, addr, time.Now()) diff --git a/tapgarden/custodian_test.go b/tapgarden/custodian_test.go index 28ca2a61cd..9f2fcf860f 100644 --- a/tapgarden/custodian_test.go +++ b/tapgarden/custodian_test.go @@ -539,6 +539,86 @@ func TestTransactionHandling(t *testing.T) { require.EqualValues(t, mockProof.Blob, dbProof) } +// TestTransactionConfirmedOnly tests that the custodian only starts the proof +// courier once a transaction has been confirmed. We also test that it correctly +// re-tries fetching proofs using a proof courier after it has been restarted. +func TestTransactionConfirmedOnly(t *testing.T) { + t.Parallel() + + runTransactionConfirmedOnlyTest(t, false) + runTransactionConfirmedOnlyTest(t, true) +} + +// runTransactionConfirmedOnlyTest runs the transaction confirmed only test, +// optionally restarting the custodian in the middle. +func runTransactionConfirmedOnlyTest(t *testing.T, withRestart bool) { + h := newHarness(t, nil) + + // Before we start the custodian, we create a few random addresses. + ctx := context.Background() + + const numAddrs = 5 + addrs := make([]*address.AddrWithKeyInfo, numAddrs) + genesis := make([]*asset.Genesis, numAddrs) + for i := 0; i < numAddrs; i++ { + addrs[i], genesis[i] = randAddr(h) + err := h.tapdbBook.InsertAddrs(ctx, *addrs[i]) + require.NoError(t, err) + } + + // We start the custodian and make sure it's started up correctly. This + // should add pending events for each of the addresses. + require.NoError(t, h.c.Start()) + t.Cleanup(func() { + require.NoError(t, h.c.Stop()) + }) + h.assertStartup() + + // We expect all addresses to be watched by the wallet now. + h.assertAddrsRegistered(addrs...) + + // To make sure the custodian adds address events for each address, we + // need to signal an unconfirmed transaction for each of them now. + outputIndexes := make([]int, numAddrs) + transactions := make([]*lndclient.Transaction, numAddrs) + for idx := range addrs { + outputIndex, tx := randWalletTx(addrs[idx]) + outputIndexes[idx] = outputIndex + transactions[idx] = tx + h.walletAnchor.SubscribeTx <- *tx + + // We also simulate that the proof courier has all the proofs + // it needs. + mockProof := randProof( + t, outputIndexes[idx], tx.Tx, genesis[idx], addrs[idx], + ) + _ = h.courier.DeliverProof(nil, mockProof) + } + + // We want events to be created for each address, they should be in the + // state where they detected a transaction. + h.assertEventsPresent(numAddrs, address.StatusTransactionDetected) + + // In case we're testing with a restart, we now restart the custodian. + if withRestart { + require.NoError(t, h.c.Stop()) + + h.c = tapgarden.NewCustodian(h.cfg) + require.NoError(t, h.c.Start()) + h.assertStartup() + } + + // Now we confirm the transactions. This should trigger the custodian to + // fetch the proof for each of the addresses. + for idx := range transactions { + tx := transactions[idx] + tx.Confirmations = 1 + h.walletAnchor.SubscribeTx <- *tx + } + + h.assertEventsPresent(numAddrs, address.StatusCompleted) +} + func mustMakeAddr(t *testing.T, gen asset.Genesis, groupKey *btcec.PublicKey, groupWitness wire.TxWitness, scriptKey btcec.PublicKey) *address.Tap {