diff --git a/examples/tokenvm/config/config.go b/examples/tokenvm/config/config.go index 721731e644..0db284c87e 100644 --- a/examples/tokenvm/config/config.go +++ b/examples/tokenvm/config/config.go @@ -28,6 +28,7 @@ const ( defaultContinuousProfilerFrequency = 1 * time.Minute defaultContinuousProfilerMaxFiles = 10 defaultStoreTransactions = true + defaultMaxOrdersPerPair = 1024 ) type Config struct { @@ -58,9 +59,8 @@ type Config struct { // Order Book // // This is denoted as - - // - // TODO: add ability to denote min rate/min amount for tracking to avoid spam - TrackedPairs []string `json:"trackedPairs"` // which asset ID pairs we care about + MaxOrdersPerPair int `json:"maxOrdersPerPair"` + TrackedPairs []string `json:"trackedPairs"` // which asset ID pairs we care about // Misc VerifySignatures bool `json:"verifySignatures"` @@ -115,6 +115,7 @@ func (c *Config) setDefault() { c.StreamingBacklogSize = c.Config.GetStreamingBacklogSize() c.VerifySignatures = c.Config.GetVerifySignatures() c.StoreTransactions = defaultStoreTransactions + c.MaxOrdersPerPair = defaultMaxOrdersPerPair } func (c *Config) GetLogLevel() logging.Level { return c.LogLevel } diff --git a/examples/tokenvm/controller/controller.go b/examples/tokenvm/controller/controller.go index 4c802bdc3f..e617665d66 100644 --- a/examples/tokenvm/controller/controller.go +++ b/examples/tokenvm/controller/controller.go @@ -145,7 +145,7 @@ func (c *Controller) Initialize( } // Initialize order book used to track all open orders - c.orderBook = orderbook.New(c, c.config.TrackedPairs) + c.orderBook = orderbook.New(c, c.config.TrackedPairs, c.config.MaxOrdersPerPair) return c.config, c.genesis, build, gossip, blockDB, stateDB, apis, consts.ActionRegistry, consts.AuthRegistry, auth.Engines(), nil } diff --git a/examples/tokenvm/orderbook/orderbook.go b/examples/tokenvm/orderbook/orderbook.go index d570a80a46..9f12e95874 100644 --- a/examples/tokenvm/orderbook/orderbook.go +++ b/examples/tokenvm/orderbook/orderbook.go @@ -14,10 +14,7 @@ import ( "go.uber.org/zap" ) -const ( - initialPairCapacity = 128 - allPairs = "*" -) +const allPairs = "*" type Order struct { ID ids.ID `json:"id"` @@ -32,17 +29,19 @@ type Order struct { type OrderBook struct { c Controller - // TODO: consider capping the number of orders in each heap (need to ensure - // that doing so does not make it possible to send a bunch of small, spam - // orders to clear -> may need to set a min order limit to watch) - orders map[string]*heap.Heap[*Order, float64] - orderToPair map[ids.ID]string // needed to delete from [CloseOrder] actions - l sync.RWMutex + // Fee required to create an order should be high enough to prevent too many + // dust orders from filling the heap. + // + // TODO: Allow operator to specify min creation supply per pair to be tracked + orders map[string]*heap.Heap[*Order, float64] + orderToPair map[ids.ID]string // needed to delete from [CloseOrder] actions + maxOrdersPerPair int + l sync.RWMutex trackAll bool } -func New(c Controller, trackedPairs []string) *OrderBook { +func New(c Controller, trackedPairs []string, maxOrdersPerPair int) *OrderBook { m := map[string]*heap.Heap[*Order, float64]{} trackAll := false if len(trackedPairs) == 1 && trackedPairs[0] == allPairs { @@ -51,15 +50,16 @@ func New(c Controller, trackedPairs []string) *OrderBook { } else { for _, pair := range trackedPairs { // We use a max heap so we return the best rates in order. - m[pair] = heap.New[*Order, float64](initialPairCapacity, true) + m[pair] = heap.New[*Order, float64](maxOrdersPerPair+1, true) c.Logger().Info("tracking order book", zap.String("pair", pair)) } } return &OrderBook{ - c: c, - orders: m, - orderToPair: map[ids.ID]string{}, - trackAll: trackAll, + c: c, + orders: m, + orderToPair: map[ids.ID]string{}, + maxOrdersPerPair: maxOrdersPerPair, + trackAll: trackAll, } } @@ -82,7 +82,7 @@ func (o *OrderBook) Add(txID ids.ID, actor ed25519.PublicKey, action *actions.Cr return case !ok && o.trackAll: o.c.Logger().Info("tracking order book", zap.String("pair", pair)) - h = heap.New[*Order, float64](initialPairCapacity, true) + h = heap.New[*Order, float64](o.maxOrdersPerPair+1, true) o.orders[pair] = h } h.Push(&heap.Entry[*Order, float64]{ @@ -92,11 +92,19 @@ func (o *OrderBook) Add(txID ids.ID, actor ed25519.PublicKey, action *actions.Cr Index: h.Len(), }) o.orderToPair[order.ID] = pair + + // Remove worst order if we are above the max we + // track per pair + if l := h.Len(); l > o.maxOrdersPerPair { + e := h.Remove(l - 1) + delete(o.orderToPair, e.ID) + } } func (o *OrderBook) Remove(id ids.ID) { o.l.Lock() defer o.l.Unlock() + pair, ok := o.orderToPair[id] if !ok { return @@ -118,6 +126,7 @@ func (o *OrderBook) Remove(id ids.ID) { func (o *OrderBook) UpdateRemaining(id ids.ID, remaining uint64) { o.l.Lock() defer o.l.Unlock() + pair, ok := o.orderToPair[id] if !ok { return @@ -138,6 +147,7 @@ func (o *OrderBook) UpdateRemaining(id ids.ID, remaining uint64) { func (o *OrderBook) Orders(pair string, limit int) []*Order { o.l.RLock() defer o.l.RUnlock() + h, ok := o.orders[pair] if !ok { return nil diff --git a/examples/tokenvm/scripts/run.sh b/examples/tokenvm/scripts/run.sh index 001eeae34c..7836af7ed8 100755 --- a/examples/tokenvm/scripts/run.sh +++ b/examples/tokenvm/scripts/run.sh @@ -121,6 +121,9 @@ fi ############################ +# When running a validator, the [trackedPairs] should be empty/limited or +# else malicious entities can attempt to stuff memory with dust orders to cause +# an OOM. echo "creating vm config" rm -f ${TMPDIR}/tokenvm.config rm -rf ${TMPDIR}/tokenvm-e2e-profiles @@ -133,10 +136,6 @@ cat < ${TMPDIR}/tokenvm.config "verifySignatures":true, "storeTransactions":true, "streamingBacklogSize": 10000000, - "gossipMaxSize": 32768, - "gossipProposerDiff": 3, - "gossipProposerDepth": 1, - "noGossipBuilderDiff": 5, "trackedPairs":["*"], "logLevel": "${LOGLEVEL}", "stateSyncServerDelay": ${STATESYNC_DELAY} diff --git a/gossiper/proposer.go b/gossiper/proposer.go index 6390844303..7d7a58c9b4 100644 --- a/gossiper/proposer.go +++ b/gossiper/proposer.go @@ -248,6 +248,7 @@ func (g *Proposer) HandleAppGossip(ctx context.Context, nodeID ids.NodeID, msg [ g.vm.Logger().Info( "tx gossip received", zap.Int("txs", len(txs)), + zap.Int("previously seen", seen), zap.Stringer("nodeID", nodeID), zap.Bool("validator", isValidator), zap.Duration("t", time.Since(start)),