Skip to content

Commit

Permalink
Jank Indexer (solana-labs#95)
Browse files Browse the repository at this point in the history
Jank Indexer V1

Co-authored-by: Noah Gundotra <noahgundotra@noahs-mbp.mynetworksettings.com>
  • Loading branch information
jarry-xiao and Noah Gundotra authored Jun 14, 2022
1 parent 7e0343d commit 72a99d1
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 119 deletions.
274 changes: 155 additions & 119 deletions contracts/sdk/gummyroll/accounts/index.ts
Original file line number Diff line number Diff line change
@@ -1,146 +1,182 @@
import {
PublicKey,
Connection,
} from '@solana/web3.js';
import * as borsh from 'borsh';
import { BN } from '@project-serum/anchor';
import { PublicKey, Connection } from "@solana/web3.js";
import * as borsh from "borsh";
import { BN } from "@project-serum/anchor";
import { assert } from "chai";
import { readPublicKey } from '../../utils';

/**
* Manually create a model for MerkleRoll in order to deserialize correctly
*/
export type OnChainMerkleRoll = {
header: MerkleRollHeader,
roll: MerkleRoll
export class OnChainMerkleRoll {
header: MerkleRollHeader;
roll: MerkleRoll;

constructor(header: MerkleRollHeader, roll: MerkleRoll) {
this.header = header;
this.roll = roll;
}

getChangeLogsWithNodeIndex(): PathNode[][] {
const mask = this.header.maxBufferSize - 1;
let pathNodeList = [];
for (let j = 0; j < this.roll.bufferSize; j++) {
let pathNodes = [];
let idx = (this.roll.activeIndex - j) & mask;
let changeLog = this.roll.changeLogs[idx];
let pathLen = changeLog.pathNodes.length;
for (const [lvl, key] of changeLog.pathNodes.entries()) {
let nodeIdx = (1 << (pathLen - lvl)) + (changeLog.index >> lvl);
pathNodes.push({
node: key,
index: nodeIdx,
});
}
pathNodes.push({
node: changeLog.root,
index: 1,
});
pathNodeList.push(pathNodes);
}
return pathNodeList;
}
}

type MerkleRollHeader = {
maxDepth: number, // u32
maxBufferSize: number, // u32
authority: PublicKey,
appendAuthority: PublicKey,
}
maxDepth: number; // u32
maxBufferSize: number; // u32
authority: PublicKey;
appendAuthority: PublicKey;
};

type MerkleRoll = {
sequenceNumber: BN, // u128
activeIndex: number, // u64
bufferSize: number, // u64
changeLogs: ChangeLog[],
rightMostPath: Path,
}
sequenceNumber: BN; // u128
activeIndex: number; // u64
bufferSize: number; // u64
changeLogs: ChangeLog[];
rightMostPath: Path;
};

export type PathNode = {
node: PublicKey;
index: number;
};

type ChangeLog = {
root: PublicKey,
pathNodes: PublicKey[]
index: number, // u32
_padding: number, // u32
}
root: PublicKey;
pathNodes: PublicKey[];
index: number; // u32
_padding: number; // u32
};

type Path = {
leaf: PublicKey,
proof: PublicKey[],
index: number,
_padding: number,
leaf: PublicKey;
proof: PublicKey[];
index: number;
_padding: number;
};

export function decodeMerkleRoll(buffer: Buffer): OnChainMerkleRoll {
let reader = new borsh.BinaryReader(buffer);

let header: MerkleRollHeader = {
maxBufferSize: reader.readU32(),
maxDepth: reader.readU32(),
authority: readPublicKey(reader),
appendAuthority: readPublicKey(reader)
};

// Decode MerkleRoll
let sequenceNumber = reader.readU128();
let activeIndex = reader.readU64().toNumber();
let bufferSize = reader.readU64().toNumber();

// Decode ChangeLogs
let changeLogs = [];
for (let i = 0; i < header.maxBufferSize; i++) {
let root = readPublicKey(reader);

let pathNodes = [];
for (let j = 0; j < header.maxDepth; j++) {
pathNodes.push(readPublicKey(reader));
}
changeLogs.push({
pathNodes,
root,
index: reader.readU32(),
_padding: reader.readU32(),
});
}

// Decode Right-Most Path
let leaf = readPublicKey(reader);
let proof = [];
let reader = new borsh.BinaryReader(buffer);

let header: MerkleRollHeader = {
maxBufferSize: reader.readU32(),
maxDepth: reader.readU32(),
authority: readPublicKey(reader),
appendAuthority: readPublicKey(reader),
};

// Decode MerkleRoll
let sequenceNumber = reader.readU128();
let activeIndex = reader.readU64().toNumber();
let bufferSize = reader.readU64().toNumber();

// Decode ChangeLogs
let changeLogs = [];
for (let i = 0; i < header.maxBufferSize; i++) {
let root = readPublicKey(reader);

let pathNodes = [];
for (let j = 0; j < header.maxDepth; j++) {
proof.push(readPublicKey(reader));
}
const rightMostPath = {
proof,
leaf,
index: reader.readU32(),
_padding: reader.readU32(),
}

const roll = {
sequenceNumber,
activeIndex,
bufferSize,
changeLogs,
rightMostPath
}

if (getMerkleRollAccountSize(header.maxDepth, header.maxBufferSize) != reader.offset) {
throw new Error("Failed to process whole buffer when deserializing Merkle Account Data")
}
return {
header,
roll
pathNodes.push(readPublicKey(reader));
}
changeLogs.push({
pathNodes,
root,
index: reader.readU32(),
_padding: reader.readU32(),
});
}

// Decode Right-Most Path
let leaf = readPublicKey(reader);
let proof = [];
for (let j = 0; j < header.maxDepth; j++) {
proof.push(readPublicKey(reader));
}
const rightMostPath = {
proof,
leaf,
index: reader.readU32(),
_padding: reader.readU32(),
};

const roll = {
sequenceNumber,
activeIndex,
bufferSize,
changeLogs,
rightMostPath,
};

if (
getMerkleRollAccountSize(header.maxDepth, header.maxBufferSize) !=
reader.offset
) {
throw new Error(
"Failed to process whole buffer when deserializing Merkle Account Data"
);
}
return new OnChainMerkleRoll(header, roll);
}

