Skip to content

Commit 2f0947d

Browse files
authored
Merge pull request #1118 from lightninglabs/aux_signer_batching_fixes
tapchannel: improve aux signer signal handling
2 parents 566b409 + 4eb1aeb commit 2f0947d

File tree

3 files changed

+194
-63
lines changed

3 files changed

+194
-63
lines changed

internal/test/helpers.go

+14
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/lightningnetwork/lnd/input"
2222
"github.com/lightningnetwork/lnd/keychain"
2323
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
24+
"github.com/lightningnetwork/lnd/lnwallet"
2425
"github.com/stretchr/testify/require"
2526
"golang.org/x/exp/constraints"
2627
)
@@ -137,6 +138,19 @@ func RandPubKey(t testing.TB) *btcec.PublicKey {
137138
return SchnorrPubKey(t, RandPrivKey(t))
138139
}
139140

141+
func RandCommitmentKeyRing(t *testing.T) lnwallet.CommitmentKeyRing {
142+
return lnwallet.CommitmentKeyRing{
143+
CommitPoint: RandPubKey(t),
144+
LocalCommitKeyTweak: RandBytes(32),
145+
LocalHtlcKeyTweak: RandBytes(32),
146+
LocalHtlcKey: RandPubKey(t),
147+
RemoteHtlcKey: RandPubKey(t),
148+
ToLocalKey: RandPubKey(t),
149+
ToRemoteKey: RandPubKey(t),
150+
RevocationKey: RandPubKey(t),
151+
}
152+
}
153+
140154
func RandBytes(num int) []byte {
141155
randLock.Lock()
142156
defer randLock.Unlock()

tapchannel/auf_leaf_signer_test.go

+142-49
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"github.com/lightningnetwork/lnd/input"
2323
"github.com/lightningnetwork/lnd/lnwallet"
2424
"github.com/lightningnetwork/lnd/lnwire"
25-
"github.com/lightningnetwork/lnd/tlv"
2625
"github.com/stretchr/testify/require"
2726
)
2827

@@ -40,10 +39,58 @@ var (
4039
)
4140

4241
testTimeout = time.Second
42+
43+
chanState = &channeldb.OpenChannel{
44+
ChanType: channeldb.AnchorOutputsBit |
45+
channeldb.ScidAliasChanBit | channeldb.SingleFunderBit |
46+
channeldb.SimpleTaprootFeatureBit |
47+
channeldb.TapscriptRootBit,
48+
IsInitiator: true,
49+
}
50+
51+
// sig job batch size when making more that one sig job.
52+
numSigJobs = int32(10)
53+
54+
// Threshold for trying to cancel or quit the aux leaf signer (allow
55+
// the signer to complete a third of the batch).
56+
sigJobCancelThreshold = numSigJobs / 3
4357
)
4458

