Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web server orders routes #1841

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions api-server/api-server-common/src/storage/impls/in_memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,71 @@ impl ApiServerInMemoryStorage {
Ok(order)
}

fn get_orders_by_height(
&self,
len: u32,
offset: u32,
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
let len = len as usize;
let offset = offset as usize;

let mut order_data: Vec<_> = self
.orders_table
.iter()
.map(|(order_id, by_height)| {
let created_height = by_height.keys().next().expect("not empty");
let latest_data = by_height.values().last().expect("not empty");
(order_id, (created_height, latest_data))
})
.collect();

order_data.sort_by_key(|(_, (height, _data))| Reverse(*height));
if offset >= order_data.len() {
return Ok(vec![]);
}

let latest_orders = order_data[offset..std::cmp::min(offset + len, order_data.len())]
.iter()
.map(|(order_id, (_, data))| (**order_id, (*data).clone()))
.collect();

Ok(latest_orders)
}

fn get_orders_for_trading_pair(
&self,
pair: (CoinOrTokenId, CoinOrTokenId),
len: u32,
offset: u32,
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
let len = len as usize;
let offset = offset as usize;

let mut order_data: Vec<_> = self
.orders_table
.iter()
.filter_map(|(id, by_height)| {
let created_height = by_height.keys().next().expect("not empty");
let latest_data = by_height.values().last().expect("not empty");
((latest_data.ask_currency == pair.0 && latest_data.give_currency == pair.1)
|| (latest_data.ask_currency == pair.1 && latest_data.give_currency == pair.0))
.then_some((*id, (*created_height, latest_data.clone())))
})
.collect();

order_data.sort_by_key(|(_, (height, _data))| Reverse(*height));
if offset >= order_data.len() {
return Ok(vec![]);
}

let latest_orders = order_data[offset..std::cmp::min(offset + len, order_data.len())]
.iter()
.map(|(order_id, (_, data))| (*order_id, (*data).clone()))
.collect();

Ok(latest_orders)
}

fn get_latest_pool_ids(
&self,
len: u32,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,21 @@ impl<'t> ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRo<'t> {
async fn get_order(&self, order_id: OrderId) -> Result<Option<Order>, ApiServerStorageError> {
self.transaction.get_order(order_id)
}

async fn get_all_orders(
&self,
len: u32,
offset: u32,
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
self.transaction.get_orders_by_height(len, offset)
}

async fn get_orders_for_trading_pair(
&self,
pair: (CoinOrTokenId, CoinOrTokenId),
len: u32,
offset: u32,
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
self.transaction.get_orders_for_trading_pair(pair, len, offset)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -502,4 +502,21 @@ impl<'t> ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRw<'t> {
async fn get_order(&self, order_id: OrderId) -> Result<Option<Order>, ApiServerStorageError> {
self.transaction.get_order(order_id)
}

async fn get_all_orders(
&self,
len: u32,
offset: u32,
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
self.transaction.get_orders_by_height(len, offset)
}

async fn get_orders_for_trading_pair(
&self,
pair: (CoinOrTokenId, CoinOrTokenId),
len: u32,
offset: u32,
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
self.transaction.get_orders_for_trading_pair(pair, len, offset)
}
}
2 changes: 1 addition & 1 deletion api-server/api-server-common/src/storage/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

pub const CURRENT_STORAGE_VERSION: u32 = 17;
pub const CURRENT_STORAGE_VERSION: u32 = 18;

pub mod in_memory;
pub mod postgres;
234 changes: 159 additions & 75 deletions api-server/api-server-common/src/storage/impls/postgres/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,12 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
)
.await?;

// Index for searching for trading pairs
self.just_execute(
"CREATE INDEX orders_currencies_index ON ml.orders (ask_currency, give_currency);",
)
.await?;

logging::log::info!("Done creating database tables");

Ok(())
Expand Down Expand Up @@ -2162,21 +2168,21 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
}

pub async fn get_order(
&mut self,
&self,
order_id: OrderId,
chain_config: &ChainConfig,
) -> Result<Option<Order>, ApiServerStorageError> {
let order_id = Address::new(chain_config, order_id)
let order_id_addr = Address::new(chain_config, order_id)
.map_err(|_| ApiServerStorageError::AddressableError)?;
let row = self
.tx
.query_opt(
r#"SELECT initially_asked, ask_balance, ask_currency, initially_given, give_balance, give_currency, conclude_destination, next_nonce, creation_block_height
r#"SELECT order_id, initially_asked, ask_balance, ask_currency, initially_given, give_balance, give_currency, conclude_destination, next_nonce, creation_block_height
FROM ml.orders
WHERE order_id = $1
AND block_height = (SELECT MAX(block_height) FROM ml.orders WHERE order_id = $1);
"#,
&[&order_id.as_str()],
&[&order_id_addr.as_str()],
)
.await
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?;
Expand All @@ -2186,78 +2192,9 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
None => return Ok(None),
};

let initially_asked: String = data.get(0);
let ask_balance: String = data.get(1);
let ask_currency: Vec<u8> = data.get(2);
let initially_given: String = data.get(3);
let give_balance: String = data.get(4);
let give_currency: Vec<u8> = data.get(5);
let conclude_destination: Vec<u8> = data.get(6);
let next_nonce: Vec<u8> = data.get(7);
let creation_block_height: i64 = data.get(8);

let initially_asked = Amount::from_fixedpoint_str(&initially_asked, 0).ok_or_else(|| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: invalid initial ask balance {initially_asked}"
))
})?;

