mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-05-02 07:50:17 +02:00
Merge pull request #36 from AndrewSamokhvalov/single-pending-channel
Only allow a single pending channel per-peer #32
This commit is contained in:
commit
3cf7ec2a51
65
config.go
65
config.go
@ -13,18 +13,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultConfigFilename = "lnd.conf"
|
defaultConfigFilename = "lnd.conf"
|
||||||
defaultDataDirname = "data"
|
defaultDataDirname = "data"
|
||||||
defaultLogLevel = "info"
|
defaultLogLevel = "info"
|
||||||
defaultLogDirname = "logs"
|
defaultLogDirname = "logs"
|
||||||
defaultLogFilename = "lnd.log"
|
defaultLogFilename = "lnd.log"
|
||||||
defaultRPCPort = 10009
|
defaultRPCPort = 10009
|
||||||
defaultSPVMode = false
|
defaultSPVMode = false
|
||||||
defaultPeerPort = 10011
|
defaultPeerPort = 10011
|
||||||
defaultRPCHost = "localhost"
|
defaultRPCHost = "localhost"
|
||||||
defaultRPCUser = "user"
|
defaultRPCUser = "user"
|
||||||
defaultRPCPass = "passwd"
|
defaultRPCPass = "passwd"
|
||||||
defaultSPVHostAdr = "localhost:18333"
|
defaultSPVHostAdr = "localhost:18333"
|
||||||
|
defaultMaxPendingChannels = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -62,13 +63,14 @@ type config struct {
|
|||||||
RPCUser string `short:"u" long:"rpcuser" description:"Username for RPC connections"`
|
RPCUser string `short:"u" long:"rpcuser" description:"Username for RPC connections"`
|
||||||
RPCPass string `short:"P" long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
|
RPCPass string `short:"P" long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
|
||||||
|
|
||||||
RPCCert string `long:"rpccert" description:"File containing btcd's certificate file"`
|
RPCCert string `long:"rpccert" description:"File containing btcd's certificate file"`
|
||||||
RawRPCCert string `long:"rawrpccert" description:"The raw bytes of btcd's PEM-encoded certificate chain which will be used to authenticate the RPC connection."`
|
RawRPCCert string `long:"rawrpccert" description:"The raw bytes of btcd's PEM-encoded certificate chain which will be used to authenticate the RPC connection."`
|
||||||
SPVHostAdr string `long:"spvhostadr" description:"Address of full bitcoin node. It is used in SPV mode."`
|
SPVHostAdr string `long:"spvhostadr" description:"Address of full bitcoin node. It is used in SPV mode."`
|
||||||
TestNet3 bool `long:"testnet" description:"Use the test network"`
|
TestNet3 bool `long:"testnet" description:"Use the test network"`
|
||||||
SimNet bool `long:"simnet" description:"Use the simulation test network"`
|
SimNet bool `long:"simnet" description:"Use the simulation test network"`
|
||||||
SegNet bool `long:"segnet" description:"Use the segragated witness test network"`
|
SegNet bool `long:"segnet" description:"Use the segragated witness test network"`
|
||||||
DebugHTLC bool `long:"debughtlc" description:"Activate the debug htlc mode. With the debug HTLC mode, all payments sent use a pre-determined R-Hash. Additionally, all HTLC's sent to a node with the debug HTLC R-Hash are immediately settled in the next available state transition."`
|
DebugHTLC bool `long:"debughtlc" description:"Activate the debug htlc mode. With the debug HTLC mode, all payments sent use a pre-determined R-Hash. Additionally, all HTLC's sent to a node with the debug HTLC R-Hash are immediately settled in the next available state transition."`
|
||||||
|
MaxPendingChannels int `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."`
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadConfig initializes and parses the config using a config file and command
|
// loadConfig initializes and parses the config using a config file and command
|
||||||
@ -81,18 +83,19 @@ type config struct {
|
|||||||
// 4) Parse CLI options and overwrite/add any specified options
|
// 4) Parse CLI options and overwrite/add any specified options
|
||||||
func loadConfig() (*config, error) {
|
func loadConfig() (*config, error) {
|
||||||
defaultCfg := config{
|
defaultCfg := config{
|
||||||
ConfigFile: defaultConfigFile,
|
ConfigFile: defaultConfigFile,
|
||||||
DataDir: defaultDataDir,
|
DataDir: defaultDataDir,
|
||||||
DebugLevel: defaultLogLevel,
|
DebugLevel: defaultLogLevel,
|
||||||
LogDir: defaultLogDir,
|
LogDir: defaultLogDir,
|
||||||
PeerPort: defaultPeerPort,
|
PeerPort: defaultPeerPort,
|
||||||
RPCPort: defaultRPCPort,
|
RPCPort: defaultRPCPort,
|
||||||
SPVMode: defaultSPVMode,
|
SPVMode: defaultSPVMode,
|
||||||
RPCHost: defaultRPCHost,
|
RPCHost: defaultRPCHost,
|
||||||
RPCUser: defaultRPCUser,
|
RPCUser: defaultRPCUser,
|
||||||
RPCPass: defaultRPCPass,
|
RPCPass: defaultRPCPass,
|
||||||
RPCCert: defaultRPCCertFile,
|
RPCCert: defaultRPCCertFile,
|
||||||
SPVHostAdr: defaultSPVHostAdr,
|
SPVHostAdr: defaultSPVHostAdr,
|
||||||
|
MaxPendingChannels: defaultMaxPendingChannels,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-parse the command line options to pick up an alternative config
|
// Pre-parse the command line options to pick up an alternative config
|
||||||
|
@ -13,8 +13,10 @@ import (
|
|||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
"github.com/BitfuryLightning/tools/rt"
|
"github.com/BitfuryLightning/tools/rt"
|
||||||
"github.com/BitfuryLightning/tools/rt/graph"
|
"github.com/BitfuryLightning/tools/rt/graph"
|
||||||
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -24,7 +26,7 @@ const (
|
|||||||
|
|
||||||
// reservationWithCtx encapsulates a pending channel reservation. This wrapper
|
// reservationWithCtx encapsulates a pending channel reservation. This wrapper
|
||||||
// struct is used internally within the funding manager to track and progress
|
// struct is used internally within the funding manager to track and progress
|
||||||
// the funding workflow initiated by incoming/outgoing meethods from the target
|
// the funding workflow initiated by incoming/outgoing methods from the target
|
||||||
// peer. Additionally, this struct houses a response and error channel which is
|
// peer. Additionally, this struct houses a response and error channel which is
|
||||||
// used to respond to the caller in the case a channel workflow is initiated
|
// used to respond to the caller in the case a channel workflow is initiated
|
||||||
// via a local signal such as RPC.
|
// via a local signal such as RPC.
|
||||||
@ -88,6 +90,14 @@ type fundingOpenMsg struct {
|
|||||||
peer *peer
|
peer *peer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fundingErrorMsg couples an lnwire.ErrorGeneric message
|
||||||
|
// with the peer who sent the message. This allows the funding
|
||||||
|
// manager properly process the error.
|
||||||
|
type fundingErrorMsg struct {
|
||||||
|
err *lnwire.ErrorGeneric
|
||||||
|
peer *peer
|
||||||
|
}
|
||||||
|
|
||||||
// pendingChannels is a map instantiated per-peer which tracks all active
|
// pendingChannels is a map instantiated per-peer which tracks all active
|
||||||
// pending single funded channels indexed by their pending channel identifier.
|
// pending single funded channels indexed by their pending channel identifier.
|
||||||
type pendingChannels map[uint64]*reservationWithCtx
|
type pendingChannels map[uint64]*reservationWithCtx
|
||||||
@ -121,7 +131,7 @@ type fundingManager struct {
|
|||||||
// state of the funding manager.
|
// state of the funding manager.
|
||||||
queries chan interface{}
|
queries chan interface{}
|
||||||
|
|
||||||
// fundingRequests is a channel used to recieve channel initiation
|
// fundingRequests is a channel used to receive channel initiation
|
||||||
// requests from a local sub-system within the daemon.
|
// requests from a local sub-system within the daemon.
|
||||||
fundingRequests chan *initFundingMsg
|
fundingRequests chan *initFundingMsg
|
||||||
|
|
||||||
@ -232,6 +242,8 @@ out:
|
|||||||
f.handleFundingSignComplete(fmsg)
|
f.handleFundingSignComplete(fmsg)
|
||||||
case *fundingOpenMsg:
|
case *fundingOpenMsg:
|
||||||
f.handleFundingOpen(fmsg)
|
f.handleFundingOpen(fmsg)
|
||||||
|
case *fundingErrorMsg:
|
||||||
|
f.handleErrorGenericMsg(fmsg)
|
||||||
}
|
}
|
||||||
case req := <-f.fundingRequests:
|
case req := <-f.fundingRequests:
|
||||||
f.handleInitFundingMsg(req)
|
f.handleInitFundingMsg(req)
|
||||||
@ -286,7 +298,7 @@ func (f *fundingManager) handlePendingChannels(msg *pendingChansReq) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// processFundingRequest sends a message to the fundingManager allowing it to
|
// processFundingRequest sends a message to the fundingManager allowing it to
|
||||||
// intiate the new funding workflow with the source peer.
|
// initiate the new funding workflow with the source peer.
|
||||||
func (f *fundingManager) processFundingRequest(msg *lnwire.SingleFundingRequest, peer *peer) {
|
func (f *fundingManager) processFundingRequest(msg *lnwire.SingleFundingRequest, peer *peer) {
|
||||||
f.fundingMsgs <- &fundingRequestMsg{msg, peer}
|
f.fundingMsgs <- &fundingRequestMsg{msg, peer}
|
||||||
}
|
}
|
||||||
@ -297,6 +309,23 @@ func (f *fundingManager) processFundingRequest(msg *lnwire.SingleFundingRequest,
|
|||||||
// TODO(roasbeef): add error chan to all, let channelManager handle
|
// TODO(roasbeef): add error chan to all, let channelManager handle
|
||||||
// error+propagate
|
// error+propagate
|
||||||
func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) {
|
func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) {
|
||||||
|
|
||||||
|
// Check number of pending channels to be smaller than maximum allowed
|
||||||
|
// number and send ErrorGeneric to remote peer if condition is violated.
|
||||||
|
if len(f.activeReservations[fmsg.peer.id]) >= cfg.MaxPendingChannels {
|
||||||
|
errMsg := &lnwire.ErrorGeneric{
|
||||||
|
ChannelPoint: &wire.OutPoint{
|
||||||
|
Hash: wire.ShaHash{},
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
Problem: "Number of pending channels exceed maximum",
|
||||||
|
ErrorID: lnwire.ErrorMaxPendingChannels,
|
||||||
|
PendingChannelID: fmsg.msg.ChannelID,
|
||||||
|
}
|
||||||
|
fmsg.peer.queueMsg(errMsg, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
msg := fmsg.msg
|
msg := fmsg.msg
|
||||||
amt := msg.FundingAmount
|
amt := msg.FundingAmount
|
||||||
delay := msg.CsvDelay
|
delay := msg.CsvDelay
|
||||||
@ -319,7 +348,7 @@ func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once the reservation has been created succesfully, we add it to this
|
// Once the reservation has been created successfully, we add it to this
|
||||||
// peers map of pending reservations to track this particular reservation
|
// peers map of pending reservations to track this particular reservation
|
||||||
// until either abort or completion.
|
// until either abort or completion.
|
||||||
f.resMtx.Lock()
|
f.resMtx.Lock()
|
||||||
@ -684,6 +713,7 @@ func (f *fundingManager) initFundingWorkflow(targetPeer *peer, req *openChanReq)
|
|||||||
// wallet, then sends a funding request to the remote peer kicking off the
|
// wallet, then sends a funding request to the remote peer kicking off the
|
||||||
// funding workflow.
|
// funding workflow.
|
||||||
func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
||||||
|
|
||||||
nodeID := msg.peer.lightningID
|
nodeID := msg.peer.lightningID
|
||||||
|
|
||||||
localAmt := msg.localFundingAmt
|
localAmt := msg.localFundingAmt
|
||||||
@ -719,6 +749,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
|||||||
if _, ok := f.activeReservations[msg.peer.id]; !ok {
|
if _, ok := f.activeReservations[msg.peer.id]; !ok {
|
||||||
f.activeReservations[msg.peer.id] = make(pendingChannels)
|
f.activeReservations[msg.peer.id] = make(pendingChannels)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.activeReservations[msg.peer.id][chanID] = &reservationWithCtx{
|
f.activeReservations[msg.peer.id][chanID] = &reservationWithCtx{
|
||||||
reservation: reservation,
|
reservation: reservation,
|
||||||
peer: msg.peer,
|
peer: msg.peer,
|
||||||
@ -754,3 +785,46 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
|||||||
)
|
)
|
||||||
msg.peer.queueMsg(fundingReq, nil)
|
msg.peer.queueMsg(fundingReq, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processErrorGeneric sends a message to the fundingManager allowing it
|
||||||
|
// to process the occurred generic error.
|
||||||
|
func (f *fundingManager) processErrorGeneric(err *lnwire.ErrorGeneric,
|
||||||
|
peer *peer) {
|
||||||
|
f.fundingMsgs <- &fundingErrorMsg{err, peer}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleErrorGenericMsg process the error which was received from remote peer,
|
||||||
|
// depends on the type of error we should do different clean up steps and
|
||||||
|
// inform user about it.
|
||||||
|
func (f *fundingManager) handleErrorGenericMsg(fmsg *fundingErrorMsg) {
|
||||||
|
if fmsg.err.ErrorID == lnwire.ErrorMaxPendingChannels {
|
||||||
|
peerID := fmsg.peer.id
|
||||||
|
chanID := fmsg.err.PendingChannelID
|
||||||
|
|
||||||
|
f.resMtx.RLock()
|
||||||
|
resCtx, ok := f.activeReservations[peerID][chanID]
|
||||||
|
f.resMtx.RUnlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
resCtx.err <- fmt.Errorf("ErrorGeneric error " +
|
||||||
|
"was returned from remote peer for channel "+
|
||||||
|
"(id: %v), but it can't be found and thereby "+
|
||||||
|
"can't be canceled.", chanID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := resCtx.reservation.Cancel(); err != nil {
|
||||||
|
resCtx.err <- fmt.Errorf("Remote peer responded "+
|
||||||
|
"with: Number of pending channels exceed "+
|
||||||
|
"maximum, but we can't cancel the reservation "+
|
||||||
|
"- %v", err)
|
||||||
|
} else {
|
||||||
|
resCtx.err <- grpc.Errorf(OpenChannelFundingError,
|
||||||
|
"Remote peer responded with: Number of "+
|
||||||
|
"pending channels exceed maximum")
|
||||||
|
}
|
||||||
|
|
||||||
|
f.resMtx.Lock()
|
||||||
|
delete(f.activeReservations[peerID], chanID)
|
||||||
|
f.resMtx.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
3
glide.lock
generated
3
glide.lock
generated
@ -155,4 +155,7 @@ imports:
|
|||||||
- naming
|
- naming
|
||||||
- transport
|
- transport
|
||||||
- peer
|
- peer
|
||||||
|
- name: github.com/go-errors/errors
|
||||||
|
version: a41850380601eeb43f4350f7d17c6bbd8944aaf8
|
||||||
|
|
||||||
testImports: []
|
testImports: []
|
||||||
|
@ -60,3 +60,4 @@ import:
|
|||||||
- package: github.com/grpc-ecosystem/grpc-gateway
|
- package: github.com/grpc-ecosystem/grpc-gateway
|
||||||
version: ^1.1.0
|
version: ^1.1.0
|
||||||
- package: github.com/aead/chacha20
|
- package: github.com/aead/chacha20
|
||||||
|
- package: github.com/go-errors/errors
|
||||||
|
30
lnd.go
30
lnd.go
@ -47,8 +47,8 @@ func lndMain() error {
|
|||||||
// Show version at startup.
|
// Show version at startup.
|
||||||
ltndLog.Infof("Version %s", version())
|
ltndLog.Infof("Version %s", version())
|
||||||
|
|
||||||
if loadedConfig.SPVMode == true {
|
if cfg.SPVMode == true {
|
||||||
shell(loadedConfig.SPVHostAdr, activeNetParams.Params)
|
shell(cfg.SPVHostAdr, activeNetParams.Params)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ func lndMain() error {
|
|||||||
|
|
||||||
// Open the channeldb, which is dedicated to storing channel, and
|
// Open the channeldb, which is dedicated to storing channel, and
|
||||||
// network related meta-data.
|
// network related meta-data.
|
||||||
chanDB, err := channeldb.Open(loadedConfig.DataDir, activeNetParams.Params)
|
chanDB, err := channeldb.Open(cfg.DataDir, activeNetParams.Params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("unable to open channeldb: ", err)
|
fmt.Println("unable to open channeldb: ", err)
|
||||||
return err
|
return err
|
||||||
@ -76,13 +76,13 @@ func lndMain() error {
|
|||||||
// specified in the config, then we'll se that directly. Otherwise, we
|
// specified in the config, then we'll se that directly. Otherwise, we
|
||||||
// attempt to read the cert from the path specified in the config.
|
// attempt to read the cert from the path specified in the config.
|
||||||
var rpcCert []byte
|
var rpcCert []byte
|
||||||
if loadedConfig.RawRPCCert != "" {
|
if cfg.RawRPCCert != "" {
|
||||||
rpcCert, err = hex.DecodeString(loadedConfig.RawRPCCert)
|
rpcCert, err = hex.DecodeString(cfg.RawRPCCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
certFile, err := os.Open(loadedConfig.RPCCert)
|
certFile, err := os.Open(cfg.RPCCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -95,15 +95,15 @@ func lndMain() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcIP, err := net.LookupHost(loadedConfig.RPCHost)
|
rpcIP, err := net.LookupHost(cfg.RPCHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("unable to resolve rpc host: %v", err)
|
fmt.Printf("unable to resolve rpc host: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
btcdHost := fmt.Sprintf("%v:%v", loadedConfig.RPCHost, activeNetParams.rpcPort)
|
btcdHost := fmt.Sprintf("%v:%v", cfg.RPCHost, activeNetParams.rpcPort)
|
||||||
btcdUser := loadedConfig.RPCUser
|
btcdUser := cfg.RPCUser
|
||||||
btcdPass := loadedConfig.RPCPass
|
btcdPass := cfg.RPCPass
|
||||||
|
|
||||||
// TODO(roasbeef): parse config here and select chosen notifier instead
|
// TODO(roasbeef): parse config here and select chosen notifier instead
|
||||||
rpcConfig := &btcrpcclient.ConnConfig{
|
rpcConfig := &btcrpcclient.ConnConfig{
|
||||||
@ -121,13 +121,13 @@ func lndMain() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): paarse config here select chosen WalletController
|
// TODO(roasbeef): parse config here select chosen WalletController
|
||||||
walletConfig := &btcwallet.Config{
|
walletConfig := &btcwallet.Config{
|
||||||
PrivatePass: []byte("hello"),
|
PrivatePass: []byte("hello"),
|
||||||
DataDir: filepath.Join(loadedConfig.DataDir, "lnwallet"),
|
DataDir: filepath.Join(cfg.DataDir, "lnwallet"),
|
||||||
RpcHost: fmt.Sprintf("%v:%v", rpcIP[0], activeNetParams.rpcPort),
|
RpcHost: fmt.Sprintf("%v:%v", rpcIP[0], activeNetParams.rpcPort),
|
||||||
RpcUser: loadedConfig.RPCUser,
|
RpcUser: cfg.RPCUser,
|
||||||
RpcPass: loadedConfig.RPCPass,
|
RpcPass: cfg.RPCPass,
|
||||||
CACert: rpcCert,
|
CACert: rpcCert,
|
||||||
NetParams: activeNetParams.Params,
|
NetParams: activeNetParams.Params,
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ func lndMain() error {
|
|||||||
// Set up the core server which will listen for incoming peer
|
// Set up the core server which will listen for incoming peer
|
||||||
// connections.
|
// connections.
|
||||||
defaultListenAddrs := []string{
|
defaultListenAddrs := []string{
|
||||||
net.JoinHostPort("", strconv.Itoa(loadedConfig.PeerPort)),
|
net.JoinHostPort("", strconv.Itoa(cfg.PeerPort)),
|
||||||
}
|
}
|
||||||
server, err := newServer(defaultListenAddrs, notifier, bio, wallet, chanDB)
|
server, err := newServer(defaultListenAddrs, notifier, bio, wallet, chanDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
526
lnd_test.go
526
lnd_test.go
@ -3,64 +3,140 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime/debug"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/roasbeef/btcd/rpctest"
|
"github.com/roasbeef/btcd/rpctest"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
"github.com/roasbeef/btcrpcclient"
|
"github.com/roasbeef/btcrpcclient"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func assertTxInBlock(block *btcutil.Block, txid *wire.ShaHash, t *testing.T) {
|
// CT is needed for:
|
||||||
|
// - have uniform way of handling panic and fatal error from test cases.
|
||||||
|
// - have ability to properly wrap errors in order to see stack trace.
|
||||||
|
// - have nice and elegant way to handle lnd process errors in one
|
||||||
|
// select structure with test cases.
|
||||||
|
type CT struct {
|
||||||
|
*testing.T
|
||||||
|
|
||||||
|
// Channel for sending retransmitted panic errors and fatal error which
|
||||||
|
// happens in test case.
|
||||||
|
errChan chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCT(t *testing.T) *CT {
|
||||||
|
return &CT{t, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *CT) Error(err error) {
|
||||||
|
if ct.errChan != nil {
|
||||||
|
ct.errChan <- fmt.Errorf(errors.Wrap(err, 1).ErrorStack())
|
||||||
|
ct.FailNow()
|
||||||
|
} else {
|
||||||
|
ct.Fatal("can't sen error when test isn't running")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf create and send the description about the error in the error channel
|
||||||
|
// and exit.
|
||||||
|
func (ct *CT) Errorf(format string, a ...interface{}) {
|
||||||
|
if ct.errChan != nil {
|
||||||
|
description := fmt.Sprintf(format, a...)
|
||||||
|
ct.errChan <- fmt.Errorf(errors.Wrap(description, 1).ErrorStack())
|
||||||
|
ct.FailNow()
|
||||||
|
} else {
|
||||||
|
ct.Fatal("can't sen error when test isn't running")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunTest wraps test case function in goroutine and also redirects the panic
|
||||||
|
// error from test case into error channel.
|
||||||
|
func (ct *CT) RunTest(net *networkHarness, test testCase) chan error {
|
||||||
|
// a channel to signal that test was exited with error
|
||||||
|
ct.errChan = make(chan error)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
// Retransmit test panic into main "process"
|
||||||
|
ct.errChan <- fmt.Errorf(err.(string))
|
||||||
|
}
|
||||||
|
close(ct.errChan)
|
||||||
|
ct.errChan = nil
|
||||||
|
}()
|
||||||
|
test(net, ct)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ct.errChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertTxInBlock(ct *CT, block *btcutil.Block, txid *wire.ShaHash) {
|
||||||
for _, tx := range block.Transactions() {
|
for _, tx := range block.Transactions() {
|
||||||
if bytes.Equal(txid[:], tx.Sha()[:]) {
|
if bytes.Equal(txid[:], tx.Sha()[:]) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Fatalf("funding tx was not included in block")
|
ct.Errorf("funding tx was not included in block")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mineBlocks mine 'num' of blocks and check that blocks are present in
|
||||||
|
// node blockchain.
|
||||||
|
func mineBlocks(ct *CT, net *networkHarness, num uint32) []*btcutil.Block {
|
||||||
|
blocks := make([]*btcutil.Block, num)
|
||||||
|
|
||||||
|
blockHashes, err := net.Miner.Node.Generate(num)
|
||||||
|
if err != nil {
|
||||||
|
ct.Errorf("unable to generate blocks: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, blockHash := range blockHashes {
|
||||||
|
block, err := net.Miner.Node.GetBlock(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
ct.Errorf("unable to get block: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks[i] = block
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// openChannelAndAssert attempts to open a channel with the specified
|
// openChannelAndAssert attempts to open a channel with the specified
|
||||||
// parameters extended from Alice to Bob. Additionally, two items are asserted
|
// parameters extended from Alice to Bob. Additionally, two items are asserted
|
||||||
// after the channel is considered open: the funding transactino should be
|
// after the channel is considered open: the funding transaction should be
|
||||||
// found within a block, and that Alice can report the status of the new
|
// found within a block, and that Alice can report the status of the new
|
||||||
// channel.
|
// channel.
|
||||||
func openChannelAndAssert(t *testing.T, net *networkHarness, ctx context.Context,
|
func openChannelAndAssert(ct *CT, net *networkHarness, ctx context.Context,
|
||||||
alice, bob *lightningNode, amount btcutil.Amount) *lnrpc.ChannelPoint {
|
alice, bob *lightningNode, amount btcutil.Amount) *lnrpc.ChannelPoint {
|
||||||
|
|
||||||
chanOpenUpdate, err := net.OpenChannel(ctx, alice, bob, amount, 1)
|
chanOpenUpdate, err := net.OpenChannel(ctx, alice, bob, amount, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to open channel: %v", err)
|
ct.Errorf("unable to open channel: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mine a block, then wait for Alice's node to notify us that the
|
// Mine a block, then wait for Alice's node to notify us that the
|
||||||
// channel has been opened. The funding transaction should be found
|
// channel has been opened. The funding transaction should be found
|
||||||
// within the newly mined block.
|
// within the newly mined block.
|
||||||
blockHash, err := net.Miner.Node.Generate(1)
|
block := mineBlocks(ct, net, 1)[0]
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to generate block: %v", err)
|
|
||||||
}
|
|
||||||
block, err := net.Miner.Node.GetBlock(blockHash[0])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to get block: %v", err)
|
|
||||||
}
|
|
||||||
fundingChanPoint, err := net.WaitForChannelOpen(ctx, chanOpenUpdate)
|
fundingChanPoint, err := net.WaitForChannelOpen(ctx, chanOpenUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error while waiting for channel open: %v", err)
|
ct.Errorf("error while waiting for channel open: %v", err)
|
||||||
}
|
}
|
||||||
fundingTxID, err := wire.NewShaHash(fundingChanPoint.FundingTxid)
|
fundingTxID, err := wire.NewShaHash(fundingChanPoint.FundingTxid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create sha hash: %v", err)
|
ct.Errorf("unable to create sha hash: %v", err)
|
||||||
}
|
}
|
||||||
assertTxInBlock(block, fundingTxID, t)
|
assertTxInBlock(ct, block, fundingTxID)
|
||||||
|
|
||||||
// The channel should be listed in the peer information returned by
|
// The channel should be listed in the peer information returned by
|
||||||
// both peers.
|
// both peers.
|
||||||
@ -68,47 +144,38 @@ func openChannelAndAssert(t *testing.T, net *networkHarness, ctx context.Context
|
|||||||
Hash: *fundingTxID,
|
Hash: *fundingTxID,
|
||||||
Index: fundingChanPoint.OutputIndex,
|
Index: fundingChanPoint.OutputIndex,
|
||||||
}
|
}
|
||||||
err = net.AssertChannelExists(ctx, alice, &chanPoint)
|
if err := net.AssertChannelExists(ctx, alice, &chanPoint); err != nil {
|
||||||
if err != nil {
|
ct.Errorf("unable to assert channel existence: %v", err)
|
||||||
t.Fatalf("unable to assert channel existence: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fundingChanPoint
|
return fundingChanPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeChannelAndAssert attemps to close a channel identified by the passed
|
// closeChannelAndAssert attempts to close a channel identified by the passed
|
||||||
// channel point owned by the passed lighting node. A fully blocking channel
|
// channel point owned by the passed lighting node. A fully blocking channel
|
||||||
// closure is attempted, therefore the passed context should be a child derived
|
// closure is attempted, therefore the passed context should be a child derived
|
||||||
// via timeout from a base parent. Additionally, once the channel has been
|
// via timeout from a base parent. Additionally, once the channel has been
|
||||||
// detected as closed, an assertion checks that the transaction is found within
|
// detected as closed, an assertion checks that the transaction is found within
|
||||||
// a block.
|
// a block.
|
||||||
func closeChannelAndAssert(t *testing.T, net *networkHarness,
|
func closeChannelAndAssert(ct *CT, net *networkHarness, ctx context.Context,
|
||||||
ctx context.Context, node *lightningNode,
|
node *lightningNode, fundingChanPoint *lnrpc.ChannelPoint) {
|
||||||
fundingChanPoint *lnrpc.ChannelPoint) {
|
|
||||||
|
|
||||||
closeUpdates, err := net.CloseChannel(ctx, node, fundingChanPoint, false)
|
closeUpdates, err := net.CloseChannel(ctx, node, fundingChanPoint, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to close channel: %v", err)
|
ct.Errorf("unable to close channel: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, generate a single block, wait for the final close status
|
// Finally, generate a single block, wait for the final close status
|
||||||
// update, then ensure that the closing transaction was included in the
|
// update, then ensure that the closing transaction was included in the
|
||||||
// block.
|
// block.
|
||||||
blockHash, err := net.Miner.Node.Generate(1)
|
block := mineBlocks(ct, net, 1)[0]
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to generate block: %v", err)
|
|
||||||
}
|
|
||||||
block, err := net.Miner.Node.GetBlock(blockHash[0])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to get block: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
closingTxid, err := net.WaitForChannelClose(ctx, closeUpdates)
|
closingTxid, err := net.WaitForChannelClose(ctx, closeUpdates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error while waiting for channel close: %v", err)
|
ct.Errorf("error while waiting for channel close: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTxInBlock(block, closingTxid, t)
|
assertTxInBlock(ct, block, closingTxid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testBasicChannelFunding performs a test exercising expected behavior from a
|
// testBasicChannelFunding performs a test exercising expected behavior from a
|
||||||
@ -116,7 +183,7 @@ func closeChannelAndAssert(t *testing.T, net *networkHarness,
|
|||||||
// Bob, then immediately closes the channel after asserting some expected post
|
// Bob, then immediately closes the channel after asserting some expected post
|
||||||
// conditions. Finally, the chain itself is checked to ensure the closing
|
// conditions. Finally, the chain itself is checked to ensure the closing
|
||||||
// transaction was mined.
|
// transaction was mined.
|
||||||
func testBasicChannelFunding(net *networkHarness, t *testing.T) {
|
func testBasicChannelFunding(net *networkHarness, ct *CT) {
|
||||||
timeout := time.Duration(time.Second * 5)
|
timeout := time.Duration(time.Second * 5)
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
|
|
||||||
@ -128,40 +195,44 @@ func testBasicChannelFunding(net *networkHarness, t *testing.T) {
|
|||||||
// assertions will be executed to ensure the funding process completed
|
// assertions will be executed to ensure the funding process completed
|
||||||
// successfully.
|
// successfully.
|
||||||
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||||
chanPoint := openChannelAndAssert(t, net, ctxt, net.Alice, net.Bob, chanAmt)
|
chanPoint := openChannelAndAssert(ct, net, ctxt, net.Alice, net.Bob, chanAmt)
|
||||||
|
|
||||||
// Finally, immediately close the channel. This function will also
|
// Finally, immediately close the channel. This function will also
|
||||||
// block until the channel is closed and will additionally assert the
|
// block until the channel is closed and will additionally assert the
|
||||||
// relevant channel closing post conditions.
|
// relevant channel closing post conditions.
|
||||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
closeChannelAndAssert(t, net, ctxt, net.Alice, chanPoint)
|
closeChannelAndAssert(ct, net, ctxt, net.Alice, chanPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testChannelBalance creates a new channel between Alice and Bob, then
|
// testChannelBalance creates a new channel between Alice and Bob, then
|
||||||
// checks channel balance to be equal amount specified while creation of channel.
|
// checks channel balance to be equal amount specified while creation of channel.
|
||||||
func testChannelBalance(net *networkHarness, t *testing.T) {
|
func testChannelBalance(net *networkHarness, ct *CT) {
|
||||||
timeout := time.Duration(time.Second * 5)
|
timeout := time.Duration(time.Second * 5)
|
||||||
ctxb := context.Background()
|
|
||||||
|
|
||||||
// Creates a helper closure to be used below which asserts the proper
|
|
||||||
// response to a channel balance RPC.
|
|
||||||
checkChannelBalance := func(node lnrpc.LightningClient, amount btcutil.Amount) {
|
|
||||||
response, err := node.ChannelBalance(ctxb, &lnrpc.ChannelBalanceRequest{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to get channel balance: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
balance := btcutil.Amount(response.Balance)
|
|
||||||
if balance != amount {
|
|
||||||
t.Fatalf("channel balance wrong: %v != %v", balance, amount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open a channel with 0.5 BTC between Alice and Bob, ensuring the
|
// Open a channel with 0.5 BTC between Alice and Bob, ensuring the
|
||||||
// channel has been opened properly.
|
// channel has been opened properly.
|
||||||
amount := btcutil.Amount(btcutil.SatoshiPerBitcoin / 2)
|
amount := btcutil.Amount(btcutil.SatoshiPerBitcoin / 2)
|
||||||
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
ctx, _ := context.WithTimeout(context.Background(), timeout)
|
||||||
chanPoint := openChannelAndAssert(t, net, ctxt, net.Alice, net.Bob, amount)
|
|
||||||
|
// Creates a helper closure to be used below which asserts the proper
|
||||||
|
// response to a channel balance RPC.
|
||||||
|
checkChannelBalance := func(node lnrpc.LightningClient,
|
||||||
|
amount btcutil.Amount) {
|
||||||
|
|
||||||
|
response, err := node.ChannelBalance(ctx, &lnrpc.ChannelBalanceRequest{})
|
||||||
|
if err != nil {
|
||||||
|
ct.Errorf("unable to get channel balance: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
balance := btcutil.Amount(response.Balance)
|
||||||
|
if balance != amount {
|
||||||
|
ct.Errorf("channel balance wrong: %v != %v", balance,
|
||||||
|
amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chanPoint := openChannelAndAssert(ct, net, ctx, net.Alice, net.Bob,
|
||||||
|
amount)
|
||||||
|
|
||||||
// As this is a single funder channel, Alice's balance should be
|
// As this is a single funder channel, Alice's balance should be
|
||||||
// exactly 0.5 BTC since now state transitions have taken place yet.
|
// exactly 0.5 BTC since now state transitions have taken place yet.
|
||||||
@ -180,8 +251,8 @@ func testChannelBalance(net *networkHarness, t *testing.T) {
|
|||||||
|
|
||||||
// Finally close the channel between Alice and Bob, asserting that the
|
// Finally close the channel between Alice and Bob, asserting that the
|
||||||
// channel has been properly closed on-chain.
|
// channel has been properly closed on-chain.
|
||||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
ctx, _ = context.WithTimeout(context.Background(), timeout)
|
||||||
closeChannelAndAssert(t, net, ctxt, net.Alice, chanPoint)
|
closeChannelAndAssert(ct, net, ctx, net.Alice, chanPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testChannelForceClosure performs a test to exercise the behavior of "force"
|
// testChannelForceClosure performs a test to exercise the behavior of "force"
|
||||||
@ -193,7 +264,7 @@ func testChannelBalance(net *networkHarness, t *testing.T) {
|
|||||||
// once the output(s) become mature.
|
// once the output(s) become mature.
|
||||||
//
|
//
|
||||||
// TODO(roabeef): also add an unsettled HTLC before force closing.
|
// TODO(roabeef): also add an unsettled HTLC before force closing.
|
||||||
func testChannelForceClosure(net *networkHarness, t *testing.T) {
|
func testChannelForceClosure(net *networkHarness, ct *CT) {
|
||||||
timeout := time.Duration(time.Second * 5)
|
timeout := time.Duration(time.Second * 5)
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
|
|
||||||
@ -204,15 +275,17 @@ func testChannelForceClosure(net *networkHarness, t *testing.T) {
|
|||||||
chanOpenUpdate, err := net.OpenChannel(ctxb, net.Alice, net.Bob,
|
chanOpenUpdate, err := net.OpenChannel(ctxb, net.Alice, net.Bob,
|
||||||
chanAmt, numFundingConfs)
|
chanAmt, numFundingConfs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to open channel: %v", err)
|
ct.Errorf("unable to open channel: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := net.Miner.Node.Generate(numFundingConfs); err != nil {
|
if _, err := net.Miner.Node.Generate(numFundingConfs); err != nil {
|
||||||
t.Fatalf("unable to mine block: %v", err)
|
ct.Errorf("unable to mine block: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||||
chanPoint, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate)
|
chanPoint, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error while waiting for channel to open: %v", err)
|
ct.Errorf("error while waiting for channel to open: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that the channel is open, immediately execute a force closure of
|
// Now that the channel is open, immediately execute a force closure of
|
||||||
@ -221,18 +294,18 @@ func testChannelForceClosure(net *networkHarness, t *testing.T) {
|
|||||||
// request.
|
// request.
|
||||||
closeUpdate, err := net.CloseChannel(ctxb, net.Alice, chanPoint, true)
|
closeUpdate, err := net.CloseChannel(ctxb, net.Alice, chanPoint, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to execute force channel closure: %v", err)
|
ct.Errorf("unable to execute force channel closure: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mine a block which should confirm the commitment transaction
|
// Mine a block which should confirm the commitment transaction
|
||||||
// broadcast as a result of the force closure.
|
// broadcast as a result of the force closure.
|
||||||
if _, err := net.Miner.Node.Generate(1); err != nil {
|
if _, err := net.Miner.Node.Generate(1); err != nil {
|
||||||
t.Fatalf("unable to generate block: %v", err)
|
ct.Errorf("unable to generate block: %v", err)
|
||||||
}
|
}
|
||||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
closingTxID, err := net.WaitForChannelClose(ctxt, closeUpdate)
|
closingTxID, err := net.WaitForChannelClose(ctxt, closeUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error while waiting for channel close: %v", err)
|
ct.Errorf("error while waiting for channel close: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently within the codebase, the default CSV is 4 relative blocks.
|
// Currently within the codebase, the default CSV is 4 relative blocks.
|
||||||
@ -241,7 +314,7 @@ func testChannelForceClosure(net *networkHarness, t *testing.T) {
|
|||||||
// or make delay a param
|
// or make delay a param
|
||||||
const defaultCSV = 4
|
const defaultCSV = 4
|
||||||
if _, err := net.Miner.Node.Generate(defaultCSV); err != nil {
|
if _, err := net.Miner.Node.Generate(defaultCSV); err != nil {
|
||||||
t.Fatalf("unable to mine blocks: %v", err)
|
ct.Errorf("unable to mine blocks: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, the sweeping transaction should now be broadcast. So
|
// At this point, the sweeping transaction should now be broadcast. So
|
||||||
@ -253,11 +326,11 @@ mempoolPoll:
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Second * 5):
|
case <-time.After(time.Second * 5):
|
||||||
t.Fatalf("sweep tx not found in mempool")
|
ct.Errorf("sweep tx not found in mempool")
|
||||||
default:
|
default:
|
||||||
mempool, err = net.Miner.Node.GetRawMempool()
|
mempool, err = net.Miner.Node.GetRawMempool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to fetch node's mempool: %v", err)
|
ct.Errorf("unable to fetch node's mempool: %v", err)
|
||||||
}
|
}
|
||||||
if len(mempool) == 0 {
|
if len(mempool) == 0 {
|
||||||
continue
|
continue
|
||||||
@ -271,7 +344,7 @@ mempoolPoll:
|
|||||||
// TODO(roasbeef): assertion may not necessarily hold with concurrent
|
// TODO(roasbeef): assertion may not necessarily hold with concurrent
|
||||||
// test executions
|
// test executions
|
||||||
if len(mempool) != 1 {
|
if len(mempool) != 1 {
|
||||||
t.Fatalf("node's mempool is wrong size, expected 1 got %v",
|
ct.Errorf("node's mempool is wrong size, expected 1 got %v",
|
||||||
len(mempool))
|
len(mempool))
|
||||||
}
|
}
|
||||||
sweepingTXID = mempool[0]
|
sweepingTXID = mempool[0]
|
||||||
@ -280,11 +353,11 @@ mempoolPoll:
|
|||||||
// the commitment transaction which was broadcast on-chain.
|
// the commitment transaction which was broadcast on-chain.
|
||||||
sweepTx, err := net.Miner.Node.GetRawTransaction(sweepingTXID)
|
sweepTx, err := net.Miner.Node.GetRawTransaction(sweepingTXID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to fetch sweep tx: %v", err)
|
ct.Errorf("unable to fetch sweep tx: %v", err)
|
||||||
}
|
}
|
||||||
for _, txIn := range sweepTx.MsgTx().TxIn {
|
for _, txIn := range sweepTx.MsgTx().TxIn {
|
||||||
if !closingTxID.IsEqual(&txIn.PreviousOutPoint.Hash) {
|
if !closingTxID.IsEqual(&txIn.PreviousOutPoint.Hash) {
|
||||||
t.Fatalf("sweep transaction not spending from commit "+
|
ct.Errorf("sweep transaction not spending from commit "+
|
||||||
"tx %v, instead spending %v",
|
"tx %v, instead spending %v",
|
||||||
closingTxID, txIn.PreviousOutPoint)
|
closingTxID, txIn.PreviousOutPoint)
|
||||||
}
|
}
|
||||||
@ -295,16 +368,17 @@ mempoolPoll:
|
|||||||
// inputs should be properly met.
|
// inputs should be properly met.
|
||||||
blockHash, err := net.Miner.Node.Generate(1)
|
blockHash, err := net.Miner.Node.Generate(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to generate block: %v", err)
|
ct.Errorf("unable to generate block: %v", err)
|
||||||
}
|
}
|
||||||
block, err := net.Miner.Node.GetBlock(blockHash[0])
|
block, err := net.Miner.Node.GetBlock(blockHash[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get block: %v", err)
|
ct.Errorf("unable to get block: %v", err)
|
||||||
}
|
}
|
||||||
assertTxInBlock(block, sweepTx.Sha(), t)
|
|
||||||
|
assertTxInBlock(ct, block, sweepTx.Sha())
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSingleHopInvoice(net *networkHarness, t *testing.T) {
|
func testSingleHopInvoice(net *networkHarness, ct *CT) {
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
timeout := time.Duration(time.Second * 5)
|
timeout := time.Duration(time.Second * 5)
|
||||||
|
|
||||||
@ -312,7 +386,7 @@ func testSingleHopInvoice(net *networkHarness, t *testing.T) {
|
|||||||
// the sole funder of the channel.
|
// the sole funder of the channel.
|
||||||
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||||
chanAmt := btcutil.Amount(100000)
|
chanAmt := btcutil.Amount(100000)
|
||||||
chanPoint := openChannelAndAssert(t, net, ctxt, net.Alice, net.Bob, chanAmt)
|
chanPoint := openChannelAndAssert(ct, net, ctxt, net.Alice, net.Bob, chanAmt)
|
||||||
|
|
||||||
// Now that the channel is open, create an invoice for Bob which
|
// Now that the channel is open, create an invoice for Bob which
|
||||||
// expects a payment of 1000 satoshis from Alice paid via a particular
|
// expects a payment of 1000 satoshis from Alice paid via a particular
|
||||||
@ -326,14 +400,14 @@ func testSingleHopInvoice(net *networkHarness, t *testing.T) {
|
|||||||
}
|
}
|
||||||
invoiceResp, err := net.Bob.AddInvoice(ctxb, invoice)
|
invoiceResp, err := net.Bob.AddInvoice(ctxb, invoice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to add invoice: %v", err)
|
ct.Errorf("unable to add invoice: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the invoice for Bob added, send a payment towards Alice paying
|
// With the invoice for Bob added, send a payment towards Alice paying
|
||||||
// to the above generated invoice.
|
// to the above generated invoice.
|
||||||
sendStream, err := net.Alice.SendPayment(ctxb)
|
sendStream, err := net.Alice.SendPayment(ctxb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create alice payment stream: %v", err)
|
ct.Errorf("unable to create alice payment stream: %v", err)
|
||||||
}
|
}
|
||||||
sendReq := &lnrpc.SendRequest{
|
sendReq := &lnrpc.SendRequest{
|
||||||
PaymentHash: invoiceResp.RHash,
|
PaymentHash: invoiceResp.RHash,
|
||||||
@ -341,10 +415,10 @@ func testSingleHopInvoice(net *networkHarness, t *testing.T) {
|
|||||||
Amt: paymentAmt,
|
Amt: paymentAmt,
|
||||||
}
|
}
|
||||||
if err := sendStream.Send(sendReq); err != nil {
|
if err := sendStream.Send(sendReq); err != nil {
|
||||||
t.Fatalf("unable to send payment: %v", err)
|
ct.Errorf("unable to send payment: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := sendStream.Recv(); err != nil {
|
if _, err := sendStream.Recv(); err != nil {
|
||||||
t.Fatalf("error when attempting recv: %v", err)
|
ct.Errorf("error when attempting recv: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bob's invoice should now be found and marked as settled.
|
// Bob's invoice should now be found and marked as settled.
|
||||||
@ -355,37 +429,38 @@ func testSingleHopInvoice(net *networkHarness, t *testing.T) {
|
|||||||
}
|
}
|
||||||
dbInvoice, err := net.Bob.LookupInvoice(ctxb, payHash)
|
dbInvoice, err := net.Bob.LookupInvoice(ctxb, payHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to lookup invoice: %v", err)
|
ct.Errorf("unable to lookup invoice: %v", err)
|
||||||
}
|
}
|
||||||
if !dbInvoice.Settled {
|
if !dbInvoice.Settled {
|
||||||
t.Fatalf("bob's invoice should be marked as settled: %v",
|
ct.Errorf("bob's invoice should be marked as settled: %v",
|
||||||
spew.Sdump(dbInvoice))
|
spew.Sdump(dbInvoice))
|
||||||
}
|
}
|
||||||
|
|
||||||
// The balances of Alice and Bob should be updated accordingly.
|
// The balances of Alice and Bob should be updated accordingly.
|
||||||
aliceBalance, err := net.Alice.ChannelBalance(ctxb, &lnrpc.ChannelBalanceRequest{})
|
aliceBalance, err := net.Alice.ChannelBalance(ctxb, &lnrpc.ChannelBalanceRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to query for alice's balance: %v", err)
|
ct.Errorf("unable to query for alice's balance: %v", err)
|
||||||
}
|
}
|
||||||
bobBalance, err := net.Bob.ChannelBalance(ctxb, &lnrpc.ChannelBalanceRequest{})
|
bobBalance, err := net.Bob.ChannelBalance(ctxb, &lnrpc.ChannelBalanceRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to query for bob's balance: %v", err)
|
ct.Errorf("unable to query for bob's balance: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if aliceBalance.Balance != int64(chanAmt-paymentAmt) {
|
if aliceBalance.Balance != int64(chanAmt-paymentAmt) {
|
||||||
t.Fatalf("Alice's balance is incorrect got %v, expected %v",
|
ct.Errorf("Alice's balance is incorrect got %v, expected %v",
|
||||||
aliceBalance, int64(chanAmt-paymentAmt))
|
aliceBalance, int64(chanAmt-paymentAmt))
|
||||||
}
|
}
|
||||||
if bobBalance.Balance != paymentAmt {
|
if bobBalance.Balance != paymentAmt {
|
||||||
t.Fatalf("Bob's balance is incorrect got %v, expected %v",
|
ct.Errorf("Bob's balance is incorrect got %v, expected %v",
|
||||||
bobBalance, paymentAmt)
|
bobBalance, paymentAmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
closeChannelAndAssert(t, net, ctxt, net.Alice, chanPoint)
|
closeChannelAndAssert(ct, net, ctxt, net.Alice, chanPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
func testMultiHopPayments(net *networkHarness, ct *CT) {
|
||||||
|
|
||||||
const chanAmt = btcutil.Amount(100000)
|
const chanAmt = btcutil.Amount(100000)
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
timeout := time.Duration(time.Second * 5)
|
timeout := time.Duration(time.Second * 5)
|
||||||
@ -393,11 +468,12 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
|||||||
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
||||||
// being the sole funder of the channel.
|
// being the sole funder of the channel.
|
||||||
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||||
chanPointAlice := openChannelAndAssert(t, net, ctxt, net.Alice, net.Bob,
|
chanPointAlice := openChannelAndAssert(ct, net, ctxt, net.Alice,
|
||||||
chanAmt)
|
net.Bob, chanAmt)
|
||||||
|
|
||||||
aliceChanTXID, err := wire.NewShaHash(chanPointAlice.FundingTxid)
|
aliceChanTXID, err := wire.NewShaHash(chanPointAlice.FundingTxid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create sha hash: %v", err)
|
ct.Errorf("unable to create sha hash: %v", err)
|
||||||
}
|
}
|
||||||
aliceFundPoint := wire.OutPoint{
|
aliceFundPoint := wire.OutPoint{
|
||||||
Hash: *aliceChanTXID,
|
Hash: *aliceChanTXID,
|
||||||
@ -411,21 +487,22 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
|||||||
// The network topology should now look like: Carol -> Alice -> Bob
|
// The network topology should now look like: Carol -> Alice -> Bob
|
||||||
carol, err := net.NewNode(nil)
|
carol, err := net.NewNode(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new nodes: %v", err)
|
ct.Errorf("unable to create new nodes: %v", err)
|
||||||
}
|
}
|
||||||
if err := net.ConnectNodes(ctxb, carol, net.Alice); err != nil {
|
if err := net.ConnectNodes(ctxb, carol, net.Alice); err != nil {
|
||||||
t.Fatalf("unable to connect carol to alice: %v", err)
|
ct.Errorf("unable to connect carol to alice: %v", err)
|
||||||
}
|
}
|
||||||
err = net.SendCoins(ctxb, btcutil.SatoshiPerBitcoin, carol)
|
err = net.SendCoins(ctxb, btcutil.SatoshiPerBitcoin, carol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to send coins to carol: %v", err)
|
ct.Errorf("unable to send coins to carol: %v", err)
|
||||||
}
|
}
|
||||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
chanPointCarol := openChannelAndAssert(t, net, ctxt, carol, net.Alice,
|
chanPointCarol := openChannelAndAssert(ct, net, ctxt, carol,
|
||||||
chanAmt)
|
net.Alice, chanAmt)
|
||||||
|
|
||||||
carolChanTXID, err := wire.NewShaHash(chanPointCarol.FundingTxid)
|
carolChanTXID, err := wire.NewShaHash(chanPointCarol.FundingTxid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create sha hash: %v", err)
|
ct.Errorf("unable to create sha hash: %v", err)
|
||||||
}
|
}
|
||||||
carolFundPoint := wire.OutPoint{
|
carolFundPoint := wire.OutPoint{
|
||||||
Hash: *carolChanTXID,
|
Hash: *carolChanTXID,
|
||||||
@ -446,7 +523,7 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
|||||||
}
|
}
|
||||||
resp, err := net.Bob.AddInvoice(ctxb, invoice)
|
resp, err := net.Bob.AddInvoice(ctxb, invoice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to add invoice: %v", err)
|
ct.Errorf("unable to add invoice: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rHashes[i] = resp.RHash
|
rHashes[i] = resp.RHash
|
||||||
@ -459,10 +536,10 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
|||||||
req := &lnrpc.ShowRoutingTableRequest{}
|
req := &lnrpc.ShowRoutingTableRequest{}
|
||||||
routingResp, err := carol.ShowRoutingTable(ctxb, req)
|
routingResp, err := carol.ShowRoutingTable(ctxb, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to query for carol's routing table: %v", err)
|
ct.Errorf("unable to query for carol's routing table: %v", err)
|
||||||
}
|
}
|
||||||
if len(routingResp.Channels) != 2 {
|
if len(routingResp.Channels) != 2 {
|
||||||
t.Fatalf("only two channels should be seen as active in the "+
|
ct.Errorf("only two channels should be seen as active in the "+
|
||||||
"network, instead %v are", len(routingResp.Channels))
|
"network, instead %v are", len(routingResp.Channels))
|
||||||
}
|
}
|
||||||
for _, link := range routingResp.Channels {
|
for _, link := range routingResp.Channels {
|
||||||
@ -476,7 +553,7 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
|||||||
link.Id2 == net.Alice.PubKeyStr:
|
link.Id2 == net.Alice.PubKeyStr:
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unkown link within routing "+
|
ct.Errorf("unkown link within routing "+
|
||||||
"table: %v", spew.Sdump(link))
|
"table: %v", spew.Sdump(link))
|
||||||
}
|
}
|
||||||
case link.Outpoint == carolFundPoint.String():
|
case link.Outpoint == carolFundPoint.String():
|
||||||
@ -488,11 +565,11 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
|||||||
link.Id2 == net.Alice.PubKeyStr:
|
link.Id2 == net.Alice.PubKeyStr:
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unkown link within routing "+
|
ct.Errorf("unkown link within routing "+
|
||||||
"table: %v", spew.Sdump(link))
|
"table: %v", spew.Sdump(link))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unkown channel %v found in routing table, "+
|
ct.Errorf("unkown channel %v found in routing table, "+
|
||||||
"only %v and %v should exist", link.Outpoint,
|
"only %v and %v should exist", link.Outpoint,
|
||||||
aliceFundPoint, carolFundPoint)
|
aliceFundPoint, carolFundPoint)
|
||||||
}
|
}
|
||||||
@ -501,7 +578,7 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
|||||||
// Using Carol as the source, pay to the 5 invoices from Bob created above.
|
// Using Carol as the source, pay to the 5 invoices from Bob created above.
|
||||||
carolPayStream, err := carol.SendPayment(ctxb)
|
carolPayStream, err := carol.SendPayment(ctxb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create payment stream for carol: %v", err)
|
ct.Errorf("unable to create payment stream for carol: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concurrently pay off all 5 of Bob's invoices. Each of the goroutines
|
// Concurrently pay off all 5 of Bob's invoices. Each of the goroutines
|
||||||
@ -518,10 +595,10 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
if err := carolPayStream.Send(sendReq); err != nil {
|
if err := carolPayStream.Send(sendReq); err != nil {
|
||||||
t.Fatalf("unable to send payment: %v", err)
|
ct.Errorf("unable to send payment: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := carolPayStream.Recv(); err != nil {
|
if _, err := carolPayStream.Recv(); err != nil {
|
||||||
t.Fatalf("unable to recv pay resp: %v", err)
|
ct.Errorf("unable to recv pay resp: %v", err)
|
||||||
}
|
}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
@ -535,16 +612,18 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Second * 10):
|
case <-time.After(time.Second * 10):
|
||||||
t.Fatalf("HLTC's not cleared after 10 seconds")
|
ct.Errorf("HLTC's not cleared after 10 seconds")
|
||||||
case <-finClear:
|
case <-finClear:
|
||||||
}
|
}
|
||||||
|
|
||||||
assertAsymmetricBalance := func(node *lightningNode,
|
assertAsymmetricBalance := func(node *lightningNode,
|
||||||
chanPoint *wire.OutPoint, localBalance, remoteBalance int64) {
|
chanPoint *wire.OutPoint, localBalance,
|
||||||
|
remoteBalance int64) {
|
||||||
|
|
||||||
listReq := &lnrpc.ListChannelsRequest{}
|
listReq := &lnrpc.ListChannelsRequest{}
|
||||||
resp, err := node.ListChannels(ctxb, listReq)
|
resp, err := node.ListChannels(ctxb, listReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to for node's channels: %v", err)
|
ct.Errorf("unable to for node's channels: %v", err)
|
||||||
}
|
}
|
||||||
for _, channel := range resp.Channels {
|
for _, channel := range resp.Channels {
|
||||||
if channel.ChannelPoint != chanPoint.String() {
|
if channel.ChannelPoint != chanPoint.String() {
|
||||||
@ -553,12 +632,12 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
|||||||
|
|
||||||
if channel.LocalBalance != localBalance ||
|
if channel.LocalBalance != localBalance ||
|
||||||
channel.RemoteBalance != remoteBalance {
|
channel.RemoteBalance != remoteBalance {
|
||||||
t.Fatalf("incorrect balances: %v",
|
ct.Errorf("incorrect balances: %v",
|
||||||
spew.Sdump(channel))
|
spew.Sdump(channel))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.Fatalf("channel not found")
|
ct.Errorf("channel not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point all the channels within our proto network should be
|
// At this point all the channels within our proto network should be
|
||||||
@ -575,12 +654,12 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) {
|
|||||||
assertAsymmetricBalance(net.Bob, &aliceFundPoint, sinkBal, sourceBal)
|
assertAsymmetricBalance(net.Bob, &aliceFundPoint, sinkBal, sourceBal)
|
||||||
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
closeChannelAndAssert(t, net, ctxt, net.Alice, chanPointAlice)
|
closeChannelAndAssert(ct, net, ctxt, net.Alice, chanPointAlice)
|
||||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
closeChannelAndAssert(t, net, ctxt, carol, chanPointCarol)
|
closeChannelAndAssert(ct, net, ctxt, carol, chanPointCarol)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInvoiceSubscriptions(net *networkHarness, t *testing.T) {
|
func testInvoiceSubscriptions(net *networkHarness, ct *CT) {
|
||||||
const chanAmt = btcutil.Amount(500000)
|
const chanAmt = btcutil.Amount(500000)
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
timeout := time.Duration(time.Second * 5)
|
timeout := time.Duration(time.Second * 5)
|
||||||
@ -588,7 +667,7 @@ func testInvoiceSubscriptions(net *networkHarness, t *testing.T) {
|
|||||||
// Open a channel with 500k satoshis between Alice and Bob with Alice
|
// Open a channel with 500k satoshis between Alice and Bob with Alice
|
||||||
// being the sole funder of the channel.
|
// being the sole funder of the channel.
|
||||||
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||||
chanPoint := openChannelAndAssert(t, net, ctxt, net.Alice, net.Bob,
|
chanPoint := openChannelAndAssert(ct, net, ctxt, net.Alice, net.Bob,
|
||||||
chanAmt)
|
chanAmt)
|
||||||
|
|
||||||
// Next create a new invoice for Bob requesting 1k satoshis.
|
// Next create a new invoice for Bob requesting 1k satoshis.
|
||||||
@ -601,7 +680,7 @@ func testInvoiceSubscriptions(net *networkHarness, t *testing.T) {
|
|||||||
}
|
}
|
||||||
invoiceResp, err := net.Bob.AddInvoice(ctxb, invoice)
|
invoiceResp, err := net.Bob.AddInvoice(ctxb, invoice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to add invoice: %v", err)
|
ct.Errorf("unable to add invoice: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new invoice subscription client for Bob, the notification
|
// Create a new invoice subscription client for Bob, the notification
|
||||||
@ -609,23 +688,23 @@ func testInvoiceSubscriptions(net *networkHarness, t *testing.T) {
|
|||||||
req := &lnrpc.InvoiceSubscription{}
|
req := &lnrpc.InvoiceSubscription{}
|
||||||
bobInvoiceSubscription, err := net.Bob.SubscribeInvoices(ctxb, req)
|
bobInvoiceSubscription, err := net.Bob.SubscribeInvoices(ctxb, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to subscribe to bob's invoice updates: %v", err)
|
ct.Errorf("unable to subscribe to bob's invoice updates: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSent := make(chan struct{})
|
updateSent := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
invoiceUpdate, err := bobInvoiceSubscription.Recv()
|
invoiceUpdate, err := bobInvoiceSubscription.Recv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to recv invoice update: %v", err)
|
ct.Errorf("unable to recv invoice update: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The invoice update should exactly match the invoice created
|
// The invoice update should exactly match the invoice created
|
||||||
// above, but should now be settled.
|
// above, but should now be settled.
|
||||||
if !invoiceUpdate.Settled {
|
if !invoiceUpdate.Settled {
|
||||||
t.Fatalf("invoice not settled but shoudl be")
|
ct.Errorf("invoice not settled but shoudl be")
|
||||||
}
|
}
|
||||||
if !bytes.Equal(invoiceUpdate.RPreimage, invoice.RPreimage) {
|
if !bytes.Equal(invoiceUpdate.RPreimage, invoice.RPreimage) {
|
||||||
t.Fatalf("payment preimages don't match: expected %v, got %v",
|
ct.Errorf("payment preimages don't match: expected %v, got %v",
|
||||||
invoice.RPreimage, invoiceUpdate.RPreimage)
|
invoice.RPreimage, invoiceUpdate.RPreimage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,7 +715,7 @@ func testInvoiceSubscriptions(net *networkHarness, t *testing.T) {
|
|||||||
// which should finalize and settle the invoice.
|
// which should finalize and settle the invoice.
|
||||||
sendStream, err := net.Alice.SendPayment(ctxb)
|
sendStream, err := net.Alice.SendPayment(ctxb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create alice payment stream: %v", err)
|
ct.Errorf("unable to create alice payment stream: %v", err)
|
||||||
}
|
}
|
||||||
sendReq := &lnrpc.SendRequest{
|
sendReq := &lnrpc.SendRequest{
|
||||||
PaymentHash: invoiceResp.RHash,
|
PaymentHash: invoiceResp.RHash,
|
||||||
@ -644,24 +723,24 @@ func testInvoiceSubscriptions(net *networkHarness, t *testing.T) {
|
|||||||
Amt: paymentAmt,
|
Amt: paymentAmt,
|
||||||
}
|
}
|
||||||
if err := sendStream.Send(sendReq); err != nil {
|
if err := sendStream.Send(sendReq); err != nil {
|
||||||
t.Fatalf("unable to send payment: %v", err)
|
ct.Errorf("unable to send payment: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := sendStream.Recv(); err != nil {
|
if _, err := sendStream.Recv(); err != nil {
|
||||||
t.Fatalf("error when attempting recv: %v", err)
|
ct.Errorf("error when attempting recv: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Second * 5):
|
case <-time.After(time.Second * 5):
|
||||||
t.Fatalf("update not sent after 5 seconds")
|
ct.Errorf("update not sent after 5 seconds")
|
||||||
case <-updateSent: // Fall through on success
|
case <-updateSent: // Fall through on success
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
closeChannelAndAssert(t, net, ctxt, net.Alice, chanPoint)
|
closeChannelAndAssert(ct, net, ctxt, net.Alice, chanPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testBasicChannelCreation test multiple channel opening and closing.
|
// testBasicChannelCreation test multiple channel opening and closing.
|
||||||
func testBasicChannelCreation(net *networkHarness, t *testing.T) {
|
func testBasicChannelCreation(net *networkHarness, ct *CT) {
|
||||||
timeout := time.Duration(time.Second * 5)
|
timeout := time.Duration(time.Second * 5)
|
||||||
ctx, _ := context.WithTimeout(context.Background(), timeout)
|
ctx, _ := context.WithTimeout(context.Background(), timeout)
|
||||||
|
|
||||||
@ -673,98 +752,193 @@ func testBasicChannelCreation(net *networkHarness, t *testing.T) {
|
|||||||
// channel has been properly open on-chain.
|
// channel has been properly open on-chain.
|
||||||
chanPoints := make([]*lnrpc.ChannelPoint, num)
|
chanPoints := make([]*lnrpc.ChannelPoint, num)
|
||||||
for i := 0; i < num; i++ {
|
for i := 0; i < num; i++ {
|
||||||
chanPoints[i] = openChannelAndAssert(t, net, ctx, net.Alice,
|
chanPoints[i] = openChannelAndAssert(ct, net, ctx, net.Alice,
|
||||||
net.Bob, amount)
|
net.Bob, amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the channel between Alice and Bob, asserting that the
|
// Close the channel between Alice and Bob, asserting that the
|
||||||
// channel has been properly closed on-chain.
|
// channel has been properly closed on-chain.
|
||||||
for _, chanPoint := range chanPoints {
|
for _, chanPoint := range chanPoints {
|
||||||
closeChannelAndAssert(t, net, ctx, net.Alice, chanPoint)
|
closeChannelAndAssert(ct, net, ctx, net.Alice, chanPoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type lndTestCase func(net *networkHarness, t *testing.T)
|
// testMaxPendingChannels checks that error is returned from remote peer if
|
||||||
|
// max pending channel number was exceeded and that '--maxpendingchannels' flag
|
||||||
|
// exists and works properly.
|
||||||
|
func testMaxPendingChannels(net *networkHarness, ct *CT) {
|
||||||
|
maxPendingChannels := defaultMaxPendingChannels + 1
|
||||||
|
amount := btcutil.Amount(btcutil.SatoshiPerBitcoin)
|
||||||
|
|
||||||
var lndTestCases = map[string]lndTestCase{
|
timeout := time.Duration(time.Second * 10)
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), timeout)
|
||||||
|
|
||||||
|
// Create a new node (Carol) with greater number of max pending
|
||||||
|
// channels.
|
||||||
|
args := []string{
|
||||||
|
fmt.Sprintf("--maxpendingchannels=%v", maxPendingChannels),
|
||||||
|
}
|
||||||
|
|
||||||
|
carol, err := net.NewNode(args)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ct.Errorf("unable to create new nodes: %v", err)
|
||||||
|
}
|
||||||
|
if err := net.ConnectNodes(ctx, net.Alice, carol); err != nil {
|
||||||
|
ct.Errorf("unable to connect carol to alice: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
carolBalance := btcutil.Amount(maxPendingChannels) * amount
|
||||||
|
if err := net.SendCoins(ctx, carolBalance, carol); err != nil {
|
||||||
|
ct.Errorf("unable to send coins to carol: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send open channel requests without generating new blocks thereby
|
||||||
|
// increasing pool of pending channels. Then check that we can't
|
||||||
|
// open the channel if the number of pending channels exceed
|
||||||
|
// max value.
|
||||||
|
openStreams := make([]lnrpc.Lightning_OpenChannelClient, maxPendingChannels)
|
||||||
|
for i := 0; i < maxPendingChannels; i++ {
|
||||||
|
stream, err := net.OpenChannel(ctx, net.Alice, carol, amount, 1)
|
||||||
|
if err != nil {
|
||||||
|
ct.Errorf("unable to open channel: %v", err)
|
||||||
|
}
|
||||||
|
openStreams[i] = stream
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carol exhausted available amount of pending channels, next open
|
||||||
|
// channel request should cause ErrorGeneric to be sent back to Alice.
|
||||||
|
_, err = net.OpenChannel(ctx, net.Alice, carol, amount, 1)
|
||||||
|
if err == nil {
|
||||||
|
ct.Errorf("error wasn't received")
|
||||||
|
} else if grpc.Code(err) != OpenChannelFundingError {
|
||||||
|
ct.Errorf("not expected error was received : %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now our channels are in pending state, in order to not
|
||||||
|
// interfere with other tests we should clean up - complete opening
|
||||||
|
// of the channel and then close it.
|
||||||
|
|
||||||
|
// Mine a block, then wait for node's to notify us that the channel
|
||||||
|
// has been opened. The funding transactions should be found within the
|
||||||
|
// newly mined block.
|
||||||
|
block := mineBlocks(ct, net, 1)[0]
|
||||||
|
|
||||||
|
chanPoints := make([]*lnrpc.ChannelPoint, maxPendingChannels)
|
||||||
|
|
||||||
|
for i, stream := range openStreams {
|
||||||
|
fundingChanPoint, err := net.WaitForChannelOpen(ctx, stream)
|
||||||
|
if err != nil {
|
||||||
|
ct.Errorf("error while waiting for channel open: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fundingTxID, err := wire.NewShaHash(fundingChanPoint.FundingTxid)
|
||||||
|
if err != nil {
|
||||||
|
ct.Errorf("unable to create sha hash: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTxInBlock(ct, block, fundingTxID)
|
||||||
|
|
||||||
|
// The channel should be listed in the peer information
|
||||||
|
// returned by both peers.
|
||||||
|
chanPoint := wire.OutPoint{
|
||||||
|
Hash: *fundingTxID,
|
||||||
|
Index: fundingChanPoint.OutputIndex,
|
||||||
|
}
|
||||||
|
if err := net.AssertChannelExists(ctx, net.Alice, &chanPoint); err != nil {
|
||||||
|
ct.Errorf("unable to assert channel existence: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
chanPoints[i] = fundingChanPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally close the channel between Alice and Carol, asserting that the
|
||||||
|
// channel has been properly closed on-chain.
|
||||||
|
for _, chanPoint := range chanPoints {
|
||||||
|
closeChannelAndAssert(ct, net, ctx, net.Alice, chanPoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCase func(net *networkHarness, ct *CT)
|
||||||
|
|
||||||
|
var testCases = map[string]testCase{
|
||||||
"basic funding flow": testBasicChannelFunding,
|
"basic funding flow": testBasicChannelFunding,
|
||||||
"channel force closure": testChannelForceClosure,
|
"channel force closure": testChannelForceClosure,
|
||||||
"channel balance": testChannelBalance,
|
"channel balance": testChannelBalance,
|
||||||
"single hop invoice": testSingleHopInvoice,
|
"single hop invoice": testSingleHopInvoice,
|
||||||
|
"max pending channel": testMaxPendingChannels,
|
||||||
"multi-hop payments": testMultiHopPayments,
|
"multi-hop payments": testMultiHopPayments,
|
||||||
|
"multiple channel creation": testBasicChannelCreation,
|
||||||
"invoice update subscription": testInvoiceSubscriptions,
|
"invoice update subscription": testInvoiceSubscriptions,
|
||||||
"multiple channel creation": testBasicChannelCreation,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLightningNetworkDaemon performs a series of integration tests amongst a
|
// TestLightningNetworkDaemon performs a series of integration tests amongst a
|
||||||
// programmatically driven network of lnd nodes.
|
// programmatically driven network of lnd nodes.
|
||||||
func TestLightningNetworkDaemon(t *testing.T) {
|
func TestLightningNetworkDaemon(t *testing.T) {
|
||||||
var (
|
ct := NewCT(t)
|
||||||
btcdHarness *rpctest.Harness
|
|
||||||
lightningNetwork *networkHarness
|
|
||||||
currentTest string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
// If one of the integration tests caused a panic within the main
|
|
||||||
// goroutine, then tear down all the harnesses in order to avoid
|
|
||||||
// any leaked processes.
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
fmt.Println("recovering from test panic: ", r)
|
|
||||||
if err := btcdHarness.TearDown(); err != nil {
|
|
||||||
fmt.Println("unable to tear btcd harnesses: ", err)
|
|
||||||
}
|
|
||||||
if err := lightningNetwork.TearDownAll(); err != nil {
|
|
||||||
fmt.Println("unable to tear lnd harnesses: ", err)
|
|
||||||
}
|
|
||||||
t.Fatalf("test %v panicked: %s", currentTest, debug.Stack())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// First create the network harness to gain access to its
|
// First create the network harness to gain access to its
|
||||||
// 'OnTxAccepted' call back.
|
// 'OnTxAccepted' call back.
|
||||||
lightningNetwork, err = newNetworkHarness()
|
lndHarness, err := newNetworkHarness()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create lightning network harness: %v", err)
|
ct.Fatalf("unable to create lightning network harness: %v", err)
|
||||||
}
|
}
|
||||||
defer lightningNetwork.TearDownAll()
|
defer lndHarness.TearDownAll()
|
||||||
|
|
||||||
handlers := &btcrpcclient.NotificationHandlers{
|
handlers := &btcrpcclient.NotificationHandlers{
|
||||||
OnTxAccepted: lightningNetwork.OnTxAccepted,
|
OnTxAccepted: lndHarness.OnTxAccepted,
|
||||||
}
|
}
|
||||||
|
|
||||||
// First create an instance of the btcd's rpctest.Harness. This will be
|
// 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
|
// used to fund the wallets of the nodes within the test network and to
|
||||||
// drive blockchain related events within the network.
|
// drive blockchain related events within the network.
|
||||||
btcdHarness, err = rpctest.New(harnessNetParams, handlers, nil)
|
btcdHarness, err := rpctest.New(harnessNetParams, handlers, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create mining node: %v", err)
|
ct.Fatalf("unable to create mining node: %v", err)
|
||||||
}
|
}
|
||||||
defer btcdHarness.TearDown()
|
defer btcdHarness.TearDown()
|
||||||
if err = btcdHarness.SetUp(true, 50); err != nil {
|
if err := btcdHarness.SetUp(true, 50); err != nil {
|
||||||
t.Fatalf("unable to set up mining node: %v", err)
|
ct.Fatalf("unable to set up mining node: %v", err)
|
||||||
}
|
}
|
||||||
if err := btcdHarness.Node.NotifyNewTransactions(false); err != nil {
|
if err := btcdHarness.Node.NotifyNewTransactions(false); err != nil {
|
||||||
t.Fatalf("unable to request transaction notifications: %v", err)
|
ct.Fatalf("unable to request transaction notifications: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the btcd harness created, we can now complete the
|
// With the btcd harness created, we can now complete the
|
||||||
// initialization of the network. args - list of lnd arguments,
|
// initialization of the network. args - list of lnd arguments,
|
||||||
// example: "--debuglevel=debug"
|
// example: "--debuglevel=debug"
|
||||||
// TODO(roasbeef): create master balanced channel with all the monies?
|
// TODO(roasbeef): create master balanced channel with all the monies?
|
||||||
if err := lightningNetwork.InitializeSeedNodes(btcdHarness, nil); err != nil {
|
if err := lndHarness.InitializeSeedNodes(btcdHarness, nil); err != nil {
|
||||||
t.Fatalf("unable to initialize seed nodes: %v", err)
|
ct.Fatalf("unable to initialize seed nodes: %v", err)
|
||||||
}
|
}
|
||||||
if err = lightningNetwork.SetUp(); err != nil {
|
if err = lndHarness.SetUp(); err != nil {
|
||||||
t.Fatalf("unable to set up test lightning network: %v", err)
|
ct.Fatalf("unable to set up test lightning network: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("Running %v integration tests", len(lndTestCases))
|
ct.Logf("Running %v integration tests", len(testCases))
|
||||||
for testName, lnTest := range lndTestCases {
|
|
||||||
t.Logf("Executing test %v", testName)
|
|
||||||
|
|
||||||
currentTest = testName
|
for name, test := range testCases {
|
||||||
lnTest(lightningNetwork, t)
|
errChan := ct.RunTest(lndHarness, test)
|
||||||
|
|
||||||
|
select {
|
||||||
|
// Receive both types of err - panic and fatal from
|
||||||
|
// one channel and raise the fatal in main goroutine.
|
||||||
|
case err := <-errChan:
|
||||||
|
if err != nil {
|
||||||
|
ct.Fatalf("Fail: (%v): exited with error: \n%v",
|
||||||
|
name, err)
|
||||||
|
}
|
||||||
|
ct.Logf("Successed: (%v)", name)
|
||||||
|
|
||||||
|
// In this case lightning node process finished with error
|
||||||
|
// status. It might be because of wrong flag, or it might
|
||||||
|
// be because of nil pointer access. Who knows!? Who knows...
|
||||||
|
// TODO(andrew.shvv) When two nodes are closing simultanisly
|
||||||
|
// it leads to panic - 'sending to the closed channel'. Fix it?
|
||||||
|
case err := <-lndHarness.lndErrorChan:
|
||||||
|
ct.Fatalf("Fail: (%v): lnd finished with error "+
|
||||||
|
"(stderr): \n%v", name, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,8 +152,8 @@ type PaymentDescriptor struct {
|
|||||||
// commitment represents a commitment to a new state within an active channel.
|
// commitment represents a commitment to a new state within an active channel.
|
||||||
// New commitments can be initiated by either side. Commitments are ordered
|
// New commitments can be initiated by either side. Commitments are ordered
|
||||||
// into a commitment chain, with one existing for both parties. Each side can
|
// into a commitment chain, with one existing for both parties. Each side can
|
||||||
// independatly extend the other side's commitment chain, up to a certain
|
// independently extend the other side's commitment chain, up to a certain
|
||||||
// "revocation window", which once reached, dissallows new commitments until
|
// "revocation window", which once reached, disallows new commitments until
|
||||||
// the local nodes receives the revocation for the remote node's chain tail.
|
// the local nodes receives the revocation for the remote node's chain tail.
|
||||||
type commitment struct {
|
type commitment struct {
|
||||||
// height represents the commitment height of this commitment, or the
|
// height represents the commitment height of this commitment, or the
|
||||||
@ -162,7 +162,7 @@ type commitment struct {
|
|||||||
|
|
||||||
// [our|their]MessageIndex are indexes into the HTLC log, up to which
|
// [our|their]MessageIndex are indexes into the HTLC log, up to which
|
||||||
// this commitment transaction includes. These indexes allow both sides
|
// this commitment transaction includes. These indexes allow both sides
|
||||||
// to independantly, and concurrent send create new commitments. Each
|
// to independently, and concurrent send create new commitments. Each
|
||||||
// new commitment sent to the remote party includes an index in the
|
// new commitment sent to the remote party includes an index in the
|
||||||
// shared log which details which of their updates we're including in
|
// shared log which details which of their updates we're including in
|
||||||
// this new commitment.
|
// this new commitment.
|
||||||
@ -232,7 +232,7 @@ func (c *commitment) toChannelDelta() (*channeldb.ChannelDelta, error) {
|
|||||||
// commitmentChain represents a chain of unrevoked commitments. The tail of the
|
// commitmentChain represents a chain of unrevoked commitments. The tail of the
|
||||||
// chain is the latest fully signed, yet unrevoked commitment. Two chains are
|
// chain is the latest fully signed, yet unrevoked commitment. Two chains are
|
||||||
// tracked, one for the local node, and another for the remote node. New
|
// tracked, one for the local node, and another for the remote node. New
|
||||||
// commitmetns we create locally extend the remote node's chain, and vice
|
// commitments we create locally extend the remote node's chain, and vice
|
||||||
// versa. Commitment chains are allowed to grow to a bounded length, after
|
// versa. Commitment chains are allowed to grow to a bounded length, after
|
||||||
// which the tail needs to be "dropped" before new commitments can be received.
|
// which the tail needs to be "dropped" before new commitments can be received.
|
||||||
// The tail is "dropped" when the owner of the chain sends a revocation for the
|
// The tail is "dropped" when the owner of the chain sends a revocation for the
|
||||||
@ -1278,7 +1278,7 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.HTLCAddRequest) uint32 {
|
|||||||
return pd.Index
|
return pd.Index
|
||||||
}
|
}
|
||||||
|
|
||||||
// SettleHTLC attempst to settle an existing outstanding received HTLC. The
|
// SettleHTLC attempts to settle an existing outstanding received HTLC. The
|
||||||
// remote log index of the HTLC settled is returned in order to facilitate
|
// remote log index of the HTLC settled is returned in order to facilitate
|
||||||
// creating the corresponding wire message. In the case the supplied pre-image
|
// creating the corresponding wire message. In the case the supplied pre-image
|
||||||
// is invalid, an error is returned.
|
// is invalid, an error is returned.
|
||||||
|
@ -302,7 +302,7 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
|
|||||||
// local node (Alice in this case) creates a new outgoing HTLC to bob, commits
|
// local node (Alice in this case) creates a new outgoing HTLC to bob, commits
|
||||||
// this change, then bob immediately commits a settlement of the HTLC after the
|
// this change, then bob immediately commits a settlement of the HTLC after the
|
||||||
// initial add is fully commited in both commit chains.
|
// initial add is fully commited in both commit chains.
|
||||||
// TODO(roasbeef): write higher level framework to excercise various states of
|
// TODO(roasbeef): write higher level framework to exercise various states of
|
||||||
// the state machine
|
// the state machine
|
||||||
// * DSL language perhaps?
|
// * DSL language perhaps?
|
||||||
// * constructed via input/output files
|
// * constructed via input/output files
|
||||||
@ -348,7 +348,7 @@ func TestSimpleAddSettleWorkflow(t *testing.T) {
|
|||||||
t.Fatalf("alice unable to sign commitment: %v", err)
|
t.Fatalf("alice unable to sign commitment: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bob recieves this signature message, then generates a signature for
|
// Bob receives this signature message, then generates a signature for
|
||||||
// Alice's commitment transaction, and the revocation to his prior
|
// Alice's commitment transaction, and the revocation to his prior
|
||||||
// commitment transaction.
|
// commitment transaction.
|
||||||
if err := bobChannel.ReceiveNewCommitment(aliceSig, bobLogIndex); err != nil {
|
if err := bobChannel.ReceiveNewCommitment(aliceSig, bobLogIndex); err != nil {
|
||||||
@ -363,12 +363,12 @@ func TestSimpleAddSettleWorkflow(t *testing.T) {
|
|||||||
t.Fatalf("unable to generate bob revocation: %v", err)
|
t.Fatalf("unable to generate bob revocation: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alice then proceses bob's signature, and generates a revocation for
|
// Alice then processes bob's signature, and generates a revocation for
|
||||||
// bob.
|
// bob.
|
||||||
if err := aliceChannel.ReceiveNewCommitment(bobSig, aliceLogIndex); err != nil {
|
if err := aliceChannel.ReceiveNewCommitment(bobSig, aliceLogIndex); err != nil {
|
||||||
t.Fatalf("alice unable to process bob's new commitment: %v", err)
|
t.Fatalf("alice unable to process bob's new commitment: %v", err)
|
||||||
}
|
}
|
||||||
// Alice then processes this revocation, sending her own revovation for
|
// Alice then processes this revocation, sending her own recovation for
|
||||||
// her prior commitment transaction. Alice shouldn't have any HTLC's to
|
// her prior commitment transaction. Alice shouldn't have any HTLC's to
|
||||||
// forward since she's sending anoutgoing HTLC.
|
// forward since she's sending anoutgoing HTLC.
|
||||||
if htlcs, err := aliceChannel.ReceiveRevocation(bobRevocation); err != nil {
|
if htlcs, err := aliceChannel.ReceiveRevocation(bobRevocation); err != nil {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package lnwallet
|
package lnwallet
|
||||||
|
|
||||||
// Config..
|
// Config is a struct which houses configuration parameters which modify the
|
||||||
|
// behaviour of LightningWallet.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// default csv time
|
// default csv time
|
||||||
// default cltv time
|
// default cltv time
|
||||||
|
@ -62,8 +62,8 @@ func (e *ErrInsufficientFunds) Error() string {
|
|||||||
|
|
||||||
// initFundingReserveReq is the first message sent to initiate the workflow
|
// initFundingReserveReq is the first message sent to initiate the workflow
|
||||||
// required to open a payment channel with a remote peer. The initial required
|
// required to open a payment channel with a remote peer. The initial required
|
||||||
// paramters are configurable accross channels. These paramters are to be chosen
|
// parameters are configurable across channels. These parameters are to be
|
||||||
// depending on the fee climate within the network, and time value of funds to
|
// chosen depending on the fee climate within the network, and time value of funds to
|
||||||
// be locked up within the channel. Upon success a ChannelReservation will be
|
// be locked up within the channel. Upon success a ChannelReservation will be
|
||||||
// created in order to track the lifetime of this pending channel. Outputs
|
// created in order to track the lifetime of this pending channel. Outputs
|
||||||
// selected will be 'locked', making them unavailable, for any other pending
|
// selected will be 'locked', making them unavailable, for any other pending
|
||||||
@ -239,7 +239,7 @@ type LightningWallet struct {
|
|||||||
|
|
||||||
// This mutex MUST be held when performing coin selection in order to
|
// This mutex MUST be held when performing coin selection in order to
|
||||||
// avoid inadvertently creating multiple funding transaction which
|
// avoid inadvertently creating multiple funding transaction which
|
||||||
// double spend inputs accross each other.
|
// double spend inputs across each other.
|
||||||
coinSelectMtx sync.RWMutex
|
coinSelectMtx sync.RWMutex
|
||||||
|
|
||||||
// A wrapper around a namespace within boltdb reserved for ln-based
|
// A wrapper around a namespace within boltdb reserved for ln-based
|
||||||
@ -263,14 +263,14 @@ type LightningWallet struct {
|
|||||||
Signer Signer
|
Signer Signer
|
||||||
|
|
||||||
// chainIO is an instance of the BlockChainIO interface. chainIO is
|
// chainIO is an instance of the BlockChainIO interface. chainIO is
|
||||||
// used to lookup the existance of outputs within the utxo set.
|
// used to lookup the existence of outputs within the UTXO set.
|
||||||
chainIO BlockChainIO
|
chainIO BlockChainIO
|
||||||
|
|
||||||
// rootKey is the root HD key dervied from a WalletController private
|
// rootKey is the root HD key derived from a WalletController private
|
||||||
// key. This rootKey is used to derive all LN specific secrets.
|
// key. This rootKey is used to derive all LN specific secrets.
|
||||||
rootKey *hdkeychain.ExtendedKey
|
rootKey *hdkeychain.ExtendedKey
|
||||||
|
|
||||||
// All messages to the wallet are to be sent accross this channel.
|
// All messages to the wallet are to be sent across this channel.
|
||||||
msgChan chan interface{}
|
msgChan chan interface{}
|
||||||
|
|
||||||
// Incomplete payment channels are stored in the map below. An intent
|
// Incomplete payment channels are stored in the map below. An intent
|
||||||
@ -423,7 +423,7 @@ func (l *LightningWallet) GetIdentitykey() (*btcec.PrivateKey, error) {
|
|||||||
return identityKey.ECPrivKey()
|
return identityKey.ECPrivKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
// requestHandler is the primary goroutine(s) resposible for handling, and
|
// requestHandler is the primary goroutine(s) responsible for handling, and
|
||||||
// dispatching relies to all messages.
|
// dispatching relies to all messages.
|
||||||
func (l *LightningWallet) requestHandler() {
|
func (l *LightningWallet) requestHandler() {
|
||||||
out:
|
out:
|
||||||
@ -455,15 +455,15 @@ out:
|
|||||||
l.wg.Done()
|
l.wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitChannelReservation kicks off the 3-step workflow required to succesfully
|
// InitChannelReservation kicks off the 3-step workflow required to successfully
|
||||||
// open a payment channel with a remote node. As part of the funding
|
// open a payment channel with a remote node. As part of the funding
|
||||||
// reservation, the inputs selected for the funding transaction are 'locked'.
|
// reservation, the inputs selected for the funding transaction are 'locked'.
|
||||||
// This ensures that multiple channel reservations aren't double spending the
|
// This ensures that multiple channel reservations aren't double spending the
|
||||||
// same inputs in the funding transaction. If reservation initialization is
|
// same inputs in the funding transaction. If reservation initialization is
|
||||||
// succesful, a ChannelReservation containing our completed contribution is
|
// successful, a ChannelReservation containing our completed contribution is
|
||||||
// returned. Our contribution contains all the items neccessary to allow the
|
// returned. Our contribution contains all the items necessary to allow the
|
||||||
// counter party to build the funding transaction, and both versions of the
|
// counter party to build the funding transaction, and both versions of the
|
||||||
// commitment transaction. Otherwise, an error occured a nil pointer along with
|
// commitment transaction. Otherwise, an error occurred a nil pointer along with
|
||||||
// an error are returned.
|
// an error are returned.
|
||||||
//
|
//
|
||||||
// Once a ChannelReservation has been obtained, two additional steps must be
|
// Once a ChannelReservation has been obtained, two additional steps must be
|
||||||
@ -500,7 +500,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
|
|||||||
reservation := NewChannelReservation(totalCapacity, req.fundingAmount,
|
reservation := NewChannelReservation(totalCapacity, req.fundingAmount,
|
||||||
req.minFeeRate, l, id, req.numConfs)
|
req.minFeeRate, l, id, req.numConfs)
|
||||||
|
|
||||||
// Grab the mutex on the ChannelReservation to ensure thead-safety
|
// Grab the mutex on the ChannelReservation to ensure thread-safety
|
||||||
reservation.Lock()
|
reservation.Lock()
|
||||||
defer reservation.Unlock()
|
defer reservation.Unlock()
|
||||||
|
|
||||||
@ -568,9 +568,9 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
|
|||||||
l.fundingLimbo[id] = reservation
|
l.fundingLimbo[id] = reservation
|
||||||
l.limboMtx.Unlock()
|
l.limboMtx.Unlock()
|
||||||
|
|
||||||
// Funding reservation request succesfully handled. The funding inputs
|
// Funding reservation request successfully handled. The funding inputs
|
||||||
// will be marked as unavailable until the reservation is either
|
// will be marked as unavailable until the reservation is either
|
||||||
// completed, or cancecled.
|
// completed, or canceled.
|
||||||
req.resp <- reservation
|
req.resp <- reservation
|
||||||
req.err <- nil
|
req.err <- nil
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,18 @@ import (
|
|||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Is returned by remote peer when number of pending channels exceed max
|
||||||
|
// value.
|
||||||
|
ErrorMaxPendingChannels = 1
|
||||||
|
)
|
||||||
|
|
||||||
// ErrorGeneric represents a generic error bound to an exact channel. The
|
// ErrorGeneric represents a generic error bound to an exact channel. The
|
||||||
// message format is purposefully general in order to allow expressino of a wide
|
// message format is purposefully general in order to allow expression of a wide
|
||||||
// array of possible errors. Each ErrorGeneric message is directed at a particular
|
// array of possible errors. Each ErrorGeneric message is directed at a particular
|
||||||
// open channel referenced by ChannelPoint.
|
// open channel referenced by ChannelPoint.
|
||||||
type ErrorGeneric struct {
|
type ErrorGeneric struct {
|
||||||
// ChannelPoint references the active channel in which the error occured
|
// ChannelPoint references the active channel in which the error occurred
|
||||||
// within. A ChannelPoint of zeroHash:0 denotes this error applies to
|
// within. A ChannelPoint of zeroHash:0 denotes this error applies to
|
||||||
// the entire established connection.
|
// the entire established connection.
|
||||||
ChannelPoint *wire.OutPoint
|
ChannelPoint *wire.OutPoint
|
||||||
@ -22,9 +28,16 @@ type ErrorGeneric struct {
|
|||||||
ErrorID uint16
|
ErrorID uint16
|
||||||
|
|
||||||
// Problem is a human-readable string further elaborating upon the
|
// Problem is a human-readable string further elaborating upon the
|
||||||
// nature of the exact error. The maxmium allowed length of this
|
// nature of the exact error. The maximum allowed length of this
|
||||||
// message is 8192 bytes.
|
// message is 8192 bytes.
|
||||||
Problem string
|
Problem string
|
||||||
|
|
||||||
|
// PendingChannelID allows peers communicate errors in the context of a
|
||||||
|
// particular pending channel. With this field, once a peer reads an
|
||||||
|
// ErrorGeneric message with the PendingChannelID field set, then they
|
||||||
|
// can forward the error to the fundingManager who can handle it
|
||||||
|
// properly.
|
||||||
|
PendingChannelID uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewErrorGeneric creates a new ErrorGeneric message.
|
// NewErrorGeneric creates a new ErrorGeneric message.
|
||||||
@ -47,6 +60,7 @@ func (c *ErrorGeneric) Decode(r io.Reader, pver uint32) error {
|
|||||||
&c.ChannelPoint,
|
&c.ChannelPoint,
|
||||||
&c.ErrorID,
|
&c.ErrorID,
|
||||||
&c.Problem,
|
&c.Problem,
|
||||||
|
&c.PendingChannelID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -64,6 +78,7 @@ func (c *ErrorGeneric) Encode(w io.Writer, pver uint32) error {
|
|||||||
c.ChannelPoint,
|
c.ChannelPoint,
|
||||||
c.ErrorID,
|
c.ErrorID,
|
||||||
c.Problem,
|
c.Problem,
|
||||||
|
c.PendingChannelID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -110,5 +125,6 @@ func (c *ErrorGeneric) String() string {
|
|||||||
fmt.Sprintf("ChannelPoint:\t%d\n", c.ChannelPoint) +
|
fmt.Sprintf("ChannelPoint:\t%d\n", c.ChannelPoint) +
|
||||||
fmt.Sprintf("ErrorID:\t%d\n", c.ErrorID) +
|
fmt.Sprintf("ErrorID:\t%d\n", c.ErrorID) +
|
||||||
fmt.Sprintf("Problem:\t%s\n", c.Problem) +
|
fmt.Sprintf("Problem:\t%s\n", c.Problem) +
|
||||||
|
fmt.Sprintf("PendingChannelID:\t%s\n", c.PendingChannelID) +
|
||||||
fmt.Sprintf("--- End ErrorGeneric ---\n")
|
fmt.Sprintf("--- End ErrorGeneric ---\n")
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ func TestErrorGenericEncodeDecode(t *testing.T) {
|
|||||||
ChannelPoint: outpoint1,
|
ChannelPoint: outpoint1,
|
||||||
ErrorID: 99,
|
ErrorID: 99,
|
||||||
Problem: "Hello world!",
|
Problem: "Hello world!",
|
||||||
|
PendingChannelID: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next encode the EG message into an empty bytes buffer.
|
// Next encode the EG message into an empty bytes buffer.
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
// responder after the previously constructed funding transaction has achieved
|
// responder after the previously constructed funding transaction has achieved
|
||||||
// a sufficient number of confirmations. It is the initiator's duty to present
|
// a sufficient number of confirmations. It is the initiator's duty to present
|
||||||
// a proof of an open channel to the responder. Otherwise, responding node may
|
// a proof of an open channel to the responder. Otherwise, responding node may
|
||||||
// be vulernable to a resource exhasution attack wherein the a requesting node
|
// be vulnerable to a resource exhaustion attack wherein the a requesting node
|
||||||
// repeatedly negotiates funding transactions which are never broadcast. If the
|
// repeatedly negotiates funding transactions which are never broadcast. If the
|
||||||
// responding node commits resources to watch the chain for each funding
|
// responding node commits resources to watch the chain for each funding
|
||||||
// transaction, then this attack is very cheap for the initiator.
|
// transaction, then this attack is very cheap for the initiator.
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SingleFundingSignComplete is the message Bob sends to Alice which delivers
|
// SingleFundingSignComplete is the message Bob sends to Alice which delivers
|
||||||
// a signature for Alice's version of the commitment transaction. After thie
|
// a signature for Alice's version of the commitment transaction. After this
|
||||||
// message is received and processed by Alice, she is free to broadcast the
|
// message is received and processed by Alice, she is free to broadcast the
|
||||||
// funding transaction.
|
// funding transaction.
|
||||||
type SingleFundingSignComplete struct {
|
type SingleFundingSignComplete struct {
|
||||||
|
@ -19,6 +19,8 @@ import (
|
|||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/grpclog"
|
"google.golang.org/grpc/grpclog"
|
||||||
|
|
||||||
|
"bytes"
|
||||||
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/roasbeef/btcd/chaincfg"
|
"github.com/roasbeef/btcd/chaincfg"
|
||||||
"github.com/roasbeef/btcd/rpctest"
|
"github.com/roasbeef/btcd/rpctest"
|
||||||
@ -69,7 +71,7 @@ func generateListeningPorts() (int, int) {
|
|||||||
|
|
||||||
// lightningNode represents an instance of lnd running within our test network
|
// lightningNode represents an instance of lnd running within our test network
|
||||||
// harness. Each lightningNode instance also fully embedds an RPC client in
|
// harness. Each lightningNode instance also fully embedds an RPC client in
|
||||||
// order to programatically drive the node.
|
// order to pragmatically drive the node.
|
||||||
type lightningNode struct {
|
type lightningNode struct {
|
||||||
cfg *config
|
cfg *config
|
||||||
|
|
||||||
@ -159,14 +161,27 @@ func (l *lightningNode) genArgs() []string {
|
|||||||
// start launches a new process running lnd. Additionally, the PID of the
|
// start launches a new process running lnd. Additionally, the PID of the
|
||||||
// launched process is saved in order to possibly kill the process forcibly
|
// launched process is saved in order to possibly kill the process forcibly
|
||||||
// later.
|
// later.
|
||||||
func (l *lightningNode) start() error {
|
func (l *lightningNode) start(lndError chan error) error {
|
||||||
args := l.genArgs()
|
args := l.genArgs()
|
||||||
|
|
||||||
l.cmd = exec.Command("lnd", args...)
|
l.cmd = exec.Command("lnd", args...)
|
||||||
|
|
||||||
|
// Redirect stderr output to buffer
|
||||||
|
var errb bytes.Buffer
|
||||||
|
l.cmd.Stderr = &errb
|
||||||
|
|
||||||
if err := l.cmd.Start(); err != nil {
|
if err := l.cmd.Start(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// If lightning node process exited with error status
|
||||||
|
// then we should transmit stderr output in main process.
|
||||||
|
if err := l.cmd.Wait(); err != nil {
|
||||||
|
lndError <- errors.New(errb.String())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
pid, err := os.Create(filepath.Join(l.cfg.DataDir,
|
pid, err := os.Create(filepath.Join(l.cfg.DataDir,
|
||||||
fmt.Sprintf("%s.pid", l.nodeId)))
|
fmt.Sprintf("%s.pid", l.nodeId)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -234,7 +249,14 @@ func (l *lightningNode) cleanup() error {
|
|||||||
|
|
||||||
// stop attempts to stop the active lnd process.
|
// stop attempts to stop the active lnd process.
|
||||||
func (l *lightningNode) stop() error {
|
func (l *lightningNode) stop() error {
|
||||||
if l.cmd == nil || l.cmd.Process == nil {
|
|
||||||
|
// We should skip node stop in case:
|
||||||
|
// - start of the node wasn't initiated
|
||||||
|
// - process wasn't spawned
|
||||||
|
// - process already finished
|
||||||
|
if l.cmd == nil ||
|
||||||
|
l.cmd.Process == nil ||
|
||||||
|
(l.cmd.ProcessState != nil && l.cmd.ProcessState.Exited()) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,6 +298,10 @@ type networkHarness struct {
|
|||||||
seenTxns chan wire.ShaHash
|
seenTxns chan wire.ShaHash
|
||||||
watchRequests chan *watchRequest
|
watchRequests chan *watchRequest
|
||||||
|
|
||||||
|
// Channel for transmitting stderr output from failed lightning node
|
||||||
|
// to main process.
|
||||||
|
lndErrorChan chan error
|
||||||
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,6 +314,7 @@ func newNetworkHarness() (*networkHarness, error) {
|
|||||||
activeNodes: make(map[int]*lightningNode),
|
activeNodes: make(map[int]*lightningNode),
|
||||||
seenTxns: make(chan wire.ShaHash),
|
seenTxns: make(chan wire.ShaHash),
|
||||||
watchRequests: make(chan *watchRequest),
|
watchRequests: make(chan *watchRequest),
|
||||||
|
lndErrorChan: make(chan error),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,7 +372,7 @@ func (n *networkHarness) SetUp() error {
|
|||||||
go func() {
|
go func() {
|
||||||
var err error
|
var err error
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err = n.Alice.start(); err != nil {
|
if err = n.Alice.start(n.lndErrorChan); err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -353,7 +380,7 @@ func (n *networkHarness) SetUp() error {
|
|||||||
go func() {
|
go func() {
|
||||||
var err error
|
var err error
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err = n.Bob.start(); err != nil {
|
if err = n.Bob.start(n.lndErrorChan); err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -462,7 +489,7 @@ func (n *networkHarness) NewNode(extraArgs []string) (*lightningNode, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := node.start(); err != nil {
|
if err := node.start(n.lndErrorChan); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -573,9 +600,9 @@ func (n *networkHarness) WaitForTxBroadcast(ctx context.Context, txid wire.ShaHa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenChannel attemps to open a channel between srcNode and destNode with the
|
// OpenChannel attempts to open a channel between srcNode and destNode with the
|
||||||
// passed channel funding parameters. If the passed context has a timeout, then
|
// passed channel funding parameters. If the passed context has a timeout, then
|
||||||
// if the timeout is reeached before the channel pending notification is
|
// if the timeout is reached before the channel pending notification is
|
||||||
// received, an error is returned.
|
// received, an error is returned.
|
||||||
func (n *networkHarness) OpenChannel(ctx context.Context,
|
func (n *networkHarness) OpenChannel(ctx context.Context,
|
||||||
srcNode, destNode *lightningNode, amt btcutil.Amount,
|
srcNode, destNode *lightningNode, amt btcutil.Amount,
|
||||||
|
22
peer.go
22
peer.go
@ -354,7 +354,7 @@ out:
|
|||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
|
|
||||||
var isChanUpate bool
|
var isChanUpdate bool
|
||||||
var targetChan *wire.OutPoint
|
var targetChan *wire.OutPoint
|
||||||
|
|
||||||
switch msg := nextMsg.(type) {
|
switch msg := nextMsg.(type) {
|
||||||
@ -373,17 +373,27 @@ out:
|
|||||||
p.remoteCloseChanReqs <- msg
|
p.remoteCloseChanReqs <- msg
|
||||||
// TODO(roasbeef): interface for htlc update msgs
|
// TODO(roasbeef): interface for htlc update msgs
|
||||||
// * .(CommitmentUpdater)
|
// * .(CommitmentUpdater)
|
||||||
|
|
||||||
|
case *lnwire.ErrorGeneric:
|
||||||
|
switch msg.ErrorID {
|
||||||
|
case lnwire.ErrorMaxPendingChannels:
|
||||||
|
p.server.fundingMgr.processErrorGeneric(msg, p)
|
||||||
|
default:
|
||||||
|
peerLog.Warn("ErrorGeneric(%v) handling isn't" +
|
||||||
|
" implemented.", msg.ErrorID)
|
||||||
|
}
|
||||||
|
|
||||||
case *lnwire.HTLCAddRequest:
|
case *lnwire.HTLCAddRequest:
|
||||||
isChanUpate = true
|
isChanUpdate = true
|
||||||
targetChan = msg.ChannelPoint
|
targetChan = msg.ChannelPoint
|
||||||
case *lnwire.HTLCSettleRequest:
|
case *lnwire.HTLCSettleRequest:
|
||||||
isChanUpate = true
|
isChanUpdate = true
|
||||||
targetChan = msg.ChannelPoint
|
targetChan = msg.ChannelPoint
|
||||||
case *lnwire.CommitRevocation:
|
case *lnwire.CommitRevocation:
|
||||||
isChanUpate = true
|
isChanUpdate = true
|
||||||
targetChan = msg.ChannelPoint
|
targetChan = msg.ChannelPoint
|
||||||
case *lnwire.CommitSignature:
|
case *lnwire.CommitSignature:
|
||||||
isChanUpate = true
|
isChanUpdate = true
|
||||||
targetChan = msg.ChannelPoint
|
targetChan = msg.ChannelPoint
|
||||||
case *lnwire.NeighborAckMessage,
|
case *lnwire.NeighborAckMessage,
|
||||||
*lnwire.NeighborHelloMessage,
|
*lnwire.NeighborHelloMessage,
|
||||||
@ -395,7 +405,7 @@ out:
|
|||||||
p.server.routingMgr.ReceiveRoutingMessage(msg, graph.NewID(([32]byte)(p.lightningID)))
|
p.server.routingMgr.ReceiveRoutingMessage(msg, graph.NewID(([32]byte)(p.lightningID)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if isChanUpate {
|
if isChanUpdate {
|
||||||
// We might be receiving an update to a newly funded
|
// We might be receiving an update to a newly funded
|
||||||
// channel in which we were the responder. Therefore
|
// channel in which we were the responder. Therefore
|
||||||
// we need to possibly block until the new channel has
|
// we need to possibly block until the new channel has
|
||||||
|
@ -27,6 +27,10 @@ import (
|
|||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
OpenChannelFundingError = 100
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
defaultAccount uint32 = waddrmgr.DefaultAccountNum
|
defaultAccount uint32 = waddrmgr.DefaultAccountNum
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user