Walk through slab nodes
This commit is contained in:
parent
cc100d59c7
commit
876c378948
|
@ -59,6 +59,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@solana/web3.js": "^0.64.0",
|
||||
"bn.js": "^5.1.2",
|
||||
"buffer-layout": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
|
55
src/index.js
55
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';
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
]);
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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==
|
||||
|
|
Loading…
Reference in New Issue