Skip to content

Commit 40f9e7f

Browse files
committed
Get order pairs
1 parent e0d5b1d commit 40f9e7f

File tree

12 files changed

+615
-201
lines changed

12 files changed

+615
-201
lines changed

api-server/api-server-common/src/storage/impls/in_memory/mod.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,40 @@ impl ApiServerInMemoryStorage {
374374
Ok(latest_orders)
375375
}
376376

377+
fn get_orders_for_trading_pair(
378+
&self,
379+
pair: (CoinOrTokenId, CoinOrTokenId),
380+
len: u32,
381+
offset: u32,
382+
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
383+
let len = len as usize;
384+
let offset = offset as usize;
385+
386+
let mut order_data: Vec<_> = self
387+
.orders_table
388+
.iter()
389+
.filter_map(|(id, by_height)| {
390+
let created_height = by_height.keys().next().expect("not empty");
391+
let latest_data = by_height.values().last().expect("not empty");
392+
((latest_data.ask_currency == pair.0 && latest_data.give_currency == pair.1)
393+
|| (latest_data.ask_currency == pair.1 && latest_data.give_currency == pair.0))
394+
.then_some((*id, (*created_height, latest_data.clone())))
395+
})
396+
.collect();
397+
398+
order_data.sort_by_key(|(_, (height, _data))| Reverse(*height));
399+
if offset >= order_data.len() {
400+
return Ok(vec![]);
401+
}
402+
403+
let latest_orders = order_data[offset..std::cmp::min(offset + len, order_data.len())]
404+
.iter()
405+
.map(|(order_id, (_, data))| (*order_id, (*data).clone()))
406+
.collect();
407+
408+
Ok(latest_orders)
409+
}
410+
377411
fn get_latest_pool_ids(
378412
&self,
379413
len: u32,

api-server/api-server-common/src/storage/impls/in_memory/transactional/read.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,4 +271,13 @@ impl<'t> ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRo<'t> {
271271
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
272272
self.transaction.get_orders_by_height(len, offset)
273273
}
274+
275+
async fn get_orders_for_trading_pair(
276+
&self,
277+
pair: (CoinOrTokenId, CoinOrTokenId),
278+
len: u32,
279+
offset: u32,
280+
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
281+
self.transaction.get_orders_for_trading_pair(pair, len, offset)
282+
}
274283
}

api-server/api-server-common/src/storage/impls/in_memory/transactional/write.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,4 +510,13 @@ impl<'t> ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRw<'t> {
510510
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
511511
self.transaction.get_orders_by_height(len, offset)
512512
}
513+
514+
async fn get_orders_for_trading_pair(
515+
&self,
516+
pair: (CoinOrTokenId, CoinOrTokenId),
517+
len: u32,
518+
offset: u32,
519+
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
520+
self.transaction.get_orders_for_trading_pair(pair, len, offset)
521+
}
513522
}

api-server/api-server-common/src/storage/impls/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
1515

16-
pub const CURRENT_STORAGE_VERSION: u32 = 17;
16+
pub const CURRENT_STORAGE_VERSION: u32 = 18;
1717

1818
pub mod in_memory;
1919
pub mod postgres;

api-server/api-server-common/src/storage/impls/postgres/queries.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,12 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
671671
)
672672
.await?;
673673

674+
// Index for searching for trading pairs
675+
self.just_execute(
676+
"CREATE INDEX orders_currencies_index ON ml.orders (ask_currency, give_currency);",
677+
)
678+
.await?;
679+
674680
logging::log::info!("Done creating database tables");
675681

676682
Ok(())
@@ -2278,6 +2284,39 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
22782284
})
22792285
.collect()
22802286
}
2287+
2288+
pub async fn get_orders_for_trading_pair(
2289+
&self,
2290+
pair: (CoinOrTokenId, CoinOrTokenId),
2291+
len: u32,
2292+
offset: u32,
2293+
chain_config: &ChainConfig,
2294+
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
2295+
let len = len as i64;
2296+
let offset = offset as i64;
2297+
self.tx
2298+
.query(
2299+
r#"
2300+
SELECT sub.order_id, initially_asked, ask_balance, ask_currency, initially_given, give_balance, give_currency, conclude_destination, next_nonce, creation_block_height
2301+
FROM (
2302+
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
2303+
FROM ml.orders
2304+
) AS sub
2305+
WHERE newest = 1 AND ((ask_currency = $1 AND give_currency = $2) OR (ask_currency = $2 AND give_currency = $1))
2306+
ORDER BY creation_block_height DESC
2307+
OFFSET $3
2308+
LIMIT $4;
2309+
"#,
2310+
&[&pair.0.encode(), &pair.1.encode(), &offset, &len],
2311+
)
2312+
.await
2313+
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?
2314+
.into_iter()
2315+
.map(|row| -> Result<(OrderId, Order), ApiServerStorageError> {
2316+
decode_order_from_row(&row, chain_config)
2317+
})
2318+
.collect()
2319+
}
22812320
}
22822321

