mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
Merge pull request #7925 from Crypt-iQ/funding_maturity
funding: wait for coinbase maturity before sending channel_ready
This commit is contained in:
commit
31ba616b1c
@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
@ -1286,6 +1287,53 @@ func (f *Manager) advancePendingChannelState(
|
||||
channel.FundingOutpoint, err)
|
||||
}
|
||||
|
||||
if blockchain.IsCoinBaseTx(confChannel.fundingTx) {
|
||||
// If it's a coinbase transaction, we need to wait for it to
|
||||
// mature. We wait out an additional MinAcceptDepth on top of
|
||||
// the coinbase maturity as an extra margin of safety.
|
||||
maturity := f.cfg.Wallet.Cfg.NetParams.CoinbaseMaturity
|
||||
numCoinbaseConfs := uint32(maturity)
|
||||
|
||||
if channel.NumConfsRequired > maturity {
|
||||
numCoinbaseConfs = uint32(channel.NumConfsRequired)
|
||||
}
|
||||
|
||||
txid := &channel.FundingOutpoint.Hash
|
||||
fundingScript, err := makeFundingScript(channel)
|
||||
if err != nil {
|
||||
log.Errorf("unable to create funding script for "+
|
||||
"ChannelPoint(%v): %v",
|
||||
channel.FundingOutpoint, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn(
|
||||
txid, fundingScript, numCoinbaseConfs,
|
||||
channel.BroadcastHeight(),
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to register for confirmation of "+
|
||||
"ChannelPoint(%v): %v",
|
||||
channel.FundingOutpoint, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case _, ok := <-confNtfn.Confirmed:
|
||||
if !ok {
|
||||
return fmt.Errorf("ChainNotifier shutting "+
|
||||
"down, can't complete funding flow "+
|
||||
"for ChannelPoint(%v)",
|
||||
channel.FundingOutpoint)
|
||||
}
|
||||
|
||||
case <-f.quit:
|
||||
return ErrFundingManagerShuttingDown
|
||||
}
|
||||
}
|
||||
|
||||
// Success, funding transaction was confirmed.
|
||||
chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint)
|
||||
log.Debugf("ChannelID(%v) is now fully confirmed! "+
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@ -36,6 +37,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnutils"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -121,6 +123,24 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
type mockChanFunder struct {
|
||||
fundingAmt btcutil.Amount
|
||||
localKey *keychain.KeyDescriptor
|
||||
remoteKey *btcec.PublicKey
|
||||
chanPoint *wire.OutPoint
|
||||
}
|
||||
|
||||
func (m *mockChanFunder) ProvisionChannel(r *chanfunding.Request) (
|
||||
chanfunding.Intent, error) {
|
||||
|
||||
shimIntent := chanfunding.NewShimIntent(
|
||||
m.fundingAmt, 0, m.localKey, m.remoteKey, m.chanPoint, 0,
|
||||
false,
|
||||
)
|
||||
|
||||
return shimIntent, nil
|
||||
}
|
||||
|
||||
type mockAliasMgr struct{}
|
||||
|
||||
func (m *mockAliasMgr) RequestAlias() (lnwire.ShortChannelID, error) {
|
||||
@ -4045,6 +4065,8 @@ func TestFundingManagerFundMax(t *testing.T) {
|
||||
// the user has provided a script and our local configuration to test that
|
||||
// GetUpfrontShutdownScript returns the expected outcome.
|
||||
func TestGetUpfrontShutdownScript(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
upfrontScript := []byte("upfront script")
|
||||
generatedScript := []byte("generated script")
|
||||
|
||||
@ -4733,3 +4755,206 @@ func TestFundingManagerZeroConf(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFundingManagerCoinbase tests that a coinbase transaction that is also a
|
||||
// funding transaction is not used until the coinbase maturity has passed.
|
||||
func TestFundingManagerCoinbase(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
alice, bob := setupFundingManagers(t)
|
||||
t.Cleanup(func() {
|
||||
tearDownFundingManagers(t, alice, bob)
|
||||
})
|
||||
|
||||
const chanSize = btcutil.Amount(1_000_000)
|
||||
|
||||
aliceKeyRing := alice.fundingMgr.cfg.Wallet.SecretKeyRing
|
||||
bobKeyRing := bob.fundingMgr.cfg.Wallet.SecretKeyRing
|
||||
|
||||
// Since we want to know the multi-sig keys upfront, derive the keys
|
||||
// at index 0.
|
||||
multiSigKeyLoc := keychain.KeyLocator{
|
||||
Family: keychain.KeyFamilyMultiSig,
|
||||
Index: 0,
|
||||
}
|
||||
|
||||
aliceKeyDesc, err := aliceKeyRing.DeriveKey(multiSigKeyLoc)
|
||||
require.NoError(t, err)
|
||||
|
||||
bobKeyDesc, err := bobKeyRing.DeriveKey(multiSigKeyLoc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Craft the coinbase transaction that will spend to the 2-of-2 p2wsh.
|
||||
fundingTx := wire.NewMsgTx(2)
|
||||
|
||||
// The all-zero hash and the index of math.MaxUint32 makes this a
|
||||
// coinbase transaction.
|
||||
txIn := &wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Index: math.MaxUint32,
|
||||
},
|
||||
}
|
||||
|
||||
fundingTx.TxIn = append(fundingTx.TxIn, txIn)
|
||||
|
||||
_, txOut, err := input.GenFundingPkScript(
|
||||
aliceKeyDesc.PubKey.SerializeCompressed(),
|
||||
bobKeyDesc.PubKey.SerializeCompressed(), int64(chanSize),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
fundingTx.TxOut = append(fundingTx.TxOut, txOut)
|
||||
|
||||
fundingOp := &wire.OutPoint{
|
||||
Hash: fundingTx.TxHash(),
|
||||
Index: 0,
|
||||
}
|
||||
|
||||
chanFunder := &mockChanFunder{
|
||||
fundingAmt: chanSize,
|
||||
localKey: &aliceKeyDesc,
|
||||
remoteKey: bobKeyDesc.PubKey,
|
||||
chanPoint: fundingOp,
|
||||
}
|
||||
|
||||
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
initReq := &InitFundingMsg{
|
||||
Peer: bob,
|
||||
TargetPubkey: bob.privKey.PubKey(),
|
||||
ChainHash: *fundingNetParams.GenesisHash,
|
||||
MinConfs: int32(1),
|
||||
LocalFundingAmt: chanSize,
|
||||
ChanFunder: chanFunder,
|
||||
Updates: updateChan,
|
||||
Err: errChan,
|
||||
}
|
||||
|
||||
alice.fundingMgr.InitFundingWorkflow(initReq)
|
||||
|
||||
// Alice should send an OpenChannel to Bob.
|
||||
var aliceMsg lnwire.Message
|
||||
select {
|
||||
case aliceMsg = <-alice.msgChan:
|
||||
case err := <-initReq.Err:
|
||||
t.Fatalf("error init funding workflow: %v", err)
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("alice did not send OpenChannel message")
|
||||
}
|
||||
|
||||
openChannelReq, ok := aliceMsg.(*lnwire.OpenChannel)
|
||||
require.True(t, ok)
|
||||
|
||||
bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice)
|
||||
|
||||
// Bob should answer with an AcceptChannel message.
|
||||
acceptChannelResponse, ok := assertFundingMsgSent(
|
||||
t, bob.msgChan, "AcceptChannel",
|
||||
).(*lnwire.AcceptChannel)
|
||||
require.True(t, ok)
|
||||
|
||||
// Forward the response to Alice.
|
||||
alice.fundingMgr.ProcessFundingMsg(acceptChannelResponse, bob)
|
||||
|
||||
// Alice responds with a FundingCreated message.
|
||||
fundingCreated, ok := assertFundingMsgSent(
|
||||
t, alice.msgChan, "FundingCreated",
|
||||
).(*lnwire.FundingCreated)
|
||||
require.True(t, ok)
|
||||
|
||||
// Give the message to Bob.
|
||||
bob.fundingMgr.ProcessFundingMsg(fundingCreated, alice)
|
||||
|
||||
// Finally, Bob should send the FundingSigned message.
|
||||
fundingSigned, ok := assertFundingMsgSent(
|
||||
t, bob.msgChan, "FundingSigned",
|
||||
).(*lnwire.FundingSigned)
|
||||
require.True(t, ok)
|
||||
|
||||
// Forward the signature to Alice.
|
||||
alice.fundingMgr.ProcessFundingMsg(fundingSigned, bob)
|
||||
|
||||
var pendingUpdate *lnrpc.OpenStatusUpdate
|
||||
select {
|
||||
case pendingUpdate = <-updateChan:
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("alice did not send OpenStatusUpdate_ChanPending")
|
||||
}
|
||||
|
||||
_, ok = pendingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
||||
require.True(t, ok)
|
||||
|
||||
// Confirm the funding transaction.
|
||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||
Tx: fundingTx,
|
||||
}
|
||||
|
||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||
Tx: fundingTx,
|
||||
}
|
||||
|
||||
// Make sure the notification about the pending channel was sent out.
|
||||
select {
|
||||
case <-alice.mockChanEvent.pendingOpenEvent:
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("alice did not send pending channel event")
|
||||
}
|
||||
select {
|
||||
case <-bob.mockChanEvent.pendingOpenEvent:
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("bob did not send pending channel event")
|
||||
}
|
||||
|
||||
// Assert that neither alice nor bob sees the channel as open yet. This
|
||||
// is because the coinbase check is hit and we must wait for more
|
||||
// confirmations.
|
||||
select {
|
||||
case <-alice.mockChanEvent.openEvent:
|
||||
t.Fatalf("alice sent an open channel event")
|
||||
case <-time.After(time.Second * 5):
|
||||
}
|
||||
|
||||
select {
|
||||
case <-bob.mockChanEvent.openEvent:
|
||||
t.Fatalf("bob sent an open channel event")
|
||||
case <-time.After(time.Second * 5):
|
||||
}
|
||||
|
||||
// Send along the oneConfChannel again and then assert that the open
|
||||
// event is sent. This serves as the 100 block + MinAcceptDepth
|
||||
// confirmation.
|
||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||
Tx: fundingTx,
|
||||
}
|
||||
|
||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||
Tx: fundingTx,
|
||||
}
|
||||
|
||||
assertMarkedOpen(t, alice, bob, fundingOp)
|
||||
|
||||
// After the funding transaction is mined, Alice will send
|
||||
// channelReady to Bob.
|
||||
channelReadyAlice, ok := assertFundingMsgSent(
|
||||
t, alice.msgChan, "ChannelReady",
|
||||
).(*lnwire.ChannelReady)
|
||||
require.True(t, ok)
|
||||
|
||||
// And similarly Bob will send channel_ready to Alice.
|
||||
channelReadyBob, ok := assertFundingMsgSent(
|
||||
t, bob.msgChan, "ChannelReady",
|
||||
).(*lnwire.ChannelReady)
|
||||
require.True(t, ok)
|
||||
|
||||
// Check that the state machine is updated accordingly
|
||||
assertChannelReadySent(t, alice, bob, fundingOp)
|
||||
|
||||
// Exchange the channelReady messages.
|
||||
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
|
||||
bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
|
||||
|
||||
// Check that they notify the breach arbiter and peer about the new
|
||||
// channel.
|
||||
assertHandleChannelReady(t, alice, bob)
|
||||
}
|
||||
|
@ -10,6 +10,22 @@ import (
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
// NewShimIntent creates a new ShimIntent. This is only used for testing.
|
||||
func NewShimIntent(localAmt, remoteAmt btcutil.Amount,
|
||||
localKey *keychain.KeyDescriptor, remoteKey *btcec.PublicKey,
|
||||
chanPoint *wire.OutPoint, thawHeight uint32, musig2 bool) *ShimIntent {
|
||||
|
||||
return &ShimIntent{
|
||||
localFundingAmt: localAmt,
|
||||
remoteFundingAmt: remoteAmt,
|
||||
localKey: localKey,
|
||||
remoteKey: remoteKey,
|
||||
chanPoint: chanPoint,
|
||||
thawHeight: thawHeight,
|
||||
musig2: musig2,
|
||||
}
|
||||
}
|
||||
|
||||
// ShimIntent is an intent created by the CannedAssembler which represents a
|
||||
// funding output to be created that was constructed outside the wallet. This
|
||||
// might be used when a hardware wallet, or a channel factory is the entity
|
||||
|
Loading…
Reference in New Issue
Block a user