mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
0407b37fce
This commit enables the user to specify he is not interested in automatically close channels with pending payments that their corresponding htlcs have timed-out. By requiring a configurable grace period uptime of our node before closing such channels, we give a chance to the other node to properly cancel the htlc and avoid unnecessary on-chain transaction. In mobile it is very important for the user experience as otherwise channels will be force closed more frequently.
459 lines
13 KiB
Go
459 lines
13 KiB
Go
package lnd
|
|
|
|
import (
|
|
"bytes"
|
|
crand "crypto/rand"
|
|
"encoding/binary"
|
|
"io"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"net"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/clock"
|
|
"github.com/lightningnetwork/lnd/contractcourt"
|
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/netann"
|
|
"github.com/lightningnetwork/lnd/shachain"
|
|
"github.com/lightningnetwork/lnd/ticker"
|
|
)
|
|
|
|
var (
|
|
alicesPrivKey = []byte{
|
|
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
|
|
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
|
|
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
|
|
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
|
|
}
|
|
|
|
bobsPrivKey = []byte{
|
|
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
|
|
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
|
|
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
|
|
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
|
|
}
|
|
|
|
// Use a hard-coded HD seed.
|
|
testHdSeed = [32]byte{
|
|
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
|
|
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
|
|
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
|
|
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
|
|
}
|
|
|
|
// Just use some arbitrary bytes as delivery script.
|
|
dummyDeliveryScript = alicesPrivKey[:]
|
|
|
|
// testTx is used as the default funding txn for single-funder channels.
|
|
testTx = &wire.MsgTx{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: wire.OutPoint{
|
|
Hash: chainhash.Hash{},
|
|
Index: 0xffffffff,
|
|
},
|
|
SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62},
|
|
Sequence: 0xffffffff,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 5000000000,
|
|
PkScript: []byte{
|
|
0x41, // OP_DATA_65
|
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
|
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
|
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
|
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
|
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
|
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
|
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
|
0xa6, // 65-byte signature
|
|
0xac, // OP_CHECKSIG
|
|
},
|
|
},
|
|
},
|
|
LockTime: 5,
|
|
}
|
|
)
|
|
|
|
// noUpdate is a function which can be used as a parameter in createTestPeer to
|
|
// call the setup code with no custom values on the channels set up.
|
|
var noUpdate = func(a, b *channeldb.OpenChannel) {}
|
|
|
|
// createTestPeer creates a channel between two nodes, and returns a peer for
|
|
// one of the nodes, together with the channel seen from both nodes. It takes
|
|
// an updateChan function which can be used to modify the default values on
|
|
// the channel states for each peer.
|
|
func createTestPeer(notifier chainntnfs.ChainNotifier, publTx chan *wire.MsgTx,
|
|
updateChan func(a, b *channeldb.OpenChannel)) (*peer, *lnwallet.LightningChannel,
|
|
*lnwallet.LightningChannel, func(), error) {
|
|
|
|
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
|
|
alicesPrivKey)
|
|
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
|
|
bobsPrivKey)
|
|
|
|
channelCapacity := btcutil.Amount(10 * 1e8)
|
|
channelBal := channelCapacity / 2
|
|
aliceDustLimit := btcutil.Amount(200)
|
|
bobDustLimit := btcutil.Amount(1300)
|
|
csvTimeoutAlice := uint32(5)
|
|
csvTimeoutBob := uint32(4)
|
|
|
|
prevOut := &wire.OutPoint{
|
|
Hash: chainhash.Hash(testHdSeed),
|
|
Index: 0,
|
|
}
|
|
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
|
|
|
|
aliceCfg := channeldb.ChannelConfig{
|
|
ChannelConstraints: channeldb.ChannelConstraints{
|
|
DustLimit: aliceDustLimit,
|
|
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
|
ChanReserve: btcutil.Amount(rand.Int63()),
|
|
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
|
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
|
CsvDelay: uint16(csvTimeoutAlice),
|
|
},
|
|
MultiSigKey: keychain.KeyDescriptor{
|
|
PubKey: aliceKeyPub,
|
|
},
|
|
RevocationBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeyPub,
|
|
},
|
|
PaymentBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeyPub,
|
|
},
|
|
DelayBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeyPub,
|
|
},
|
|
HtlcBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeyPub,
|
|
},
|
|
}
|
|
bobCfg := channeldb.ChannelConfig{
|
|
ChannelConstraints: channeldb.ChannelConstraints{
|
|
DustLimit: bobDustLimit,
|
|
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
|
ChanReserve: btcutil.Amount(rand.Int63()),
|
|
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
|
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
|
CsvDelay: uint16(csvTimeoutBob),
|
|
},
|
|
MultiSigKey: keychain.KeyDescriptor{
|
|
PubKey: bobKeyPub,
|
|
},
|
|
RevocationBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeyPub,
|
|
},
|
|
PaymentBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeyPub,
|
|
},
|
|
DelayBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeyPub,
|
|
},
|
|
HtlcBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeyPub,
|
|
},
|
|
}
|
|
|
|
bobRoot, err := chainhash.NewHash(bobKeyPriv.Serialize())
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
bobPreimageProducer := shachain.NewRevocationProducer(*bobRoot)
|
|
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
bobCommitPoint := input.ComputeCommitmentPoint(bobFirstRevoke[:])
|
|
|
|
aliceRoot, err := chainhash.NewHash(aliceKeyPriv.Serialize())
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
alicePreimageProducer := shachain.NewRevocationProducer(*aliceRoot)
|
|
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])
|
|
|
|
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
|
|
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
|
|
bobCommitPoint, *fundingTxIn, true,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
alicePath, err := ioutil.TempDir("", "alicedb")
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
dbAlice, err := channeldb.Open(alicePath)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
bobPath, err := ioutil.TempDir("", "bobdb")
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
dbBob, err := channeldb.Open(bobPath)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
estimator := chainfee.NewStaticEstimator(12500, 0)
|
|
feePerKw, err := estimator.EstimateFeePerKW(1)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
// TODO(roasbeef): need to factor in commit fee?
|
|
aliceCommit := channeldb.ChannelCommitment{
|
|
CommitHeight: 0,
|
|
LocalBalance: lnwire.NewMSatFromSatoshis(channelBal),
|
|
RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal),
|
|
FeePerKw: btcutil.Amount(feePerKw),
|
|
CommitFee: feePerKw.FeeForWeight(input.CommitWeight),
|
|
CommitTx: aliceCommitTx,
|
|
CommitSig: bytes.Repeat([]byte{1}, 71),
|
|
}
|
|
bobCommit := channeldb.ChannelCommitment{
|
|
CommitHeight: 0,
|
|
LocalBalance: lnwire.NewMSatFromSatoshis(channelBal),
|
|
RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal),
|
|
FeePerKw: btcutil.Amount(feePerKw),
|
|
CommitFee: feePerKw.FeeForWeight(input.CommitWeight),
|
|
CommitTx: bobCommitTx,
|
|
CommitSig: bytes.Repeat([]byte{1}, 71),
|
|
}
|
|
|
|
var chanIDBytes [8]byte
|
|
if _, err := io.ReadFull(crand.Reader, chanIDBytes[:]); err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
shortChanID := lnwire.NewShortChanIDFromInt(
|
|
binary.BigEndian.Uint64(chanIDBytes[:]),
|
|
)
|
|
|
|
aliceChannelState := &channeldb.OpenChannel{
|
|
LocalChanCfg: aliceCfg,
|
|
RemoteChanCfg: bobCfg,
|
|
IdentityPub: aliceKeyPub,
|
|
FundingOutpoint: *prevOut,
|
|
ShortChannelID: shortChanID,
|
|
ChanType: channeldb.SingleFunderTweaklessBit,
|
|
IsInitiator: true,
|
|
Capacity: channelCapacity,
|
|
RemoteCurrentRevocation: bobCommitPoint,
|
|
RevocationProducer: alicePreimageProducer,
|
|
RevocationStore: shachain.NewRevocationStore(),
|
|
LocalCommitment: aliceCommit,
|
|
RemoteCommitment: aliceCommit,
|
|
Db: dbAlice,
|
|
Packager: channeldb.NewChannelPackager(shortChanID),
|
|
FundingTxn: testTx,
|
|
}
|
|
bobChannelState := &channeldb.OpenChannel{
|
|
LocalChanCfg: bobCfg,
|
|
RemoteChanCfg: aliceCfg,
|
|
IdentityPub: bobKeyPub,
|
|
FundingOutpoint: *prevOut,
|
|
ChanType: channeldb.SingleFunderTweaklessBit,
|
|
IsInitiator: false,
|
|
Capacity: channelCapacity,
|
|
RemoteCurrentRevocation: aliceCommitPoint,
|
|
RevocationProducer: bobPreimageProducer,
|
|
RevocationStore: shachain.NewRevocationStore(),
|
|
LocalCommitment: bobCommit,
|
|
RemoteCommitment: bobCommit,
|
|
Db: dbBob,
|
|
Packager: channeldb.NewChannelPackager(shortChanID),
|
|
}
|
|
|
|
// Set custom values on the channel states.
|
|
updateChan(aliceChannelState, bobChannelState)
|
|
|
|
aliceAddr := &net.TCPAddr{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Port: 18555,
|
|
}
|
|
|
|
if err := aliceChannelState.SyncPending(aliceAddr, 0); err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
bobAddr := &net.TCPAddr{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Port: 18556,
|
|
}
|
|
|
|
if err := bobChannelState.SyncPending(bobAddr, 0); err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
cleanUpFunc := func() {
|
|
os.RemoveAll(bobPath)
|
|
os.RemoveAll(alicePath)
|
|
}
|
|
|
|
aliceSigner := &mockSigner{aliceKeyPriv}
|
|
bobSigner := &mockSigner{bobKeyPriv}
|
|
|
|
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
|
channelAlice, err := lnwallet.NewLightningChannel(
|
|
aliceSigner, aliceChannelState, alicePool,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
alicePool.Start()
|
|
|
|
bobPool := lnwallet.NewSigPool(1, bobSigner)
|
|
channelBob, err := lnwallet.NewLightningChannel(
|
|
bobSigner, bobChannelState, bobPool,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
bobPool.Start()
|
|
|
|
chainIO := &mockChainIO{
|
|
bestHeight: fundingBroadcastHeight,
|
|
}
|
|
wallet := &lnwallet.LightningWallet{
|
|
WalletController: &mockWalletController{
|
|
rootKey: aliceKeyPriv,
|
|
publishedTransactions: publTx,
|
|
},
|
|
}
|
|
cc := &chainControl{
|
|
feeEstimator: estimator,
|
|
chainIO: chainIO,
|
|
chainNotifier: notifier,
|
|
wallet: wallet,
|
|
}
|
|
|
|
breachArbiter := &breachArbiter{}
|
|
|
|
chainArb := contractcourt.NewChainArbitrator(
|
|
contractcourt.ChainArbitratorConfig{
|
|
Notifier: notifier,
|
|
ChainIO: chainIO,
|
|
IsForwardedHTLC: func(chanID lnwire.ShortChannelID,
|
|
htlcIndex uint64) bool {
|
|
|
|
return true
|
|
},
|
|
Clock: clock.NewDefaultClock(),
|
|
}, dbAlice,
|
|
)
|
|
chainArb.WatchNewChannel(aliceChannelState)
|
|
|
|
s := &server{
|
|
chanDB: dbAlice,
|
|
cc: cc,
|
|
breachArbiter: breachArbiter,
|
|
chainArb: chainArb,
|
|
}
|
|
|
|
_, currentHeight, err := s.cc.chainIO.GetBestBlock()
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
htlcSwitch, err := htlcswitch.New(htlcswitch.Config{
|
|
DB: dbAlice,
|
|
SwitchPackager: channeldb.NewSwitchPackager(),
|
|
Notifier: notifier,
|
|
FwdEventTicker: ticker.New(
|
|
htlcswitch.DefaultFwdEventInterval),
|
|
LogEventTicker: ticker.New(
|
|
htlcswitch.DefaultLogInterval),
|
|
AckEventTicker: ticker.New(
|
|
htlcswitch.DefaultAckInterval),
|
|
}, uint32(currentHeight))
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
if err = htlcSwitch.Start(); err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
s.htlcSwitch = htlcSwitch
|
|
|
|
nodeSignerAlice := netann.NewNodeSigner(aliceKeyPriv)
|
|
|
|
const chanActiveTimeout = time.Minute
|
|
|
|
chanStatusMgr, err := netann.NewChanStatusManager(&netann.ChanStatusConfig{
|
|
ChanStatusSampleInterval: 30 * time.Second,
|
|
ChanEnableTimeout: chanActiveTimeout,
|
|
ChanDisableTimeout: 2 * time.Minute,
|
|
DB: dbAlice,
|
|
Graph: dbAlice.ChannelGraph(),
|
|
MessageSigner: nodeSignerAlice,
|
|
OurPubKey: aliceKeyPub,
|
|
IsChannelActive: s.htlcSwitch.HasActiveLink,
|
|
ApplyChannelUpdate: func(*lnwire.ChannelUpdate) error { return nil },
|
|
})
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
if err = chanStatusMgr.Start(); err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
s.chanStatusMgr = chanStatusMgr
|
|
|
|
alicePeer := &peer{
|
|
addr: &lnwire.NetAddress{
|
|
IdentityKey: aliceKeyPub,
|
|
Address: aliceAddr,
|
|
},
|
|
|
|
server: s,
|
|
sendQueue: make(chan outgoingMsg, 1),
|
|
outgoingQueue: make(chan outgoingMsg, outgoingQueueLen),
|
|
|
|
activeChannels: make(map[lnwire.ChannelID]*lnwallet.LightningChannel),
|
|
newChannels: make(chan *newChannelMsg, 1),
|
|
|
|
activeChanCloses: make(map[lnwire.ChannelID]*channelCloser),
|
|
localCloseChanReqs: make(chan *htlcswitch.ChanClose),
|
|
chanCloseMsgs: make(chan *closeMsg),
|
|
|
|
chanActiveTimeout: chanActiveTimeout,
|
|
|
|
queueQuit: make(chan struct{}),
|
|
quit: make(chan struct{}),
|
|
}
|
|
|
|
chanID := lnwire.NewChanIDFromOutPoint(channelAlice.ChannelPoint())
|
|
alicePeer.activeChannels[chanID] = channelAlice
|
|
|
|
alicePeer.wg.Add(1)
|
|
go alicePeer.channelManager()
|
|
|
|
return alicePeer, channelAlice, channelBob, cleanUpFunc, nil
|
|
}
|