staticaddr: accept production taproot channels

LND v0.21 exposes CommitmentType_TAPROOT as the production taproot
channel commitment type, while SIMPLE_TAPROOT remains a legacy taproot
enum. Static address channel opens previously rejected TAPROOT and only
classified SIMPLE_TAPROOT as a taproot output for fee and weight
estimates.

Accept TAPROOT in the static address open-channel validator and keep
accepting SIMPLE_TAPROOT for compatibility. Treat both taproot
commitment enums as P2TR outputs for deposit-selection and withdrawal
fee estimates. Callers using the production enum then get the same
weight accounting as the legacy taproot enum.

This does not change the CLI mapping for user-facing
channel_type=taproot. It only makes the static address path compatible
with callers that already send LND production taproot commitment type.
This commit is contained in:
Boris Nagaev 2026-06-11 14:22:43 -05:00
parent a92f6bc875
commit 09c92527ea
No known key found for this signature in database
6 changed files with 46 additions and 25 deletions

View file

@ -773,6 +773,9 @@ func resolveCommitmentType(commitmentType lnrpc.CommitmentType) (
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
return lnrpc.CommitmentType_SIMPLE_TAPROOT, nil
case lnrpc.CommitmentType_TAPROOT:
return lnrpc.CommitmentType_TAPROOT, nil
default:
return lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE, fmt.Errorf(
"unsupported commitment type %v", commitmentType,

View file

@ -573,6 +573,11 @@ func TestResolveCommitmentType(t *testing.T) {
commitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
expectedType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
},
{
name: "production taproot supported",
commitmentType: lnrpc.CommitmentType_TAPROOT,
expectedType: lnrpc.CommitmentType_TAPROOT,
},
{
name: "legacy rejected",
commitmentType: lnrpc.CommitmentType_LEGACY,

View file

@ -232,8 +232,10 @@ func estimateFee(numInputs int, feeRate chainfee.SatPerKWeight,
// Add the funding output based on commitment type.
switch commitmentType {
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
case lnrpc.CommitmentType_SIMPLE_TAPROOT,
lnrpc.CommitmentType_TAPROOT:
we.AddP2TROutput()
default:
we.AddP2WSHOutput()
}

View file

@ -271,9 +271,6 @@ func TestSelectDeposits(t *testing.T) {
// High fee rate: 100 sat/vbyte = 25000 sat/kw.
highFeeRate := chainfee.SatPerKVByte(100_000).FeePerKWeight()
anchors := lnrpc.CommitmentType_ANCHORS
taproot := lnrpc.CommitmentType_SIMPLE_TAPROOT
tests := []struct {
name string
deposits []*deposit.Deposit
@ -290,7 +287,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(1_000, 2_000),
amount: 1_000_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantErr: "insufficient funds",
},
{
@ -298,7 +295,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(100_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantErr: "insufficient funds",
},
{
@ -310,7 +307,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(51_000),
amount: 50_000,
feeRate: highFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantErr: "insufficient funds",
},
{
@ -327,7 +324,7 @@ func TestSelectDeposits(t *testing.T) {
),
amount: 400_000,
feeRate: highFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 2,
validate: func(t *testing.T, selected []*deposit.Deposit) {
require.Equal(
@ -345,7 +342,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(500_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 1,
},
{
@ -353,7 +350,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(60_000, 60_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 2,
},
{
@ -361,7 +358,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(10_000, 200_000, 50_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 1,
validate: func(t *testing.T, selected []*deposit.Deposit) {
// Should pick the 200k deposit.
@ -388,13 +385,13 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(35_500, 35_500, 10_000),
amount: 50_000,
feeRate: highFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 3,
validate: func(t *testing.T, selected []*deposit.Deposit) {
total := depositSum(selected)
fee := estimateFee(
len(selected), highFeeRate,
anchors,
lnrpc.CommitmentType_ANCHORS,
)
require.GreaterOrEqual(
t, total,
@ -407,7 +404,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(40_000, 40_000, 40_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 3,
},
{
@ -415,7 +412,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(100_000, 50_000),
amount: 99_000,
feeRate: 0,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 1,
validate: func(t *testing.T, selected []*deposit.Deposit) {
// With zero fee, 100k covers 99k + 0 + dust.
@ -430,12 +427,12 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(200_000, 100_000, 50_000),
amount: 100_000,
feeRate: highFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
validate: func(t *testing.T, selected []*deposit.Deposit) {
total := depositSum(selected)
fee := estimateFee(
len(selected), highFeeRate,
anchors,
lnrpc.CommitmentType_ANCHORS,
)
require.GreaterOrEqual(
t, total,
@ -448,7 +445,15 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(500_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: taproot,
commitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
wantCount: 1,
},
{
name: "production taproot commitment type",
deposits: makeDeposits(500_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: lnrpc.CommitmentType_TAPROOT,
wantCount: 1,
},
{
@ -459,12 +464,12 @@ func TestSelectDeposits(t *testing.T) {
),
amount: 50_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
validate: func(t *testing.T, selected []*deposit.Deposit) {
total := depositSum(selected)
fee := estimateFee(
len(selected), lowFeeRate,
anchors,
lnrpc.CommitmentType_ANCHORS,
)
require.GreaterOrEqual(
t, total,
@ -484,12 +489,12 @@ func TestSelectDeposits(t *testing.T) {
),
amount: 150_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
validate: func(t *testing.T, selected []*deposit.Deposit) {
total := depositSum(selected)
fee := estimateFee(
len(selected), lowFeeRate,
anchors,
lnrpc.CommitmentType_ANCHORS,
)
// Core invariant: selected amount covers
// requested amount + fee + dust.
@ -506,7 +511,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(10_000, 20_000, 300_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 1,
validate: func(t *testing.T, selected []*deposit.Deposit) {
require.Equal(
@ -525,7 +530,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(60_000, 60_000),
amount: 50_000,
feeRate: highFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 2,
},
}

View file

@ -235,6 +235,11 @@ func TestCalculateWithdrawalTxValuesCommitmentTypeParity(t *testing.T) {
commitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
addr: taprootAddr,
},
{
name: "production taproot and p2tr",
commitmentType: lnrpc.CommitmentType_TAPROOT,
addr: taprootAddr,
},
}
selectedAmounts := []btcutil.Amount{

View file

@ -1108,7 +1108,8 @@ func WithdrawalTxWeight(numInputs int, sweepAddress btcutil.Address,
if commitmentType != lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE {
switch commitmentType {
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
case lnrpc.CommitmentType_SIMPLE_TAPROOT,
lnrpc.CommitmentType_TAPROOT:
weightEstimator.AddP2TROutput()
default: