From 876c378948fe79449ab21d83bb96f66943ec979c Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Thu, 6 Aug 2020 12:29:06 -0700 Subject: [PATCH] Walk through slab nodes --- package.json | 1 + src/index.js | 55 +--------------------------------------------- src/index.test.js | 46 ++++++++++++++++++++++++++++++-------- src/layout.js | 19 ++++++++++++++++ src/slab-layout.js | 49 +++++++++++++++++++++++++++++++++++++++++ src/slab.js | 48 ++++++++++++++++++++++++++++++++++++++++ yarn.lock | 2 +- 7 files changed, 156 insertions(+), 64 deletions(-) create mode 100644 src/slab-layout.js create mode 100644 src/slab.js diff --git a/package.json b/package.json index 3fa4f31..dfd2045 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ }, "dependencies": { "@solana/web3.js": "^0.64.0", + "bn.js": "^5.1.2", "buffer-layout": "^1.2.0" } } diff --git a/src/index.js b/src/index.js index 4e6ef37..c954184 100644 --- a/src/index.js +++ b/src/index.js @@ -1,54 +1 @@ -import { u8, nu64, u32, seq, blob, struct, union, offset } from 'buffer-layout'; -import { zeros, publicKeyLayout } from './layout'; - -const SLAB_HEADER_LAYOUT = struct( - [ - u32('bumpIndex'), - zeros(4), - u32('freeListLen'), - zeros(4), - u32('freeListHead'), - u32('rootNode'), - u32('leafCount'), - zeros(4), - ], - 'header', -); - -const SLAB_NODE_LAYOUT = union(u32('tag'), blob(60), 'node'); -SLAB_NODE_LAYOUT.addVariant(0, struct([]), 'uninitialized'); -SLAB_NODE_LAYOUT.addVariant( - 1, - struct([u32('prefixLen'), blob(16, 'key'), seq(u32(), 2, 'children')]), - 'innerNode', -); -SLAB_NODE_LAYOUT.addVariant( - 2, - struct([ - u8('ownerSlot'), - blob(3), - blob(16, 'key'), - publicKeyLayout('owner'), - nu64('quantity'), - ]), - 'leafNode', -); -SLAB_NODE_LAYOUT.addVariant(3, struct([u32('next')]), 'freeNode'); -SLAB_NODE_LAYOUT.addVariant(4, struct([]), 'lastFreeNode'); - -const SLAB_LAYOUT = struct([ - SLAB_HEADER_LAYOUT, - seq( - SLAB_NODE_LAYOUT, - offset( - SLAB_HEADER_LAYOUT.layoutFor('bumpIndex'), - SLAB_HEADER_LAYOUT.offsetOf('bumpIndex') - SLAB_HEADER_LAYOUT.span, - ), - 'nodes', - ), -]); - -export function parseSlab(slab) { - const { header, nodes } = SLAB_LAYOUT.decode(slab); - return { header, nodes }; -} +export { Slab } from './slab'; diff --git a/src/index.test.js b/src/index.test.js index 37704ba..54be72d 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -1,15 +1,43 @@ -import { parseSlab } from './index'; +import { Slab } from './index'; +import BN from 'bn.js'; + +const SLAB_BUFFER = Buffer.from( + '0900000000000000020000000000000008000000000000000400000000000000010000001e00000000000040952fe4da5c1f3c860200000004000000030000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b000000000000000200000002000000000000a0ca17726dae0f1e4301000000111111111111111111111111111111111111111111111111111111111111111141010000000000000200000001000000d20a3f4eeee073c3f60fe98e010000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b00000000000000020000000300000000000040952fe4da5c1f3c8602000000131313131313131313131313131313131313131313131313131313131313131340e2010000000000010000001f0000000500000000000000000000000000000005000000060000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b00000000000000020000000400000004000000000000000000000000000000171717171717171717171717171717171717171717171717171717171717171702000000000000000100000020000000000000a0ca17726dae0f1e430100000001000000020000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b000000000000000400000000000000040000000000000000000000000000001717171717171717171717171717171717171717171717171717171717171717020000000000000003000000070000000500000000000000000000000000000017171717171717171717171717171717171717171717171717171717171717170200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', +); describe('slab', () => { + let slab; + it('parses', () => { - const slab = parseSlab( - Buffer.from( - '0900000000000000020000000000000008000000000000000400000000000000010000001e00000000000040952fe4da5c1f3c860200000004000000030000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b000000000000000200000002000000000000a0ca17726dae0f1e4301000000111111111111111111111111111111111111111111111111111111111111111141010000000000000200000001000000d20a3f4eeee073c3f60fe98e010000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b00000000000000020000000300000000000040952fe4da5c1f3c8602000000131313131313131313131313131313131313131313131313131313131313131340e2010000000000010000001f0000000500000000000000000000000000000005000000060000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b00000000000000020000000400000004000000000000000000000000000000171717171717171717171717171717171717171717171717171717171717171702000000000000000100000020000000000000a0ca17726dae0f1e430100000001000000020000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b000000000000000400000000000000040000000000000000000000000000001717171717171717171717171717171717171717171717171717171717171717020000000000000003000000070000000500000000000000000000000000000017171717171717171717171717171717171717171717171717171717171717170200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', - ), + slab = Slab.from(SLAB_BUFFER); + expect(slab).toBeTruthy(); + expect(slab.header.bumpIndex).toBe(9); + expect(slab.nodes).toHaveLength(9); + }); + + it('finds nodes', () => { + expect(slab.get(new BN('123456789012345678901234567890')).ownerSlot).toBe( + 1, ); - console.log(slab); - console.log(slab.nodes[1]); - console.log(slab.nodes[1].leafNode.owner.toBase58()); + expect(slab.get(new BN('100000000000000000000000000000')).ownerSlot).toBe( + 2, + ); + expect(slab.get(new BN('200000000000000000000000000000')).ownerSlot).toBe( + 3, + ); + expect(slab.get(4).ownerSlot).toBe(4); + }); + + it('does not find nonexistant nodes', () => { + expect(slab.get(0)).toBeNull(); + expect(slab.get(3)).toBeNull(); + expect(slab.get(5)).toBeNull(); + expect(slab.get(6)).toBeNull(); + expect(slab.get(new BN('200000000000000000000000000001'))).toBeNull(); + expect(slab.get(new BN('100000000000000000000000000001'))).toBeNull(); + expect(slab.get(new BN('123456789012345678901234567889'))).toBeNull(); + expect(slab.get(new BN('123456789012345678901234567891'))).toBeNull(); + expect(slab.get(new BN('99999999999999999999999999999'))).toBeNull(); }); }); diff --git a/src/layout.js b/src/layout.js index 71637cc..1825d95 100644 --- a/src/layout.js +++ b/src/layout.js @@ -1,5 +1,6 @@ import { Blob } from 'buffer-layout'; import { PublicKey } from '@solana/web3.js'; +import BN from 'bn.js'; class Zeros extends Blob { decode(b, offset) { @@ -32,3 +33,21 @@ class PublicKeyLayout extends Blob { export function publicKeyLayout(property) { return new PublicKeyLayout(property); } + +class BNLayout extends Blob { + decode(b, offset) { + return new BN(super.decode(b, offset), 10, 'le'); + } + + encode(src, b, offset) { + return super.encode(src.toArrayLike(Buffer, 'le', this.span), b, offset); + } +} + +export function u64(property) { + return new BNLayout(8, property); +} + +export function u128(property) { + return new BNLayout(16, property); +} diff --git a/src/slab-layout.js b/src/slab-layout.js new file mode 100644 index 0000000..f863134 --- /dev/null +++ b/src/slab-layout.js @@ -0,0 +1,49 @@ +import { blob, offset, seq, struct, u32, u8, union } from 'buffer-layout'; +import { publicKeyLayout, u128, u64, zeros } from './layout'; + +const SLAB_HEADER_LAYOUT = struct( + [ + u32('bumpIndex'), + zeros(4), // Consider slabs with more than 2^32 nodes to be invalid + u32('freeListLen'), + zeros(4), + u32('freeListHead'), + u32('root'), + u32('leafCount'), + zeros(4), + ], + 'header', +); + +const SLAB_NODE_LAYOUT = union(u32('tag'), blob(60), 'node'); +SLAB_NODE_LAYOUT.addVariant(0, struct([]), 'uninitialized'); +SLAB_NODE_LAYOUT.addVariant( + 1, + struct([u32('prefixLen'), u128('key'), seq(u32(), 2, 'children')]), + 'innerNode', +); +SLAB_NODE_LAYOUT.addVariant( + 2, + struct([ + u8('ownerSlot'), + blob(3), + u128('key'), + publicKeyLayout('owner'), + u64('quantity'), + ]), + 'leafNode', +); +SLAB_NODE_LAYOUT.addVariant(3, struct([u32('next')]), 'freeNode'); +SLAB_NODE_LAYOUT.addVariant(4, struct([]), 'lastFreeNode'); + +export const SLAB_LAYOUT = struct([ + SLAB_HEADER_LAYOUT, + seq( + SLAB_NODE_LAYOUT, + offset( + SLAB_HEADER_LAYOUT.layoutFor('bumpIndex'), + SLAB_HEADER_LAYOUT.offsetOf('bumpIndex') - SLAB_HEADER_LAYOUT.span, + ), + 'nodes', + ), +]); diff --git a/src/slab.js b/src/slab.js new file mode 100644 index 0000000..28aa8a1 --- /dev/null +++ b/src/slab.js @@ -0,0 +1,48 @@ +import BN from 'bn.js'; +import { SLAB_LAYOUT } from './slab-layout'; + +export class Slab { + constructor(header, nodes) { + this.header = header; + this.nodes = nodes; + } + + static from(buffer) { + const { header, nodes } = SLAB_LAYOUT.decode(buffer); + return new Slab(header, nodes); + } + + get = (searchKey) => { + if (this.header.leafCount === 0) { + return null; + } + if (!(searchKey instanceof BN)) { + searchKey = new BN(searchKey); + } + let index = this.header.root; + while (true) { + const { leafNode, innerNode } = this.nodes[index]; + if (leafNode) { + if (leafNode.key.eq(searchKey)) { + return leafNode; + } + return null; + } else if (innerNode) { + if ( + !innerNode.key + .xor(searchKey) + .iushrn(128 - innerNode.prefixLen) + .isZero() + ) { + return null; + } + index = + innerNode.children[ + searchKey.testn(128 - innerNode.prefixLen - 1) ? 1 : 0 + ]; + } else { + throw new Error('Invalid slab'); + } + } + }; +} diff --git a/yarn.lock b/yarn.lock index e7c0b40..ef52444 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2603,7 +2603,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== -bn.js@^5.0.0, bn.js@^5.1.1: +bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0" integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==