export function getMerkleRollAccountSize(maxDepth: number, maxBufferSize: number): number {
let headerSize = 8 + 32 + 32;
let changeLogSize = (maxDepth * 32 + 32 + 4 + 4) * maxBufferSize;
let rightMostPathSize = maxDepth * 32 + 32 + 4 + 4;
let merkleRollSize = 8 + 8 + 16 + changeLogSize + rightMostPathSize;
return merkleRollSize + headerSize;
export function getMerkleRollAccountSize(
maxDepth: number,
maxBufferSize: number
): number {
let headerSize = 8 + 32 + 32;
let changeLogSize = (maxDepth * 32 + 32 + 4 + 4) * maxBufferSize;
let rightMostPathSize = maxDepth * 32 + 32 + 4 + 4;
let merkleRollSize = 8 + 8 + 16 + changeLogSize + rightMostPathSize;
return merkleRollSize + headerSize;
}

export async function assertOnChainMerkleRollProperties(
connection: Connection,
expectedMaxDepth: number,
expectedMaxBufferSize: number,
expectedAuthority: PublicKey,
expectedRoot: PublicKey,
merkleRollPubkey: PublicKey
connection: Connection,
expectedMaxDepth: number,
expectedMaxBufferSize: number,
expectedAuthority: PublicKey,
expectedRoot: PublicKey,
merkleRollPubkey: PublicKey
) {
const merkleRoll = await connection.getAccountInfo(merkleRollPubkey);
const merkleRollAcct = decodeMerkleRoll(merkleRoll.data);
assert(
merkleRollAcct.header.maxDepth === expectedMaxDepth,
`Max depth does not match ${merkleRollAcct.header.maxDepth}, expected ${expectedMaxDepth}`
);
assert(
merkleRollAcct.header.maxBufferSize === expectedMaxBufferSize,
`Max buffer size does not match ${merkleRollAcct.header.maxBufferSize}, expected ${expectedMaxBufferSize}`
);

assert(
merkleRollAcct.header.authority.equals(expectedAuthority),
"Failed to write auth pubkey"
);

assert(
merkleRollAcct.roll.changeLogs[0].root.equals(expectedRoot),
"On chain root does not match root passed in instruction"
);
const merkleRoll = await connection.getAccountInfo(merkleRollPubkey);
const merkleRollAcct = decodeMerkleRoll(merkleRoll.data);

assert(
merkleRollAcct.header.maxDepth === expectedMaxDepth,
`Max depth does not match ${merkleRollAcct.header.maxDepth}, expected ${expectedMaxDepth}`
);
assert(
merkleRollAcct.header.maxBufferSize === expectedMaxBufferSize,
`Max buffer size does not match ${merkleRollAcct.header.maxBufferSize}, expected ${expectedMaxBufferSize}`
);

assert(
merkleRollAcct.header.authority.equals(expectedAuthority),
"Failed to write auth pubkey"
);

assert(
merkleRollAcct.roll.changeLogs[0].root.equals(expectedRoot),
"On chain root does not match root passed in instruction"
);
}
17 changes: 17 additions & 0 deletions contracts/sdk/gummyroll/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
import { PublicKey } from '@solana/web3.js';
export * from './instructions';
export * from './accounts';
export * from './types';

/**
* Program address
*
* @category constants
* @category generated
*/
export const PROGRAM_ADDRESS = 'GRoLLMza82AiYN7W9S9KCCtCyyPRAQP2ifBy4v4D5RMD'

/**
* Program public key
*
* @category constants
* @category generated
*/
export const PROGRAM_ID = new PublicKey(PROGRAM_ADDRESS)

0 comments on commit 72a99d1

Please sign in to comment.