Merge pull request #4130 from cfromknecht/open-channel-refactor

rpcserver: align OpenChannel + OpenChannelSync request parsing
This commit is contained in:
Olaoluwa Osuntokun 2020-03-31 18:05:00 -07:00 committed by GitHub
commit 77df8e3a43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 99 additions and 132 deletions

@ -214,9 +214,15 @@ func openChannelAndAssert(ctx context.Context, t *harnessTest,
net *lntest.NetworkHarness, alice, bob *lntest.HarnessNode,
p lntest.OpenChannelParams) *lnrpc.ChannelPoint {
chanOpenUpdate, err := net.OpenChannel(
ctx, alice, bob, p,
)
// Wait until we are able to fund a channel successfully. This wait
// prevents us from erroring out when trying to create a channel while
// the node is starting up.
var chanOpenUpdate lnrpc.Lightning_OpenChannelClient
err := wait.NoError(func() error {
var err error
chanOpenUpdate, err = net.OpenChannel(ctx, alice, bob, p)
return err
}, defaultTimeout)
if err != nil {
t.Fatalf("unable to open channel: %v", err)
}

@ -1603,19 +1603,38 @@ func newPsbtAssembler(req *lnrpc.OpenChannelRequest, normalizedMinConfs int32,
), nil
}
// OpenChannel attempts to open a singly funded channel specified in the
// request to a remote peer.
func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
updateStream lnrpc.Lightning_OpenChannelServer) error {
rpcsLog.Tracef("[openchannel] request to NodeKey(%v) "+
"allocation(us=%v, them=%v)", in.NodePubkeyString,
in.LocalFundingAmount, in.PushSat)
// canOpenChannel returns an error if the necessary subsystems for channel
// funding are not ready.
func (r *rpcServer) canOpenChannel() error {
// We can't open a channel until the main server has started.
if !r.server.Started() {
return ErrServerNotActive
}
// Creation of channels before the wallet syncs up is currently
// disallowed.
isSynced, _, err := r.server.cc.wallet.IsSynced()
if err != nil {
return err
}
if !isSynced {
return errors.New("channels cannot be created before the " +
"wallet is fully synced")
}
return nil
}
// praseOpenChannelReq parses an OpenChannelRequest message into the server's
// native openChanReq struct. The logic is abstracted so that it can be shared
// between OpenChannel and OpenChannelSync.
func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
isSync bool) (*openChanReq, error) {
rpcsLog.Debugf("[openchannel] request to NodeKey(%x) "+
"allocation(us=%v, them=%v)", in.NodePubkey,
in.LocalFundingAmount, in.PushSat)
localFundingAmt := btcutil.Amount(in.LocalFundingAmount)
remoteInitialBalance := btcutil.Amount(in.PushSat)
minHtlcIn := lnwire.MilliSatoshi(in.MinHtlcMsat)
@ -1627,15 +1646,15 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
//
// TODO(roasbeef): incorporate base fee?
if remoteInitialBalance >= localFundingAmt {
return fmt.Errorf("amount pushed to remote peer for initial " +
"state must be below the local funding amount")
return nil, fmt.Errorf("amount pushed to remote peer for " +
"initial state must be below the local funding amount")
}
// Ensure that the user doesn't exceed the current soft-limit for
// channel size. If the funding amount is above the soft-limit, then
// we'll reject the request.
if localFundingAmt > MaxFundingAmount {
return fmt.Errorf("funding amount is too large, the max "+
return nil, fmt.Errorf("funding amount is too large, the max "+
"channel size is: %v", MaxFundingAmount)
}
@ -1643,8 +1662,8 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
// level, we'll ensure that the output we create after accounting for
// fees that a dust output isn't created.
if localFundingAmt < minChanFundingSize {
return fmt.Errorf("channel is too small, the minimum channel "+
"size is: %v SAT", int64(minChanFundingSize))
return nil, fmt.Errorf("channel is too small, the minimum "+
"channel size is: %v SAT", int64(minChanFundingSize))
}
// Then, we'll extract the minimum number of confirmations that each
@ -1652,36 +1671,50 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
// satisfy.
minConfs, err := extractOpenChannelMinConfs(in)
if err != nil {
return err
return nil, err
}
var (
nodePubKey *btcec.PublicKey
nodePubKeyBytes []byte
)
// TODO(roasbeef): also return channel ID?
// Ensure that the NodePubKey is set before attempting to use it
if len(in.NodePubkey) == 0 {
return fmt.Errorf("NodePubKey is not set")
}
var nodePubKey *btcec.PublicKey
// Parse the raw bytes of the node key into a pubkey object so we
// can easily manipulate it.
nodePubKey, err = btcec.ParsePubKey(in.NodePubkey, btcec.S256())
if err != nil {
return err
// Parse the remote pubkey the NodePubkey field of the request. If it's
// not present, we'll fallback to the deprecated version that parses the
// key from a hex string if this is for REST for backwards compatibility.
switch {
// Parse the raw bytes of the node key into a pubkey object so we can
// easily manipulate it.
case len(in.NodePubkey) > 0:
nodePubKey, err = btcec.ParsePubKey(in.NodePubkey, btcec.S256())
if err != nil {
return nil, err
}
// 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.
case isSync:
keyBytes, err := hex.DecodeString(in.NodePubkeyString)
if err != nil {
return nil, err
}
nodePubKey, err = btcec.ParsePubKey(keyBytes, btcec.S256())
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("NodePubkey is not set")
}
// Making a channel to ourselves wouldn't be of any use, so we
// explicitly disallow them.
if nodePubKey.IsEqual(r.server.identityPriv.PubKey()) {
return fmt.Errorf("cannot open channel to self")
return nil, fmt.Errorf("cannot open channel to self")
}
nodePubKeyBytes = nodePubKey.SerializeCompressed()
// Based on the passed fee related parameters, we'll determine an
// appropriate fee rate for the funding transaction.
satPerKw := chainfee.SatPerKVByte(in.SatPerByte * 1000).FeePerKWeight()
@ -1692,7 +1725,7 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
},
)
if err != nil {
return err
return nil, err
}
rpcsLog.Debugf("[openchannel]: using fee of %v sat/kw for funding tx",
@ -1700,13 +1733,14 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
script, err := parseUpfrontShutdownAddress(in.CloseAddress)
if err != nil {
return fmt.Errorf("error parsing upfront shutdown: %v", err)
return nil, fmt.Errorf("error parsing upfront shutdown: %v",
err)
}
// Instruct the server to trigger the necessary events to attempt to
// open a new channel. A stream is returned in place, this stream will
// be used to consume updates of the state of the pending channel.
req := &openChanReq{
return &openChanReq{
targetPubkey: nodePubKey,
chainHash: *activeNetParams.GenesisHash,
localFundingAmt: localFundingAmt,
@ -1717,6 +1751,21 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
remoteCsvDelay: remoteCsvDelay,
minConfs: minConfs,
shutdownScript: script,
}, nil
}
// OpenChannel attempts to open a singly funded channel specified in the
// request to a remote peer.
func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
updateStream lnrpc.Lightning_OpenChannelServer) error {
if err := r.canOpenChannel(); err != nil {
return err
}
req, err := r.parseOpenChannelReq(in, false)
if err != nil {
return err
}
// If the user has provided a shim, then we'll now augment the based
@ -1752,7 +1801,7 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
// transaction.
copy(req.pendingChanID[:], psbtShim.PendingChanId)
req.chanFunder, err = newPsbtAssembler(
in, minConfs, psbtShim,
in, req.minConfs, psbtShim,
&r.server.cc.wallet.Cfg.NetParams,
)
if err != nil {
@ -1769,7 +1818,7 @@ out:
select {
case err := <-errChan:
rpcsLog.Errorf("unable to open channel to NodeKey(%x): %v",
nodePubKeyBytes, err)
req.targetPubkey.SerializeCompressed(), err)
return err
case fundingUpdate := <-updateChan:
rpcsLog.Tracef("[openchannel] sending update: %v",
@ -1801,7 +1850,7 @@ out:
}
rpcsLog.Tracef("[openchannel] success NodeKey(%x), ChannelPoint(%v)",
nodePubKeyBytes, outpoint)
req.targetPubkey.SerializeCompressed(), outpoint)
return nil
}
@ -1812,109 +1861,21 @@ out:
func (r *rpcServer) OpenChannelSync(ctx context.Context,
in *lnrpc.OpenChannelRequest) (*lnrpc.ChannelPoint, error) {
rpcsLog.Tracef("[openchannel] request to NodeKey(%v) "+
"allocation(us=%v, them=%v)", in.NodePubkeyString,
in.LocalFundingAmount, in.PushSat)
// We don't allow new channels to be open while the server is still
// syncing, as otherwise we may not be able to obtain the relevant
// notifications.
if !r.server.Started() {
return nil, ErrServerNotActive
}
// Creation of channels before the wallet syncs up is currently
// disallowed.
isSynced, _, err := r.server.cc.wallet.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.
keyBytes, err := hex.DecodeString(in.NodePubkeyString)
if err != nil {
return nil, err
}
nodepubKey, err := btcec.ParsePubKey(keyBytes, btcec.S256())
if err != nil {
if err := r.canOpenChannel(); err != nil {
return nil, err
}
localFundingAmt := btcutil.Amount(in.LocalFundingAmount)
remoteInitialBalance := btcutil.Amount(in.PushSat)
minHtlcIn := lnwire.MilliSatoshi(in.MinHtlcMsat)
remoteCsvDelay := uint16(in.RemoteCsvDelay)
// Ensure that the initial balance of the remote party (if pushing
// satoshis) does not exceed the amount the local party has requested
// for funding.
if remoteInitialBalance >= localFundingAmt {
return nil, fmt.Errorf("amount pushed to remote peer for " +
"initial state must be below the local funding amount")
}
// Restrict the size of the channel we'll actually open. At a later
// level, we'll ensure that the output we create after accounting for
// fees that a dust output isn't created.
if localFundingAmt < minChanFundingSize {
return nil, fmt.Errorf("channel is too small, the minimum channel "+
"size is: %v SAT", int64(minChanFundingSize))
}
// Then, we'll extract the minimum number of confirmations that each
// output we use to fund the channel's funding transaction should
// satisfy.
minConfs, err := extractOpenChannelMinConfs(in)
req, err := r.parseOpenChannelReq(in, true)
if err != nil {
return nil, err
}
// Based on the passed fee related parameters, we'll determine an
// appropriate fee rate for the funding transaction.
satPerKw := chainfee.SatPerKVByte(in.SatPerByte * 1000).FeePerKWeight()
feeRate, err := sweep.DetermineFeePerKw(
r.server.cc.feeEstimator, sweep.FeePreference{
ConfTarget: uint32(in.TargetConf),
FeeRate: satPerKw,
},
)
if err != nil {
return nil, err
}
rpcsLog.Tracef("[openchannel] target sat/kw for funding tx: %v",
int64(feeRate))
script, err := parseUpfrontShutdownAddress(in.CloseAddress)
if err != nil {
return nil, fmt.Errorf("error parsing upfront shutdown: %v", err)
}
req := &openChanReq{
targetPubkey: nodepubKey,
chainHash: *activeNetParams.GenesisHash,
localFundingAmt: localFundingAmt,
pushAmt: lnwire.NewMSatFromSatoshis(remoteInitialBalance),
minHtlcIn: minHtlcIn,
fundingFeePerKw: feeRate,
private: in.Private,
remoteCsvDelay: remoteCsvDelay,
minConfs: minConfs,
shutdownScript: script,
}
updateChan, errChan := r.server.OpenChannel(req)
select {
// If an error occurs them immediately return the error to the client.
case err := <-errChan:
rpcsLog.Errorf("unable to open channel to NodeKey(%x): %v",
nodepubKey, err)
req.targetPubkey.SerializeCompressed(), err)
return nil, err
// Otherwise, wait for the first channel update. The first update sent