diff --git a/fundingmanager.go b/fundingmanager.go index 80fe9297..b2c233e3 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -1,19 +1,18 @@ package main import ( - "fmt" "sync" "sync/atomic" + "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/rt/graph" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" - - "github.com/lightningnetwork/lnd/routing/rt/graph" "google.golang.org/grpc" ) @@ -306,7 +305,7 @@ func (f *fundingManager) processFundingRequest(msg *lnwire.SingleFundingRequest, f.fundingMsgs <- &fundingRequestMsg{msg, peer} } -// handleSingleFundingRequest creates an initial 'ChannelReservation' within +// handleFundingRequest creates an initial 'ChannelReservation' within // the wallet, then responds to the source peer with a single funder response // message progressing the funding workflow. // TODO(roasbeef): add error chan to all, let channelManager handle @@ -413,11 +412,16 @@ func (f *fundingManager) processFundingResponse(msg *lnwire.SingleFundingRespons // outpoint, and a commitment signature to the remote peer. func (f *fundingManager) handleFundingResponse(fmsg *fundingResponseMsg) { msg := fmsg.msg + peerID := fmsg.peer.id + chanID := fmsg.msg.ChannelID sourcePeer := fmsg.peer - f.resMtx.RLock() - resCtx := f.activeReservations[fmsg.peer.id][msg.ChannelID] - f.resMtx.RUnlock() + resCtx, err := f.getReservationCtx(peerID, chanID) + if err != nil { + fndgLog.Warnf("can' find reservation (peerID:%v, chanID:%v)", + peerID, chanID) + return + } fndgLog.Infof("Recv'd fundingResponse for pendingID(%v)", msg.ChannelID) @@ -463,14 +467,14 @@ func (f *fundingManager) handleFundingResponse(fmsg *fundingResponseMsg) { // the peer's readHandler once the channel is open. fmsg.peer.barrierInits <- *outPoint - fndgLog.Infof("Generated ChannelPoint(%v) for pendingID(%v)", - outPoint, msg.ChannelID) + fndgLog.Infof("Generated ChannelPoint(%v) for pendingID(%v)", outPoint, + chanID) revocationKey := resCtx.reservation.OurContribution().RevocationKey obsfucator := resCtx.reservation.StateNumObfuscator() - fundingComplete := lnwire.NewSingleFundingComplete(msg.ChannelID, - outPoint, commitSig, revocationKey, obsfucator) + fundingComplete := lnwire.NewSingleFundingComplete(chanID, outPoint, + commitSig, revocationKey, obsfucator) sourcePeer.queueMsg(fundingComplete, nil) } @@ -485,9 +489,12 @@ func (f *fundingManager) processFundingComplete(msg *lnwire.SingleFundingComplet // processed, a signature is sent to the remote peer allowing it to broadcast // the funding transaction, progressing the workflow into the final stage. func (f *fundingManager) handleFundingComplete(fmsg *fundingCompleteMsg) { - f.resMtx.RLock() - resCtx := f.activeReservations[fmsg.peer.id][fmsg.msg.ChannelID] - f.resMtx.RUnlock() + resCtx, err := f.getReservationCtx(fmsg.peer.id, fmsg.msg.ChannelID) + if err != nil { + fndgLog.Warnf("can' find reservation (peerID:%v, chanID:%v)", + fmsg.peer.id, fmsg.msg.ChannelID) + return + } // The channel initiator has responded with the funding outpoint of the // final funding transaction, as well as a signature for our version of @@ -507,8 +514,8 @@ func (f *fundingManager) handleFundingComplete(fmsg *fundingCompleteMsg) { // With all the necessary data available, attempt to advance the // funding workflow to the next stage. If this succeeds then the // funding transaction will broadcast after our next message. - err := resCtx.reservation.CompleteReservationSingle(revokeKey, - fundingOut, commitSig, obsfucator) + err = resCtx.reservation.CompleteReservationSingle(revokeKey, fundingOut, + commitSig, obsfucator) if err != nil { // TODO(roasbeef): better error logging: peerID, channelID, etc. fndgLog.Errorf("unable to complete single reservation: %v", err) @@ -551,10 +558,14 @@ func (f *fundingManager) processFundingSignComplete(msg *lnwire.SingleFundingSig // proofs of transaction inclusion. func (f *fundingManager) handleFundingSignComplete(fmsg *fundingSignCompleteMsg) { chanID := fmsg.msg.ChannelID + peerID := fmsg.peer.id - f.resMtx.RLock() - resCtx := f.activeReservations[fmsg.peer.id][chanID] - f.resMtx.RUnlock() + resCtx, err := f.getReservationCtx(peerID, chanID) + if err != nil { + fndgLog.Warnf("can' find reservation (peerID:%v, chanID:%v)", + peerID, chanID) + return + } // The remote peer has responded with a signature for our commitment // transaction. We'll verify the signature for validity, then commit @@ -594,12 +605,10 @@ func (f *fundingManager) handleFundingSignComplete(fmsg *fundingSignCompleteMsg) case openChan := <-resCtx.reservation.DispatchChan(): // This reservation is no longer pending as the funding // transaction has been fully confirmed. - f.resMtx.Lock() - delete(f.activeReservations[fmsg.peer.id], chanID) - f.resMtx.Unlock() + f.deleteReservationCtx(peerID, chanID) fndgLog.Infof("ChannelPoint(%v) with peerID(%v) is now active", - fundingPoint, fmsg.peer.id) + fundingPoint, peerID) // Now that the channel is open, we need to notify a // number of parties of this event. @@ -669,9 +678,15 @@ func (f *fundingManager) processFundingOpenProof(msg *lnwire.SingleFundingOpenPr // the initiating node is verified, which if correct, marks the channel as open // to the source peer. func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { - f.resMtx.RLock() - resCtx := f.activeReservations[fmsg.peer.id][fmsg.msg.ChannelID] - f.resMtx.RUnlock() + chanID := fmsg.msg.ChannelID + peerID := fmsg.peer.id + + resCtx, err := f.getReservationCtx(peerID, chanID) + if err != nil { + fndgLog.Warnf("can' find reservation (peerID:%v, chanID:%v)", + peerID, chanID) + return + } // The channel initiator has claimed the channel is now open, so we'll // verify the contained SPV proof for validity. @@ -690,12 +705,10 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { // The reservation has been completed, therefore we can stop tracking // it within our active reservations map. - f.resMtx.Lock() - delete(f.activeReservations[fmsg.peer.id], fmsg.msg.ChannelID) - f.resMtx.Unlock() + f.deleteReservationCtx(peerID, chanID) fndgLog.Infof("FundingOpen: ChannelPoint(%v) with peerID(%v) is now open", - resCtx.reservation.FundingOutpoint, fmsg.peer.id) + resCtx.reservation.FundingOutpoint, peerID) // Notify the L3 routing manager of the newly active channel link. capacity := int64(resCtx.reservation.OurContribution().FundingAmount + @@ -818,37 +831,67 @@ func (f *fundingManager) processErrorGeneric(err *lnwire.ErrorGeneric, // depends on the type of error we should do different clean up steps and // inform user about it. func (f *fundingManager) handleErrorGenericMsg(fmsg *fundingErrorMsg) { - switch fmsg.err.Code { + e := fmsg.err + + switch e.Code { case lnwire.ErrorMaxPendingChannels: peerID := fmsg.peer.id chanID := fmsg.err.PendingChannelID - f.resMtx.RLock() - resCtx, ok := f.activeReservations[peerID][chanID] - f.resMtx.RUnlock() - - if !ok { - fndgLog.Warnf("ErrorGeneric error was returned from " + - "remote peer for unknown channel (id: %v)") + if ctx, err := f.cancelReservationCtx(peerID, chanID); err != nil { + fndgLog.Warnf("unable to delete reservation: %v", err) + return + } else { + ctx.err <- grpc.Errorf(e.Code.ToGrpcCode(), e.Problem) return } - if err := resCtx.reservation.Cancel(); err != nil { - resCtx.err <- fmt.Errorf("max pending channels "+ - "exceeded -- unable to cancel reservation: %v", - err) - } else { - resCtx.err <- grpc.Errorf(ErrorMaxPendingChannels, - "unable to create channel, max number of "+ - "pending channels exceeded.") - } - - // TODO(roasbeef): possibly cancel funding barrier in peer's - // channelManager? - f.resMtx.Lock() - delete(f.activeReservations[peerID], chanID) - f.resMtx.Unlock() default: - fndgLog.Warnf("unknown funding error %v", fmsg.err) + fndgLog.Warnf("unknown funding error (%v:%v)", e.Code, e.Problem) } } + +// cancelReservationCtx do all needed work in order to securely cancel the +// reservation. +func (f *fundingManager) cancelReservationCtx(peerID int32, + chanID uint64) (*reservationWithCtx, error) { + + ctx, err := f.getReservationCtx(peerID, chanID) + if err != nil { + return nil, errors.Errorf("can't find reservation: %v", + err) + } + + if err := ctx.reservation.Cancel(); err != nil { + ctx.err <- err + return nil, errors.Errorf("can't cancel reservation: %v", + err) + } + + f.deleteReservationCtx(peerID, chanID) + return ctx, nil +} + +// deleteReservationCtx is needed in order to securely delete the reservation. +func (f *fundingManager) deleteReservationCtx(peerID int32, chanID uint64) { + // TODO(roasbeef): possibly cancel funding barrier in peer's + // channelManager? + f.resMtx.Lock() + delete(f.activeReservations[peerID], chanID) + f.resMtx.Unlock() +} + +// getReservationCtx returns the reservation context by peer id and channel id. +func (f *fundingManager) getReservationCtx(peerID int32, + chanID uint64) (*reservationWithCtx, error) { + + f.resMtx.RLock() + resCtx, ok := f.activeReservations[peerID][chanID] + f.resMtx.RUnlock() + + if !ok { + return nil, errors.Errorf("unknown channel (id: %v)", chanID) + } + + return resCtx, nil +} diff --git a/lnd_test.go b/lnd_test.go index 63ca6f23..bab77c9a 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -16,6 +16,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/rpctest" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcrpcclient" @@ -886,7 +887,7 @@ func testMaxPendingChannels(net *networkHarness, t *harnessTest) { _, err = net.OpenChannel(ctx, net.Alice, carol, amount, 1) if err == nil { t.Fatalf("error wasn't received") - } else if grpc.Code(err) != ErrorMaxPendingChannels { + } else if grpc.Code(err) != lnwire.ErrorMaxPendingChannels.ToGrpcCode() { t.Fatalf("not expected error was received: %v", err) } @@ -1243,6 +1244,22 @@ func TestLightningNetworkDaemon(t *testing.T) { OnTxAccepted: lndHarness.OnTxAccepted, } + // Spawn a new goroutine to watch for any fatal errors that any of the + // running lnd processes encounter. If an error occurs, then the test + // fails immediately with a fatal error, as far as fatal is happening + // inside goroutine main goroutine would not be finished at the same + // time as we receive fatal error from lnd process. + testsFin := make(chan struct{}) + go func() { + select { + case err := <-lndHarness.ProcessErrors(): + ht.Fatalf("lnd finished with error (stderr): "+ + "\n%v", err) + case <-testsFin: + return + } + }() + // First create an instance of the btcd's rpctest.Harness. This will be // used to fund the wallets of the nodes within the test network and to // drive blockchain related events within the network. @@ -1269,22 +1286,6 @@ func TestLightningNetworkDaemon(t *testing.T) { ht.Fatalf("unable to set up test lightning network: %v", err) } - // Spawn a new goroutine to watch for any fatal errors that any of the - // running lnd processes encounter. If an error occurs, then the test - // fails immediately with a fatal error, as far as fatal is happening - // inside goroutine main goroutine would not be finished at the same - // time as we receive fatal error from lnd process. - testsFin := make(chan struct{}) - go func() { - select { - case err := <-lndHarness.ProcessErrors(): - ht.Fatalf("lnd finished with error (stderr): "+ - "\n%v", err) - case <-testsFin: - return - } - }() - t.Logf("Running %v integration tests", len(testsCases)) for _, testCase := range testsCases { ht.RunTestCase(testCase, lndHarness) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index f13a3959..ee6daf27 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -1585,7 +1585,7 @@ func (lc *LightningChannel) AddHTLC(htlc *lnwire.HTLCAddRequest) (uint32, error) EntryType: Add, RHash: PaymentHash(htlc.RedemptionHashes[0]), Timeout: htlc.Expiry, - Amount: btcutil.Amount(htlc.Amount), + Amount: htlc.Amount, Index: lc.ourLogCounter, } @@ -1611,7 +1611,7 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.HTLCAddRequest) (uint32, er EntryType: Add, RHash: PaymentHash(htlc.RedemptionHashes[0]), Timeout: htlc.Expiry, - Amount: btcutil.Amount(htlc.Amount), + Amount: htlc.Amount, Index: lc.theirLogCounter, } diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 6d7ed4a7..ae48c292 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -355,7 +355,7 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { htlc := &lnwire.HTLCAddRequest{ RedemptionHashes: [][32]byte{paymentHash}, // TODO(roasbeef): properly switch to credits: (1 msat) - Amount: lnwire.CreditsAmount(1e8), + Amount: btcutil.Amount(1e8), Expiry: uint32(5), } @@ -628,7 +628,7 @@ func TestCheckCommitTxSize(t *testing.T) { return &lnwire.HTLCAddRequest{ RedemptionHashes: [][32]byte{paymentHash}, - Amount: lnwire.CreditsAmount(1e7), + Amount: btcutil.Amount(1e7), Expiry: uint32(5), }, returnPreimage } @@ -742,7 +742,7 @@ func TestCheckHTLCNumberConstraint(t *testing.T) { paymentHash := fastsha256.Sum256(preimage) return &lnwire.HTLCAddRequest{ RedemptionHashes: [][32]byte{paymentHash}, - Amount: lnwire.CreditsAmount(1e7), + Amount: btcutil.Amount(1e7), Expiry: uint32(5), } } @@ -864,7 +864,7 @@ func TestStateUpdatePersistence(t *testing.T) { rHash := fastsha256.Sum256(alicePreimage[:]) h := &lnwire.HTLCAddRequest{ RedemptionHashes: [][32]byte{rHash}, - Amount: lnwire.CreditsAmount(1000), + Amount: btcutil.Amount(1000), Expiry: uint32(10), } @@ -874,7 +874,7 @@ func TestStateUpdatePersistence(t *testing.T) { rHash := fastsha256.Sum256(bobPreimage[:]) bobh := &lnwire.HTLCAddRequest{ RedemptionHashes: [][32]byte{rHash}, - Amount: lnwire.CreditsAmount(1000), + Amount: btcutil.Amount(1000), Expiry: uint32(10), } bobChannel.AddHTLC(bobh) diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index 4eaeab13..92aeac76 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -908,12 +908,6 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, // TODO(roasbeef): bob verify alice's sig } -func testFundingReservationInvalidCounterpartySigs(miner *rpctest.Harness, lnwallet *lnwallet.LightningWallet, t *testing.T) { -} - -func testFundingTransactionTxFees(miner *rpctest.Harness, lnwallet *lnwallet.LightningWallet, t *testing.T) { -} - func testListTransactionDetails(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) { t.Log("Running list transaction details test") @@ -1234,10 +1228,10 @@ var walletTests = []func(miner *rpctest.Harness, w *lnwallet.LightningWallet, te testSingleFunderReservationWorkflowResponder, testFundingTransactionLockedOutputs, testFundingCancellationNotEnoughFunds, - testFundingReservationInvalidCounterpartySigs, testTransactionSubscriptions, testListTransactionDetails, testSignOutputPrivateTweak, + testCancelNonExistantReservation, } type testLnWallet struct { @@ -1324,28 +1318,28 @@ func TestLightningWallet(t *testing.T) { } // Funding via 20 outputs with 4BTC each. - lnwallet, err := createTestWallet(tempTestDir, miningNode, netParams, + lnw, err := createTestWallet(tempTestDir, miningNode, netParams, chainNotifier, wc, signer, bio) if err != nil { t.Fatalf("unable to create test ln wallet: %v", err) } // The wallet should now have 80BTC available for spending. - assertProperBalance(t, lnwallet, 1, 80) + assertProperBalance(t, lnw, 1, 80) // Execute every test, clearing possibly mutated wallet state after // each step. for _, walletTest := range walletTests { - walletTest(miningNode, lnwallet, t) + walletTest(miningNode, lnw, t) // TODO(roasbeef): possible reset mining node's chainstate to // initial level, cleanly wipe buckets - if err := clearWalletState(lnwallet); err != nil && + if err := clearWalletState(lnw); err != nil && err != bolt.ErrBucketNotFound { t.Fatalf("unable to wipe wallet state: %v", err) } } - lnwallet.Shutdown() + lnw.Shutdown() } } diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index a7bcc453..dfcd0f34 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -12,7 +12,7 @@ import ( // ChannelContribution is the primary constituent of the funding workflow within // lnwallet. Each side first exchanges their respective contributions along with -// channel specific paramters like the min fee/KB. Once contributions have been +// channel specific parameters like the min fee/KB. Once contributions have been // exchanged, each side will then produce signatures for all their inputs to the // funding transactions, and finally a signature for the other party's version // of the commitment transaction. @@ -98,10 +98,6 @@ type ChannelReservation struct { // fundingTx is the funding transaction for this pending channel. fundingTx *wire.MsgTx - // For CLTV it is nLockTime, for CSV it's nSequence, for segwit it's - // not needed - fundingLockTime uint32 - // In order of sorted inputs. Sorting is done in accordance // to BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki. ourFundingInputScripts []*InputScript @@ -228,7 +224,7 @@ func (r *ChannelReservation) OurContribution() *ChannelContribution { return r.ourContribution } -// ProcesContribution verifies the counterparty's contribution to the pending +// ProcessContribution verifies the counterparty's contribution to the pending // payment channel. As a result of this incoming message, lnwallet is able to // build the funding transaction, and both commitment transactions. Once this // message has been processed, all signatures to inputs to the funding diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 57e27ffa..ba069620 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -768,7 +768,6 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { // With the funding tx complete, create both commitment transactions. // TODO(roasbeef): much cleanup + de-duplication - pendingReservation.fundingLockTime = theirContribution.CsvDelay ourBalance := ourContribution.FundingAmount theirBalance := theirContribution.FundingAmount ourCommitKey := ourContribution.CommitKey diff --git a/lnwire/error_generic.go b/lnwire/error_generic.go index 0c49dc82..412c976a 100644 --- a/lnwire/error_generic.go +++ b/lnwire/error_generic.go @@ -5,12 +5,21 @@ import ( "io" "github.com/roasbeef/btcd/wire" + "google.golang.org/grpc/codes" ) // ErrorCode represents the short error code for each of the defined errors // within the Lightning Network protocol spec. type ErrorCode uint16 +// ToGrpcCode is used to generate gRPC specific code which will be propagated +// to the ln rpc client. This code is used to have more detailed view of what +// goes wrong and also in order to have the ability pragmatically determine +// the error and take specific actions on the client side. +func (e ErrorCode) ToGrpcCode() codes.Code { + return (codes.Code)(e) + 100 +} + const ( // ErrorMaxPendingChannels is returned by remote peer when the number // of active pending channels exceeds their maximum policy limit. diff --git a/lnwire/htlc_addrequest.go b/lnwire/htlc_addrequest.go index 451cbb3b..f2ec49bc 100644 --- a/lnwire/htlc_addrequest.go +++ b/lnwire/htlc_addrequest.go @@ -5,6 +5,7 @@ import ( "io" "github.com/roasbeef/btcd/wire" + "github.com/roasbeef/btcutil" ) // HTLCAddRequest is the message sent by Alice to Bob when she wishes to add an @@ -28,7 +29,7 @@ type HTLCAddRequest struct { // Difference between hop and first item in blob is the fee to complete // Amount is the number of credits this HTLC is worth. - Amount CreditsAmount + Amount btcutil.Amount // RefundContext is for payment cancellation // TODO(j): not currently in use, add later diff --git a/lnwire/htlc_addrequest_test.go b/lnwire/htlc_addrequest_test.go index 8558c61b..75efe631 100644 --- a/lnwire/htlc_addrequest_test.go +++ b/lnwire/htlc_addrequest_test.go @@ -4,6 +4,7 @@ import ( "bytes" "reflect" "testing" + "github.com/roasbeef/btcutil" ) func TestHTLCAddRequestEncodeDecode(t *testing.T) { @@ -14,7 +15,7 @@ func TestHTLCAddRequestEncodeDecode(t *testing.T) { addReq := &HTLCAddRequest{ ChannelPoint: outpoint1, Expiry: uint32(144), - Amount: CreditsAmount(123456000), + Amount: btcutil.Amount(123456000), ContractType: uint8(17), RedemptionHashes: redemptionHashes, OnionBlob: []byte{255, 0, 255, 0, 255, 0, 255, 0}, diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index a641f92a..3d82685b 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -32,7 +32,7 @@ type HTLCKey int64 // HTLC lists on either side will increment this height. As a result this value // should always be monotonically increasing. Any CommitSignature or // CommitRevocation messages will reference a value for the commitment height -// up to which it covers. HTLC's are only explicltly excluded by sending +// up to which it covers. HTLC's are only explicitly excluded by sending // HTLCReject messages referencing a particular HTLCKey. type CommitHeight uint64 diff --git a/peer.go b/peer.go index 3af48e92..0b91d406 100644 --- a/peer.go +++ b/peer.go @@ -1532,7 +1532,7 @@ func logEntryToHtlcPkt(chanPoint wire.OutPoint, } msg = &lnwire.HTLCAddRequest{ - Amount: lnwire.CreditsAmount(pd.Amount), + Amount: btcutil.Amount(pd.Amount), RedemptionHashes: [][32]byte{pd.RHash}, OnionBlob: b.Bytes(), } diff --git a/rpcserver.go b/rpcserver.go index 5c4a9a63..050db288 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -29,12 +29,6 @@ import ( "golang.org/x/net/context" ) -const ( - // ErrorMaxPendingChannels is an additional gRPC error, which is - // returned if max pending channel restriction was violated. - ErrorMaxPendingChannels = 100 -) - var ( defaultAccount uint32 = waddrmgr.DefaultAccountNum ) @@ -771,7 +765,7 @@ func (r *rpcServer) constructPaymentRoute(destPubkey []byte, amt int64, // meta-data within this packet will be used to route the payment // through the network. htlcAdd := &lnwire.HTLCAddRequest{ - Amount: lnwire.CreditsAmount(amt), + Amount: btcutil.Amount(amt), RedemptionHashes: [][32]byte{rHash}, OnionBlob: sphinxPacket, }