mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-04 09:48:19 +01:00
lntemp: expand package to support testBasicChannelFunding
This commit adds more supporting methods to support the test `testBasicChannelFunding`.
This commit is contained in:
parent
85210b947f
commit
19981ac9bd
5 changed files with 359 additions and 4 deletions
|
@ -2,6 +2,7 @@ package lntemp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -20,6 +21,12 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// defaultMinerFeeRate specifies the fee rate in sats when sending
|
||||||
|
// outputs from the miner.
|
||||||
|
defaultMinerFeeRate = 7500
|
||||||
|
)
|
||||||
|
|
||||||
// TestCase defines a test case that's been used in the integration test.
|
// TestCase defines a test case that's been used in the integration test.
|
||||||
type TestCase struct {
|
type TestCase struct {
|
||||||
// Name specifies the test name.
|
// Name specifies the test name.
|
||||||
|
@ -179,10 +186,7 @@ func (h *HarnessTest) SetupStandbyNodes() {
|
||||||
PkScript: addrScript,
|
PkScript: addrScript,
|
||||||
Value: 10 * btcutil.SatoshiPerBitcoin,
|
Value: 10 * btcutil.SatoshiPerBitcoin,
|
||||||
}
|
}
|
||||||
_, err = h.Miner.SendOutputs(
|
h.Miner.SendOutput(output, 7500)
|
||||||
[]*wire.TxOut{output}, 7500,
|
|
||||||
)
|
|
||||||
require.NoError(h, err, "send output failed")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -676,3 +680,81 @@ func (h *HarnessTest) CloseChannel(hn *node.HarnessNode,
|
||||||
|
|
||||||
return h.assertChannelClosed(hn, cp, false, stream)
|
return h.assertChannelClosed(hn, cp, false, stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNeutrinoBackend returns a bool indicating whether the node is using a
|
||||||
|
// neutrino as its backend. This is useful when we want to skip certain tests
|
||||||
|
// which cannot be done with a neutrino backend.
|
||||||
|
func (h *HarnessTest) IsNeutrinoBackend() bool {
|
||||||
|
return h.manager.chainBackend.Name() == NeutrinoBackendName
|
||||||
|
}
|
||||||
|
|
||||||
|
// fundCoins attempts to send amt satoshis from the internal mining node to the
|
||||||
|
// targeted lightning node. The confirmed boolean indicates whether the
|
||||||
|
// transaction that pays to the target should confirm. For neutrino backend,
|
||||||
|
// the `confirmed` param is ignored.
|
||||||
|
func (h *HarnessTest) fundCoins(amt btcutil.Amount, target *node.HarnessNode,
|
||||||
|
addrType lnrpc.AddressType, confirmed bool) {
|
||||||
|
|
||||||
|
initialBalance := target.RPC.WalletBalance()
|
||||||
|
|
||||||
|
// First, obtain an address from the target lightning node, preferring
|
||||||
|
// to receive a p2wkh address s.t the output can immediately be used as
|
||||||
|
// an input to a funding transaction.
|
||||||
|
req := &lnrpc.NewAddressRequest{Type: addrType}
|
||||||
|
resp := target.RPC.NewAddress(req)
|
||||||
|
addr := h.DecodeAddress(resp.Address)
|
||||||
|
addrScript := h.PayToAddrScript(addr)
|
||||||
|
|
||||||
|
// Generate a transaction which creates an output to the target
|
||||||
|
// pkScript of the desired amount.
|
||||||
|
output := &wire.TxOut{
|
||||||
|
PkScript: addrScript,
|
||||||
|
Value: int64(amt),
|
||||||
|
}
|
||||||
|
h.Miner.SendOutput(output, defaultMinerFeeRate)
|
||||||
|
|
||||||
|
// Encode the pkScript in hex as this the format that it will be
|
||||||
|
// returned via rpc.
|
||||||
|
expPkScriptStr := hex.EncodeToString(addrScript)
|
||||||
|
|
||||||
|
// Now, wait for ListUnspent to show the unconfirmed transaction
|
||||||
|
// containing the correct pkscript.
|
||||||
|
//
|
||||||
|
// Since neutrino doesn't support unconfirmed outputs, skip this check.
|
||||||
|
if !h.IsNeutrinoBackend() {
|
||||||
|
utxos := h.AssertNumUTXOsUnconfirmed(target, 1)
|
||||||
|
|
||||||
|
// Assert that the lone unconfirmed utxo contains the same
|
||||||
|
// pkscript as the output generated above.
|
||||||
|
pkScriptStr := utxos[0].PkScript
|
||||||
|
require.Equal(h, pkScriptStr, expPkScriptStr,
|
||||||
|
"pkscript mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the transaction should remain unconfirmed, then we'll wait until
|
||||||
|
// the target node's unconfirmed balance reflects the expected balance
|
||||||
|
// and exit.
|
||||||
|
if !confirmed && !h.IsNeutrinoBackend() {
|
||||||
|
expectedBalance := btcutil.Amount(
|
||||||
|
initialBalance.UnconfirmedBalance,
|
||||||
|
) + amt
|
||||||
|
h.WaitForBalanceUnconfirmed(target, expectedBalance)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we'll generate 2 new blocks to ensure the output gains a
|
||||||
|
// sufficient number of confirmations and wait for the balance to
|
||||||
|
// reflect what's expected.
|
||||||
|
h.Miner.MineBlocks(2)
|
||||||
|
|
||||||
|
expectedBalance := btcutil.Amount(initialBalance.ConfirmedBalance) + amt
|
||||||
|
h.WaitForBalanceConfirmed(target, expectedBalance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FundCoins attempts to send amt satoshis from the internal mining node to the
|
||||||
|
// targeted lightning node using a P2WKH address. 2 blocks are mined after in
|
||||||
|
// order to confirm the transaction.
|
||||||
|
func (h *HarnessTest) FundCoins(amt btcutil.Amount, hn *node.HarnessNode) {
|
||||||
|
h.fundCoins(amt, hn, lnrpc.AddressType_WITNESS_PUBKEY_HASH, true)
|
||||||
|
}
|
||||||
|
|
|
@ -2,17 +2,24 @@ package lntemp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntemp/node"
|
"github.com/lightningnetwork/lnd/lntemp/node"
|
||||||
"github.com/lightningnetwork/lnd/lntemp/rpc"
|
"github.com/lightningnetwork/lnd/lntemp/rpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WaitForBlockchainSync waits until the node is synced to chain.
|
// WaitForBlockchainSync waits until the node is synced to chain.
|
||||||
|
@ -498,3 +505,185 @@ func (h *HarnessTest) WaitForGraphSync(hn *node.HarnessNode) {
|
||||||
}, DefaultTimeout)
|
}, DefaultTimeout)
|
||||||
require.NoError(h, err, "%s: timeout while sync to graph", hn.Name())
|
require.NoError(h, err, "%s: timeout while sync to graph", hn.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssertNumUTXOsWithConf waits for the given number of UTXOs with the
|
||||||
|
// specified confirmations range to be available or fails if that isn't the
|
||||||
|
// case before the default timeout.
|
||||||
|
//
|
||||||
|
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
|
||||||
|
// previous state of the node's UTXOs. The previous state is snapshotted when
|
||||||
|
// finishing a previous test case via the cleanup function in `Subtest`. In
|
||||||
|
// other words, this assertion only checks the new changes made in the current
|
||||||
|
// test.
|
||||||
|
func (h *HarnessTest) AssertNumUTXOsWithConf(hn *node.HarnessNode,
|
||||||
|
expectedUtxos int, max, min int32) []*lnrpc.Utxo {
|
||||||
|
|
||||||
|
var unconfirmed bool
|
||||||
|
|
||||||
|
old := hn.State.UTXO.Confirmed
|
||||||
|
if max == 0 {
|
||||||
|
old = hn.State.UTXO.Unconfirmed
|
||||||
|
unconfirmed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var utxos []*lnrpc.Utxo
|
||||||
|
err := wait.NoError(func() error {
|
||||||
|
req := &walletrpc.ListUnspentRequest{
|
||||||
|
Account: "",
|
||||||
|
MaxConfs: max,
|
||||||
|
MinConfs: min,
|
||||||
|
UnconfirmedOnly: unconfirmed,
|
||||||
|
}
|
||||||
|
resp := hn.RPC.ListUnspent(req)
|
||||||
|
total := len(resp.Utxos)
|
||||||
|
|
||||||
|
if total-old == expectedUtxos {
|
||||||
|
utxos = resp.Utxos[old:]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errNumNotMatched(hn.Name(), "num of UTXOs",
|
||||||
|
expectedUtxos, total-old, total, old)
|
||||||
|
}, DefaultTimeout)
|
||||||
|
require.NoError(h, err, "timeout waiting for UTXOs")
|
||||||
|
|
||||||
|
return utxos
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertNumUTXOsUnconfirmed asserts the expected num of unconfirmed utxos are
|
||||||
|
// seen.
|
||||||
|
//
|
||||||
|
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
|
||||||
|
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
|
||||||
|
// details.
|
||||||
|
func (h *HarnessTest) AssertNumUTXOsUnconfirmed(hn *node.HarnessNode,
|
||||||
|
num int) []*lnrpc.Utxo {
|
||||||
|
|
||||||
|
return h.AssertNumUTXOsWithConf(hn, num, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertNumUTXOsConfirmed asserts the expected num of confirmed utxos are
|
||||||
|
// seen, which means the returned utxos have at least one confirmation.
|
||||||
|
//
|
||||||
|
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
|
||||||
|
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
|
||||||
|
// details.
|
||||||
|
func (h *HarnessTest) AssertNumUTXOsConfirmed(hn *node.HarnessNode,
|
||||||
|
num int) []*lnrpc.Utxo {
|
||||||
|
|
||||||
|
return h.AssertNumUTXOsWithConf(hn, num, math.MaxInt32, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertNumUTXOs asserts the expected num of utxos are seen, including
|
||||||
|
// confirmed and unconfirmed outputs.
|
||||||
|
//
|
||||||
|
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
|
||||||
|
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
|
||||||
|
// details.
|
||||||
|
func (h *HarnessTest) AssertNumUTXOs(hn *node.HarnessNode,
|
||||||
|
num int) []*lnrpc.Utxo {
|
||||||
|
|
||||||
|
return h.AssertNumUTXOsWithConf(hn, num, math.MaxInt32, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForBalanceConfirmed waits until the node sees the expected confirmed
|
||||||
|
// balance in its wallet.
|
||||||
|
func (h *HarnessTest) WaitForBalanceConfirmed(hn *node.HarnessNode,
|
||||||
|
expected btcutil.Amount) {
|
||||||
|
|
||||||
|
var lastBalance btcutil.Amount
|
||||||
|
err := wait.NoError(func() error {
|
||||||
|
resp := hn.RPC.WalletBalance()
|
||||||
|
|
||||||
|
lastBalance = btcutil.Amount(resp.ConfirmedBalance)
|
||||||
|
if lastBalance == expected {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("expected %v, only have %v", expected,
|
||||||
|
lastBalance)
|
||||||
|
}, DefaultTimeout)
|
||||||
|
|
||||||
|
require.NoError(h, err, "timeout waiting for confirmed balances")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForBalanceUnconfirmed waits until the node sees the expected unconfirmed
|
||||||
|
// balance in its wallet.
|
||||||
|
func (h *HarnessTest) WaitForBalanceUnconfirmed(hn *node.HarnessNode,
|
||||||
|
expected btcutil.Amount) {
|
||||||
|
|
||||||
|
var lastBalance btcutil.Amount
|
||||||
|
err := wait.NoError(func() error {
|
||||||
|
resp := hn.RPC.WalletBalance()
|
||||||
|
|
||||||
|
lastBalance = btcutil.Amount(resp.UnconfirmedBalance)
|
||||||
|
if lastBalance == expected {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("expected %v, only have %v", expected,
|
||||||
|
lastBalance)
|
||||||
|
}, DefaultTimeout)
|
||||||
|
|
||||||
|
require.NoError(h, err, "timeout waiting for unconfirmed balances")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random32Bytes generates a random 32 bytes which can be used as a pay hash,
|
||||||
|
// preimage, etc.
|
||||||
|
func (h *HarnessTest) Random32Bytes() []byte {
|
||||||
|
randBuf := make([]byte, lntypes.HashSize)
|
||||||
|
|
||||||
|
_, err := rand.Read(randBuf)
|
||||||
|
require.NoErrorf(h, err, "internal error, cannot generate random bytes")
|
||||||
|
|
||||||
|
return randBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeAddress decodes a given address and asserts there's no error.
|
||||||
|
func (h *HarnessTest) DecodeAddress(addr string) btcutil.Address {
|
||||||
|
resp, err := btcutil.DecodeAddress(addr, harnessNetParams)
|
||||||
|
require.NoError(h, err, "DecodeAddress failed")
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// PayToAddrScript creates a new script from the given address and asserts
|
||||||
|
// there's no error.
|
||||||
|
func (h *HarnessTest) PayToAddrScript(addr btcutil.Address) []byte {
|
||||||
|
addrScript, err := txscript.PayToAddrScript(addr)
|
||||||
|
require.NoError(h, err, "PayToAddrScript failed")
|
||||||
|
|
||||||
|
return addrScript
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertChannelBalanceResp makes a ChannelBalance request and checks the
|
||||||
|
// returned response matches the expected.
|
||||||
|
func (h *HarnessTest) AssertChannelBalanceResp(hn *node.HarnessNode,
|
||||||
|
expected *lnrpc.ChannelBalanceResponse) {
|
||||||
|
|
||||||
|
resp := hn.RPC.ChannelBalance()
|
||||||
|
require.True(h, proto.Equal(expected, resp), "balance is incorrect "+
|
||||||
|
"got: %v, want: %v", resp, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChannelByChanPoint tries to find a channel matching the channel point and
|
||||||
|
// asserts. It returns the channel found.
|
||||||
|
func (h *HarnessTest) GetChannelByChanPoint(hn *node.HarnessNode,
|
||||||
|
chanPoint *lnrpc.ChannelPoint) *lnrpc.Channel {
|
||||||
|
|
||||||
|
channel, err := h.findChannel(hn, chanPoint)
|
||||||
|
require.NoErrorf(h, err, "channel not found using %v", chanPoint)
|
||||||
|
|
||||||
|
return channel
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChannelCommitType retrieves the active channel commitment type for the
|
||||||
|
// given chan point.
|
||||||
|
func (h *HarnessTest) GetChannelCommitType(hn *node.HarnessNode,
|
||||||
|
chanPoint *lnrpc.ChannelPoint) lnrpc.CommitmentType {
|
||||||
|
|
||||||
|
c := h.GetChannelByChanPoint(hn, chanPoint)
|
||||||
|
|
||||||
|
return c.CommitmentType
|
||||||
|
}
|
||||||
|
|
|
@ -266,3 +266,38 @@ func (h *HarnessMiner) AssertTxInMempool(txid *chainhash.Hash) *wire.MsgTx {
|
||||||
require.NoError(h, err, "timeout checking mempool")
|
require.NoError(h, err, "timeout checking mempool")
|
||||||
return msgTx
|
return msgTx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendOutputsWithoutChange uses the miner to send the given outputs using the
|
||||||
|
// specified fee rate and returns the txid.
|
||||||
|
func (h *HarnessMiner) SendOutputsWithoutChange(outputs []*wire.TxOut,
|
||||||
|
feeRate btcutil.Amount) *chainhash.Hash {
|
||||||
|
|
||||||
|
txid, err := h.Harness.SendOutputsWithoutChange(
|
||||||
|
outputs, feeRate,
|
||||||
|
)
|
||||||
|
require.NoErrorf(h, err, "failed to send output")
|
||||||
|
|
||||||
|
return txid
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTransaction uses the miner to create a transaction using the given
|
||||||
|
// outputs using the specified fee rate and returns the transaction.
|
||||||
|
func (h *HarnessMiner) CreateTransaction(outputs []*wire.TxOut,
|
||||||
|
feeRate btcutil.Amount) *wire.MsgTx {
|
||||||
|
|
||||||
|
tx, err := h.Harness.CreateTransaction(outputs, feeRate, false)
|
||||||
|
require.NoErrorf(h, err, "failed to create transaction")
|
||||||
|
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendOutput creates, signs, and finally broadcasts a transaction spending
|
||||||
|
// the harness' available mature coinbase outputs to create the new output.
|
||||||
|
func (h *HarnessMiner) SendOutput(newOutput *wire.TxOut,
|
||||||
|
feeRate btcutil.Amount) *chainhash.Hash {
|
||||||
|
|
||||||
|
hash, err := h.Harness.SendOutputs([]*wire.TxOut{newOutput}, feeRate)
|
||||||
|
require.NoErrorf(h, err, "failed to send outputs")
|
||||||
|
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
|
@ -214,3 +215,26 @@ func (h *HarnessRPC) CloseChannel(
|
||||||
|
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FundingStateStep makes a RPC call to FundingStateStep and asserts.
|
||||||
|
func (h *HarnessRPC) FundingStateStep(
|
||||||
|
msg *lnrpc.FundingTransitionMsg) *lnrpc.FundingStateStepResp {
|
||||||
|
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := h.LN.FundingStateStep(ctxt, msg)
|
||||||
|
h.NoError(err, "FundingStateStep")
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// FundingStateStepAssertErr makes a RPC call to FundingStateStep and asserts
|
||||||
|
// there's an error.
|
||||||
|
func (h *HarnessRPC) FundingStateStepAssertErr(m *lnrpc.FundingTransitionMsg) {
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := h.LN.FundingStateStep(ctxt, m)
|
||||||
|
require.Error(h, err, "expected an error from FundingStateStep")
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package rpc
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,3 +23,27 @@ func (h *HarnessRPC) ListUnspent(
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeriveKey makes a RPC call to the DeriveKey and asserts.
|
||||||
|
func (h *HarnessRPC) DeriveKey(kl *signrpc.KeyLocator) *signrpc.KeyDescriptor {
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
key, err := h.WalletKit.DeriveKey(ctxt, kl)
|
||||||
|
h.NoError(err, "DeriveKey")
|
||||||
|
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendOutputs makes a RPC call to the node's WalletKitClient and asserts.
|
||||||
|
func (h *HarnessRPC) SendOutputs(
|
||||||
|
req *walletrpc.SendOutputsRequest) *walletrpc.SendOutputsResponse {
|
||||||
|
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := h.WalletKit.SendOutputs(ctxt, req)
|
||||||
|
h.NoError(err, "SendOutputs")
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue