168 lines
5.2 KiB
Go
168 lines
5.2 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,
|
|
)
|
|
}
|
|
|
|
// If the request asks for a reward session and the tower has them
|
|
// disabled, we will reject the request.
|
|
if s.cfg.DisableReward && req.BlobType.Has(blob.FlagReward) {
|
|
log.Debugf("Rejecting CreateSession from %s, reward "+
|
|
"sessions disabled", id)
|
|
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,
|
|
}
|
|
}
|