pool/clientdb/order_event.go
Matt Morehouse 8b239fab7e
clientdb: move WriteElement[s] to own package
Move WriteElement[s] to the codec package so that packages other than
clientdb can use it.

For the codec package, we avoid depending on other packages within Pool
so that no circular dependencies develop. This requires us to move some
of the uintXX casting logic in clientdb/codec.go to call sites.
2023-05-16 17:30:59 -05:00

498 lines
15 KiB
Go

package clientdb
import (
"bytes"
"fmt"
"io"
"time"
"github.com/lightninglabs/pool/auctioneerrpc"
"github.com/lightninglabs/pool/codec"
"github.com/lightninglabs/pool/event"
"github.com/lightninglabs/pool/order"
"github.com/lightninglabs/pool/poolrpc"
"go.etcd.io/bbolt"
)
// OrderEvent is the main interface for order specific events.
type OrderEvent interface {
event.Event
// Nonce returns the nonce of the order this event refers to.
Nonce() order.Nonce
}
// CreatedEvent is an event implementation that tracks the creation of an order.
// This is distinct from the order state change to allow us to efficiently
// filter all events by their type to get the creation timestamps of all orders.
type CreatedEvent struct {
// timestamp is the unique timestamp the event was created/recorded at.
timestamp time.Time
// Nonce of the order this event refers to.
nonce order.Nonce
}
// NewCreatedEvent creates a new CreatedEvent from an order with the current
// system time as the timestamp.
func NewCreatedEvent(o order.Order) *CreatedEvent {
return &CreatedEvent{
timestamp: time.Now(),
nonce: o.Nonce(),
}
}
// Type returns the type of the event.
//
// NOTE: This is part of the event.Event interface.
func (e *CreatedEvent) Type() event.Type {
return event.TypeOrderCreated
}
// Timestamp is the time the event happened. This will be made unique once it is
// stored. To avoid collisions, the timestamp is adjusted on the nanosecond
// scale to reach uniqueness.
//
// NOTE: This is part of the event.Event interface.
func (e *CreatedEvent) Timestamp() time.Time {
return e.timestamp
}
// SetTimestamp updates the timestamp of the event. This is needed to adjust
// timestamps in case they collide to ensure the global uniqueness of all event
// timestamps.
//
// NOTE: This is part of the event.Event interface.
func (e *CreatedEvent) SetTimestamp(ts time.Time) {
e.timestamp = ts
}
// String returns a human readable representation of the event.
//
// NOTE: This is part of the event.Event interface.
func (e *CreatedEvent) String() string {
return "OrderCreated"
}
// Serialize writes the event data to a binary storage format. This does not
// serialize the event type as that's handled generically to allow for easy
// filtering.
//
// NOTE: This is part of the event.Event interface.
func (e *CreatedEvent) Serialize(w *bytes.Buffer) error {
return codec.WriteElements(w, e.nonce[:])
}
// Deserialize reads the event data from a binary storage format. This does not
// deserialize the event type as that's handled generically to allow for easy
// filtering.
//
// NOTE: This is part of the event.Event interface.
func (e *CreatedEvent) Deserialize(r io.Reader) error {
return ReadElements(r, &e.nonce)
}
// Nonce returns the nonce of the order this event refers to.
//
// NOTE: This is part of the order.OrderEvent interface.
func (e *CreatedEvent) Nonce() order.Nonce {
return e.nonce
}
// A compile time assertion to make sure CreatedEvent implements both the
// event.Event and order.OrderEvent interface.
var _ event.Event = (*CreatedEvent)(nil)
var _ OrderEvent = (*CreatedEvent)(nil)
// UpdatedEvent is an event implementation that tracks the updates of an order.
// This event is only meant for updates that we also persist in the database.
// Temporary state changes and other updates that occur during match making are
// tracked by MatchEvent.
type UpdatedEvent struct {
// timestamp is the unique timestamp the event was created/recorded at.
timestamp time.Time
// Nonce of the order this event refers to.
nonce order.Nonce
// PrevState is the state the order had previous to the state change.
PrevState order.State
// NewState is the state the order had after the state change.
NewState order.State
// UnitsFilled is the number of units that was filled at the moment of
// the update.
UnitsFilled order.SupplyUnit
}
// NewUpdatedEvent creates a new UpdatedEvent from an order and its previous
// state with the current system time as the timestamp.
func NewUpdatedEvent(prevState order.State,
o order.Order) *UpdatedEvent {
return &UpdatedEvent{
timestamp: time.Now(),
nonce: o.Nonce(),
PrevState: prevState,
NewState: o.Details().State,
UnitsFilled: o.Details().Units - o.Details().UnitsUnfulfilled,
}
}
// Type returns the type of the event.
//
// NOTE: This is part of the event.Event interface.
func (e *UpdatedEvent) Type() event.Type {
return event.TypeOrderStateChange
}
// Timestamp is the time the event happened. This will be made unique once it is
// stored. To avoid collisions, the timestamp is adjusted on the nanosecond
// scale to reach uniqueness.
//
// NOTE: This is part of the event.Event interface.
func (e *UpdatedEvent) Timestamp() time.Time {
return e.timestamp
}
// SetTimestamp updates the timestamp of the event. This is needed to adjust
// timestamps in case they collide to ensure the global uniqueness of all event
// timestamps.
//
// NOTE: This is part of the event.Event interface.
func (e *UpdatedEvent) SetTimestamp(ts time.Time) {
e.timestamp = ts
}
// String returns a human readable representation of the event.
//
// NOTE: This is part of the event.Event interface.
func (e *UpdatedEvent) String() string {
return fmt.Sprintf("OrderUpdate(%v)", e.NewState)
}
// Serialize writes the event data to a binary storage format. This does not
// serialize the event type as that's handled generically to allow for easy
// filtering.
//
// NOTE: This is part of the event.Event interface.
func (e *UpdatedEvent) Serialize(w *bytes.Buffer) error {
return codec.WriteElements(
w, e.nonce[:], uint8(e.PrevState), uint8(e.NewState),
uint64(e.UnitsFilled),
)
}
// Deserialize reads the event data from a binary storage format. This does not
// deserialize the event type as that's handled generically to allow for easy
// filtering.
//
// NOTE: This is part of the event.Event interface.
func (e *UpdatedEvent) Deserialize(r io.Reader) error {
return ReadElements(
r, &e.nonce, &e.PrevState, &e.NewState, &e.UnitsFilled,
)
}
// Nonce returns the nonce of the order this event refers to.
//
// NOTE: This is part of the order.OrderEvent interface.
func (e *UpdatedEvent) Nonce() order.Nonce {
return e.nonce
}
// A compile time assertion to make sure UpdatedEvent implements both the
// event.Event and order.OrderEvent interface.
var _ event.Event = (*UpdatedEvent)(nil)
var _ OrderEvent = (*UpdatedEvent)(nil)
// MatchEvent is an event implementation that tracks the match making process of
// an order.
type MatchEvent struct {
// timestamp is the unique timestamp the event was created/recorded at.
timestamp time.Time
// Nonce of the order this event refers to.
nonce order.Nonce
// MatchState is the state of the order matching process the event was
// created in.
MatchState order.MatchState
// UnitsFilled is the number of units that was or would have been filled
// for the current match attempt the order was in when this event was
// created.
UnitsFilled order.SupplyUnit
// MatchedOrder is the order counterpart that our order was matched to
// in the current match attempt this event was created for.
MatchedOrder order.Nonce
// RejectReason is set to the reason we rejected the match in case the
// MatchState is set to MatchStateRejected.
RejectReason uint32
}
// NewMatchEvent creates a new MatchEvent from an order and its matched
// counterpart with the given time as the timestamp.
func NewMatchEvent(ts time.Time, nonce order.Nonce, state order.MatchState,
unitsFilled order.SupplyUnit, matchedOrder order.Nonce,
rejectReason uint32) *MatchEvent {
return &MatchEvent{
timestamp: ts,
nonce: nonce,
MatchState: state,
UnitsFilled: unitsFilled,
MatchedOrder: matchedOrder,
RejectReason: rejectReason,
}
}
// Type returns the type of the event.
//
// NOTE: This is part of the event.Event interface.
func (e *MatchEvent) Type() event.Type {
return event.TypeOrderMatch
}
// Timestamp is the time the event happened. This will be made unique once it is
// stored. To avoid collisions, the timestamp is adjusted on the nanosecond
// scale to reach uniqueness.
//
// NOTE: This is part of the event.Event interface.
func (e *MatchEvent) Timestamp() time.Time {
return e.timestamp
}
// SetTimestamp updates the timestamp of the event. This is needed to adjust
// timestamps in case they collide to ensure the global uniqueness of all event
// timestamps.
//
// NOTE: This is part of the event.Event interface.
func (e *MatchEvent) SetTimestamp(ts time.Time) {
e.timestamp = ts
}
// String returns a human readable representation of the event.
//
// NOTE: This is part of the event.Event interface.
func (e *MatchEvent) String() string {
return fmt.Sprintf("OrderMatch(%v)", e.MatchState)
}
// Serialize writes the event data to a binary storage format. This does not
// serialize the event type as that's handled generically to allow for easy
// filtering.
//
// NOTE: This is part of the event.Event interface.
func (e *MatchEvent) Serialize(w *bytes.Buffer) error {
return codec.WriteElements(
w, e.nonce[:], uint8(e.MatchState), uint64(e.UnitsFilled),
e.MatchedOrder[:], e.RejectReason,
)
}
// Deserialize reads the event data from a binary storage format. This does not
// deserialize the event type as that's handled generically to allow for easy
// filtering.
//
// NOTE: This is part of the event.Event interface.
func (e *MatchEvent) Deserialize(r io.Reader) error {
return ReadElements(
r, &e.nonce, &e.MatchState, &e.UnitsFilled, &e.MatchedOrder,
&e.RejectReason,
)
}
// Nonce returns the nonce of the order this event refers to.
//
// NOTE: This is part of the order.OrderEvent interface.
func (e *MatchEvent) Nonce() order.Nonce {
return e.nonce
}
// A compile time assertion to make sure MatchEvent implements both the
// event.Event and order.OrderEvent interface.
var _ event.Event = (*MatchEvent)(nil)
var _ OrderEvent = (*MatchEvent)(nil)
// GetOrderEvents returns all events of an order by looking up the event
// reference keys in the order bucket.
func (db *DB) GetOrderEvents(o order.Nonce) ([]event.Event, error) {
var events []event.Event
err := db.View(func(tx *bbolt.Tx) error {
ordersBucket, err := getBucket(tx, ordersBucketKey)
if err != nil {
return err
}
orderBucket := ordersBucket.Bucket(o[:])
if orderBucket == nil {
return ErrNoOrder
}
eventSubBucket := orderBucket.Bucket(eventRefSubBucket)
if eventSubBucket == nil {
return fmt.Errorf("order event sub bucket not found")
}
// We first need to collect all timestamps from the order
// bucket.
eventTimestampMap := make(map[time.Time]struct{})
err = eventSubBucket.ForEach(func(k, v []byte) error {
// Only look at keys with correct length.
if len(k) != event.TimestampLength {
return nil
}
// Assert there are no corrupt values, in the reference
// key we only store the event's type, not the value
// itself.
if len(v) != 1 {
return fmt.Errorf("unexpected timestamp "+
"value length: %d", len(v))
}
ts := time.Unix(0, int64(byteOrder.Uint64(k)))
eventTimestampMap[ts] = struct{}{}
return nil
})
if err != nil {
return err
}
// Now get the events themselves and return them. This is always
// sorted by the events' timestamps.
predicate := func(ts time.Time, _ event.Type) bool {
_, ok := eventTimestampMap[ts]
return ok
}
events, err = getEventsTX(tx, predicate)
return err
})
if err != nil {
return nil, err
}
return events, nil
}
// StoreOrderEvents stores a list of individual order events in a single
// database transaction. The events' timestamps are adjusted on the nanosecond
// scale to ensure they're unique.
func (db *DB) StoreOrderEvents(events []OrderEvent) error {
// Pre-adjust the timestamps so we get as few collisions later as
// possible. We need to convert the type because slices of interfaces
// aren't compatible by default.
baseEvents := make([]event.Event, len(events))
for idx, evt := range events {
baseEvents[idx] = evt
}
event.MakeUniqueTimestamps(baseEvents)
return db.Update(func(tx *bbolt.Tx) error {
ordersBucket, err := getBucket(tx, ordersBucketKey)
if err != nil {
return err
}
// Each event could be for a different order so we have to look
// up the individual order bucket in each iteration.
for _, evt := range events {
nonce := evt.Nonce()
orderBucket := ordersBucket.Bucket(nonce[:])
if orderBucket == nil {
return ErrNoOrder
}
if err := storeEventTX(orderBucket, evt); err != nil {
return err
}
}
return nil
})
}
// StoreBatchEvents creates a match event of the given match state for each of
// our orders involved in a batch and stores it to the main event store. In case
// of a batch reject the RPC reason enum value can optionally be specified.
func (db *DB) StoreBatchEvents(batch *order.Batch, state order.MatchState,
rejectReason poolrpc.MatchRejectReason) error {
ts := time.Now()
events := make([]OrderEvent, 0, len(batch.MatchedOrders))
for nonce, matchedOrders := range batch.MatchedOrders {
for _, matchedOrder := range matchedOrders {
events = append(events, NewMatchEvent(
ts, nonce, state, matchedOrder.UnitsFilled,
matchedOrder.Order.Nonce(),
uint32(rejectReason),
))
}
}
if err := db.StoreOrderEvents(events); err != nil {
return fmt.Errorf("error storing match events: %w", err)
}
return nil
}
// StoreBatchPartialRejectEvents creates a reject match event for each of our
// orders involved in a batch and stores it to the main event store, including
// the reason for the reject.
func (db *DB) StoreBatchPartialRejectEvents(batch *order.Batch,
partialRejects map[order.Nonce]*auctioneerrpc.OrderReject) error {
// In case an order itself wasn't rejected but was just in the same
// batch as a reject, we mark it in the event as such. If it was, this
// default value will be overwritten accordingly.
const defaultRejectReason = uint32(
poolrpc.MatchRejectReason_PARTIAL_REJECT_COLLATERAL,
)
// The RPC names are so long, we create more shortcuts here to increase
// readability.
const reasonFundingFailed = uint32(
poolrpc.MatchRejectReason_PARTIAL_REJECT_CHANNEL_FUNDING_FAILED,
)
const reasonDuplicatePeer = uint32(
poolrpc.MatchRejectReason_PARTIAL_REJECT_DUPLICATE_PEER,
)
ts := time.Now()
events := make([]OrderEvent, 0, len(batch.MatchedOrders))
for nonce, matchedOrders := range batch.MatchedOrders {
for _, matchedOrder := range matchedOrders {
otherNonce := matchedOrder.Order.Nonce()
evt := NewMatchEvent(
ts, nonce, order.MatchStateRejected,
matchedOrder.UnitsFilled, otherNonce,
defaultRejectReason,
)
reject, ok := partialRejects[otherNonce]
if ok {
switch reject.ReasonCode {
case auctioneerrpc.OrderReject_CHANNEL_FUNDING_FAILED:
evt.RejectReason = reasonFundingFailed
case auctioneerrpc.OrderReject_DUPLICATE_PEER:
evt.RejectReason = reasonDuplicatePeer
}
}
events = append(events, evt)
}
}
if err := db.StoreOrderEvents(events); err != nil {
return fmt.Errorf("error storing match events: %w", err)
}
return nil
}