Skip to content

Commit a2e2614

Browse files
authored
[Server] Wait for signed boarding ins/forfeit txs only if required (#497)
* Skip waiting for signed boarding ins/forfeit txs if none is required * Fix * Fixes * Revert changes to tx builder * Add todo
1 parent d2bf89c commit a2e2614

File tree

6 files changed

+129
-50
lines changed

6 files changed

+129
-50
lines changed

nak.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Use official Go image as base
2-
FROM golang:1.23.3-alpine
2+
FROM golang:1.24.1-alpine
33

44
# Install git (needed for go install)
55
RUN apk add --no-cache git

server/internal/core/application/covenant.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,14 @@ func (s *covenantService) SignVtxos(ctx context.Context, forfeitTxs []string) er
397397
}
398398

399399
func (s *covenantService) SignRoundTx(ctx context.Context, signedRoundTx string) error {
400+
numSignedInputs, err := s.builder.CountSignedTaprootInputs(signedRoundTx)
401+
if err != nil {
402+
return fmt.Errorf("failed to count number of signed boarding inputs: %s", err)
403+
}
404+
if numSignedInputs == 0 {
405+
return nil
406+
}
407+
400408
s.currentRoundLock.Lock()
401409
defer s.currentRoundLock.Unlock()
402410
currentRound := s.currentRound

server/internal/core/application/covenantless.go

Lines changed: 84 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,10 @@ func (s *covenantlessService) UpdateTxRequestStatus(_ context.Context, id string
759759
}
760760

761761
func (s *covenantlessService) SignVtxos(ctx context.Context, forfeitTxs []string) error {
762+
if len(forfeitTxs) <= 0 {
763+
return nil
764+
}
765+
762766
if err := s.forfeitTxs.sign(forfeitTxs); err != nil {
763767
return err
764768
}
@@ -774,6 +778,14 @@ func (s *covenantlessService) SignVtxos(ctx context.Context, forfeitTxs []string
774778
}
775779

776780
func (s *covenantlessService) SignRoundTx(ctx context.Context, signedRoundTx string) error {
781+
numSignedInputs, err := s.builder.CountSignedTaprootInputs(signedRoundTx)
782+
if err != nil {
783+
return fmt.Errorf("failed to count number of signed boarding inputs: %s", err)
784+
}
785+
if numSignedInputs == 0 {
786+
return nil
787+
}
788+
777789
s.currentRoundLock.Lock()
778790
defer s.currentRoundLock.Unlock()
779791
currentRound := s.currentRound
@@ -1469,68 +1481,95 @@ func (s *covenantlessService) finalizeRound(notes []note.Note, roundEndTime time
14691481
}
14701482
}()
14711483

1472-
remainingTime := time.Until(roundEndTime)
1473-
select {
1474-
case <-s.forfeitsBoardingSigsChan:
1475-
log.Debug("all forfeit txs and boarding inputs signatures have been sent")
1476-
case <-time.After(remainingTime):
1477-
log.Debug("timeout waiting for forfeit txs and boarding inputs signatures")
1478-
}
1479-
1480-
forfeitTxs, err := s.forfeitTxs.pop()
1481-
if err != nil {
1482-
changes = round.Fail(fmt.Errorf("failed to finalize round: %s", err))
1483-
log.WithError(err).Warn("failed to finalize round")
1484-
return
1485-
}
1486-
1487-
if err := s.verifyForfeitTxsSigs(forfeitTxs); err != nil {
1488-
changes = round.Fail(err)
1489-
log.WithError(err).Warn("failed to validate forfeit txs")
1490-
return
1491-
}
1492-
1493-
log.Debugf("signing round transaction %s\n", round.Id)
1494-
1495-
boardingInputsIndexes := make([]int, 0)
1496-
boardingInputs := make([]domain.VtxoKey, 0)
14971484
roundTx, err := psbt.NewFromRawBytes(strings.NewReader(round.UnsignedTx), true)
14981485
if err != nil {
14991486
log.Debugf("failed to parse round tx: %s", round.UnsignedTx)
15001487
changes = round.Fail(fmt.Errorf("failed to parse round tx: %s", err))
15011488
log.WithError(err).Warn("failed to parse round tx")
15021489
return
15031490
}
1491+
includesBoardingInputs := false
1492+
for _, in := range roundTx.Inputs {
1493+
// TODO: this is ok as long as the server doesn't use taproot address too!
1494+
// We need to find a better way to understand if an in input is ours or if
1495+
// it's a boarding one.
1496+
scriptType := txscript.GetScriptClass(in.WitnessUtxo.PkScript)
1497+
if scriptType == txscript.WitnessV1TaprootTy {
1498+
includesBoardingInputs = true
1499+
break
1500+
}
1501+
}
15041502

1505-
for i, in := range roundTx.Inputs {
1506-
if len(in.TaprootLeafScript) > 0 {
1507-
if len(in.TaprootScriptSpendSig) == 0 {
1508-
err = fmt.Errorf("missing tapscript spend sig for input %d", i)
1509-
changes = round.Fail(err)
1510-
log.WithError(err).Warn("missing boarding sig")
1511-
return
1512-
}
1503+
txToSign := round.UnsignedTx
1504+
boardingInputs := make([]domain.VtxoKey, 0)
1505+
forfeitTxs := make([]string, 0)
15131506

1514-
boardingInputsIndexes = append(boardingInputsIndexes, i)
1515-
boardingInputs = append(boardingInputs, domain.VtxoKey{
1516-
Txid: roundTx.UnsignedTx.TxIn[i].PreviousOutPoint.Hash.String(),
1517-
VOut: roundTx.UnsignedTx.TxIn[i].PreviousOutPoint.Index,
1518-
})
1507+
if len(s.forfeitTxs.forfeitTxs) > 0 || includesBoardingInputs {
1508+
remainingTime := time.Until(roundEndTime)
1509+
select {
1510+
case <-s.forfeitsBoardingSigsChan:
1511+
log.Debug("all forfeit txs and boarding inputs signatures have been sent")
1512+
case <-time.After(remainingTime):
1513+
log.Debug("timeout waiting for forfeit txs and boarding inputs signatures")
15191514
}
1520-
}
15211515

1522-
signedRoundTx := round.UnsignedTx
1516+
s.currentRoundLock.Lock()
1517+
round := s.currentRound
1518+
s.currentRoundLock.Unlock()
1519+
1520+
roundTx, err := psbt.NewFromRawBytes(strings.NewReader(round.UnsignedTx), true)
1521+
if err != nil {
1522+
log.Debugf("failed to parse round tx: %s", round.UnsignedTx)
1523+
changes = round.Fail(fmt.Errorf("failed to parse round tx: %s", err))
1524+
log.WithError(err).Warn("failed to parse round tx")
1525+
return
1526+
}
1527+
txToSign = round.UnsignedTx
15231528

1524-
if len(boardingInputsIndexes) > 0 {
1525-
signedRoundTx, err = s.wallet.SignTransactionTapscript(ctx, signedRoundTx, boardingInputsIndexes)
1529+
forfeitTxs, err = s.forfeitTxs.pop()
15261530
if err != nil {
1527-
changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err))
1528-
log.WithError(err).Warn("failed to sign round tx")
1531+
changes = round.Fail(fmt.Errorf("failed to finalize round: %s", err))
1532+
log.WithError(err).Warn("failed to finalize round")
1533+
return
1534+
}
1535+
1536+
if err := s.verifyForfeitTxsSigs(forfeitTxs); err != nil {
1537+
changes = round.Fail(err)
1538+
log.WithError(err).Warn("failed to validate forfeit txs")
15291539
return
15301540
}
1541+
1542+
boardingInputsIndexes := make([]int, 0)
1543+
for i, in := range roundTx.Inputs {
1544+
if len(in.TaprootLeafScript) > 0 {
1545+
if len(in.TaprootScriptSpendSig) == 0 {
1546+
err = fmt.Errorf("missing tapscript spend sig for input %d", i)
1547+
changes = round.Fail(err)
1548+
log.WithError(err).Warn("missing boarding sig")
1549+
return
1550+
}
1551+
1552+
boardingInputsIndexes = append(boardingInputsIndexes, i)
1553+
boardingInputs = append(boardingInputs, domain.VtxoKey{
1554+
Txid: roundTx.UnsignedTx.TxIn[i].PreviousOutPoint.Hash.String(),
1555+
VOut: roundTx.UnsignedTx.TxIn[i].PreviousOutPoint.Index,
1556+
})
1557+
}
1558+
}
1559+
1560+
if len(boardingInputsIndexes) > 0 {
1561+
txToSign, err = s.wallet.SignTransactionTapscript(ctx, txToSign, boardingInputsIndexes)
1562+
if err != nil {
1563+
changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err))
1564+
log.WithError(err).Warn("failed to sign round tx")
1565+
return
1566+
}
1567+
}
15311568
}
15321569

1533-
signedRoundTx, err = s.wallet.SignTransaction(ctx, signedRoundTx, true)
1570+
log.Debugf("signing transaction %s\n", round.Id)
1571+
1572+
signedRoundTx, err := s.wallet.SignTransaction(ctx, txToSign, true)
15341573
if err != nil {
15351574
changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err))
15361575
log.WithError(err).Warn("failed to sign round tx")

server/internal/core/ports/tx_builder.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,6 @@ type TxBuilder interface {
5757
// FindLeaves returns all the leaves txs that are reachable from the given outpoint
5858
FindLeaves(vtxoTree tree.TxTree, fromtxid string, vout uint32) (leaves []tree.Node, err error)
5959
VerifyAndCombinePartialTx(dest string, src string) (string, error)
60+
CountSignedTaprootInputs(tx string) (int, error)
6061
GetTxID(tx string) (string, error)
6162
}

server/internal/infrastructure/tx-builder/covenant/builder.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,22 @@ func (b *txBuilder) minRelayFeeConnectorTx() (uint64, error) {
10001000
return b.wallet.MinRelayFee(context.Background(), uint64(common.ConnectorTxSize))
10011001
}
10021002

1003+
func (b *txBuilder) CountSignedTaprootInputs(tx string) (int, error) {
1004+
ptx, err := psetv2.NewPsetFromBase64(tx)
1005+
if err != nil {
1006+
return -1, err
1007+
}
1008+
1009+
signedInputsCount := 0
1010+
for _, input := range ptx.Inputs {
1011+
if len(input.TapScriptSig) == 0 || len(input.TapLeafScript) == 0 {
1012+
continue
1013+
}
1014+
signedInputsCount++
1015+
}
1016+
return signedInputsCount, nil
1017+
}
1018+
10031019
// This method aims to verify and add partial signature from boarding input
10041020
func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string, error) {
10051021
roundPset, err := psetv2.NewPsetFromBase64(dest)

server/internal/infrastructure/tx-builder/covenantless/builder.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,23 @@ func (b *txBuilder) minRelayFeeConnectorTx() (uint64, error) {
10491049
return b.wallet.MinRelayFee(context.Background(), uint64(common.ConnectorTxSize))
10501050
}
10511051

1052+
func (b *txBuilder) CountSignedTaprootInputs(tx string) (int, error) {
1053+
ptx, err := psbt.NewFromRawBytes(strings.NewReader(tx), true)
1054+
if err != nil {
1055+
return -1, err
1056+
}
1057+
1058+
signedInputsCount := 0
1059+
for _, in := range ptx.Inputs {
1060+
if len(in.TaprootScriptSpendSig) == 0 || len(in.TaprootLeafScript) == 0 {
1061+
continue
1062+
}
1063+
1064+
signedInputsCount++
1065+
}
1066+
return signedInputsCount, nil
1067+
}
1068+
10521069
func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string, error) {
10531070
roundTx, err := psbt.NewFromRawBytes(strings.NewReader(dest), true)
10541071
if err != nil {
@@ -1064,12 +1081,10 @@ func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string,
10641081
return "", fmt.Errorf("txids do not match")
10651082
}
10661083

1067-
for i, in := range sourceTx.Inputs {
1068-
isMultisigTaproot := len(in.TaprootLeafScript) > 0
1084+
for i, sourceInput := range sourceTx.Inputs {
1085+
isMultisigTaproot := len(sourceInput.TaprootLeafScript) > 0
10691086
if isMultisigTaproot {
10701087
// check if the source tx signs the leaf
1071-
sourceInput := sourceTx.Inputs[i]
1072-
10731088
if len(sourceInput.TaprootScriptSpendSig) == 0 {
10741089
continue
10751090
}

0 commit comments

Comments
 (0)