Walk through slab nodes

This commit is contained in:
Gary Wang 2020-08-06 12:29:06 -07:00
parent cc100d59c7
commit 876c378948
7 changed files with 156 additions and 64 deletions

View File

@ -59,6 +59,7 @@
},
"dependencies": {
"@solana/web3.js": "^0.64.0",
"bn.js": "^5.1.2",
"buffer-layout": "^1.2.0"
}
}

View File

@ -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';

View File

@ -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();
});
});

View File

@ -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);
}

49
src/slab-layout.js Normal file
View File

@ -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',
),
]);

48
src/slab.js Normal file
View File

@ -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');
}
}
};
}

View File

@ -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==