22832322
fn decode_order_from_row(

api-server/api-server-common/src/storage/impls/postgres/transactional/read.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,4 +382,16 @@ impl<'a> ApiServerStorageRead for ApiServerPostgresTransactionalRo<'a> {
382382

383383
Ok(res)
384384
}
385+
386+
async fn get_orders_for_trading_pair(
387+
&self,
388+
pair: (CoinOrTokenId, CoinOrTokenId),
389+
len: u32,
390+
offset: u32,
391+
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
392+
let conn = QueryFromConnection::new(self.connection.as_ref().expect(CONN_ERR));
393+
let res = conn.get_orders_for_trading_pair(pair, len, offset, &self.chain_config).await?;
394+
395+
Ok(res)
396+
}
385397
}

api-server/api-server-common/src/storage/impls/postgres/transactional/write.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,4 +690,16 @@ impl<'a> ApiServerStorageRead for ApiServerPostgresTransactionalRw<'a> {
690690

691691
Ok(res)
692692
}
693+
694+
async fn get_orders_for_trading_pair(
695+
&self,
696+
pair: (CoinOrTokenId, CoinOrTokenId),
697+
len: u32,
698+
offset: u32,
699+
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError> {
700+
let conn = QueryFromConnection::new(self.connection.as_ref().expect(CONN_ERR));
701+
let res = conn.get_orders_for_trading_pair(pair, len, offset, &self.chain_config).await?;
702+
703+
Ok(res)
704+
}
693705
}

api-server/api-server-common/src/storage/storage_api/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,13 @@ pub trait ApiServerStorageRead: Sync {
608608
len: u32,
609609
offset: u32,
610610
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError>;
611+
612+
async fn get_orders_for_trading_pair(
613+
&self,
614+
pair: (CoinOrTokenId, CoinOrTokenId),
615+
len: u32,
616+
offset: u32,
617+
) -> Result<Vec<(OrderId, Order)>, ApiServerStorageError>;
611618
}
612619

613620
#[async_trait::async_trait]

