diff --git a/staticaddr/openchannel/manager.go b/staticaddr/openchannel/manager.go index b7472c57..acf18732 100644 --- a/staticaddr/openchannel/manager.go +++ b/staticaddr/openchannel/manager.go @@ -299,6 +299,18 @@ func (m *Manager) OpenChannel(ctx context.Context, err) } + // Check for duplicate outpoints which would lead to fee + // miscalculation and an invalid PSBT with the same input + // listed twice. + seen := make(map[wire.OutPoint]struct{}, len(outpoints)) + for _, op := range outpoints { + if _, ok := seen[op]; ok { + return nil, fmt.Errorf("duplicate outpoint "+ + "%v in request", op) + } + seen[op] = struct{}{} + } + deposits, allActive = m.cfg.DepositManager.AllOutpointsActiveDeposits( outpoints, deposit.Deposited, diff --git a/staticaddr/openchannel/manager_test.go b/staticaddr/openchannel/manager_test.go index 807d275f..db330e01 100644 --- a/staticaddr/openchannel/manager_test.go +++ b/staticaddr/openchannel/manager_test.go @@ -210,6 +210,34 @@ func testOutPoint(b byte) wire.OutPoint { } } +func TestOpenChannelDuplicateOutpoints(t *testing.T) { + t.Parallel() + + op := testOutPoint(1) + manager := &Manager{ + cfg: &Config{}, + } + + req := &lnrpc.OpenChannelRequest{ + NodePubkey: make([]byte, 33), + LocalFundingAmount: 100000, + SatPerVbyte: 10, + Outpoints: []*lnrpc.OutPoint{ + { + TxidStr: op.Hash.String(), + OutputIndex: op.Index, + }, + { + TxidStr: op.Hash.String(), + OutputIndex: op.Index, + }, + }, + } + + _, err := manager.OpenChannel(context.Background(), req) + require.ErrorContains(t, err, "duplicate outpoint") +} + func TestValidateInitialPsbtFlags(t *testing.T) { t.Parallel()