let ask_balance = Amount::from_fixedpoint_str(&ask_balance, 0).ok_or_else(|| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: invalid ask balance {ask_balance}"
))
})?;
let (decoded_order_id, order) = decode_order_from_row(&data, chain_config)?;
assert_eq!(order_id, decoded_order_id);

let ask_currency =
CoinOrTokenId::decode_all(&mut ask_currency.as_slice()).map_err(|e| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: {e}"
))
})?;

let initially_given = Amount::from_fixedpoint_str(&initially_given, 0).ok_or_else(|| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: invalid initial give balance {initially_given}"
))
})?;

let give_balance = Amount::from_fixedpoint_str(&give_balance, 0).ok_or_else(|| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: invalid give balance {give_balance}"
))
})?;

let give_currency =
CoinOrTokenId::decode_all(&mut give_currency.as_slice()).map_err(|e| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: {e}"
))
})?;

let conclude_destination = Destination::decode_all(&mut conclude_destination.as_slice())
.map_err(|e| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: {e}"
))
})?;

let next_nonce = AccountNonce::decode_all(&mut next_nonce.as_slice()).map_err(|e| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: {e}"
))
})?;

let order = Order {
creation_block_height: BlockHeight::new(creation_block_height as u64),
conclude_destination,
ask_balance,
initially_asked,
ask_currency,
give_balance,
give_currency,
initially_given,
next_nonce,
};
Ok(Some(order))
}

Expand Down Expand Up @@ -2315,6 +2252,153 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {

Ok(())
}

pub async fn get_orders_by_height(
&self,
len: u32,
offset: u32,
chain_config: &ChainConfig,
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
let len = len as i64;
let offset = offset as i64;
self.tx
.query(
r#"
SELECT sub.order_id, initially_asked, ask_balance, ask_currency, initially_given, give_balance, give_currency, conclude_destination, next_nonce, creation_block_height
FROM (
SELECT order_id, initially_asked, ask_balance, ask_currency, initially_given, give_balance, give_currency, conclude_destination, next_nonce, creation_block_height, block_height, ROW_NUMBER() OVER(PARTITION BY order_id ORDER BY block_height DESC) as newest
FROM ml.orders
) AS sub
WHERE newest = 1
ORDER BY creation_block_height DESC
OFFSET $1
LIMIT $2;
"#,
&[&offset, &len],
)
.await
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?
.into_iter()
.map(|row| -> Result<(OrderId, Order), ApiServerStorageError> {
decode_order_from_row(&row, chain_config)
})
.collect()
}

