pool/order/batch_storer.go
2022-09-12 17:20:12 +02:00

168 lines
5.1 KiB
Go

package order
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/pool/account"
"github.com/lightninglabs/pool/auctioneerrpc"
)
// batchStorer is a type that implements BatchStorer and can persist a batch to
// the local trader database.
type batchStorer struct {
orderStore Store
getAccount func(*btcec.PublicKey) (*account.Account, error)
}
// StorePendingBatch makes sure all changes executed by a batch are correctly
// and atomically staged to the database. It is assumed that the batch has
// previously been fully validated and that all diffs contained are consistent!
// Once the batch has been finalized/confirmed on-chain, then the stage
// modifications will be applied atomically as a result of MarkBatchComplete.
//
// NOTE: This method is part of the BatchStorer interface.
func (s *batchStorer) StorePendingBatch(batch *Batch) error {
// Prepare the order modifications first.
orders := make([]Nonce, len(batch.MatchedOrders))
orderModifiers := make([][]Modifier, len(orders))
orderIndex := 0
for nonce, theirOrders := range batch.MatchedOrders {
// Get our order first to find out the number of unfulfilled
// units.
ourOrder, err := s.orderStore.GetOrder(nonce)
if err != nil {
return fmt.Errorf("error getting order: %v", err)
}
orders[orderIndex] = nonce
// Find out if the order has unfulfilled units left or not.
unitsUnfulfilled := ourOrder.Details().UnitsUnfulfilled
for _, theirOrder := range theirOrders {
unitsUnfulfilled -= theirOrder.UnitsFilled
}
switch {
// The order has been fully filled and can be archived.
case unitsUnfulfilled == 0:
orderModifiers[orderIndex] = []Modifier{
StateModifier(StateExecuted),
UnitsFulfilledModifier(0),
}
// The order has not been fully filled, but it cannot be matched
// again due to its remaining unfulfilled units being below its
// allowed minimum, so we'll archive it.
case unitsUnfulfilled < ourOrder.Details().MinUnitsMatch:
orderModifiers[orderIndex] = []Modifier{
StateModifier(StateExecuted),
UnitsFulfilledModifier(unitsUnfulfilled),
}
// Some units were not yet filled.
default:
orderModifiers[orderIndex] = []Modifier{
StateModifier(StatePartiallyFilled),
UnitsFulfilledModifier(unitsUnfulfilled),
}
}
orderIndex++
}
// Next create our account modifiers.
accounts := make([]*account.Account, len(batch.AccountDiffs))
accountModifiers := make([][]account.Modifier, len(accounts))
for idx, diff := range batch.AccountDiffs {
// Get the current state of the account first so we can create
// a proper diff.
acct, err := s.getAccount(diff.AccountKey)
if err != nil {
return fmt.Errorf("error getting account: %v", err)
}
accounts[idx] = acct
var modifiers []account.Modifier
// Determine the new state of the account and set the on-chain
// attributes accordingly.
switch diff.EndingState {
// The account output has been recreated and needs to wait to be
// confirmed again.
case auctioneerrpc.AccountDiff_OUTPUT_RECREATED:
modifiers = append(
modifiers,
account.StateModifier(account.StatePendingBatch),
account.OutPointModifier(wire.OutPoint{
Index: uint32(diff.OutpointIndex),
Hash: batch.BatchTX.TxHash(),
}),
account.IncrementBatchKey(),
)
// The account expiry needs to be updated only when the
// client supports it.
if batch.Version.SupportsAccountExtension() &&
diff.NewExpiry != 0 {
modifiers = append(
modifiers,
account.ExpiryModifier(diff.NewExpiry),
)
}
// The account version can be upgraded during the batch
// if the client supports it.
if batch.Version.SupportsAccountTaprootUpgrade() &&
diff.NewVersion > acct.Version {
modifiers = append(
modifiers,
account.VersionModifier(diff.NewVersion),
)
}
// The account was fully spent on-chain. We need to wait for the
// batch (spend) TX to be confirmed still.
case auctioneerrpc.AccountDiff_OUTPUT_FULLY_SPENT,
auctioneerrpc.AccountDiff_OUTPUT_DUST_ADDED_TO_FEES,
auctioneerrpc.AccountDiff_OUTPUT_DUST_EXTENDED_OFFCHAIN:
modifiers = append(
modifiers,
account.StateModifier(account.StatePendingClosed),
)
default:
return fmt.Errorf("invalid ending account state %d",
diff.EndingState)
}
// Finally update the account value, height hint, and its latest
// transaction.
modifiers = append(
modifiers, account.ValueModifier(diff.EndingBalance),
)
modifiers = append(
modifiers, account.HeightHintModifier(batch.HeightHint),
)
modifiers = append(
modifiers, account.LatestTxModifier(batch.BatchTX),
)
accountModifiers[idx] = modifiers
}
// Everything is ready to be persisted now.
return s.orderStore.StorePendingBatch(
batch, orders, orderModifiers, accounts, accountModifiers,
)
}
// MarkBatchComplete marks a pending batch as complete, allowing a trader to
// participate in a new batch.
func (s *batchStorer) MarkBatchComplete() error {
return s.orderStore.MarkBatchComplete()
}
// A compile-time constraint to ensure batchStorer implements BatchStorer.
var _ BatchStorer = (*batchStorer)(nil)