package lntest import ( "errors" "flag" "fmt" "io" "net" "os" "sync/atomic" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" ) const ( // defaultNodePort is the start of the range for listening ports of // harness nodes. Ports are monotonically increasing starting from this // number and are determined by the results of nextAvailablePort(). defaultNodePort = 5555 // ListenerFormat is the format string that is used to generate local // listener addresses. ListenerFormat = "127.0.0.1:%d" // NeutrinoBackendName is the name of the neutrino backend. NeutrinoBackendName = "neutrino" ) type DatabaseBackend int const ( BackendBbolt DatabaseBackend = iota BackendEtcd BackendPostgres ) var ( // lastPort is the last port determined to be free for use by a new // node. It should be used atomically. lastPort uint32 = defaultNodePort // logOutput is a flag that can be set to append the output from the // seed nodes to log files. logOutput = flag.Bool("logoutput", false, "log output from node n to file output-n.log") // logSubDir is the default directory where the logs are written to if // logOutput is true. logSubDir = flag.String("logdir", ".", "default dir to write logs to") // goroutineDump is a flag that can be set to dump the active // goroutines of test nodes on failure. goroutineDump = flag.Bool("goroutinedump", false, "write goroutine dump from node n to file pprof-n.log") // btcdExecutable is the full path to the btcd binary. btcdExecutable = flag.String( "btcdexec", "", "full path to btcd binary", ) ) // NextAvailablePort returns the first port that is available for listening by // a new node. It panics if no port is found and the maximum available TCP port // is reached. func NextAvailablePort() int { port := atomic.AddUint32(&lastPort, 1) for port < 65535 { // If there are no errors while attempting to listen on this // port, close the socket and return it as available. While it // could be the case that some other process picks up this port // between the time the socket is closed and it's reopened in // the harness node, in practice in CI servers this seems much // less likely than simply some other process already being // bound at the start of the tests. addr := fmt.Sprintf(ListenerFormat, port) l, err := net.Listen("tcp4", addr) if err == nil { err := l.Close() if err == nil { return int(port) } } port = atomic.AddUint32(&lastPort, 1) } // No ports available? Must be a mistake. panic("no ports available for listening") } // ApplyPortOffset adds the given offset to the lastPort variable, making it // possible to run the tests in parallel without colliding on the same ports. func ApplyPortOffset(offset uint32) { _ = atomic.AddUint32(&lastPort, offset) } // GetLogDir returns the passed --logdir flag or the default value if it wasn't // set. func GetLogDir() string { if logSubDir != nil && *logSubDir != "" { return *logSubDir } return "." } // GetBtcdBinary returns the full path to the binary of the custom built btcd // executable or an empty string if none is set. func GetBtcdBinary() string { if btcdExecutable != nil { return *btcdExecutable } return "" } // GenerateBtcdListenerAddresses is a function that returns two listener // addresses with unique ports and should be used to overwrite rpctest's // default generator which is prone to use colliding ports. func GenerateBtcdListenerAddresses() (string, string) { return fmt.Sprintf(ListenerFormat, NextAvailablePort()), fmt.Sprintf(ListenerFormat, NextAvailablePort()) } // MakeOutpoint returns the outpoint of the channel's funding transaction. func MakeOutpoint(chanPoint *lnrpc.ChannelPoint) (wire.OutPoint, error) { fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { return wire.OutPoint{}, err } return wire.OutPoint{ Hash: *fundingTxID, Index: chanPoint.OutputIndex, }, nil } // CheckChannelPolicy checks that the policy matches the expected one. func CheckChannelPolicy(policy, expectedPolicy *lnrpc.RoutingPolicy) error { if policy.FeeBaseMsat != expectedPolicy.FeeBaseMsat { return fmt.Errorf("expected base fee %v, got %v", expectedPolicy.FeeBaseMsat, policy.FeeBaseMsat) } if policy.FeeRateMilliMsat != expectedPolicy.FeeRateMilliMsat { return fmt.Errorf("expected fee rate %v, got %v", expectedPolicy.FeeRateMilliMsat, policy.FeeRateMilliMsat) } if policy.TimeLockDelta != expectedPolicy.TimeLockDelta { return fmt.Errorf("expected time lock delta %v, got %v", expectedPolicy.TimeLockDelta, policy.TimeLockDelta) } if policy.MinHtlc != expectedPolicy.MinHtlc { return fmt.Errorf("expected min htlc %v, got %v", expectedPolicy.MinHtlc, policy.MinHtlc) } if policy.MaxHtlcMsat != expectedPolicy.MaxHtlcMsat { return fmt.Errorf("expected max htlc %v, got %v", expectedPolicy.MaxHtlcMsat, policy.MaxHtlcMsat) } if policy.Disabled != expectedPolicy.Disabled { return errors.New("edge should be disabled but isn't") } return nil } // CopyFile copies the file src to dest. func CopyFile(dest, src string) error { s, err := os.Open(src) if err != nil { return err } defer s.Close() d, err := os.Create(dest) if err != nil { return err } if _, err := io.Copy(d, s); err != nil { d.Close() return err } return d.Close() }