mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-18 21:35:24 +01:00
multi: allow skipping the PSBT finalize step
The FundingPsbtFinalize step is a safety measure that assures the final signed funding transaction has the same TXID as was registered during the funding flow and was used for the commitment transactions. This step is cumbersome to use if the whole funding process is completed external to lnd. We allow the finalize step to be skipped for such cases. The API user/script will need to make sure things are verified (and possibly cleaned up) properly.
This commit is contained in:
parent
d4136002c1
commit
1608faf199
@ -126,7 +126,7 @@ type Wallet interface {
|
||||
// PsbtFundingVerify looks up a previously registered funding intent by
|
||||
// its pending channel ID and tries to advance the state machine by
|
||||
// verifying the passed PSBT.
|
||||
PsbtFundingVerify([32]byte, *psbt.Packet) error
|
||||
PsbtFundingVerify([32]byte, *psbt.Packet, bool) error
|
||||
|
||||
// PsbtFundingFinalize looks up a previously registered funding intent
|
||||
// by its pending channel ID and tries to advance the state machine by
|
||||
@ -355,7 +355,7 @@ func (b *Batcher) BatchFund(ctx context.Context,
|
||||
// each of the channels.
|
||||
for _, channel := range b.channels {
|
||||
err = b.cfg.Wallet.PsbtFundingVerify(
|
||||
channel.pendingChanID, unsignedPacket,
|
||||
channel.pendingChanID, unsignedPacket, false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error verifying PSBT: %v", err)
|
||||
|
@ -233,7 +233,7 @@ func (h *testHarness) ReleaseOutput(_ context.Context,
|
||||
return &walletrpc.ReleaseOutputResponse{}, nil
|
||||
}
|
||||
|
||||
func (h *testHarness) PsbtFundingVerify([32]byte, *psbt.Packet) error {
|
||||
func (h *testHarness) PsbtFundingVerify([32]byte, *psbt.Packet, bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2132,6 +2132,20 @@ message FundingPsbtVerify {
|
||||
|
||||
// The pending channel ID of the channel to get the PSBT for.
|
||||
bytes pending_chan_id = 2;
|
||||
|
||||
/*
|
||||
Can only be used if the no_publish flag was set to true in the OpenChannel
|
||||
call meaning that the caller is solely responsible for publishing the final
|
||||
funding transaction. If skip_finalize is set to true then lnd will not wait
|
||||
for a FundingPsbtFinalize state step and instead assumes that a transaction
|
||||
with the same TXID as the passed in PSBT will eventually confirm.
|
||||
IT IS ABSOLUTELY IMPERATIVE that the TXID of the transaction that is
|
||||
eventually published does have the _same TXID_ as the verified PSBT. That
|
||||
means no inputs or outputs can change, only signatures can be added. If the
|
||||
TXID changes between this call and the publish step then the channel will
|
||||
never be created and the funds will be in limbo.
|
||||
*/
|
||||
bool skip_finalize = 3;
|
||||
}
|
||||
|
||||
message FundingPsbtFinalize {
|
||||
|
@ -4175,6 +4175,10 @@
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The pending channel ID of the channel to get the PSBT for."
|
||||
},
|
||||
"skip_finalize": {
|
||||
"type": "boolean",
|
||||
"description": "Can only be used if the no_publish flag was set to true in the OpenChannel\ncall meaning that the caller is solely responsible for publishing the final\nfunding transaction. If skip_finalize is set to true then lnd will not wait\nfor a FundingPsbtFinalize state step and instead assumes that a transaction\nwith the same TXID as the passed in PSBT will eventually confirm.\nIT IS ABSOLUTELY IMPERATIVE that the TXID of the transaction that is\neventually published does have the _same TXID_ as the verified PSBT. That\nmeans no inputs or outputs can change, only signatures can be added. If the\nTXID changes between this call and the publish step then the channel will\nnever be created and the funds will be in limbo."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -213,7 +213,7 @@ func (i *PsbtIntent) FundingParams() (btcutil.Address, int64, *psbt.Packet,
|
||||
// Verify makes sure the PSBT that is given to the intent has an output that
|
||||
// sends to the channel funding multisig address with the correct amount. A
|
||||
// simple check that at least a single input has been specified is performed.
|
||||
func (i *PsbtIntent) Verify(packet *psbt.Packet) error {
|
||||
func (i *PsbtIntent) Verify(packet *psbt.Packet, skipFinalize bool) error {
|
||||
if packet == nil {
|
||||
return fmt.Errorf("PSBT is nil")
|
||||
}
|
||||
@ -264,7 +264,22 @@ func (i *PsbtIntent) Verify(packet *psbt.Packet) error {
|
||||
"malleability: %v", err)
|
||||
}
|
||||
|
||||
// In case we aren't going to publish any transaction, we now have
|
||||
// everything we need and can skip the Finalize step.
|
||||
i.PendingPsbt = packet
|
||||
if !i.shouldPublish && skipFinalize {
|
||||
i.FinalTX = packet.UnsignedTx
|
||||
i.State = PsbtFinalized
|
||||
|
||||
// Signal the funding manager that it can now continue with its
|
||||
// funding flow as the PSBT is now complete .
|
||||
i.signalPsbtReady.Do(func() {
|
||||
close(i.PsbtReady)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
i.State = PsbtVerified
|
||||
return nil
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ func TestPsbtIntent(t *testing.T) {
|
||||
}
|
||||
|
||||
// Verify the dummy PSBT with the intent.
|
||||
err = psbtIntent.Verify(pendingPsbt)
|
||||
err = psbtIntent.Verify(pendingPsbt, false)
|
||||
if err != nil {
|
||||
t.Fatalf("error verifying pending PSBT: %v", err)
|
||||
}
|
||||
@ -271,61 +271,68 @@ func TestPsbtVerify(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
expectedErr string
|
||||
doVerify func(int64, *psbt.Packet, *PsbtIntent) error
|
||||
name string
|
||||
expectedErr string
|
||||
shouldPublish bool
|
||||
doVerify func(int64, *psbt.Packet, *PsbtIntent) error
|
||||
}{
|
||||
{
|
||||
name: "nil packet",
|
||||
expectedErr: "PSBT is nil",
|
||||
name: "nil packet",
|
||||
expectedErr: "PSBT is nil",
|
||||
shouldPublish: true,
|
||||
doVerify: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
return i.Verify(nil)
|
||||
return i.Verify(nil, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wrong state",
|
||||
name: "wrong state",
|
||||
shouldPublish: true,
|
||||
expectedErr: "invalid state. got user_canceled " +
|
||||
"expected output_known",
|
||||
doVerify: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
i.State = PsbtInitiatorCanceled
|
||||
return i.Verify(p)
|
||||
return i.Verify(p, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "output not found, value wrong",
|
||||
expectedErr: "funding output not found in PSBT",
|
||||
name: "output not found, value wrong",
|
||||
shouldPublish: true,
|
||||
expectedErr: "funding output not found in PSBT",
|
||||
doVerify: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
p.UnsignedTx.TxOut[0].Value = 123
|
||||
return i.Verify(p)
|
||||
return i.Verify(p, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "output not found, pk script wrong",
|
||||
expectedErr: "funding output not found in PSBT",
|
||||
name: "output not found, pk script wrong",
|
||||
shouldPublish: true,
|
||||
expectedErr: "funding output not found in PSBT",
|
||||
doVerify: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
p.UnsignedTx.TxOut[0].PkScript = []byte{1, 2, 3}
|
||||
return i.Verify(p)
|
||||
return i.Verify(p, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no inputs",
|
||||
expectedErr: "PSBT has no inputs",
|
||||
name: "no inputs",
|
||||
shouldPublish: true,
|
||||
expectedErr: "PSBT has no inputs",
|
||||
doVerify: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
return i.Verify(p)
|
||||
return i.Verify(p, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "input(s) too small",
|
||||
name: "input(s) too small",
|
||||
shouldPublish: true,
|
||||
expectedErr: "input amount sum must be larger than " +
|
||||
"output amount sum",
|
||||
doVerify: func(amt int64, p *psbt.Packet,
|
||||
@ -337,11 +344,12 @@ func TestPsbtVerify(t *testing.T) {
|
||||
Value: int64(chanCapacity),
|
||||
},
|
||||
}}
|
||||
return i.Verify(p)
|
||||
return i.Verify(p, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing witness-utxo field",
|
||||
name: "missing witness-utxo field",
|
||||
shouldPublish: true,
|
||||
expectedErr: "cannot use TX for channel funding, not " +
|
||||
"all inputs are SegWit spends, risk of " +
|
||||
"malleability: input 1 is non-SegWit spend " +
|
||||
@ -370,13 +378,66 @@ func TestPsbtVerify(t *testing.T) {
|
||||
txOut,
|
||||
},
|
||||
},
|
||||
}}
|
||||
return i.Verify(p)
|
||||
},
|
||||
}
|
||||
return i.Verify(p, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "input correct",
|
||||
expectedErr: "",
|
||||
name: "skip verify",
|
||||
shouldPublish: false,
|
||||
expectedErr: "",
|
||||
doVerify: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
txOut := &wire.TxOut{
|
||||
Value: int64(chanCapacity/2) + 1,
|
||||
}
|
||||
p.UnsignedTx.TxIn = []*wire.TxIn{
|
||||
{},
|
||||
{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
p.Inputs = []psbt.PInput{
|
||||
{
|
||||
WitnessUtxo: txOut,
|
||||
},
|
||||
{
|
||||
WitnessUtxo: txOut,
|
||||
},
|
||||
}
|
||||
|
||||
if err := i.Verify(p, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if i.FinalTX != p.UnsignedTx {
|
||||
return fmt.Errorf("expected final TX " +
|
||||
"to be set")
|
||||
}
|
||||
if i.State != PsbtFinalized {
|
||||
return fmt.Errorf("expected state to " +
|
||||
"be finalized")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-i.PsbtReady:
|
||||
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
return fmt.Errorf("expected PSBT " +
|
||||
"ready to be signaled")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "input correct",
|
||||
shouldPublish: true,
|
||||
expectedErr: "",
|
||||
doVerify: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
@ -398,7 +459,7 @@ func TestPsbtVerify(t *testing.T) {
|
||||
{
|
||||
WitnessUtxo: txOut,
|
||||
}}
|
||||
return i.Verify(p)
|
||||
return i.Verify(p, false)
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -425,6 +486,7 @@ func TestPsbtVerify(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Reset the state from a previous test and create a new
|
||||
// pending PSBT that we can manipulate.
|
||||
psbtIntent.shouldPublish = tc.shouldPublish
|
||||
psbtIntent.State = PsbtOutputKnown
|
||||
_, amt, pendingPsbt, err := psbtIntent.FundingParams()
|
||||
if err != nil {
|
||||
@ -613,7 +675,7 @@ func TestPsbtFinalize(t *testing.T) {
|
||||
},
|
||||
FinalScriptWitness: []byte{0x01, 0x00},
|
||||
}}
|
||||
err = psbtIntent.Verify(pendingPsbt)
|
||||
err = psbtIntent.Verify(pendingPsbt, false)
|
||||
if err != nil {
|
||||
t.Fatalf("error verifying PSBT: %v", err)
|
||||
}
|
||||
|
@ -573,7 +573,7 @@ func (l *LightningWallet) RegisterFundingIntent(expectedID [32]byte,
|
||||
// pending channel ID and tries to advance the state machine by verifying the
|
||||
// passed PSBT.
|
||||
func (l *LightningWallet) PsbtFundingVerify(pendingChanID [32]byte,
|
||||
packet *psbt.Packet) error {
|
||||
packet *psbt.Packet, skipFinalize bool) error {
|
||||
|
||||
l.intentMtx.Lock()
|
||||
defer l.intentMtx.Unlock()
|
||||
@ -587,7 +587,13 @@ func (l *LightningWallet) PsbtFundingVerify(pendingChanID [32]byte,
|
||||
if !ok {
|
||||
return fmt.Errorf("incompatible funding intent")
|
||||
}
|
||||
err := psbtIntent.Verify(packet)
|
||||
|
||||
if skipFinalize && psbtIntent.ShouldPublishFundingTX() {
|
||||
return fmt.Errorf("cannot set skip_finalize for channel that " +
|
||||
"did not set no_publish")
|
||||
}
|
||||
|
||||
err := psbtIntent.Verify(packet, skipFinalize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error verifying PSBT: %v", err)
|
||||
}
|
||||
|
@ -7135,7 +7135,7 @@ func (r *rpcServer) FundingStateStep(ctx context.Context,
|
||||
}
|
||||
|
||||
err = r.server.cc.Wallet.PsbtFundingVerify(
|
||||
pendingChanID, packet,
|
||||
pendingChanID, packet, in.GetPsbtVerify().SkipFinalize,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
Loading…
Reference in New Issue
Block a user