45-
// TestAuxLeafSigner tests the AuxLeafSigner implementation.
46-
func TestAuxLeafSigner(t *testing.T) {
59+
// RandAuxSigJob generates a basic aux signer job with random key material.
60+
func RandAuxSigJob(t *testing.T, cancelChan chan struct{},
61+
commitBlob lfn.Option[[]byte], outputIdx int32) lnwallet.AuxSigJob {
62+
63+
keyDesc, _ := test.RandKeyDesc(t)
64+
keyRing := test.RandCommitmentKeyRing(t)
65+
66+
return lnwallet.AuxSigJob{
67+
SignDesc: input.SignDescriptor{
68+
KeyDesc: keyDesc,
69+
},
70+
BaseAuxJob: lnwallet.BaseAuxJob{
71+
OutputIndex: outputIdx,
72+
KeyRing: keyRing,
73+
HTLC: lnwallet.PaymentDescriptor{
74+
HtlcIndex: 0,
75+
Amount: lnwire.NewMSatFromSatoshis(
76+
354,
77+
),
78+
EntryType: lnwallet.Add,
79+
},
80+
Incoming: false,
81+
CommitBlob: commitBlob,
82+
HtlcLeaf: input.AuxTapLeaf{},
83+
},
84+
Resp: make(chan lnwallet.AuxSigJobResp, 1),
85+
Cancel: cancelChan,
86+
}
87+
}
88+
89+
// setupAuxLeafSigner sets up an AuxLeafSigner instance and a batch of sig jobs
90+
// to use in unit tests.
91+
func setupAuxLeafSigner(t *testing.T, numJobs int32) (*AuxLeafSigner,
92+
chan struct{}, *wire.MsgTx, []lnwallet.AuxSigJob) {
93+
4794
cfg := &LeafSignerConfig{
4895
ChainParams: testChainParams,
4996
Signer: &mockVirtualSigner{},
@@ -52,30 +99,8 @@ func TestAuxLeafSigner(t *testing.T) {
5299
signer := NewAuxLeafSigner(cfg)
53100
require.NoError(t, signer.Start())
54101

55-
defer func() {
56-
require.NoError(t, signer.Stop())
57-
}()
58-
59-
chanState := &channeldb.OpenChannel{
60-
ChanType: channeldb.AnchorOutputsBit |
61-
channeldb.ScidAliasChanBit | channeldb.SingleFunderBit |
62-
channeldb.SimpleTaprootFeatureBit |
63-
channeldb.TapscriptRootBit,
64-
IsInitiator: true,
65-
}
66102
randInputProof := randProof(t)
67103
commitTx := &randInputProof.AnchorTx
68-
keyRing := lnwallet.CommitmentKeyRing{
69-
CommitPoint: test.RandPubKey(t),
70-
LocalCommitKeyTweak: test.RandBytes(32),
71-
LocalHtlcKeyTweak: test.RandBytes(32),
72-
LocalHtlcKey: test.RandPubKey(t),
73-
RemoteHtlcKey: test.RandPubKey(t),
74-
ToLocalKey: test.RandPubKey(t),
75-
ToRemoteKey: test.RandPubKey(t),
76-
RevocationKey: test.RandPubKey(t),
77-
}
78-
79104
outgoingHtlcs := make(map[input.HtlcIndex][]*cmsg.AssetOutput)
80105
outgoingHtlcs[0] = []*cmsg.AssetOutput{
81106
cmsg.NewAssetOutput(
@@ -87,33 +112,28 @@ func TestAuxLeafSigner(t *testing.T) {
87112
com := cmsg.NewCommitment(
88113
nil, nil, outgoingHtlcs, nil, lnwallet.CommitAuxLeaves{},
89114
)
115+
cancelChan := make(chan struct{})
90116

91-
randKeyDesc, _ := test.RandKeyDesc(t)
92-
93-
jobs := []lnwallet.AuxSigJob{
94-
{
95-
SignDesc: input.SignDescriptor{
96-
KeyDesc: randKeyDesc,
97-
},
98-
BaseAuxJob: lnwallet.BaseAuxJob{
99-
OutputIndex: 0,
100-
KeyRing: keyRing,
101-
HTLC: lnwallet.PaymentDescriptor{
102-
HtlcIndex: 0,
103-
Amount: lnwire.NewMSatFromSatoshis(
104-
354,
105-
),
106-
EntryType: lnwallet.Add,
107-
},
108-
Incoming: false,
109-
CommitBlob: lfn.Some[tlv.Blob](com.Bytes()),
110-
HtlcLeaf: input.AuxTapLeaf{},
111-
},
112-
Resp: make(chan lnwallet.AuxSigJobResp),
113-
Cancel: make(chan struct{}),
114-
},
117+
// Constructing multiple jobs will allow us to assert that later jobs
118+
// are cancelled successfully.
119+
jobs := make([]lnwallet.AuxSigJob, 0, numJobs)
120+
for idx := range numJobs {
121+
newJob := RandAuxSigJob(
122+
t, cancelChan, lfn.Some(com.Bytes()), idx,
123+
)
124+
jobs = append(jobs, newJob)
115125
}
116126

127+
return signer, cancelChan, commitTx, jobs
128+
}
129+
130+
// TestAuxLeafSigner tests the AuxLeafSigner implementation.
131+
func TestAuxLeafSigner(t *testing.T) {
132+
signer, _, commitTx, jobs := setupAuxLeafSigner(t, 1)
133+
defer func() {
134+
require.NoError(t, signer.Stop())
135+
}()
136+
117137
err := signer.SubmitSecondLevelSigBatch(chanState, commitTx, jobs)
118138
require.NoError(t, err)
119139

@@ -131,6 +151,79 @@ func TestAuxLeafSigner(t *testing.T) {
131151
}
132152
}
133153

154+
// TestAuxLeafSignerCancel tests that the AuxLeafSigner will handle a cancel
155+
// signal correctly, which involves skipping all remaining sig jobs.
156+
func TestAuxLeafSignerCancel(t *testing.T) {
157+
// Constructing multiple jobs will allow us to assert that later jobs
158+
// are cancelled successfully.
159+
signer, cancelChan, commitTx, jobs := setupAuxLeafSigner(t, numSigJobs)
160+
defer func() {
161+
require.NoError(t, signer.Stop())
162+
}()
163+
164+
err := signer.SubmitSecondLevelSigBatch(chanState, commitTx, jobs)
165+
require.NoError(t, err)
166+
167+
select {
168+
case <-time.After(testTimeout):
169+
t.Fatalf("timeout waiting for response")
170+
case <-jobs[sigJobCancelThreshold].Resp:
171+
// Send the cancel signal; jobs at the end of the batch should
172+
// not be processed.
173+
close(cancelChan)
174+
}
175+
176+
signer.Wg.Wait()
177+
178+
// Once the aux signer finishes handling the batch, the last job of the
179+
// batch should have an empty response channel. Otherwise, the signer
180+
// failed to skip that job after the cancel channel was closed.
181+
select {
182+
case <-jobs[numSigJobs-1].Resp:
183+
t.Fatalf("Job cancellation failed")
184+
default:
185+
}
186+
}
187+
188+
// TestAuxLeafSignerCancelAndQuit tests that the AuxLeafSigner will handle a
189+
// quit signal correctly, which involves ending sig job handling as soon as
190+
// possible. This test also sends a cancel signal before the quit signal, to
191+
// check that quits are handled correctly alongside other sent signals.
192+
func TestAuxLeafSignerCancelAndQuit(t *testing.T) {
193+
// Constructing multiple jobs will allow us to assert that later jobs
194+
// are skipped successfully after sending the quit signal.
195+
signer, cancelChan, commitTx, jobs := setupAuxLeafSigner(t, numSigJobs)
196+
defer func() {
197+
require.NoError(t, signer.Stop())
198+
}()
199+
200+
err := signer.SubmitSecondLevelSigBatch(chanState, commitTx, jobs)
201+
require.NoError(t, err)
202+
203+
select {
204+
case <-time.After(testTimeout):
205+
t.Fatalf("timeout waiting for response")
206+
case <-jobs[sigJobCancelThreshold].Resp:
207+
// Another component could have sent the cancel signal; we'll
208+
// send that before the quit signal.
209+
close(cancelChan)
210+
time.Sleep(time.Millisecond)
211+
212+
// Send the quit signal; jobs at the end of the batch should not
213+
// be processed.
214+
require.NoError(t, signer.Stop())
215+
}
216+
217+
// Once the aux signer stops, the last job of the batch should have an
218+
// an empty response. Otherwise, the signer failed to stop as soon as
219+
// the quit signal was sent.
220+
select {
221+
case <-jobs[numSigJobs-1].Resp:
222+
t.Fatalf("Aux signer quitting failed")
223+
default:
224+
}
225+
}
226+
134227
// mockVirtualSigner is a mock implementation of the VirtualSigner interface.
135228
type mockVirtualSigner struct {
136229
}

tapchannel/aux_leaf_signer.go

+38-14
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import (
2828
"github.com/lightningnetwork/lnd/tlv"
2929
)
3030

31+
// shutdownErr is used in multiple spots when exiting the sig batch processor.
32+
var shutdownErr = fmt.Errorf("tapd is shutting down")
33+
3134
// VirtualPacketSigner is an interface that can be used to sign virtual packets.
3235
type VirtualPacketSigner interface {
3336
// SignVirtualPacket signs the virtual transaction of the given packet
@@ -241,43 +244,49 @@ func (s *AuxLeafSigner) processAuxSigBatch(chanState *channeldb.OpenChannel,
241244
defer s.Wg.Done()
242245

243246
log.Tracef("Processing %d aux sig jobs", len(sigJobs))
244-
245247
for idx := range sigJobs {
246248
sigJob := sigJobs[idx]
247-
cancelAndErr := func(err error) {
249+
respondErr := func(err error) {
248250
log.Errorf("Error processing aux sig job: %v", err)
249251

250-
close(sigJob.Cancel)
251252
sigJob.Resp <- lnwallet.AuxSigJobResp{
252253
Err: err,
253254
}
254255
}
255256

256-
// If we're shutting down, we cancel the job and return.
257+
// Check for cancel or quit signals before beginning the job.
257258
select {
259+
case <-sigJob.Cancel:
260+
continue
258261
case <-s.Quit:
259-
cancelAndErr(fmt.Errorf("tapd is shutting down"))
262+
respondErr(shutdownErr)
260263
return
261-
262264
default:
263265
}
264266

265267
// If there is no commit blob, this isn't a custom channel. We
266268
// still need to signal the job as done though, even if we don't
267269
// have a signature to return.
268270
if sigJob.CommitBlob.IsNone() {
269-
sigJob.Resp <- lnwallet.AuxSigJobResp{
271+
select {
272+
case sigJob.Resp <- lnwallet.AuxSigJobResp{
270273
HtlcIndex: sigJob.HTLC.HtlcIndex,
274+
}:
275+
continue
276+
case <-sigJob.Cancel:
277+
continue
278+
case <-s.Quit:
279+
respondErr(shutdownErr)
280+
return
271281
}
272-
continue
273282
}
274283

275284
com, err := cmsg.DecodeCommitment(
276285
sigJob.CommitBlob.UnsafeFromSome(),
277286
)
278287
if err != nil {
279-
cancelAndErr(fmt.Errorf("error decoding commitment: "+
280-
"%w", err))
288+
respondErr(fmt.Errorf("error decoding commitment: %w",
289+
err))
281290
return
282291
}
283292

@@ -299,26 +308,41 @@ func (s *AuxLeafSigner) processAuxSigBatch(chanState *channeldb.OpenChannel,
299308
// If the HTLC doesn't have any asset outputs, it's not an
300309
// asset HTLC, so we can skip it.
301310
if len(htlcOutputs) == 0 {
302-
sigJob.Resp <- lnwallet.AuxSigJobResp{
311+
select {
312+
case sigJob.Resp <- lnwallet.AuxSigJobResp{
303313
HtlcIndex: sigJob.HTLC.HtlcIndex,
314+
}:
315+
continue
316+
case <-sigJob.Cancel:
317+
continue
318+
case <-s.Quit:
319+
respondErr(shutdownErr)
320+
return
304321
}
305-
continue
306322
}
307323

308324
resp, err := s.generateHtlcSignature(
309325
chanState, commitTx, htlcOutputs, sigJob.SignDesc,
310326
sigJob.BaseAuxJob,
311327
)
312328
if err != nil {
313-
cancelAndErr(fmt.Errorf("error generating HTLC "+
329+
respondErr(fmt.Errorf("error generating HTLC "+
314330
"signature: %w", err))
315331
return
316332
}
317333

318334
// Success!
319335
log.Tracef("Generated HTLC signature for HTLC with index %d",
320336
sigJob.HTLC.HtlcIndex)
321-
sigJob.Resp <- resp
337+
338+
select {
339+
case sigJob.Resp <- resp:
340+
case <-sigJob.Cancel:
341+
continue
342+
case <-s.Quit:
343+
respondErr(shutdownErr)
344+
return
345+
}
322346
}
323347
}
324348

0 commit comments

Comments
 (0)