chanfunding: add PSBT assembler and intent
We add a new funding assembler and intent type that handle channel funding through the use of a PSBT. The PsbtIntent is in itself a simple state machine that can be stepped through the process of assembling the required information for the funding output, verifying a user supplied PSBT for correctness, accepting a fully signed PSBT and then assembling the funding wire message.
This commit is contained in:
3 files changed
+1102
-1
No files matched your search
@@ -0,0 +1,577 @@
|
||||
package chanfunding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/psbt"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
var (
|
||||
localPrivkey = []byte{1, 2, 3, 4, 5, 6}
|
||||
remotePrivkey = []byte{6, 5, 4, 3, 2, 1}
|
||||
chanCapacity btcutil.Amount = 644000
|
||||
params = chaincfg.RegressionNetParams
|
||||
defaultTimeout = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
// TestPsbtIntent tests the basic happy path of the PSBT assembler and intent.
|
||||
func TestPsbtIntent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a simple assembler and ask it to provision a channel to get
|
||||
// the funding intent.
|
||||
a := NewPsbtAssembler(chanCapacity, nil, ¶ms)
|
||||
intent, err := a.ProvisionChannel(&Request{LocalAmt: chanCapacity})
|
||||
if err != nil {
|
||||
t.Fatalf("error provisioning channel: %v", err)
|
||||
}
|
||||
psbtIntent, ok := intent.(*PsbtIntent)
|
||||
if !ok {
|
||||
t.Fatalf("intent was not a PsbtIntent")
|
||||
}
|
||||
if psbtIntent.State != PsbtShimRegistered {
|
||||
t.Fatalf("unexpected state. got %d wanted %d", psbtIntent.State,
|
||||
PsbtShimRegistered)
|
||||
}
|
||||
|
||||
// The first step with the intent is that the funding manager starts
|
||||
// negotiating with the remote peer and they accept. By accepting, they
|
||||
// send over their multisig key that's going to be used for the funding
|
||||
// output. With that known, we can start crafting a PSBT.
|
||||
_, localPubkey := btcec.PrivKeyFromBytes(btcec.S256(), localPrivkey)
|
||||
_, remotePubkey := btcec.PrivKeyFromBytes(btcec.S256(), remotePrivkey)
|
||||
psbtIntent.BindKeys(
|
||||
&keychain.KeyDescriptor{PubKey: localPubkey}, remotePubkey,
|
||||
)
|
||||
if psbtIntent.State != PsbtOutputKnown {
|
||||
t.Fatalf("unexpected state. got %d wanted %d", psbtIntent.State,
|
||||
PsbtOutputKnown)
|
||||
}
|
||||
|
||||
// Make sure the output script address is correct.
|
||||
script, _, err := input.GenFundingPkScript(
|
||||
localPubkey.SerializeCompressed(),
|
||||
remotePubkey.SerializeCompressed(), int64(chanCapacity),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("error calculating script: %v", err)
|
||||
}
|
||||
witnessScriptHash := sha256.Sum256(script)
|
||||
addr, err := btcutil.NewAddressWitnessScriptHash(
|
||||
witnessScriptHash[:], ¶ms,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to encode address: %v", err)
|
||||
}
|
||||
fundingAddr, amt, pendingPsbt, err := psbtIntent.FundingParams()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get funding params: %v", err)
|
||||
}
|
||||
if addr.EncodeAddress() != fundingAddr.EncodeAddress() {
|
||||
t.Fatalf("unexpected address. got %s wanted %s", fundingAddr,
|
||||
addr)
|
||||
}
|
||||
if amt != int64(chanCapacity) {
|
||||
t.Fatalf("unexpected amount. got %d wanted %d", amt,
|
||||
chanCapacity)
|
||||
}
|
||||
|
||||
// Parse and check the returned PSBT packet.
|
||||
if pendingPsbt == nil {
|
||||
t.Fatalf("expected pending PSBT to be returned")
|
||||
}
|
||||
if len(pendingPsbt.UnsignedTx.TxOut) != 1 {
|
||||
t.Fatalf("unexpected number of outputs. got %d wanted %d",
|
||||
len(pendingPsbt.UnsignedTx.TxOut), 1)
|
||||
}
|
||||
txOut := pendingPsbt.UnsignedTx.TxOut[0]
|
||||
if !bytes.Equal(txOut.PkScript[2:], witnessScriptHash[:]) {
|
||||
t.Fatalf("unexpected PK script in output. got %x wanted %x",
|
||||
txOut.PkScript[2:], witnessScriptHash)
|
||||
}
|
||||
if txOut.Value != int64(chanCapacity) {
|
||||
t.Fatalf("unexpected value in output. got %d wanted %d",
|
||||
txOut.Value, chanCapacity)
|
||||
}
|
||||
|
||||
// Add an input to the pending TX to simulate it being funded.
|
||||
pendingPsbt.UnsignedTx.TxIn = []*wire.TxIn{
|
||||
{PreviousOutPoint: wire.OutPoint{Index: 0}},
|
||||
}
|
||||
pendingPsbt.Inputs = []psbt.PInput{
|
||||
{WitnessUtxo: &wire.TxOut{Value: int64(chanCapacity + 1)}},
|
||||
}
|
||||
|
||||
// Verify the dummy PSBT with the intent.
|
||||
err = psbtIntent.Verify(pendingPsbt)
|
||||
if err != nil {
|
||||
t.Fatalf("error verifying pending PSBT: %v", err)
|
||||
}
|
||||
if psbtIntent.State != PsbtVerified {
|
||||
t.Fatalf("unexpected state. got %d wanted %d", psbtIntent.State,
|
||||
PsbtVerified)
|
||||
}
|
||||
|
||||
// Add some fake witness data to the transaction so it thinks it's
|
||||
// signed.
|
||||
pendingPsbt.Inputs[0].WitnessUtxo = &wire.TxOut{
|
||||
Value: int64(chanCapacity) * 2,
|
||||
PkScript: []byte{99, 99, 99},
|
||||
}
|
||||
pendingPsbt.Inputs[0].FinalScriptSig = []byte{88, 88, 88}
|
||||
pendingPsbt.Inputs[0].FinalScriptWitness = []byte{2, 0, 0}
|
||||
|
||||
// If we call Finalize, the intent will signal to the funding manager
|
||||
// that it can continue with the funding flow. We want to make sure
|
||||
// the signal arrives.
|
||||
var wg sync.WaitGroup
|
||||
errChan := make(chan error, 1)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
select {
|
||||
case err := <-psbtIntent.PsbtReady:
|
||||
errChan <- err
|
||||
|
||||
case <-time.After(defaultTimeout):
|
||||
errChan <- fmt.Errorf("timed out")
|
||||
}
|
||||
}()
|
||||
err = psbtIntent.Finalize(pendingPsbt)
|
||||
if err != nil {
|
||||
t.Fatalf("error finalizing pending PSBT: %v", err)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// We should have a nil error in our channel now.
|
||||
err = <-errChan
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error after finalize: %v", err)
|
||||
}
|
||||
if psbtIntent.State != PsbtFinalized {
|
||||
t.Fatalf("unexpected state. got %d wanted %d", psbtIntent.State,
|
||||
PsbtFinalized)
|
||||
}
|
||||
|
||||
// Make sure the funding transaction can be compiled.
|
||||
_, err = psbtIntent.CompileFundingTx()
|
||||
if err != nil {
|
||||
t.Fatalf("error compiling funding TX from PSBT: %v", err)
|
||||
}
|
||||
if psbtIntent.State != PsbtFundingTxCompiled {
|
||||
t.Fatalf("unexpected state. got %d wanted %d", psbtIntent.State,
|
||||
PsbtFundingTxCompiled)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPsbtIntentBasePsbt tests that a channel funding output can be appended to
|
||||
// a given base PSBT in the funding flow.
|
||||
func TestPsbtIntentBasePsbt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// First create a dummy PSBT with a single output.
|
||||
pendingPsbt, err := psbt.New(
|
||||
[]*wire.OutPoint{{}}, []*wire.TxOut{
|
||||
{Value: 999, PkScript: []byte{99, 88, 77}},
|
||||
}, 2, 0, []uint32{0},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create dummy PSBT")
|
||||
}
|
||||
|
||||
// Generate the funding multisig keys and the address so we can compare
|
||||
// it to the output of the intent.
|
||||
_, localPubkey := btcec.PrivKeyFromBytes(btcec.S256(), localPrivkey)
|
||||
_, remotePubkey := btcec.PrivKeyFromBytes(btcec.S256(), remotePrivkey)
|
||||
// Make sure the output script address is correct.
|
||||
script, _, err := input.GenFundingPkScript(
|
||||
localPubkey.SerializeCompressed(),
|
||||
remotePubkey.SerializeCompressed(), int64(chanCapacity),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("error calculating script: %v", err)
|
||||
}
|
||||
witnessScriptHash := sha256.Sum256(script)
|
||||
addr, err := btcutil.NewAddressWitnessScriptHash(
|
||||
witnessScriptHash[:], ¶ms,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to encode address: %v", err)
|
||||
}
|
||||
|
||||
// Now as the next step, create a new assembler/intent pair with a base
|
||||
// PSBT to see that we can add an additional output to it.
|
||||
a := NewPsbtAssembler(chanCapacity, pendingPsbt, ¶ms)
|
||||
intent, err := a.ProvisionChannel(&Request{LocalAmt: chanCapacity})
|
||||
if err != nil {
|
||||
t.Fatalf("error provisioning channel: %v", err)
|
||||
}
|
||||
psbtIntent, ok := intent.(*PsbtIntent)
|
||||
if !ok {
|
||||
t.Fatalf("intent was not a PsbtIntent")
|
||||
}
|
||||
psbtIntent.BindKeys(
|
||||
&keychain.KeyDescriptor{PubKey: localPubkey}, remotePubkey,
|
||||
)
|
||||
newAddr, amt, twoOutPsbt, err := psbtIntent.FundingParams()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get funding params: %v", err)
|
||||
}
|
||||
if addr.EncodeAddress() != newAddr.EncodeAddress() {
|
||||
t.Fatalf("unexpected address. got %s wanted %s", newAddr,
|
||||
addr)
|
||||
}
|
||||
if amt != int64(chanCapacity) {
|
||||
t.Fatalf("unexpected amount. got %d wanted %d", amt,
|
||||
chanCapacity)
|
||||
}
|
||||
if len(twoOutPsbt.UnsignedTx.TxOut) != 2 {
|
||||
t.Fatalf("unexpected number of outputs. got %d wanted %d",
|
||||
len(twoOutPsbt.UnsignedTx.TxOut), 2)
|
||||
}
|
||||
if len(twoOutPsbt.UnsignedTx.TxIn) != 1 {
|
||||
t.Fatalf("unexpected number of inputs. got %d wanted %d",
|
||||
len(twoOutPsbt.UnsignedTx.TxIn), 1)
|
||||
}
|
||||
txOld := pendingPsbt.UnsignedTx
|
||||
txNew := twoOutPsbt.UnsignedTx
|
||||
prevoutEqual := reflect.DeepEqual(
|
||||
txOld.TxIn[0].PreviousOutPoint, txNew.TxIn[0].PreviousOutPoint,
|
||||
)
|
||||
if !prevoutEqual {
|
||||
t.Fatalf("inputs changed. got %s wanted %s",
|
||||
spew.Sdump(txOld.TxIn[0].PreviousOutPoint),
|
||||
spew.Sdump(txNew.TxIn[0].PreviousOutPoint))
|
||||
}
|
||||
if !reflect.DeepEqual(txOld.TxOut[0], txNew.TxOut[0]) {
|
||||
t.Fatalf("existing output changed. got %v wanted %v",
|
||||
txOld.TxOut[0], txNew.TxOut[0])
|
||||
}
|
||||
}
|
||||
|
||||
// TestPsbtVerify tests the PSBT verification process more deeply than just
|
||||
// the happy path.
|
||||
func TestPsbtVerify(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
expectedErr string
|
||||
doVerify func(int64, *psbt.Packet, *PsbtIntent) error
|
||||
}{
|
||||
{
|
||||
name: "nil packet",
|
||||
expectedErr: "PSBT is nil",
|
||||
doVerify: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
return i.Verify(nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wrong state",
|
||||
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)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "output not found, value wrong",
|
||||
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)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "output not found, pk script wrong",
|
||||
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)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no inputs",
|
||||
expectedErr: "PSBT has no inputs",
|
||||
doVerify: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
return i.Verify(p)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "input(s) too small",
|
||||
expectedErr: "input amount sum must be larger than " +
|
||||
"output amount sum",
|
||||
doVerify: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
p.UnsignedTx.TxIn = []*wire.TxIn{{}}
|
||||
p.Inputs = []psbt.PInput{{
|
||||
WitnessUtxo: &wire.TxOut{
|
||||
Value: int64(chanCapacity),
|
||||
},
|
||||
}}
|
||||
return i.Verify(p)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "input correct",
|
||||
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,
|
||||
},
|
||||
{
|
||||
NonWitnessUtxo: &wire.MsgTx{
|
||||
TxOut: []*wire.TxOut{
|
||||
txOut,
|
||||
},
|
||||
},
|
||||
}}
|
||||
return i.Verify(p)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create a simple assembler and ask it to provision a channel to get
|
||||
// the funding intent.
|
||||
a := NewPsbtAssembler(chanCapacity, nil, ¶ms)
|
||||
intent, err := a.ProvisionChannel(&Request{LocalAmt: chanCapacity})
|
||||
if err != nil {
|
||||
t.Fatalf("error provisioning channel: %v", err)
|
||||
}
|
||||
psbtIntent := intent.(*PsbtIntent)
|
||||
|
||||
// Bind our test keys to get the funding parameters.
|
||||
_, localPubkey := btcec.PrivKeyFromBytes(btcec.S256(), localPrivkey)
|
||||
_, remotePubkey := btcec.PrivKeyFromBytes(btcec.S256(), remotePrivkey)
|
||||
psbtIntent.BindKeys(
|
||||
&keychain.KeyDescriptor{PubKey: localPubkey}, remotePubkey,
|
||||
)
|
||||
|
||||
// Loop through all our test cases.
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
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.State = PsbtOutputKnown
|
||||
_, amt, pendingPsbt, err := psbtIntent.FundingParams()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get funding params: %v", err)
|
||||
}
|
||||
|
||||
err = tc.doVerify(amt, pendingPsbt, psbtIntent)
|
||||
if err != nil && tc.expectedErr != "" &&
|
||||
err.Error() != tc.expectedErr {
|
||||
|
||||
t.Fatalf("unexpected error, got '%v' wanted "+
|
||||
"'%v'", err, tc.expectedErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPsbtFinalize tests the PSBT finalization process more deeply than just
|
||||
// the happy path.
|
||||
func TestPsbtFinalize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
expectedErr string
|
||||
doFinalize func(int64, *psbt.Packet, *PsbtIntent) error
|
||||
}{
|
||||
{
|
||||
name: "nil packet",
|
||||
expectedErr: "PSBT is nil",
|
||||
doFinalize: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
return i.Finalize(nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wrong state",
|
||||
expectedErr: "invalid state. got user_canceled " +
|
||||
"expected verified",
|
||||
doFinalize: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
i.State = PsbtInitiatorCanceled
|
||||
return i.Finalize(p)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not verified first",
|
||||
expectedErr: "PSBT was not verified first",
|
||||
doFinalize: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
i.State = PsbtVerified
|
||||
i.PendingPsbt = nil
|
||||
return i.Finalize(p)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "output value changed",
|
||||
expectedErr: "outputs differ from verified PSBT: " +
|
||||
"output 0 is different",
|
||||
doFinalize: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
p.UnsignedTx.TxOut[0].Value = 123
|
||||
return i.Finalize(p)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "output pk script changed",
|
||||
expectedErr: "outputs differ from verified PSBT: " +
|
||||
"output 0 is different",
|
||||
doFinalize: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
p.UnsignedTx.TxOut[0].PkScript = []byte{3, 2, 1}
|
||||
return i.Finalize(p)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "input previous outpoint index changed",
|
||||
expectedErr: "inputs differ from verified PSBT: " +
|
||||
"previous outpoint of input 0 is different",
|
||||
doFinalize: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
p.UnsignedTx.TxIn[0].PreviousOutPoint.Index = 0
|
||||
return i.Finalize(p)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "input previous outpoint hash changed",
|
||||
expectedErr: "inputs differ from verified PSBT: " +
|
||||
"previous outpoint of input 0 is different",
|
||||
doFinalize: func(amt int64, p *psbt.Packet,
|
||||
i *PsbtIntent) error {
|
||||
|
||||
prevout := &p.UnsignedTx.TxIn[0].PreviousOutPoint
|
||||
prevout.Hash = chainhash.Hash{77, 88, 99, 11}
|
||||
return i.Finalize(p)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create a simple assembler and ask it to provision a channel to get
|
||||
// the funding intent.
|
||||
a := NewPsbtAssembler(chanCapacity, nil, ¶ms)
|
||||
intent, err := a.ProvisionChannel(&Request{LocalAmt: chanCapacity})
|
||||
if err != nil {
|
||||
t.Fatalf("error provisioning channel: %v", err)
|
||||
}
|
||||
psbtIntent := intent.(*PsbtIntent)
|
||||
|
||||
// Bind our test keys to get the funding parameters.
|
||||
_, localPubkey := btcec.PrivKeyFromBytes(btcec.S256(), localPrivkey)
|
||||
_, remotePubkey := btcec.PrivKeyFromBytes(btcec.S256(), remotePrivkey)
|
||||
psbtIntent.BindKeys(
|
||||
&keychain.KeyDescriptor{PubKey: localPubkey}, remotePubkey,
|
||||
)
|
||||
|
||||
// Loop through all our test cases.
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
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.State = PsbtOutputKnown
|
||||
_, amt, pendingPsbt, err := psbtIntent.FundingParams()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get funding params: %v", err)
|
||||
}
|
||||
|
||||
// We need to have a simulated transaction here that is
|
||||
// fully funded and signed.
|
||||
pendingPsbt.UnsignedTx.TxIn = []*wire.TxIn{{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Index: 1,
|
||||
Hash: chainhash.Hash{1, 2, 3},
|
||||
},
|
||||
}}
|
||||
pendingPsbt.Inputs = []psbt.PInput{{
|
||||
WitnessUtxo: &wire.TxOut{
|
||||
Value: int64(chanCapacity) + 1,
|
||||
PkScript: []byte{1, 2, 3},
|
||||
},
|
||||
FinalScriptWitness: []byte{0x01, 0x00},
|
||||
}}
|
||||
err = psbtIntent.Verify(pendingPsbt)
|
||||
if err != nil {
|
||||
t.Fatalf("error verifying PSBT: %v", err)
|
||||
}
|
||||
|
||||
// Deep clone the PSBT so we don't modify the pending
|
||||
// one that was registered during Verify.
|
||||
pendingPsbt = clonePsbt(t, pendingPsbt)
|
||||
|
||||
err = tc.doFinalize(amt, pendingPsbt, psbtIntent)
|
||||
if (err == nil && tc.expectedErr != "") ||
|
||||
(err != nil && err.Error() != tc.expectedErr) {
|
||||
|
||||
t.Fatalf("unexpected error, got '%v' wanted "+
|
||||
"'%v'", err, tc.expectedErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// clonePsbt creates a clone of a PSBT packet by serializing then de-serializing
|
||||
// it.
|
||||
func clonePsbt(t *testing.T, p *psbt.Packet) *psbt.Packet {
|
||||
var buf bytes.Buffer
|
||||
err := p.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("error serializing PSBT: %v", err)
|
||||
}
|
||||
newPacket, err := psbt.NewFromRawBytes(&buf, false)
|
||||
if err != nil {
|
||||
t.Fatalf("error unserializing PSBT: %v", err)
|
||||
}
|
||||
return newPacket
|
||||
}
|
||||
Reference in new issue
Block a user