Skip to content

Commit

Permalink
tapgarden: start courier only after confirmation
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
guggero committed Dec 11, 2023
1 parent a0cf666 commit 6669e43
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 7 deletions.
29 changes: 22 additions & 7 deletions tapgarden/custodian.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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())
Expand Down
80 changes: 80 additions & 0 deletions tapgarden/custodian_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 6669e43

Please sign in to comment.