api-server/stack-test-suite/tests/v2/orders.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,171 @@ async fn create_fill_conclude_order(#[case] seed: Seed) {
177177

178178
task.abort();
179179
}
180+
181+
#[rstest]
182+
#[trace]
183+
#[case(Seed::from_entropy())]
184+
#[tokio::test]
185+
async fn order_pairs(#[case] seed: Seed) {
186+
use common::{chain::tokens::TokenId, primitives::H256};
187+
188+
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
189+
let addr = listener.local_addr().unwrap();
190+
191+
let (tx, rx) = tokio::sync::oneshot::channel();
192+
193+
let task = tokio::spawn(async move {
194+
let web_server_state = {
195+
let mut rng = make_seedable_rng(seed);
196+
197+
let chain_config = create_unit_test_config();
198+
199+
let chainstate_blocks = {
200+
let mut tf = TestFramework::builder(&mut rng)
201+
.with_chain_config(chain_config.clone())
202+
.build();
203+
204+
// Issue and mint some tokens to create an order with different currencies
205+
let issue_and_mint_result =
206+
helpers::issue_and_mint_tokens_from_genesis(&mut rng, &mut tf);
207+
208+
// Create order
209+
let order_data = OrderData::new(
210+
Destination::AnyoneCanSpend,
211+
OutputValue::Coin(Amount::from_atoms(10)),
212+
OutputValue::TokenV1(issue_and_mint_result.token_id, Amount::from_atoms(10)),
213+
);
214+
let order_id = make_order_id(&issue_and_mint_result.tokens_outpoint);
215+
let tx_1 = TransactionBuilder::new()
216+
.add_input(
217+
TxInput::Utxo(issue_and_mint_result.tokens_outpoint),
218+
InputWitness::NoSignature(None),
219+
)
220+
.add_output(TxOutput::CreateOrder(Box::new(order_data)))
221+
.build();
222+
223+
let block1 = tf.make_block_builder().add_transaction(tx_1.clone()).build(&mut rng);
224+
tf.process_block(block1.clone(), BlockSource::Local).unwrap();
225+
226+
_ = tx.send((
227+
Address::new(&chain_config, order_id).unwrap().into_string(),
228+
chain_config.coin_ticker().to_owned(),
229+
Address::new(&chain_config, issue_and_mint_result.token_id)
230+
.unwrap()
231+
.into_string(),
232+
));
233+
234+
vec![issue_and_mint_result.issue_block, issue_and_mint_result.mint_block, block1]
235+
};
236+
237+
let storage = {
238+
let mut storage = TransactionalApiServerInMemoryStorage::new(&chain_config);
239+
240+
let mut db_tx = storage.transaction_rw().await.unwrap();
241+
db_tx.reinitialize_storage(&chain_config).await.unwrap();
242+
db_tx.commit().await.unwrap();
243+
244+
storage
245+
};
246+
247+
let chain_config = Arc::new(chain_config);
248+
let mut local_node = BlockchainState::new(Arc::clone(&chain_config), storage);
249+
local_node.scan_genesis(chain_config.genesis_block()).await.unwrap();
250+
local_node.scan_blocks(BlockHeight::new(0), chainstate_blocks).await.unwrap();
251+
252+
ApiServerWebServerState {
253+
db: Arc::new(local_node.storage().clone_storage().await),
254+
chain_config: Arc::clone(&chain_config),
255+
rpc: Arc::new(DummyRPC {}),
256+
cached_values: Arc::new(CachedValues {
257+
feerate_points: RwLock::new((get_time(), vec![])),
258+
}),
259+
time_getter: Default::default(),
260+
}
261+
};
262+
263+
web_server(listener, web_server_state, true).await
264+
});
265+
266+
let (order_id, ml, tkn) = rx.await.unwrap();
267+
268+
// ML_TKN
269+
{
270+
let url = format!("/api/v2/order/pair/{}_{}?offset=0&items={}", ml, tkn, 1);
271+
272+
let response = reqwest::get(format!("http://{}:{}{url}", addr.ip(), addr.port()))
273+
.await
274+
.unwrap();
275+
assert_eq!(response.status(), 200);
276+
277+
let body = response.text().await.unwrap();
278+
let body: serde_json::Value = serde_json::from_str(&body).unwrap();
279+
let arr_body = body.as_array().unwrap();
280+
281+
assert_eq!(arr_body.len(), 1);
282+
assert_eq!(
283+
arr_body[0].as_object().unwrap().get("order_id").unwrap(),
284+
&serde_json::Value::String(order_id.clone())
285+
);
286+
}
287+
288+
// TKN_ML
289+
{
290+
let url = format!("/api/v2/order/pair/{}_{}?offset=0&items={}", tkn, ml, 1);
291+
292+
let response = reqwest::get(format!("http://{}:{}{url}", addr.ip(), addr.port()))
293+
.await
294+
.unwrap();
295+
assert_eq!(response.status(), 200);
296+
297+
let body = response.text().await.unwrap();
298+
let body: serde_json::Value = serde_json::from_str(&body).unwrap();
299+
let arr_body = body.as_array().unwrap();
300+
301+
assert_eq!(arr_body.len(), 1);
302+
assert_eq!(
303+
arr_body[0].as_object().unwrap().get("order_id").unwrap(),
304+
&serde_json::Value::String(order_id)
305+
);
306+
}
307+
308+
let mut rng = make_seedable_rng(seed);
309+
310+
// Random ticker
311+
let random_ticker = test_utils::random_ascii_alphanumeric_string(&mut rng, 3..5);
312+
let url = format!(
313+
"/api/v2/order/pair/{}_{}?offset=0&items={}",
314+
random_ticker, ml, 1
315+
);
316+
317+
let response = reqwest::get(format!("http://{}:{}{url}", addr.ip(), addr.port()))
318+
.await
319+
.unwrap();
320+
assert_eq!(response.status(), 400);
321+
322+
let body = response.text().await.unwrap();
323+
let body: serde_json::Value = serde_json::from_str(&body).unwrap();
324+
325+
assert_eq!(body["error"].as_str().unwrap(), "Invalid token Id");
326+
327+
// Random token
328+
let chain_config = create_unit_test_config();
329+
let random_token_id = TokenId::new(H256::random_using(&mut rng));
330+
let random_token_id = Address::new(&chain_config, random_token_id).unwrap().into_string();
331+
332+
let url = format!(
333+
"/api/v2/order/pair/{}_{}?offset=0&items={}",
334+
random_token_id, ml, 1
335+
);
336+
337+
let response = reqwest::get(format!("http://{}:{}{url}", addr.ip(), addr.port()))
338+
.await
339+
.unwrap();
340+
assert_eq!(response.status(), 200);
341+
let body = response.text().await.unwrap();
342+
let body: serde_json::Value = serde_json::from_str(&body).unwrap();
343+
let arr_body = body.as_array().unwrap();
344+
assert!(arr_body.is_empty());
345+
346+
task.abort();
347+
}

0 commit comments

Comments
 (0)