pool/sidecar/interfaces.go
2023-05-16 17:30:59 -05:00

346 lines
10 KiB
Go

package sidecar
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/pool/codec"
)
// Version is the version of the sidecar ticket format.
type Version uint8
const (
// VersionDefault is the initial version of the ticket format.
VersionDefault Version = 0
// VersionUnannouncedZeroConf is the fist version of the ticket format
// that added support for unannounced and zero conf channels.
VersionUnannouncedZeroConf Version = 1
)
// State is the state a sidecar ticket currently is in. Each updater of the
// ticket is responsible for also setting the state correctly after adding their
// data according to their role.
type State uint8
const (
// StateCreated is the state a ticket is in after it's been first
// created and the offer information was added but not signed yet.
StateCreated State = 0
// StateOffered is the state a ticket is in after the offer data was
// signed by the sidecar provider.
StateOffered State = 1
// StateRegistered is the state a ticket is in after the recipient has
// registered it in their system and added their information.
StateRegistered State = 2
// StateOrdered is the state a ticket is in after the bid order for the
// sidecar channel was submitted and the order information was added to
// the ticket. The ticket now also contains a signature of the provider
// over the order digest.
StateOrdered State = 3
// StateExpectingChannel is the state a ticket is in after it's been
// returned to the channel recipient and their node was instructed to
// start listening for batch events for it.
StateExpectingChannel State = 4
// StateCompleted is the state a ticket is in after the sidecar channel
// was successfully opened and the bid order completed.
StateCompleted State = 5
// StateCanceled is the state a ticket is in after the sidecar channel
// bid order was canceled by the taker.
StateCanceled State = 6
)
// String returns the string representation of a sidecar ticket state.
func (s State) String() string {
switch s {
case StateCreated:
return "created"
case StateOffered:
return "offered"
case StateRegistered:
return "registered"
case StateOrdered:
return "ordered"
case StateExpectingChannel:
return "expecting channel"
case StateCompleted:
return "completed"
case StateCanceled:
return "canceled"
default:
return fmt.Sprintf("unknown <%d>", s)
}
}
// IsTerminal returns true if the ticket is in a final state and will not be
// updated ever again.
func (s State) IsTerminal() bool {
switch s {
case StateCompleted, StateCanceled:
return true
default:
return false
}
}
// Offer is a struct holding the information that a sidecar channel provider is
// committing to when offering to buy a channel for the recipient. The sidecar
// channel flow is initiated by the provider creating a ticket and adding its
// signed offer to it.
type Offer struct {
// Capacity is the channel capacity of the sidecar channel in satoshis.
Capacity btcutil.Amount
// PushAmt is the amount in satoshis that will be pushed to the
// recipient of the channel to kick start them with inbound capacity.
// If this is non-zero then the provider must pay for the push amount as
// well as all other costs from their Pool account. Makers can opt out
// of supporting push amounts when submitting ask orders so this will
// reduce the matching chances somewhat.
PushAmt btcutil.Amount
// LeaseDurationBlocks is the number of blocks the offered channel in
// this offer would be leased for.
LeaseDurationBlocks uint32
// SignPubKey is the public key for corresponding to the private key
// that signed the SigOfferDigest below and, in a later state, the
// SigOrderDigest of the Order struct.
SignPubKey *btcec.PublicKey
// SigOfferDigest is a signature over the offer digest, signed with the
// private key that corresponds to the SignPubKey above.
SigOfferDigest *ecdsa.Signature
// Auto determines if the provider requires that the ticket be
// completed using an automated negotiation sequence.
Auto bool
// UnannouncedChannel determines if this ticket is interested in
// unannounced channels or not.
UnannouncedChannel bool
// ZeroConfChannel determines if this ticket is interested in zero conf
// channels or not.
ZeroConfChannel bool
}
// Recipient is a struct holding the information about the recipient of the
// sidecar channel in question.
type Recipient struct {
// NodePubKey is the recipient nodes' identity public key that is
// advertised in the bid order.
NodePubKey *btcec.PublicKey
// MultiSigPubKey is a public key to which the recipient node has the
// private key to. It is the key that will be used as one of the 2-of-2
// multisig keys of the channel funding transaction output and is
// advertised in the bid order.
MultiSigPubKey *btcec.PublicKey
// MultiSigKeyIndex is the derivation index of the MultiSigPubKey.
MultiSigKeyIndex uint32
}
// Order is a struct holding the information about the sidecar bid order after
// it's been submitted by the provider.
type Order struct {
// BidNonce is the order nonce of the bid order that was submitted for
// purchasing the sidecar channel.
BidNonce [32]byte
// SigOrderDigest is a signature over the order digest, signed with the
// private key that corresponds to the SignPubKey in the Offer struct.
SigOrderDigest *ecdsa.Signature
}
// Execution is a struct holding information about the sidecar bid order during
// its execution.
type Execution struct {
// PendingChannelID is the pending channel ID of the currently
// registered funding shim that was added by the recipient node to
// receive the channel.
PendingChannelID [32]byte
}
// Ticket is a struct holding all the information for establishing/buying a
// sidecar channel. It is meant to be used in a PSBT like manner where each
// participant incrementally adds their information according to their role and
// the current step.
type Ticket struct {
// ID is a pseudorandom identifier of the ticket.
ID [8]byte
// Version is the version of the ticket serialization format.
Version Version
// State is the current state of the ticket. The state is updated by
// each participant and is therefore not covered in any of the signature
// digests.
State State
// Offer contains the initial conditions offered by the sidecar channel
// provider. Every ticket must start with an offer and therefore this
// member can never be empty or nil.
Offer Offer
// Recipient contains the information about the receiver node of the
// sidecar channel. This field must be set for all states greater or
// equal to StateRegistered.
Recipient *Recipient
// Order contains the information about the order that was submitted for
// leasing the sidecar channel represented by this ticket. This field
// must be set for all states greater or equal to StateOrdered.
Order *Order
// Execution contains the information about the execution part of the
// sidecar channel. This information is not meant to be exchanged
// between the participating parties but is included in the ticket to
// make it easy to serialize/deserialize a ticket state within the local
// database.
Execution *Execution
}
// NewTicket creates a new sidecar ticket with the given version and offer
// information.
func NewTicket(capacity, pushAmt btcutil.Amount,
duration uint32, offerPubKey *btcec.PublicKey,
auto bool, unannounced, zeroConf bool) (*Ticket, error) {
// The ticket should always use the minimum version that supports
// all the features encoded in it so it can be used by clients using
// older pool versions.
version := VersionDefault
if unannounced || zeroConf {
version = VersionUnannouncedZeroConf
}
t := &Ticket{
Version: version,
State: StateOffered,
Offer: Offer{
Capacity: capacity,
PushAmt: pushAmt,
LeaseDurationBlocks: duration,
SignPubKey: offerPubKey,
Auto: auto,
UnannouncedChannel: unannounced,
ZeroConfChannel: zeroConf,
},
}
// The linter flags this, even though crypto/rand is being used...
if _, err := rand.Read(t.ID[:]); err != nil { // nolint:gosec
return nil, err
}
return t, nil
}
// OfferDigest returns a message digest over the offer fields.
func (t *Ticket) OfferDigest() ([32]byte, error) {
var (
msg bytes.Buffer
result [sha256.Size]byte
)
switch t.Version {
case VersionDefault:
err := codec.WriteElements(
&msg, t.ID[:], uint8(t.Version), t.Offer.Capacity,
t.Offer.PushAmt, t.Offer.Auto,
)
if err != nil {
return result, err
}
case VersionUnannouncedZeroConf:
err := codec.WriteElements(
&msg, t.ID[:], uint8(t.Version), t.Offer.Capacity,
t.Offer.PushAmt, t.Offer.Auto,
t.Offer.UnannouncedChannel, t.Offer.ZeroConfChannel,
)
if err != nil {
return result, err
}
default:
return result, fmt.Errorf("unknown version %d", t.Version)
}
return sha256.Sum256(msg.Bytes()), nil
}
// OrderDigest returns a message digest over the order fields.
func (t *Ticket) OrderDigest() ([32]byte, error) {
var (
msg bytes.Buffer
result [sha256.Size]byte
)
if t.State < StateOrdered || t.Order == nil {
return result, fmt.Errorf("invalid state for order digest")
}
switch t.Version {
case VersionDefault:
err := codec.WriteElements(
&msg, t.ID[:], uint8(t.Version), t.Offer.Capacity,
t.Offer.PushAmt, t.Order.BidNonce[:],
)
if err != nil {
return result, err
}
case VersionUnannouncedZeroConf:
err := codec.WriteElements(
&msg, t.ID[:], uint8(t.Version), t.Offer.Capacity,
t.Offer.PushAmt, t.Offer.Auto,
t.Offer.UnannouncedChannel, t.Offer.ZeroConfChannel,
)
if err != nil {
return result, err
}
default:
return result, fmt.Errorf("unknown version %d", t.Version)
}
return sha256.Sum256(msg.Bytes()), nil
}
// Store is the interface a persistent storage must implement for storing and
// retrieving sidecar tickets.
type Store interface {
// AddSidecar adds a record for the sidecar order to the database.
AddSidecar(sidecar *Ticket) error
// UpdateSidecar updates a sidecar order in the database.
UpdateSidecar(sidecar *Ticket) error
// Sidecar retrieves a specific sidecar by its ID and provider signing
// key (offer signature pubkey) or returns ErrNoSidecar if it's not
// found.
Sidecar(id [8]byte, offerSignPubKey *btcec.PublicKey) (*Ticket, error)
// Sidecars retrieves all known sidecar orders from the database.
Sidecars() ([]*Ticket, error)
}