Skip to content

Commit e966327

Browse files
committed
tapchannel: add reanchorAssetOutputs for proof reanchoring
Introduce reanchorAssetOutputs to centralize the logic for anchoring proofs to the actual commitment transaction. This function sets the correct output index in the inclusion proof by matching the asset's Taproot output in the transaction. Used to keep proof reanchoring consistent across aux_sweeper and aux_closer by reusing a shared anchoring path.
1 parent 3ddfb81 commit e966327

File tree

2 files changed

+127
-4
lines changed

2 files changed

+127
-4
lines changed

tapchannel/aux_sweeper.go

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,84 @@ func assetOutputToVPacket(fundingInputProofs map[asset.ID]*proof.Proof,
10901090
return nil
10911091
}
10921092

1093+
// reanchorAssetOutputs updates each asset output proof so that it references
1094+
// the actual commitment transaction output. Proofs are initially built using
1095+
// a synthetic commitment template. Once the real commitment transaction is
1096+
// known, we rewrite the proof's anchor transaction and output indexes.
1097+
func reanchorAssetOutputs(ctx context.Context,
1098+
chainBridge tapgarden.ChainBridge, commitTx wire.MsgTx,
1099+
commitTxBlockHeight uint32, outputs []*cmsg.AssetOutput) error {
1100+
1101+
if len(outputs) == 0 {
1102+
return nil
1103+
}
1104+
1105+
// Only fetch block data if any proof is missing it.
1106+
proofBlockParams, err := proofParamsForCommitTx(
1107+
ctx, chainBridge, commitTxBlockHeight, commitTx,
1108+
)
1109+
if err != nil {
1110+
return fmt.Errorf("constructing proof block params: %w", err)
1111+
}
1112+
1113+
for _, output := range outputs {
1114+
p := &output.Proof.Val
1115+
1116+
// Derive the Taproot output script for this proof so we can
1117+
// locate the correct output index in the real commitment
1118+
// transaction.
1119+
pkScript, tapKey, err := p.TaprootOutputScript()
1120+
if err != nil {
1121+
return err
1122+
}
1123+
1124+
var idx = -1
1125+
for outIdx, txOut := range commitTx.TxOut {
1126+
if bytes.Equal(txOut.PkScript, pkScript) {
1127+
idx = outIdx
1128+
break
1129+
}
1130+
}
1131+
1132+
if idx < 0 {
1133+
return fmt.Errorf("no matching commit output found "+
1134+
"for asset %x (tap_key=%x)", output.AssetID.Val,
1135+
schnorr.SerializePubKey(tapKey))
1136+
}
1137+
1138+
err = p.UpdateTransitionProof(&proofBlockParams)
1139+
if err != nil {
1140+
return fmt.Errorf("failed to populate proof block: %w",
1141+
err)
1142+
}
1143+
1144+
// Ensure the anchor transaction actually spends the previous
1145+
// asset outpoint. Return an error if the stored PrevOut doesn't
1146+
// match any input.
1147+
if len(commitTx.TxIn) == 0 {
1148+
return fmt.Errorf("commit tx %v has no inputs",
1149+
commitTx.TxHash())
1150+
}
1151+
prevMatches := false
1152+
for _, txIn := range commitTx.TxIn {
1153+
if txIn.PreviousOutPoint == p.PrevOut {
1154+
prevMatches = true
1155+
break
1156+
}
1157+
}
1158+
if !prevMatches {
1159+
return fmt.Errorf("commit tx does not spend PrevOut "+
1160+
"(txid=%s, prev_out=%s)",
1161+
commitTx.TxHash().String(), p.PrevOut.String())
1162+
}
1163+
1164+
p.AnchorTx = commitTx
1165+
p.InclusionProof.OutputIndex = uint32(idx)
1166+
}
1167+
1168+
return nil
1169+
}
1170+
10931171
// anchorOutputAllocations is a helper function that creates a set of
10941172
// allocations for the anchor outputs. We'll use this later to create the proper
10951173
// exclusion proofs.
@@ -1846,11 +1924,21 @@ func (a *AuxSweeper) resolveContract(
18461924
"skipping", req.CommitTx.TxHash())
18471925
}
18481926

1927+
if req.CommitTx == nil {
1928+
return lfn.Errf[returnType]("no commitment transaction "+
1929+
"found for chan_point=%v", req.ChanPoint)
1930+
}
1931+
commitTx := *req.CommitTx
1932+
18491933
// The input proofs above were made originally using the fake commit tx
1850-
// as an anchor. We now know the real commit tx, so we'll swap that in
1851-
// to ensure the outpoints used below are correct.
1852-
for _, assetOut := range assetOutputs {
1853-
assetOut.Proof.Val.AnchorTx = *req.CommitTx
1934+
// as an anchor. We now know the real commit tx, so we'll bind each
1935+
// proof to the actual commitment output that carries the asset.
1936+
if err := reanchorAssetOutputs(
1937+
ctx, a.cfg.ChainBridge, commitTx, req.CommitTxBlockHeight,
1938+
assetOutputs,
1939+
); err != nil {
1940+
return lfn.Errf[returnType]("unable to re-anchor asset "+
1941+
"outputs: %w", err)
18541942
}
18551943

18561944
log.Infof("Sweeping %v asset outputs (second_level=%v): %v",

tapchannel/proof_utils.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,38 @@ func updateProofsFromShortChanID(ctx context.Context,
6666

6767
return nil
6868
}
69+
70+
// proofParamsForCommitTx creates proof params using the given block height and
71+
// commitment transaction. The transaction must be included in the block at that
72+
// height.
73+
func proofParamsForCommitTx(ctx context.Context,
74+
chainBridge tapgarden.ChainBridge, blockHeight uint32,
75+
commitTx wire.MsgTx) (proof.BaseProofParams, error) {
76+
77+
var zero proof.BaseProofParams
78+
79+
block, err := chainBridge.GetBlockByHeight(ctx, int64(blockHeight))
80+
if err != nil {
81+
return zero, err
82+
}
83+
84+
txHash := commitTx.TxHash()
85+
txIdx := -1
86+
for idx, tx := range block.Transactions {
87+
if tx.TxHash() == txHash {
88+
txIdx = idx
89+
break
90+
}
91+
}
92+
if txIdx < 0 {
93+
return zero, fmt.Errorf("commit tx %v not found in block %v",
94+
txHash, block.BlockHash())
95+
}
96+
97+
return proof.BaseProofParams{
98+
Block: block,
99+
BlockHeight: blockHeight,
100+
Tx: block.Transactions[txIdx],
101+
TxIndex: txIdx,
102+
}, nil
103+
}

0 commit comments

Comments
 (0)