Add bench-exchange (#3826)
This commit is contained in:
parent
bc17edcda3
commit
2518e95fb0
|
@ -1435,6 +1435,16 @@ dependencies = [
|
|||
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.39"
|
||||
|
@ -2198,6 +2208,35 @@ dependencies = [
|
|||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-bench-exchange"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"bincode 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bs58 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"solana 0.14.0",
|
||||
"solana-client 0.14.0",
|
||||
"solana-drone 0.14.0",
|
||||
"solana-exchange-api 0.14.0",
|
||||
"solana-exchange-program 0.14.0",
|
||||
"solana-logger 0.14.0",
|
||||
"solana-metrics 0.14.0",
|
||||
"solana-netutil 0.14.0",
|
||||
"solana-sdk 0.14.0",
|
||||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ws 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-bench-streamer"
|
||||
version = "0.14.0"
|
||||
|
@ -3275,6 +3314,23 @@ dependencies = [
|
|||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ws"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ws"
|
||||
version = "0.8.0"
|
||||
|
@ -3477,6 +3533,7 @@ dependencies = [
|
|||
"checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b"
|
||||
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
|
||||
"checksum nom 4.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22293d25d3f33a8567cc8a1dc20f40c7eeb761ce83d0fcca059858580790cac3"
|
||||
"checksum num-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d9fe8fcafd1b86a37ce8a1cfa15ae504817e0c8c2e7ad42767371461ac1d316d"
|
||||
"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea"
|
||||
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
|
||||
"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1"
|
||||
|
@ -3624,6 +3681,7 @@ dependencies = [
|
|||
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
|
||||
"checksum ws 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "329d3e6dd450a9c5c73024e1047f0be7e24121a68484eb0b5368977bee3cf8c3"
|
||||
"checksum ws 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcacc3ba9c1ee43e3fd0846a25489ff22f8906e90775d51b6edbae4b95d71f4"
|
||||
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||
"checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[workspace]
|
||||
members = [
|
||||
".",
|
||||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"drone",
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "0.14.0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.2.0"
|
||||
clap = "2.32.0"
|
||||
bincode = "1.1.2"
|
||||
env_logger = "0.6.0"
|
||||
itertools = "0.8.0"
|
||||
log = "0.4.6"
|
||||
num-traits = "0.2"
|
||||
num-derive = "0.2"
|
||||
rayon = "1.0.3"
|
||||
serde = "1.0.87"
|
||||
serde_derive = "1.0.87"
|
||||
serde_json = "1.0.38"
|
||||
# solana-runtime = { path = "../solana/runtime"}
|
||||
solana = { path = "../core", version = "0.14.0" }
|
||||
solana-client = { path = "../client", version = "0.14.0" }
|
||||
solana-drone = { path = "../drone", version = "0.14.0" }
|
||||
solana-exchange-api = { path = "../instruction-processors/exchange_api", version = "0.14.0" }
|
||||
solana-exchange-program = { path = "../instruction-processors/exchange_program", version = "0.14.0" }
|
||||
solana-logger = { path = "../logger", version = "0.14.0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.14.0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.14.0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.14.0" }
|
||||
ws = "0.7.9"
|
||||
untrusted = "0.6.2"
|
||||
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
erasure = []
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,173 @@
|
|||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::{crate_description, crate_name, crate_version, value_t, App, Arg, ArgMatches};
|
||||
use solana_drone::drone::DRONE_PORT;
|
||||
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
|
||||
use untrusted::Input;
|
||||
|
||||
pub struct Config {
|
||||
pub network_addr: SocketAddr,
|
||||
pub drone_addr: SocketAddr,
|
||||
pub identity: Keypair,
|
||||
pub threads: usize,
|
||||
pub num_nodes: usize,
|
||||
pub duration: Duration,
|
||||
pub trade_delay: u64,
|
||||
pub fund_amount: u64,
|
||||
pub batch_size: usize,
|
||||
pub account_groups: usize,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
network_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
|
||||
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
|
||||
identity: Keypair::new(),
|
||||
num_nodes: 1,
|
||||
threads: 4,
|
||||
duration: Duration::new(u64::max_value(), 0),
|
||||
trade_delay: 0,
|
||||
fund_amount: 100_000,
|
||||
batch_size: 100,
|
||||
account_groups: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(crate_version!())
|
||||
.arg(
|
||||
Arg::with_name("network")
|
||||
.short("n")
|
||||
.long("network")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("127.0.0.1:8001")
|
||||
.help("Network's gossip entry point; defaults to 127.0.0.1:8001"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("drone")
|
||||
.short("d")
|
||||
.long("drone")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("127.0.0.1:9900")
|
||||
.help("Location of the drone; defaults to 127.0.0.1:9900"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("identity")
|
||||
.short("i")
|
||||
.long("identity")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.help("File containing a client identity (keypair)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("threads")
|
||||
.long("threads")
|
||||
.value_name("<threads>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("4")
|
||||
.help("Number of threads submitting transactions"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num-nodes")
|
||||
.long("num-nodes")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("1")
|
||||
.help("Wait for NUM nodes to converge"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("duration")
|
||||
.long("duration")
|
||||
.value_name("SECS")
|
||||
.takes_value(true)
|
||||
.default_value("60")
|
||||
.help("Seconds to run benchmark, then exit; default is forever"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("trade-delay")
|
||||
.long("trade-delay")
|
||||
.value_name("<delay>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("0")
|
||||
.help("Delay between trade requests in milliseconds"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("fund-amount")
|
||||
.long("fund-amount")
|
||||
.value_name("<fund>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("100000")
|
||||
.help("Number of lamports to fund to each signer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("batch-size")
|
||||
.long("batch-size")
|
||||
.value_name("<batch>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("1000")
|
||||
.help("Number of bulk trades to submit between trade delays"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("account-groups")
|
||||
.long("account-groups")
|
||||
.value_name("<groups>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("100")
|
||||
.help("Number of account groups to cycle for each batch"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
let mut args = Config::default();
|
||||
|
||||
args.network_addr = matches
|
||||
.value_of("network")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.expect("Failed to parse network");
|
||||
args.drone_addr = matches
|
||||
.value_of("drone")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.expect("Failed to parse drone address");
|
||||
|
||||
if matches.is_present("identity") {
|
||||
args.identity = read_keypair(matches.value_of("identity").unwrap())
|
||||
.expect("can't read client identity");
|
||||
} else {
|
||||
args.identity = {
|
||||
let seed = [42_u8; 32];
|
||||
Keypair::from_seed_unchecked(Input::from(&seed)).unwrap()
|
||||
};
|
||||
}
|
||||
args.threads = value_t!(matches.value_of("threads"), usize).expect("Failed to parse threads");
|
||||
args.num_nodes =
|
||||
value_t!(matches.value_of("num-nodes"), usize).expect("Failed to parse num-nodes");
|
||||
let duration = value_t!(matches.value_of("duration"), u64).expect("Failed to parse duration");
|
||||
args.duration = Duration::from_secs(duration);
|
||||
args.trade_delay =
|
||||
value_t!(matches.value_of("trade-delay"), u64).expect("Failed to parse trade-delay");
|
||||
args.fund_amount =
|
||||
value_t!(matches.value_of("fund-amount"), u64).expect("Failed to parse fund-amount");
|
||||
args.batch_size =
|
||||
value_t!(matches.value_of("batch-size"), usize).expect("Failed to parse batch-size");
|
||||
args.account_groups = value_t!(matches.value_of("account-groups"), usize)
|
||||
.expect("Failed to parse account-groups");
|
||||
|
||||
args
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
pub mod bench;
|
||||
mod cli;
|
||||
pub mod order_book;
|
||||
|
||||
use crate::bench::{airdrop_lamports, do_bench_exchange, Config};
|
||||
use log::*;
|
||||
use solana::cluster_info::FULLNODE_PORT_RANGE;
|
||||
use solana::gossip_service::discover_nodes;
|
||||
use solana_client::thin_client::create_client;
|
||||
use solana_client::thin_client::ThinClient;
|
||||
use solana_sdk::signature::KeypairUtil;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
|
||||
let matches = cli::build_args().get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
||||
let cli::Config {
|
||||
network_addr,
|
||||
drone_addr,
|
||||
identity,
|
||||
threads,
|
||||
num_nodes,
|
||||
duration,
|
||||
trade_delay,
|
||||
fund_amount,
|
||||
batch_size,
|
||||
account_groups,
|
||||
..
|
||||
} = cli_config;
|
||||
|
||||
info!("Connecting to the cluster");
|
||||
let nodes = discover_nodes(&network_addr, num_nodes).unwrap_or_else(|_| {
|
||||
panic!("Failed to discover nodes");
|
||||
});
|
||||
info!("{} nodes found", nodes.len());
|
||||
if nodes.len() < num_nodes {
|
||||
panic!("Error: Insufficient nodes discovered");
|
||||
}
|
||||
|
||||
let client_ctors: Vec<_> = nodes
|
||||
.iter()
|
||||
.map(|node| {
|
||||
let cluster_entrypoint = node.clone();
|
||||
let cluster_addrs = cluster_entrypoint.client_facing_addr();
|
||||
move || -> ThinClient { create_client(cluster_addrs, FULLNODE_PORT_RANGE) }
|
||||
})
|
||||
.collect();
|
||||
|
||||
info!("Funding keypair: {}", identity.pubkey());
|
||||
|
||||
let client = client_ctors[0]();
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
airdrop_lamports(
|
||||
&client,
|
||||
&drone_addr,
|
||||
&identity,
|
||||
fund_amount * (accounts_in_groups + 1) as u64 * 2,
|
||||
);
|
||||
|
||||
let config = Config {
|
||||
identity,
|
||||
threads,
|
||||
duration,
|
||||
trade_delay,
|
||||
fund_amount,
|
||||
batch_size,
|
||||
account_groups,
|
||||
};
|
||||
|
||||
do_bench_exchange(client_ctors, config);
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
use itertools::EitherOrBoth::{Both, Left, Right};
|
||||
use itertools::Itertools;
|
||||
use log::*;
|
||||
use solana_exchange_api::exchange_state::*;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
use std::{error, fmt};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ToOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: TradeOrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for ToOrder {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
other.info.price.cmp(&self.info.price)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for ToOrder {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct FromOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: TradeOrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for FromOrder {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.info.price.cmp(&other.info.price)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for FromOrder {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OrderBook {
|
||||
// TODO scale to x token types
|
||||
to_ab: BinaryHeap<ToOrder>,
|
||||
from_ab: BinaryHeap<FromOrder>,
|
||||
}
|
||||
impl fmt::Display for OrderBook {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"+-Order Book--------------------------+-------------------------------------+"
|
||||
)?;
|
||||
for (i, it) in self
|
||||
.to_ab
|
||||
.iter()
|
||||
.zip_longest(self.from_ab.iter())
|
||||
.enumerate()
|
||||
{
|
||||
match it {
|
||||
Both(to, from) => writeln!(
|
||||
f,
|
||||
"| T AB {:8} for {:8}/{:8} | F AB {:8} for {:8}/{:8} |{}",
|
||||
to.info.tokens,
|
||||
SCALER,
|
||||
to.info.price,
|
||||
from.info.tokens,
|
||||
SCALER,
|
||||
from.info.price,
|
||||
i
|
||||
)?,
|
||||
Left(to) => writeln!(
|
||||
f,
|
||||
"| T AB {:8} for {:8}/{:8} | |{}",
|
||||
to.info.tokens, SCALER, to.info.price, i
|
||||
)?,
|
||||
Right(from) => writeln!(
|
||||
f,
|
||||
"| | F AB {:8} for {:8}/{:8} |{}",
|
||||
from.info.tokens, SCALER, from.info.price, i
|
||||
)?,
|
||||
}
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
"+-------------------------------------+-------------------------------------+"
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OrderBook {
|
||||
// TODO
|
||||
// pub fn cancel(&mut self, pubkey: Pubkey) -> Result<(), Box<dyn error::Error>> {
|
||||
// Ok(())
|
||||
// }
|
||||
pub fn push(
|
||||
&mut self,
|
||||
pubkey: Pubkey,
|
||||
info: TradeOrderInfo,
|
||||
) -> Result<(), Box<dyn error::Error>> {
|
||||
check_trade(info.direction, info.tokens, info.price)?;
|
||||
match info.direction {
|
||||
Direction::To => {
|
||||
self.to_ab.push(ToOrder { pubkey, info });
|
||||
}
|
||||
Direction::From => {
|
||||
self.from_ab.push(FromOrder { pubkey, info });
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn pop(&mut self) -> Option<(ToOrder, FromOrder)> {
|
||||
if let Some(pair) = Self::pop_pair(&mut self.to_ab, &mut self.from_ab) {
|
||||
return Some(pair);
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn get_num_outstanding(&self) -> (usize, usize) {
|
||||
(self.to_ab.len(), self.from_ab.len())
|
||||
}
|
||||
|
||||
fn pop_pair(
|
||||
to_ab: &mut BinaryHeap<ToOrder>,
|
||||
from_ab: &mut BinaryHeap<FromOrder>,
|
||||
) -> Option<(ToOrder, FromOrder)> {
|
||||
let to = to_ab.peek()?;
|
||||
let from = from_ab.peek()?;
|
||||
if from.info.price < to.info.price {
|
||||
debug!("Trade not viable");
|
||||
return None;
|
||||
}
|
||||
let to = to_ab.pop()?;
|
||||
let from = from_ab.pop()?;
|
||||
Some((to, from))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue