lnd.xprv/watchtower/wtserver/create_session.go
Conner Fromknecht 98c2d329e5
watchtower/multi: embed TxPolicy in wtpolicy.Policy
This commit splits out the parameters that shape the justice transaction
into their own struct, which then embedded within the overarching
wtpolicy.Policy which may have additional parameters describing
operation of the session.

This is done as a preliminary step to support comparison of sessions
based on matching TxPolicy configurations. This prevents otherwise
identical Policies from being counted as different if operational
parameters like MaxUpdates differ, even if it has no material difference
to the justice transaction.
2019-06-13 19:54:20 -07:00

157 lines
4.8 KiB
Go

package wtserver
import (
"github.com/btcsuite/btcd/txscript"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
"github.com/lightningnetwork/lnd/watchtower/wtwire"
)
// handleCreateSession processes a CreateSession message from the peer, and returns
// a CreateSessionReply in response. This method will only succeed if no existing
// session info is known about the session id. If an existing session is found,
// the reward address is returned in case the client lost our reply.
func (s *Server) handleCreateSession(peer Peer, id *wtdb.SessionID,
req *wtwire.CreateSession) error {
// TODO(conner): validate accept against policy
// Query the db for session info belonging to the client's session id.
existingInfo, err := s.cfg.DB.GetSessionInfo(id)
switch {
// We already have a session, though it is currently unused. We'll allow
// the client to recommit the session if it wanted to change the policy.
case err == nil && existingInfo.LastApplied == 0:
// We already have a session corresponding to this session id, return an
// error signaling that it already exists in our database. We return the
// reward address to the client in case they were not able to process
// our reply earlier.
case err == nil && existingInfo.LastApplied > 0:
log.Debugf("Already have session for %s", id)
return s.replyCreateSession(
peer, id, wtwire.CreateSessionCodeAlreadyExists,
existingInfo.LastApplied, existingInfo.RewardAddress,
)
// Some other database error occurred, return a temporary failure.
case err != wtdb.ErrSessionNotFound:
log.Errorf("unable to load session info for %s", id)
return s.replyCreateSession(
peer, id, wtwire.CodeTemporaryFailure, 0, nil,
)
}
// Ensure that the requested blob type is supported by our tower.
if !blob.IsSupportedType(req.BlobType) {
log.Debugf("Rejecting CreateSession from %s, unsupported blob "+
"type %s", id, req.BlobType)
return s.replyCreateSession(
peer, id, wtwire.CreateSessionCodeRejectBlobType, 0,
nil,
)
}
// Now that we've established that this session does not exist in the
// database, retrieve the sweep address that will be given to the
// client. This address is to be included by the client when signing
// sweep transactions destined for this tower, if its negotiated output
// is not dust.
var rewardScript []byte
if req.BlobType.Has(blob.FlagReward) {
rewardAddress, err := s.cfg.NewAddress()
if err != nil {
log.Errorf("Unable to generate reward addr for %s: %v",
id, err)
return s.replyCreateSession(
peer, id, wtwire.CodeTemporaryFailure, 0, nil,
)
}
// Construct the pkscript the client should pay to when signing
// justice transactions for this session.
rewardScript, err = txscript.PayToAddrScript(rewardAddress)
if err != nil {
log.Errorf("Unable to generate reward script for "+
"%s: %v", id, err)
return s.replyCreateSession(
peer, id, wtwire.CodeTemporaryFailure, 0, nil,
)
}
}
// TODO(conner): create invoice for upfront payment
// Assemble the session info using the agreed upon parameters, reward
// address, and session id.
info := wtdb.SessionInfo{
ID: *id,
Policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{
BlobType: req.BlobType,
RewardBase: req.RewardBase,
RewardRate: req.RewardRate,
SweepFeeRate: req.SweepFeeRate,
},
MaxUpdates: req.MaxUpdates,
},
RewardAddress: rewardScript,
}
// Insert the session info into the watchtower's database. If
// successful, the session will now be ready for use.
err = s.cfg.DB.InsertSessionInfo(&info)
if err != nil {
log.Errorf("Unable to create session for %s: %v", id, err)
return s.replyCreateSession(
peer, id, wtwire.CodeTemporaryFailure, 0, nil,
)
}
log.Infof("Accepted session for %s", id)
return s.replyCreateSession(
peer, id, wtwire.CodeOK, 0, rewardScript,
)
}
// replyCreateSession sends a response to a CreateSession from a client. If the
// status code in the reply is OK, the error from the write will be bubbled up.
// Otherwise, this method returns a connection error to ensure we don't continue
// communication with the client.
func (s *Server) replyCreateSession(peer Peer, id *wtdb.SessionID,
code wtwire.ErrorCode, lastApplied uint16, data []byte) error {
if s.cfg.NoAckCreateSession {
return &connFailure{
ID: *id,
Code: code,
}
}
msg := &wtwire.CreateSessionReply{
Code: code,
LastApplied: lastApplied,
Data: data,
}
err := s.sendMessage(peer, msg)
if err != nil {
log.Errorf("unable to send CreateSessionReply to %s", id)
}
// Return the write error if the request succeeded.
if code == wtwire.CodeOK {
return err
}
// Otherwise the request failed, return a connection failure to
// disconnect the client.
return &connFailure{
ID: *id,
Code: code,
}
}