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:
Wilmer Paulino 2021-07-26 16:06:59 -07:00 committed by Olaoluwa Osuntokun
parent 321e3c3598
commit f9c2861354
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
10 changed files with 673 additions and 655 deletions

File diff suppressed because it is too large Load diff

View file

@ -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 {

View file

@ -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.

View file

@ -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),

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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.

View file

@ -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)
}

View file

@ -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.

View file

@ -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)