From a658fabf481a0ede6e94d860bf19fff49bc0d66c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 24 Jan 2017 17:12:51 -0800 Subject: [PATCH] funding: disallow channel creation before lnd is synced to the chain This commit adds a new restriction around funding channels at the daemon level: lnd nodes will not allow either the initiation or the acceptance of a channel before the node is fully synced to the best known chain. This fixes a class of bug that arises when a new node joins the network and either attempts to open a channel or has a channel extended to them before the node is fully synced to the network. --- fundingmanager.go | 44 +++++++++++++++++++++++++++++++++++------ lnd_test.go | 4 ++-- lnwire/error_generic.go | 11 ++++++++--- rpcserver.go | 12 +++++++++++ 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/fundingmanager.go b/fundingmanager.go index 5a6ed63c..9b49d22a 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -7,6 +7,7 @@ import ( "sync/atomic" "time" + "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" @@ -336,7 +337,29 @@ func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) { Index: 0, }, Problem: "Number of pending channels exceed maximum", - Code: lnwire.ErrorMaxPendingChannels, + Code: lnwire.ErrMaxPendingChannels, + PendingChannelID: fmsg.msg.ChannelID, + } + fmsg.peer.queueMsg(errMsg, nil) + return + } + + // We'll also reject any requests to create channels until we're fully + // synced to the network as we won't be able to properly validate the + // confirmation of the funding transaction. + isSynced, err := f.wallet.IsSynced() + if err != nil { + fndgLog.Errorf("unable to query wallet: %v", err) + return + } + if !isSynced { + errMsg := &lnwire.ErrorGeneric{ + ChannelPoint: &wire.OutPoint{ + Hash: chainhash.Hash{}, + Index: 0, + }, + Problem: "Synchronizing blockchain", + Code: lnwire.ErrSynchronizingChain, PendingChannelID: fmsg.msg.ChannelID, } fmsg.peer.queueMsg(errMsg, nil) @@ -979,18 +1002,27 @@ func (f *fundingManager) handleErrorGenericMsg(fmsg *fundingErrorMsg) { e := fmsg.err switch e.Code { - case lnwire.ErrorMaxPendingChannels: + case lnwire.ErrMaxPendingChannels: + fallthrough + case lnwire.ErrSynchronizingChain: peerID := fmsg.peer.id chanID := fmsg.err.PendingChannelID - if ctx, err := f.cancelReservationCtx(peerID, chanID); err != nil { + resCtx, err := f.cancelReservationCtx(peerID, chanID) + if err != nil { fndgLog.Warnf("unable to delete reservation: %v", err) return - } else { - ctx.err <- grpc.Errorf(e.Code.ToGrpcCode(), e.Problem) - return } + fndgLog.Errorf("Received funding error from %v: %v", fmsg.peer, + newLogClosure(func() string { + return spew.Sdump(e) + }), + ) + + resCtx.err <- grpc.Errorf(e.Code.ToGrpcCode(), e.Problem) + return + default: fndgLog.Warnf("unknown funding error (%v:%v)", e.Code, e.Problem) } diff --git a/lnd_test.go b/lnd_test.go index b14ed491..5863f2ec 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -236,7 +236,7 @@ func testBasicChannelFunding(net *networkHarness, t *harnessTest) { } if bobBal.Balance != int64(pushAmt) { t.Fatalf("bob's balance is incorrect: expected %v got %v", - pushAmt, bobBal) + pushAmt, bobBal.Balance) } // Finally, immediately close the channel. This function will also @@ -1124,7 +1124,7 @@ func testMaxPendingChannels(net *networkHarness, t *harnessTest) { _, err = net.OpenChannel(ctx, net.Alice, carol, amount, 0, 1) if err == nil { t.Fatalf("error wasn't received") - } else if grpc.Code(err) != lnwire.ErrorMaxPendingChannels.ToGrpcCode() { + } else if grpc.Code(err) != lnwire.ErrMaxPendingChannels.ToGrpcCode() { t.Fatalf("not expected error was received: %v", err) } diff --git a/lnwire/error_generic.go b/lnwire/error_generic.go index cddf61ee..6d1ff778 100644 --- a/lnwire/error_generic.go +++ b/lnwire/error_generic.go @@ -21,9 +21,14 @@ func (e ErrorCode) ToGrpcCode() codes.Code { } const ( - // ErrorMaxPendingChannels is returned by remote peer when the number - // of active pending channels exceeds their maximum policy limit. - ErrorMaxPendingChannels ErrorCode = 1 + // ErrMaxPendingChannels is returned by remote peer when the number of + // active pending channels exceeds their maximum policy limit. + ErrMaxPendingChannels ErrorCode = 1 + + // ErrSynchronizingChain is returned by a remote peer that receives a + // channel update or a funding request while their still syncing to the + // latest state of the blockchain. + ErrSynchronizingChain ErrorCode = 2 ) // ErrorGeneric represents a generic error bound to an exact channel. The diff --git a/rpcserver.go b/rpcserver.go index 0cdaa445..6abcac38 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/rand" "encoding/hex" + "errors" "fmt" "io" "math" @@ -331,6 +332,17 @@ func (r *rpcServer) OpenChannelSync(ctx context.Context, "allocation(us=%v, them=%v) numconfs=%v", in.TargetPeerId, in.LocalFundingAmount, in.PushSat, in.NumConfs) + // Creation of channels before the wallet syncs up is currently + // disallowed. + isSynced, err := r.server.lnwallet.IsSynced() + if err != nil { + return nil, err + } + if !isSynced { + return nil, errors.New("channels cannot be created before the " + + "wallet is fully synced") + } + // Decode the provided target node's public key, parsing it into a pub // key object. For all sync call, byte slices are expected to be // encoded as hex strings.