mirror of
https://github.com/lightninglabs/pool.git
synced 2026-08-13 12:33:04 +02:00
293 lines
9.6 KiB
Go
293 lines
9.6 KiB
Go
package order
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightninglabs/pool/account"
|
|
"github.com/lightninglabs/pool/poolrpc"
|
|
"github.com/lightninglabs/pool/terms"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
)
|
|
|
|
// BatchVersion is the type for the batch verification protocol.
|
|
type BatchVersion uint32
|
|
|
|
const (
|
|
// DefaultVersion is the first implemented version of the batch
|
|
// verification protocol.
|
|
DefaultVersion BatchVersion = 0
|
|
|
|
// CurrentVersion must point to the latest implemented version of the
|
|
// batch verification protocol. Both server and client should always
|
|
// refer to this constant. If a client's binary is not updated in time
|
|
// it will point to a previous version than the server and the mismatch
|
|
// will be detected during the OrderMatchPrepare call.
|
|
CurrentVersion = DefaultVersion
|
|
)
|
|
|
|
// BatchID is a 33-byte point that uniquely identifies this batch. This ID
|
|
// will be used later for account key derivation when constructing the batch
|
|
// execution transaction.
|
|
type BatchID [33]byte
|
|
|
|
// NewBatchID returns a new batch ID for the given public key.
|
|
func NewBatchID(pub *btcec.PublicKey) BatchID {
|
|
var b BatchID
|
|
copy(b[:], pub.SerializeCompressed())
|
|
return b
|
|
}
|
|
|
|
// AccountDiff represents a matching+clearing event for a trader's account.
|
|
// This diff shows the total balance delta along with a breakdown for each item
|
|
// for a trader's account.
|
|
type AccountDiff struct {
|
|
// AccountKeyRaw is the raw serialized account public key this diff
|
|
// refers to.
|
|
AccountKeyRaw [33]byte
|
|
|
|
// AccountKey is the parsed account public key this diff refers to.
|
|
AccountKey *btcec.PublicKey
|
|
|
|
// EndingState is the ending on-chain state of the account after the
|
|
// executed batch as the auctioneer calculated it.
|
|
EndingState poolrpc.AccountDiff_AccountState
|
|
|
|
// EndingBalance is the ending balance for a trader's account.
|
|
EndingBalance btcutil.Amount
|
|
|
|
// OutpointIndex is the index of the re-created account output in the
|
|
// batch transaction. This is set to -1 if no account output has been
|
|
// created because the leftover value was considered to be dust.
|
|
OutpointIndex int32
|
|
}
|
|
|
|
// validateEndingState validates that the ending state of an account as
|
|
// proposed by the server is correct.
|
|
func (d *AccountDiff) validateEndingState(tx *wire.MsgTx,
|
|
acct *account.Account) error {
|
|
|
|
state := d.EndingState
|
|
wrongStateErr := fmt.Errorf(
|
|
"unexpected state %d for ending balance %d", state,
|
|
d.EndingBalance,
|
|
)
|
|
|
|
// Depending on the final amount of the account, we might get
|
|
// dust which is handled differently.
|
|
if d.EndingBalance < MinNoDustAccountSize {
|
|
// The ending balance of the account is too small to be spent
|
|
// by a simple transaction and not create a dust output. We
|
|
// expect the server to set the state correctly and not re-
|
|
// create an account outpoint.
|
|
if state != poolrpc.AccountDiff_OUTPUT_DUST_EXTENDED_OFFCHAIN &&
|
|
state != poolrpc.AccountDiff_OUTPUT_DUST_ADDED_TO_FEES &&
|
|
state != poolrpc.AccountDiff_OUTPUT_FULLY_SPENT {
|
|
|
|
return wrongStateErr
|
|
}
|
|
if d.OutpointIndex >= 0 {
|
|
return fmt.Errorf("unexpected outpoint index for dust " +
|
|
"account")
|
|
}
|
|
} else {
|
|
// There should be enough balance left to justify a new account
|
|
// output. We should get the outpoint from the server.
|
|
if state != poolrpc.AccountDiff_OUTPUT_RECREATED {
|
|
return wrongStateErr
|
|
}
|
|
if d.OutpointIndex < 0 {
|
|
return fmt.Errorf("outpoint index invalid for non-"+
|
|
"dust account with state %d and balance %d",
|
|
state, d.EndingBalance)
|
|
}
|
|
|
|
// Make sure the outpoint index is correct and there is an
|
|
// output with the correct amount there.
|
|
if d.OutpointIndex >= int32(len(tx.TxOut)) {
|
|
return fmt.Errorf("outpoint index out of bounds")
|
|
}
|
|
out := tx.TxOut[d.OutpointIndex]
|
|
if btcutil.Amount(out.Value) != d.EndingBalance {
|
|
return fmt.Errorf("invalid account output amount. got "+
|
|
"%d expected %d", out.Value, d.EndingBalance)
|
|
}
|
|
|
|
// Final check, make sure we arrive at the same script for the
|
|
// new account output.
|
|
nextScript, err := acct.NextOutputScript()
|
|
if err != nil {
|
|
return fmt.Errorf("could not derive next account "+
|
|
"script: %v", err)
|
|
}
|
|
if !bytes.Equal(out.PkScript, nextScript) {
|
|
return fmt.Errorf("unexpected account output "+
|
|
"script: want %x got %x", nextScript,
|
|
out.PkScript)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Batch is all the information the auctioneer sends to each trader for them to
|
|
// validate a batch execution.
|
|
type Batch struct {
|
|
// ID is the batch's unique ID. If multiple messages come in with the
|
|
// same ID, they are to be considered to be the _same batch_ with
|
|
// updated matches. Any previous version of a batch with that ID should
|
|
// be discarded in that case.
|
|
ID BatchID
|
|
|
|
// BatchVersion is the version of the batch verification protocol.
|
|
Version BatchVersion
|
|
|
|
// MatchedOrders is a map between all trader's orders and the other
|
|
// orders that were matched to them in the batch.
|
|
MatchedOrders map[Nonce][]*MatchedOrder
|
|
|
|
// AccountDiffs is the calculated difference for each trader's account
|
|
// that was involved in the batch.
|
|
AccountDiffs []*AccountDiff
|
|
|
|
// ExecutionFee is the FeeSchedule that was used by the server to
|
|
// calculate the execution fee.
|
|
ExecutionFee terms.FeeSchedule
|
|
|
|
// ClearingPrice is the fixed rate the orders were cleared at.
|
|
ClearingPrice FixedRatePremium
|
|
|
|
// BatchTX is the complete batch transaction with all non-witness data
|
|
// fully populated.
|
|
BatchTX *wire.MsgTx
|
|
|
|
// BatchTxFeeRate is the miner fee rate in sat/kW that was chosen for
|
|
// the batch transaction.
|
|
BatchTxFeeRate chainfee.SatPerKWeight
|
|
|
|
// FeeRebate is the rebate that was offered to the trader if another
|
|
// batch participant wanted to pay more fees for a faster confirmation.
|
|
FeeRebate btcutil.Amount
|
|
}
|
|
|
|
// Fetcher describes a function that's able to fetch the latest version of an
|
|
// order based on its nonce.
|
|
type Fetcher func(Nonce) (Order, error)
|
|
|
|
// CancelPendingFundingShims cancels all funding shims we registered when
|
|
// preparing for this batch. This should be called if for any reason we need to
|
|
// reject the batch, so we're able to process any subsequent modified batches.
|
|
func (b *Batch) CancelPendingFundingShims(lndClient lnrpc.LightningClient,
|
|
fetchOrder Fetcher) error {
|
|
|
|
// Since we support partial matches, a given bid of ours could've been
|
|
// matched with multiple asks, so we'll iterate through all those to
|
|
// ensure we unregister all the created shims.
|
|
ctxb := context.Background()
|
|
for ourOrderNonce, matchedOrders := range b.MatchedOrders {
|
|
ourOrder, err := fetchOrder(ourOrderNonce)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
orderIsAsk := ourOrder.Type() == TypeAsk
|
|
|
|
// If the order as an ask, then we don't need to do anything,
|
|
// as we only register funding shims for incoming channels (so
|
|
// buys).
|
|
if orderIsAsk {
|
|
continue
|
|
}
|
|
|
|
// For each ask order that was matched with this bid, we'll
|
|
// re-derive the pending chan ID key used, then attempt to
|
|
// unregister it.
|
|
for _, matchedOrder := range matchedOrders {
|
|
bidNonce := ourOrder.Nonce()
|
|
askNonce := matchedOrder.Order.Nonce()
|
|
pendingChanID := PendingChanKey(
|
|
askNonce, bidNonce,
|
|
)
|
|
|
|
cancelShimMsg := &lnrpc.FundingTransitionMsg_ShimCancel{
|
|
ShimCancel: &lnrpc.FundingShimCancel{
|
|
PendingChanId: pendingChanID[:],
|
|
},
|
|
}
|
|
|
|
_, err = lndClient.FundingStateStep(
|
|
ctxb, &lnrpc.FundingTransitionMsg{
|
|
Trigger: cancelShimMsg,
|
|
},
|
|
)
|
|
if err != nil {
|
|
log.Warnf("Unable to unregister funding shim "+
|
|
"(pendingChanID=%x) for order=%v",
|
|
pendingChanID[:], bidNonce)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MatchedOrder is the other side to one of our matched orders. It contains all
|
|
// the information that is needed to validate the match and to start negotiating
|
|
// the channel opening with the matched trader's node.
|
|
type MatchedOrder struct {
|
|
// Order contains the details of the other order as sent by the server.
|
|
Order Order
|
|
|
|
// MultiSigKey is a key of the node creating the order that will be used
|
|
// to craft the channel funding TX's 2-of-2 multi signature output.
|
|
MultiSigKey [33]byte
|
|
|
|
// NodeKey is the identity public key of the node creating the order.
|
|
NodeKey [33]byte
|
|
|
|
// NodeAddrs is the list of network addresses of the node creating the
|
|
// order.
|
|
NodeAddrs []net.Addr
|
|
|
|
// UnitsFilled is the number of units that were matched by this order.
|
|
UnitsFilled SupplyUnit
|
|
}
|
|
|
|
// BatchSignature is a map type that is keyed by a trader's account key and
|
|
// contains the multi-sig signature for the input that
|
|
// spends from the current account in a batch.
|
|
type BatchSignature map[[33]byte]*btcec.Signature
|
|
|
|
// BatchVerifier is an interface that can verify a batch from the point of view
|
|
// of the trader.
|
|
type BatchVerifier interface {
|
|
// Verify makes sure the batch prepared by the server is correct and
|
|
// can be accepted by the trader.
|
|
Verify(*Batch) error
|
|
}
|
|
|
|
// BatchSigner is an interface that can sign for a trader's account inputs in
|
|
// a batch.
|
|
type BatchSigner interface {
|
|
// Sign returns the witness stack of all account inputs in a batch that
|
|
// belong to the trader.
|
|
Sign(*Batch) (BatchSignature, error)
|
|
}
|
|
|
|
// BatchStorer is an interface that can store a batch to the local database by
|
|
// applying all the diffs to the orders and accounts.
|
|
type BatchStorer interface {
|
|
// StorePendingBatch makes sure all changes executed by a pending batch
|
|
// are correctly and atomically stored to the database.
|
|
StorePendingBatch(_ *Batch, bestHeight uint32) error
|
|
|
|
// MarkBatchComplete marks a pending batch as complete, allowing a
|
|
// trader to participate in a new batch.
|
|
MarkBatchComplete() error
|
|
}
|