pub async fn get_orders_for_trading_pair(
&self,
pair: (CoinOrTokenId, CoinOrTokenId),
len: u32,
offset: u32,
chain_config: &ChainConfig,
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
let len = len as i64;
let offset = offset as i64;
self.tx
.query(
r#"
SELECT sub.order_id, initially_asked, ask_balance, ask_currency, initially_given, give_balance, give_currency, conclude_destination, next_nonce, creation_block_height
FROM (
SELECT order_id, initially_asked, ask_balance, ask_currency, initially_given, give_balance, give_currency, conclude_destination, next_nonce, creation_block_height, block_height, ROW_NUMBER() OVER(PARTITION BY order_id ORDER BY block_height DESC) as newest
FROM ml.orders
) AS sub
WHERE newest = 1 AND ((ask_currency = $1 AND give_currency = $2) OR (ask_currency = $2 AND give_currency = $1))
ORDER BY creation_block_height DESC
OFFSET $3
LIMIT $4;
"#,
&[&pair.0.encode(), &pair.1.encode(), &offset, &len],
)
.await
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?
.into_iter()
.map(|row| -> Result<(OrderId, Order), ApiServerStorageError> {
decode_order_from_row(&row, chain_config)
})
.collect()
}
}

fn decode_order_from_row(
data: &tokio_postgres::Row,
chain_config: &ChainConfig,
) -> Result<(OrderId, Order), ApiServerStorageError> {
let order_id: String = data.get(0);
let initially_asked: String = data.get(1);
let ask_balance: String = data.get(2);
let ask_currency: Vec<u8> = data.get(3);
let initially_given: String = data.get(4);
let give_balance: String = data.get(5);
let give_currency: Vec<u8> = data.get(6);
let conclude_destination: Vec<u8> = data.get(7);
let next_nonce: Vec<u8> = data.get(8);
let creation_block_height: i64 = data.get(9);

let order_id = Address::<OrderId>::from_string(chain_config, order_id)
.map_err(|_| ApiServerStorageError::AddressableError)?
.into_object();

let initially_asked = Amount::from_fixedpoint_str(&initially_asked, 0).ok_or_else(|| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: invalid initial ask balance {initially_asked}"
))
})?;

let ask_balance = Amount::from_fixedpoint_str(&ask_balance, 0).ok_or_else(|| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: invalid ask balance {ask_balance}"
))
})?;

let ask_currency = CoinOrTokenId::decode_all(&mut ask_currency.as_slice()).map_err(|e| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: {e}"
))
})?;

let initially_given = Amount::from_fixedpoint_str(&initially_given, 0).ok_or_else(|| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: invalid initial give balance {initially_given}"
))
})?;

let give_balance = Amount::from_fixedpoint_str(&give_balance, 0).ok_or_else(|| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: invalid give balance {give_balance}"
))
})?;

let give_currency = CoinOrTokenId::decode_all(&mut give_currency.as_slice()).map_err(|e| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: {e}"
))
})?;

let conclude_destination = Destination::decode_all(&mut conclude_destination.as_slice())
.map_err(|e| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: {e}"
))
})?;

let next_nonce = AccountNonce::decode_all(&mut next_nonce.as_slice()).map_err(|e| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for order {order_id}: {e}"
))
})?;

let order = Order {
creation_block_height: BlockHeight::new(creation_block_height as u64),
conclude_destination,
ask_balance,
initially_asked,
ask_currency,
give_balance,
give_currency,
initially_given,
next_nonce,
};
Ok((order_id, order))
}

fn amount_to_str(amount: Amount) -> String {
Expand Down
Loading
Loading