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

speed up BookSide decoding #263

Merged
merged 2 commits into from
May 16, 2024
Merged
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
2 changes: 1 addition & 1 deletion 3rdparty/anchor
Submodule anchor updated 302 files
74 changes: 62 additions & 12 deletions ts/client/src/accounts/bookSide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ import {
LeafNode,
InnerNode,
U64_MAX_BN,
AnyNode,
} from '..';
import { BN } from '@coral-xyz/anchor';
import { Order } from '../structs/order';

function decodeOrderTreeRootStruct(data: Buffer) {
const maybeNode = data.readUInt32LE(0);
const leafCount = data.readUInt32LE(4);
return { maybeNode, leafCount };
}

export class BookSide {
public clusterTime: BN;

Expand All @@ -24,6 +31,40 @@ export class BookSide {
this.clusterTime = new BN(0);
}

public static decodeAccountfromBuffer(data: Buffer): BookSideAccount {
// TODO: add discriminator parsing & check
const roots = [
decodeOrderTreeRootStruct(data.subarray(8)),
decodeOrderTreeRootStruct(data.subarray(16))];

// skip reserved
let offset = 56+256;

const orderTreeType = data.readUInt8(offset);
const bumpIndex = data.readUInt32LE(offset+4);
const freeListLen = data.readUInt32LE(offset+8);
const freeListHead = data.readUInt32LE(offset+12);

// skip more reserved data
offset += 16 + 512;

const nodes: any[] = [];
for (let i = 0; i < 1024; ++i) {
const tag = data.readUInt8(offset);
const nodeData = data.subarray(offset, offset+88);
nodes.push({tag, nodeData});
offset += 88;
}

// this result has a slightly different layout than the regular account
// it doesn't include reserved data and it's AnyNodes don't have the field
// data: number[] (excluding the tag prefix byte)
// but nodeData: Buffer (including the tag prefix byte)
const result = { roots, nodes: { orderTreeType, bumpIndex, freeListLen, freeListHead, nodes }};

return result as any;
}

public *items(): Generator<Order> {
const fGen = this.fixedItems();
const oPegGen = this.oraclePeggedItems();
Expand Down Expand Up @@ -71,10 +112,10 @@ export class BookSide {
const index = stack.pop()!;
const node = this.account.nodes.nodes[index];
if (node.tag === BookSide.INNER_NODE_TAG) {
const innerNode = this.toInnerNode(node.data);
const innerNode = this.toInnerNode(node);
stack.push(innerNode.children[right], innerNode.children[left]);
} else if (node.tag === BookSide.LEAF_NODE_TAG) {
const leafNode = this.toLeafNode(node.data);
const leafNode = this.toLeafNode(node);
const expiryTimestamp = leafNode.timeInForce
? leafNode.timestamp.add(new BN(leafNode.timeInForce))
: U64_MAX_BN;
Expand All @@ -100,10 +141,10 @@ export class BookSide {
const index = stack.pop()!;
const node = this.account.nodes.nodes[index];
if (node.tag === BookSide.INNER_NODE_TAG) {
const innerNode = this.toInnerNode(node.data);
const innerNode = this.toInnerNode(node);
stack.push(innerNode.children[right], innerNode.children[left]);
} else if (node.tag === BookSide.LEAF_NODE_TAG) {
const leafNode = this.toLeafNode(node.data);
const leafNode = this.toLeafNode(node);
const expiryTimestamp = leafNode.timeInForce
? leafNode.timestamp.add(new BN(leafNode.timeInForce))
: U64_MAX_BN;
Expand Down Expand Up @@ -153,14 +194,23 @@ export class BookSide {
private static INNER_NODE_TAG = 1;
private static LEAF_NODE_TAG = 2;

private toInnerNode(data: number[]): InnerNode {
return (this.market.client.program as any)._coder.types.typeLayouts
.get('InnerNode')
.decode(Buffer.from([BookSide.INNER_NODE_TAG].concat(data)));
private toInnerNode(node: AnyNode): InnerNode {
const layout = (this.market.client.program as any)._coder.types.typeLayouts.get('InnerNode');
// need to differentiate between accounts loaded via anchor and decodeAccountfromBuffer
if ( 'nodeData' in node) {
return layout.decode(node['nodeData']);
} else {
return layout.decode(Buffer.from([BookSide.INNER_NODE_TAG].concat(node.data)));
}
}
private toLeafNode(data: number[]): LeafNode {
return (this.market.client.program as any)._coder.types.typeLayouts
.get('LeafNode')
.decode(Buffer.from([BookSide.LEAF_NODE_TAG].concat(data)));

private toLeafNode(node: AnyNode): LeafNode {
const layout = (this.market.client.program as any)._coder.types.typeLayouts.get('LeafNode');
// need to differentiate between accounts loaded via anchor and decodeAccountfromBuffer
if ( 'nodeData' in node) {
return layout.decode(node['nodeData']);
} else {
return layout.decode(Buffer.from([BookSide.LEAF_NODE_TAG].concat(node.data)));
}
}
}
13 changes: 4 additions & 9 deletions ts/client/src/accounts/market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
toNative,
MarketAccount,
OpenBookV2Client,
BookSideAccount,
BookSide,
SideUtils,
nameToString,
Expand Down Expand Up @@ -103,18 +102,14 @@ export class Market {
}

public async loadBids(): Promise<BookSide> {
const bidSide = (await this.client.program.account.bookSide.fetch(
this.account.bids,
)) as BookSideAccount;
this.bids = new BookSide(this, this.account.bids, bidSide, SideUtils.Bid);
const bidsAi = await this.client.connection.getAccountInfo(this.account.bids);
this.bids = new BookSide(this, this.account.bids, BookSide.decodeAccountfromBuffer(bidsAi!.data), SideUtils.Bid);
return this.bids;
}

public async loadAsks(): Promise<BookSide> {
const askSide = (await this.client.program.account.bookSide.fetch(
this.account.asks,
)) as BookSideAccount;
this.asks = new BookSide(this, this.account.asks, askSide, SideUtils.Ask);
const asksAi = await this.client.connection.getAccountInfo(this.account.asks);
this.asks = new BookSide(this, this.account.asks, BookSide.decodeAccountfromBuffer(asksAi!.data), SideUtils.Ask);
return this.asks;
}

Expand Down
27 changes: 26 additions & 1 deletion ts/client/src/test/market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
findAllMarkets,
Watcher,
sleep,
BookSide,
} from '..';
import { initReadOnlyOpenbookClient } from './util';

Expand Down Expand Up @@ -42,6 +43,29 @@ async function testDecodeMarket(): Promise<void> {
console.log(market.toPrettyString());
}


async function benchDecodeMarket(): Promise<void> {
const client = initReadOnlyOpenbookClient();
const marketPk = new PublicKey(
'CFSMrBssNG8Ud1edW59jNLnq2cwrQ9uY5cM3wXmqRJj3',
);
const market = await Market.load(client, marketPk);
await market.loadOrderBook();
await market.loadEventHeap();

const bookSideAccount = await client.connection.getAccountInfo(market.bids!.pubkey);

const start = new Date();
for (let i = 0; i < 10000; ++i) {
const side = client.program.coder.accounts.decode("bookSide", bookSideAccount!.data);
market.bids!.account = side;
market.bids!.getL2(16);
}
const end = new Date();
console.log({start, end, duration: end.valueOf() - start.valueOf()});
console.log();
};

async function testWatchMarket(): Promise<void> {
const client = initReadOnlyOpenbookClient();
const marketPk = new PublicKey(
Expand Down Expand Up @@ -103,4 +127,5 @@ async function testMarketLots(): Promise<void> {
// void testFindAllMarkets();
// void testDecodeMarket();
// void testWatchMarket();
void testMarketLots();
// void testMarketLots();
void benchDecodeMarket();
5 changes: 2 additions & 3 deletions ts/client/src/utils/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ export class Watcher {
}

addBookSide(bookSide: BookSide): this {
const { market, pubkey } = bookSide;
const { pubkey } = bookSide;
this.accountSubs[pubkey.toBase58()] = this.connection.onAccountChange(
pubkey,
(ai) => {
bookSide.account = market.client.program.coder.accounts.decode(
'bookSide',
bookSide.account = BookSide.decodeAccountfromBuffer(
ai.data,
);
},
Expand Down
Loading