mirror of
https://github.com/lightninglabs/pool.git
synced 2026-08-13 12:33:04 +02:00
order+auctioneer: parse height hint from auctioneer's proposed batch
The auctioneer will now provide a height hint along with every proposed batch it sends to traders. This height hint is then used by each trader to base off their absolute lease expiration height, such that they can agree on the same value and avoid any divergences due to block propagation discrepancies between them.
This commit is contained in:
parent
321e3c3598
commit
f9c2861354
10 changed files with 673 additions and 655 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -545,6 +545,13 @@ message OrderMatchPrepare {
|
|||
within and the discovered market clearing price.
|
||||
*/
|
||||
map<uint32, MatchedMarket> matched_markets = 10;
|
||||
|
||||
/*
|
||||
The earliest absolute height in the chain in which the batch transaction can
|
||||
be found within. This will be used by traders to base off their absolute
|
||||
channel lease maturity height.
|
||||
*/
|
||||
uint32 batch_height_hint = 11;
|
||||
}
|
||||
|
||||
message OrderMatchSignBegin {
|
||||
|
|
@ -565,11 +572,8 @@ message OrderMatchFinalize {
|
|||
*/
|
||||
bytes batch_txid = 2;
|
||||
|
||||
/*
|
||||
The current block height at the time the batch transaction was published to
|
||||
the network.
|
||||
*/
|
||||
uint32 height_hint = 3;
|
||||
// Don't re-use, this was a field that was removed.
|
||||
reserved 3;
|
||||
}
|
||||
|
||||
message SubscribeError {
|
||||
|
|
|
|||
|
|
@ -202,6 +202,11 @@ type Batch struct {
|
|||
// 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
|
||||
|
||||
// HeightHint represents the earliest absolute height in the chain in
|
||||
// which the batch transaction can be found within. This will be used by
|
||||
// traders to base off their absolute channel lease maturity height.
|
||||
HeightHint uint32
|
||||
}
|
||||
|
||||
// Fetcher describes a function that's able to fetch the latest version of an
|
||||
|
|
@ -304,7 +309,7 @@ type BatchSignature map[[33]byte]*btcec.Signature
|
|||
type BatchVerifier interface {
|
||||
// Verify makes sure the batch prepared by the server is correct and
|
||||
// can be accepted by the trader.
|
||||
Verify(*Batch) error
|
||||
Verify(_ *Batch, bestHeight uint32) error
|
||||
}
|
||||
|
||||
// BatchSigner is an interface that can sign for a trader's account inputs in
|
||||
|
|
@ -320,7 +325,7 @@ type BatchSigner interface {
|
|||
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
|
||||
StorePendingBatch(_ *Batch) error
|
||||
|
||||
// MarkBatchComplete marks a pending batch as complete, allowing a
|
||||
// trader to participate in a new batch.
|
||||
|
|
|
|||
|
|
@ -9,13 +9,6 @@ import (
|
|||
"github.com/lightninglabs/pool/auctioneerrpc"
|
||||
)
|
||||
|
||||
const (
|
||||
// heightHintPadding is the padding we add to our best known height to
|
||||
// avoid any discrepancies in block propagation between us and the
|
||||
// auctioneer.
|
||||
heightHintPadding = -3
|
||||
)
|
||||
|
||||
// batchStorer is a type that implements BatchStorer and can persist a batch to
|
||||
// the local trader database.
|
||||
type batchStorer struct {
|
||||
|
|
@ -30,7 +23,7 @@ type batchStorer struct {
|
|||
// 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, bestHeight uint32) error {
|
||||
func (s *batchStorer) StorePendingBatch(batch *Batch) error {
|
||||
// Prepare the order modifications first.
|
||||
orders := make([]Nonce, len(batch.MatchedOrders))
|
||||
orderModifiers := make([][]Modifier, len(orders))
|
||||
|
|
@ -80,13 +73,6 @@ func (s *batchStorer) StorePendingBatch(batch *Batch, bestHeight uint32) error {
|
|||
// Next create our account modifiers.
|
||||
accounts := make([]*account.Account, len(batch.AccountDiffs))
|
||||
accountModifiers := make([][]account.Modifier, len(accounts))
|
||||
|
||||
// Each account will have the same height hint applied.
|
||||
heightHint := int64(bestHeight) + heightHintPadding
|
||||
if heightHint < 0 {
|
||||
heightHint = 0
|
||||
}
|
||||
|
||||
for idx, diff := range batch.AccountDiffs {
|
||||
// Get the current state of the account first so we can create
|
||||
// a proper diff.
|
||||
|
|
@ -135,7 +121,7 @@ func (s *batchStorer) StorePendingBatch(batch *Batch, bestHeight uint32) error {
|
|||
modifiers, account.ValueModifier(diff.EndingBalance),
|
||||
)
|
||||
modifiers = append(
|
||||
modifiers, account.HeightHintModifier(uint32(heightHint)),
|
||||
modifiers, account.HeightHintModifier(batch.HeightHint),
|
||||
)
|
||||
modifiers = append(
|
||||
modifiers, account.LatestTxModifier(batch.BatchTX),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import (
|
|||
func TestBatchStorer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const bestHeight = 1337
|
||||
var (
|
||||
storeMock = newMockStore()
|
||||
storer = &batchStorer{
|
||||
|
|
@ -106,6 +105,7 @@ func TestBatchStorer(t *testing.T) {
|
|||
AccountDiffs: accountDiffs,
|
||||
BatchTX: batchTx,
|
||||
BatchTxFeeRate: chainfee.FeePerKwFloor,
|
||||
HeightHint: 1337,
|
||||
}
|
||||
|
||||
// Create the starting database state now.
|
||||
|
|
@ -120,7 +120,7 @@ func TestBatchStorer(t *testing.T) {
|
|||
}
|
||||
|
||||
// Pass the assembled batch to the storer now.
|
||||
err := storer.StorePendingBatch(batch, bestHeight)
|
||||
err := storer.StorePendingBatch(batch)
|
||||
if err != nil {
|
||||
t.Fatalf("error storing batch: %v", err)
|
||||
}
|
||||
|
|
@ -166,10 +166,9 @@ func TestBatchStorer(t *testing.T) {
|
|||
t.Fatalf("invalid account expiry, got %d wanted %d",
|
||||
smallAcct.Value, 144)
|
||||
}
|
||||
heightHint := uint32(bestHeight + heightHintPadding)
|
||||
if smallAcct.HeightHint != heightHint {
|
||||
if smallAcct.HeightHint != batch.HeightHint {
|
||||
t.Fatalf("invalid account height hint, got %d wanted %d",
|
||||
smallAcct.Value, heightHint)
|
||||
smallAcct.HeightHint, batch.HeightHint)
|
||||
}
|
||||
|
||||
if bigAcct.State != account.StatePendingBatch {
|
||||
|
|
@ -184,9 +183,9 @@ func TestBatchStorer(t *testing.T) {
|
|||
t.Fatalf("invalid account expiry, got %d wanted %d",
|
||||
bigAcct.Value, 144)
|
||||
}
|
||||
if bigAcct.HeightHint != heightHint {
|
||||
if bigAcct.HeightHint != batch.HeightHint {
|
||||
t.Fatalf("invalid account height hint, got %d wanted %d",
|
||||
bigAcct.Value, heightHint)
|
||||
bigAcct.HeightHint, batch.HeightHint)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ const (
|
|||
// deriveKeyTimeout is the number of seconds we allow the wallet to take
|
||||
// to derive a key.
|
||||
deriveKeyTimeout = 10 * time.Second
|
||||
|
||||
// heightHintPadding is the padding we subtract/add to our best known
|
||||
// height to avoid any discrepancies in block propagation between us and
|
||||
// the auctioneer.
|
||||
heightHintPadding = 3
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -68,7 +73,7 @@ type batchVerifier struct {
|
|||
// accepted by the trader.
|
||||
//
|
||||
// NOTE: This method is part of the BatchVerifier interface.
|
||||
func (v *batchVerifier) Verify(batch *Batch) error {
|
||||
func (v *batchVerifier) Verify(batch *Batch, bestHeight uint32) error {
|
||||
// First of all, make sure we're using the same batch validation version
|
||||
// as the server. Otherwise we bail out of the batch. This should
|
||||
// already be handled when the client connects/authenticates. But
|
||||
|
|
@ -77,6 +82,13 @@ func (v *batchVerifier) Verify(batch *Batch) error {
|
|||
return ErrVersionMismatch
|
||||
}
|
||||
|
||||
// Reject the batch if we're too far in the past or future compared to
|
||||
// the auctioneer.
|
||||
if bestHeight < batch.HeightHint-heightHintPadding ||
|
||||
bestHeight > batch.HeightHint+heightHintPadding {
|
||||
return ErrInvalidBatchHeightHint
|
||||
}
|
||||
|
||||
// First go through all orders that were matched for us. We'll make sure
|
||||
// we know of the order and that the numbers check out on a high level.
|
||||
tallies := make(map[[33]byte]*AccountTally)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ var (
|
|||
func TestBatchVerifier(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const bestHeight = 1337
|
||||
var (
|
||||
walletKit = test.NewMockWalletKit()
|
||||
batchID BatchID
|
||||
|
|
@ -61,7 +62,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
doVerify: func(v BatchVerifier, a *Ask, b1, b2 *Bid,
|
||||
b *Batch) error {
|
||||
|
||||
return v.Verify(&Batch{Version: 999})
|
||||
return v.Verify(&Batch{Version: 999}, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -72,7 +73,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
|
||||
arr := make([]*MatchedOrder, 0)
|
||||
b.MatchedOrders[Nonce{99, 99}] = arr
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -87,7 +88,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
Order: a,
|
||||
},
|
||||
)
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -97,7 +98,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b *Batch) error {
|
||||
|
||||
b.MatchedOrders[a.nonce][0].NodeKey = nodePubkey
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -107,7 +108,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b *Batch) error {
|
||||
|
||||
a.LeaseDuration = 100
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -117,7 +118,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b *Batch) error {
|
||||
|
||||
a.FixedRate = 20000
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -128,7 +129,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
|
||||
delete(b.MatchedOrders, a.nonce)
|
||||
b2.LeaseDuration = 5000
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -139,7 +140,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
|
||||
delete(b.MatchedOrders, a.nonce)
|
||||
a.FixedRate = b1.FixedRate + 1
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -149,7 +150,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b *Batch) error {
|
||||
|
||||
b.BatchTX.TxOut[0].Value = 123
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -159,7 +160,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b *Batch) error {
|
||||
|
||||
b.BatchTX.TxOut[0].PkScript = []byte{99, 88}
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -171,7 +172,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b.BatchTX.TxOut[0].Value = 900_000
|
||||
b.MatchedOrders[a.nonce][0].UnitsFilled = 9
|
||||
b.MatchedOrders[b1.nonce][0].UnitsFilled = 9
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -181,7 +182,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b *Batch) error {
|
||||
|
||||
a.MinUnitsMatch = b1.MinUnitsMatch * 100
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -191,7 +192,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b *Batch) error {
|
||||
|
||||
b.BatchTxFeeRate *= 2
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -202,7 +203,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
|
||||
delete(b.MatchedOrders, a.nonce)
|
||||
b1.FixedRate = uint32(clearingPrice) - 1
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -257,7 +258,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
// Verification should fail as the first match
|
||||
// has an ask with a price greater than the
|
||||
// clearing price.
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -267,7 +268,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b *Batch) error {
|
||||
|
||||
b.ExecutionFee = terms.NewLinearFeeSchedule(1, 1)
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -280,7 +281,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b.BatchTX.TxOut[2].Value += 2220
|
||||
b.AccountDiffs[0].EndingBalance += 2220
|
||||
b.AccountDiffs[1].EndingBalance += 2220
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -294,7 +295,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b.AccountDiffs[0].EndingBalance += 2220
|
||||
b.AccountDiffs[1].EndingBalance += 2220
|
||||
b.AccountDiffs[1].EndingState = stateRecreated
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -306,7 +307,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
b *Batch) error {
|
||||
|
||||
b1.SelfChanBalance = 100
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -317,7 +318,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
|
||||
b.BatchTX.TxOut[0].Value += 100
|
||||
b1.SelfChanBalance = 100
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -326,7 +327,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
doVerify: func(v BatchVerifier, a *Ask, b1, b2 *Bid,
|
||||
b *Batch) error {
|
||||
|
||||
return v.Verify(b)
|
||||
return v.Verify(b, bestHeight)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -505,6 +506,7 @@ func TestBatchVerifier(t *testing.T) {
|
|||
},
|
||||
BatchTX: batchTx,
|
||||
BatchTxFeeRate: chainfee.FeePerKwFloor,
|
||||
HeightHint: bestHeight,
|
||||
}
|
||||
|
||||
// Create the starting database state now.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package order
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
|
@ -35,6 +36,12 @@ var (
|
|||
// implement the same batch verification version as the server.
|
||||
ErrVersionMismatch = fmt.Errorf("version %d mismatches server version",
|
||||
CurrentBatchVersion)
|
||||
|
||||
// ErrInvalidBatchHeightHint is an error returned by a trader upon
|
||||
// verifying a batch when its proposed height hint is outside of the
|
||||
// trader's acceptable range.
|
||||
ErrInvalidBatchHeightHint = errors.New("proposed batch height hint is " +
|
||||
"outside of acceptable range")
|
||||
)
|
||||
|
||||
// ManagerConfig contains all of the required dependencies for the Manager to
|
||||
|
|
@ -299,10 +306,10 @@ func (m *Manager) validateOrder(order Order, acct *account.Account,
|
|||
}
|
||||
|
||||
// OrderMatchValidate verifies an incoming batch is sane before accepting it.
|
||||
func (m *Manager) OrderMatchValidate(batch *Batch) error {
|
||||
func (m *Manager) OrderMatchValidate(batch *Batch, bestHeight uint32) error {
|
||||
// Make sure we have no objection to the current batch. Then store
|
||||
// it in case it ends up being the final version.
|
||||
err := m.batchVerifier.Verify(batch)
|
||||
err := m.batchVerifier.Verify(batch, bestHeight)
|
||||
if err != nil {
|
||||
// This error will lead to us sending an OrderMatchReject
|
||||
// message and canceling all funding shims we might already have
|
||||
|
|
@ -330,13 +337,13 @@ func (m *Manager) PendingBatch() *Batch {
|
|||
// belong to the trader. Before sending off the signature to the auctioneer,
|
||||
// we'll also persist the batch to disk as pending to ensure we can recover
|
||||
// after a crash.
|
||||
func (m *Manager) BatchSign(bestHeight uint32) (BatchSignature, error) {
|
||||
func (m *Manager) BatchSign() (BatchSignature, error) {
|
||||
sig, err := m.batchSigner.Sign(m.pendingBatch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = m.batchStorer.StorePendingBatch(m.pendingBatch, bestHeight)
|
||||
err = m.batchStorer.StorePendingBatch(m.pendingBatch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to store batch: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,6 +266,7 @@ func ParseRPCBatch(prepareMsg *auctioneerrpc.OrderMatchPrepare) (*Batch,
|
|||
MatchedOrders: make(map[Nonce][]*MatchedOrder),
|
||||
BatchTX: &wire.MsgTx{},
|
||||
ClearingPrices: make(map[uint32]FixedRatePremium),
|
||||
HeightHint: prepareMsg.BatchHeightHint,
|
||||
}
|
||||
|
||||
// Parse matched orders market by market.
|
||||
|
|
|
|||
|
|
@ -350,7 +350,8 @@ func (s *rpcServer) handleServerMessage(
|
|||
}
|
||||
|
||||
// Do an in-depth verification of the batch.
|
||||
err = s.orderManager.OrderMatchValidate(batch)
|
||||
bestHeight := atomic.LoadUint32(&s.bestHeight)
|
||||
err = s.orderManager.OrderMatchValidate(batch, bestHeight)
|
||||
if err != nil {
|
||||
// We can't accept the batch, something went wrong.
|
||||
rpcLog.Errorf("Error validating batch: %v", err)
|
||||
|
|
@ -393,8 +394,7 @@ func (s *rpcServer) handleServerMessage(
|
|||
"num_orders=%v", batch.ID[:], len(batch.MatchedOrders))
|
||||
|
||||
// Sign for the accounts in the batch.
|
||||
bestHeight := atomic.LoadUint32(&s.bestHeight)
|
||||
sigs, err := s.orderManager.BatchSign(bestHeight)
|
||||
sigs, err := s.orderManager.BatchSign()
|
||||
if err != nil {
|
||||
rpcLog.Errorf("Error signing batch: %v", err)
|
||||
return s.sendRejectBatch(batch, err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue