Skip to content

Commit

Permalink
Merge pull request #4004 from stellar/release-horizon-v2.9.0
Browse files Browse the repository at this point in the history
Merge release-horizon-v2.9.0 into master
  • Loading branch information
bartekn authored Oct 12, 2021
2 parents 678540d + 50b38f6 commit b147bca
Show file tree
Hide file tree
Showing 39 changed files with 1,401 additions and 339 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ commands:
parameters:
core-version:
type: string
default: "18.0.0-734.e381447.focal"
default: "18.0.3-746.f3baea6.focal"
steps:
- run:
name: Install Stellar Core <<#parameters.core-version>> (version <<parameters.core-version>>)<</parameters.core-version>>
Expand Down
18 changes: 16 additions & 2 deletions exp/orderbook/dfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ type Venues struct {
}

type searchState interface {
considerPools() bool

isTerminalNode(
currentAsset string,
currentAssetAmount xdr.Int64,
Expand Down Expand Up @@ -110,7 +112,9 @@ func dfs(
return nil
}

for nextAssetString, venues := range state.venues(currentAssetString) {
edges := state.venues(currentAssetString)
for i := 0; i < len(edges); i++ {
nextAssetString, venues := edges[i].key, edges[i].value
if contains(visitedAssetStrings, nextAssetString) {
continue
}
Expand Down Expand Up @@ -160,6 +164,7 @@ type sellingGraphSearchState struct {
targetAssets map[string]xdr.Int64
validateSourceBalance bool
paths []Path
includePools bool
}

func (state *sellingGraphSearchState) isTerminalNode(
Expand Down Expand Up @@ -217,6 +222,10 @@ func (state *sellingGraphSearchState) consumeOffers(
return nextAsset, positiveMin(currentBestAmount, nextAmount), err
}

func (state *sellingGraphSearchState) considerPools() bool {
return state.includePools
}

func (state *sellingGraphSearchState) consumePool(
pool xdr.LiquidityPoolEntry,
currentAsset xdr.Asset,
Expand All @@ -239,6 +248,7 @@ type buyingGraphSearchState struct {
sourceAssetAmount xdr.Int64
targetAssets map[string]bool
paths []Path
includePools bool
}

func (state *buyingGraphSearchState) isTerminalNode(
Expand Down Expand Up @@ -291,6 +301,10 @@ func (state *buyingGraphSearchState) consumeOffers(
return nextAsset, max(nextAmount, currentBestAmount), err
}

func (state *buyingGraphSearchState) considerPools() bool {
return state.includePools
}

func (state *buyingGraphSearchState) consumePool(
pool xdr.LiquidityPoolEntry,
currentAsset xdr.Asset,
Expand Down Expand Up @@ -438,7 +452,7 @@ func processVenues(
// We evaluate the pool venue (if any) before offers, because pool exchange
// rates can only be evaluated with an amount.
poolAmount := xdr.Int64(0)
if pool := venues.pool; pool.Body.ConstantProduct != nil {
if pool := venues.pool; state.considerPools() && pool.Body.ConstantProduct != nil {
amount, err := state.consumePool(pool, currentAsset, currentAssetAmount)
if err == nil {
nextAsset = getOtherAsset(currentAsset, pool)
Expand Down
90 changes: 56 additions & 34 deletions exp/orderbook/edges.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,67 @@ import (
// edgeSet maintains a mapping of strings (asset keys) to a set of venues, which
// is composed of a sorted lists of offers and, optionally, a liquidity pool.
// The offers are sorted by ascending price (in terms of the buying asset).
type edgeSet map[string]Venues
type edgeSet []edge

type edge struct {
key string
value Venues
}

func (e edgeSet) find(key string) int {
for i := 0; i < len(e); i++ {
if e[i].key == key {
return i
}
}
return -1
}

// addOffer will insert the given offer into the edge set
func (e edgeSet) addOffer(key string, offer xdr.OfferEntry) {
func (e edgeSet) addOffer(key string, offer xdr.OfferEntry) edgeSet {
// The list of offers in a venue is sorted by cheapest to most expensive
// price to convert buyingAsset to sellingAsset
venues := e[key]
if len(venues.offers) == 0 {
e[key] = Venues{
offers: []xdr.OfferEntry{offer},
pool: venues.pool,
}
return
i := e.find(key)
if i < 0 {
return append(e, edge{key: key, value: Venues{offers: []xdr.OfferEntry{offer}}})
}

offers := e[i].value.offers
// find the smallest i such that Price of offers[i] > Price of offer
insertIndex := sort.Search(len(venues.offers), func(i int) bool {
return offer.Price.Cheaper(venues.offers[i].Price)
insertIndex := sort.Search(len(offers), func(j int) bool {
return offer.Price.Cheaper(offers[j].Price)
})

// then insert it into the slice (taken from Method 2 at
// https://github.com/golang/go/wiki/SliceTricks#insert).
offers := append(venues.offers, xdr.OfferEntry{}) // add to end
offers = append(offers, xdr.OfferEntry{}) // add to end
copy(offers[insertIndex+1:], offers[insertIndex:]) // shift right by 1
offers[insertIndex] = offer // insert

e[key] = Venues{offers: offers, pool: venues.pool}
e[i].value = Venues{offers: offers, pool: e[i].value.pool}
return e
}

// addPool makes `pool` a viable venue at `key`.
func (e edgeSet) addPool(key string, pool xdr.LiquidityPoolEntry) {
venues := e[key]
venues.pool = pool
e[key] = venues
func (e edgeSet) addPool(key string, pool xdr.LiquidityPoolEntry) edgeSet {
i := e.find(key)
if i < 0 {
return append(e, edge{key: key, value: Venues{pool: pool}})
}
e[i].value.pool = pool
return e
}

// removeOffer will delete the given offer from the edge set, returning whether
// or not the given offer was actually found.
func (e edgeSet) removeOffer(key string, offerID xdr.Int64) bool {
venues := e[key]
offers := venues.offers
func (e edgeSet) removeOffer(key string, offerID xdr.Int64) (edgeSet, bool) {
i := e.find(key)
if i < 0 {
return e, false
}

offers := e[i].value.offers
updatedOffers := offers
contains := false
for i, offer := range offers {
if offer.OfferId != offerID {
Expand All @@ -59,29 +78,32 @@ func (e edgeSet) removeOffer(key string, offerID xdr.Int64) bool {

// remove the entry in the slice at this location (taken from
// https://github.com/golang/go/wiki/SliceTricks#cut).
offers = append(offers[:i], offers[i+1:]...)
updatedOffers = append(offers[:i], offers[i+1:]...)
contains = true
break
}

if !contains {
return false
return e, false
}

if len(offers) == 0 && venues.pool.Body.ConstantProduct == nil {
delete(e, key)
} else {
venues.offers = offers
e[key] = venues
if len(updatedOffers) == 0 && e[i].value.pool.Body.ConstantProduct == nil {
return append(e[:i], e[i+1:]...), true
}

return true
e[i].value.offers = updatedOffers
return e, true
}

func (e edgeSet) removePool(key string) {
e[key] = Venues{offers: e[key].offers}
}
func (e edgeSet) removePool(key string) edgeSet {
i := e.find(key)
if i < 0 {
return e
}

if len(e[i].value.offers) == 0 {
return append(e[:i], e[i+1:]...)
}

func (e edgeSet) isEmpty(key string) bool {
return len(e[key].offers) == 0 && e[key].pool.Body.ConstantProduct == nil
e[i].value = Venues{offers: e[i].value.offers}
return e
}
60 changes: 21 additions & 39 deletions exp/orderbook/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ func (graph *OrderBookGraph) Offers() []xdr.OfferEntry {

var offers []xdr.OfferEntry
for _, edges := range graph.venuesForSellingAsset {
for _, venues := range edges {
offers = append(offers, venues.offers...)
for _, edge := range edges {
offers = append(offers, edge.value.offers...)
}
}

Expand Down Expand Up @@ -202,19 +202,8 @@ func (graph *OrderBookGraph) addOffer(offer xdr.OfferEntry) error {
buyingAsset: buying, sellingAsset: selling,
}

// First, ensure the internal structure of the graph is sound by creating
// empty venues if none exist yet.
if _, ok := graph.venuesForSellingAsset[selling]; !ok {
graph.venuesForSellingAsset[selling] = edgeSet{}
}

if _, ok := graph.venuesForBuyingAsset[buying]; !ok {
graph.venuesForBuyingAsset[buying] = edgeSet{}
}

// Now shove the new offer into them.
graph.venuesForSellingAsset[selling].addOffer(buying, offer)
graph.venuesForBuyingAsset[buying].addOffer(selling, offer)
graph.venuesForSellingAsset[selling] = graph.venuesForSellingAsset[selling].addOffer(buying, offer)
graph.venuesForBuyingAsset[buying] = graph.venuesForBuyingAsset[buying].addOffer(selling, offer)

return nil
}
Expand All @@ -226,23 +215,13 @@ func (graph *OrderBookGraph) addPool(pool xdr.LiquidityPoolEntry) {
x, y := getPoolAssets(pool)
graph.liquidityPools[tradingPair{x, y}] = pool

// Either there have already been offers added for the trading pair,
// or we need to create the internal map structure.
for _, asset := range []string{x, y} {
for _, table := range []map[string]edgeSet{
graph.venuesForBuyingAsset,
graph.venuesForSellingAsset,
} {
if _, ok := table[asset]; !ok {
table[asset] = edgeSet{}
}
}
for _, table := range []map[string]edgeSet{
graph.venuesForBuyingAsset,
graph.venuesForSellingAsset,
} {
table[x] = table[x].addPool(y, pool)
table[y] = table[y].addPool(x, pool)
}

graph.venuesForBuyingAsset[x].addPool(y, pool)
graph.venuesForBuyingAsset[y].addPool(x, pool)
graph.venuesForSellingAsset[x].addPool(y, pool)
graph.venuesForSellingAsset[y].addPool(x, pool)
}

// removeOffer deletes a given offer from the order book graph
Expand All @@ -256,18 +235,22 @@ func (graph *OrderBookGraph) removeOffer(offerID xdr.Int64) error {

if set, ok := graph.venuesForSellingAsset[pair.sellingAsset]; !ok {
return errOfferNotPresent
} else if !set.removeOffer(pair.buyingAsset, offerID) {
} else if set, ok = set.removeOffer(pair.buyingAsset, offerID); !ok {
return errOfferNotPresent
} else if len(set) == 0 {
delete(graph.venuesForSellingAsset, pair.sellingAsset)
} else {
graph.venuesForSellingAsset[pair.sellingAsset] = set
}

if set, ok := graph.venuesForBuyingAsset[pair.buyingAsset]; !ok {
return errOfferNotPresent
} else if !set.removeOffer(pair.sellingAsset, offerID) {
} else if set, ok = set.removeOffer(pair.sellingAsset, offerID); !ok {
return errOfferNotPresent
} else if len(set) == 0 {
delete(graph.venuesForBuyingAsset, pair.buyingAsset)
} else {
graph.venuesForBuyingAsset[pair.buyingAsset] = set
}

return nil
Expand All @@ -287,12 +270,7 @@ func (graph *OrderBookGraph) removePool(pool xdr.LiquidityPoolEntry) {
graph.venuesForBuyingAsset,
graph.venuesForSellingAsset,
} {
if venues, ok := table[asset]; ok {
venues.removePool(otherAsset)
if venues.isEmpty(otherAsset) {
delete(venues, otherAsset)
}
} // should we panic on !ok?
table[asset] = table[asset].removePool(otherAsset)
}
}

Expand All @@ -319,6 +297,7 @@ func (graph *OrderBookGraph) FindPaths(
sourceAssetBalances []xdr.Int64,
validateSourceBalance bool,
maxAssetsPerPath int,
includePools bool,
) ([]Path, uint32, error) {
destinationAssetString := destinationAsset.String()
sourceAssetsMap := make(map[string]xdr.Int64, len(sourceAssets))
Expand All @@ -335,6 +314,7 @@ func (graph *OrderBookGraph) FindPaths(
targetAssets: sourceAssetsMap,
validateSourceBalance: validateSourceBalance,
paths: []Path{},
includePools: includePools,
}
graph.lock.RLock()
err := dfs(
Expand Down Expand Up @@ -377,6 +357,7 @@ func (graph *OrderBookGraph) FindFixedPaths(
amountToSpend xdr.Int64,
destinationAssets []xdr.Asset,
maxAssetsPerPath int,
includePools bool,
) ([]Path, uint32, error) {
target := map[string]bool{}
for _, destinationAsset := range destinationAssets {
Expand All @@ -390,6 +371,7 @@ func (graph *OrderBookGraph) FindFixedPaths(
sourceAssetAmount: amountToSpend,
targetAssets: target,
paths: []Path{},
includePools: includePools,
}
graph.lock.RLock()
err := dfs(
Expand Down
Loading

0 comments on commit b147bca

Please sign in to comment.