Skip to content

Commit

Permalink
Merge pull request #43 from orochi-network/feature/accumulation
Browse files Browse the repository at this point in the history
Accumulation. Initial Commit
  • Loading branch information
chiro-hiro authored Oct 29, 2023
2 parents b640e89 + a95b1cb commit 54bfc79
Show file tree
Hide file tree
Showing 16 changed files with 529 additions and 12 deletions.
7 changes: 3 additions & 4 deletions packages/zkdb/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

129 changes: 129 additions & 0 deletions packages/zkdb/src/core/database-contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {
Field,
SmartContract,
State,
state,
Reducer,
method,
UInt64,
AccountUpdate,
Provable,
Mina
} from 'o1js';
import { Action } from '../rollup/action.js';
import { RollUpProof } from '../rollup/offchain-rollup.js';
import { RollupService } from '../rollup/rollup-service.js';

export class DatabaseContract extends SmartContract {
@state(Field) rootCommitment = State<Field>();
@state(Field) currentActionState = State<Field>();

reducer = Reducer({ actionType: Action });

events = {
INSERT: Field,
REMOVE: Field,
};

@method init() {
this.account.provedState.assertEquals(this.account.provedState.get());
this.account.provedState.get().assertFalse();

super.init();

this.currentActionState.set(Reducer.initialActionState);
const merkleTreeRoot = Provable.witness(Field, () => RollupService.get().getMerkleTree().getRoot())
this.rootCommitment.set(merkleTreeRoot);
}

// TODO: Figured out how to replace the Field with IDocument / ISchema, which must be provable
@method insert(index: UInt64, hash: Field) {
this.reducer.dispatch(
new Action({
type: Field(0),
index: index,
hash: hash,
})
);
this.emitEvent('INSERT', hash);
}

@method remove(index: UInt64) {
this.reducer.dispatch(
new Action({
type: Field(1),
index: index,
hash: Field(0),
})
);
this.emitEvent('REMOVE', index.toFields()[0]);
}

@method rollup(proof: RollUpProof) {
this.rootCommitment.getAndAssertEquals();
this.currentActionState.getAndAssertEquals();

proof.verify();

this.rootCommitment.assertEquals(proof.publicInput.onChainRoot);
this.currentActionState.assertEquals(proof.publicInput.onChainActionState);

// If we have no error we can garantee that action state is truthworthy
this.reducer.getActions({
fromActionState: this.currentActionState.get(),
endActionState: proof.publicOutput.newActionState,
});

// const newActionState = Provable.witness(Field, () => pendingActions.reduce((oldActionState, actions) => {
// const action = actions[0];
// const actionsHash = AccountUpdate.Actions.hash([Action.toFields(action)]);
// return AccountUpdate.Actions.updateSequenceState(oldActionState, actionsHash)
// }, this.currentActionState.get()));

// proof.publicOutput.newActionState.assertEquals(proof.publicOutput.newActionState);

this.rootCommitment.set(proof.publicOutput.newRoot);
this.currentActionState.set(proof.publicOutput.newActionState);
}

getState(): Field {
return this.rootCommitment.get();
}

getActionState(): Field {
return this.currentActionState.get();
}

async getUnprocessedActions(size: number): Promise<Action[]> {
const fromActionState = this.getActionState();

return (await this.reducer.fetchActions({ fromActionState }))
.slice(0, size)
.map((action) => action[0]);
}

getEndActionState(actions: Action[]): Field {
let fromActionState = this.getActionState();

for (const action of actions) {
const actionHashs = AccountUpdate.Actions.hash([Action.toFields(action)]);
fromActionState = AccountUpdate.Actions.updateSequenceState(
fromActionState,
actionHashs
);
}

return fromActionState;
}

async rollUp(proof: RollUpProof): Promise<void> {
const creds = RollupService.get().getCredentials();

const tx = await Mina.transaction(creds.getFeePayer(), () => {
this.rollup(proof);
});

await tx.prove();
await tx.sign([creds.getFeePayerKey(), creds.getZkAppKey()]).send();
}
}
3 changes: 3 additions & 0 deletions packages/zkdb/src/core/database-witness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { MerkleWitness } from "o1js";

export class DatabaseMerkleWitness extends MerkleWitness(8) {}
3 changes: 2 additions & 1 deletion packages/zkdb/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './common.js';
export * from './schema.js';
export * from './loader.js';
export * from './zkdb-storage.js';
export * from './zkdb-storage.js'
export * from './database-witness.js'
16 changes: 12 additions & 4 deletions packages/zkdb/src/core/zkdb-storage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Field } from 'o1js';
import { Field, MerkleTree } from 'o1js';
import { MerkleProof } from '../merkle-tree/common.js';
import {
StorageEngine,
Expand Down Expand Up @@ -291,6 +291,10 @@ export class ZKDatabaseStorage {
return this.metadata.merkle.getRoot();
}

public getMerkleTree(): MerkleTree {
return this.metadata.merkle.toMerkleTree();
}

/**
* Get the witness of the index.
* @param index Document index.
Expand Down Expand Up @@ -331,8 +335,9 @@ export class ZKDatabaseStorage {
* Update a document by index.
* @param index Document index.
* @param document Document instance.
* @returns Document index
*/
public async updateByIndex(index: number, document: IDocument) {
public async updateByIndex(index: number, document: IDocument): Promise<number> {
const digest = document.hash();
// Write file with the index as filename to ipfs
await this.storageEngine.writeFile(
Expand All @@ -341,13 +346,15 @@ export class ZKDatabaseStorage {
);
await this.metadata.merkle.setLeaf(BigInt(index), digest);
await this.metadata.save();
return index;
}

/**
* Add a new document to the current collection.
* @param document Document instance.
* @returns Document index
*/
public async add(document: IDocument) {
public async add(document: IDocument): Promise<number> {
const entires = Object.entries(document.index());
for (let i = 0; i < entires.length; i += 1) {
const [key, value] = entires[i];
Expand All @@ -357,7 +364,8 @@ export class ZKDatabaseStorage {
}
// Add a new record to indexer
const [result] = this.metadata.indexer.add(document.index()).get();
await this.updateByIndex(result.index, document);
const index = await this.updateByIndex(result.index, document);
return index;
}

/**
Expand Down
88 changes: 88 additions & 0 deletions packages/zkdb/src/examples/rollup/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
Mina,
PrivateKey,
AccountUpdate,
UInt32,
CircuitString,
UInt64,
Provable
} from 'o1js';
import { User } from './user.js';
import { ZKDatabaseStorage } from '../../core/zkdb-storage.js';
import { DatabaseContract } from '../../core/database-contract.js';
import { RollupService } from '../../rollup/rollup-service.js';
import { Credentials } from '../../models/credentials.js';

let doProofs = false;

const merkleHeight = 8;

(async () => {
let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs });
Mina.setActiveInstance(Local);
let initialBalance = 10_000_000_000;

let feePayerKey = Local.testAccounts[0].privateKey;
let feePayer = Local.testAccounts[0].publicKey;

// the zkapp account
let zkappKey = PrivateKey.random();
let zkappAddress = zkappKey.toPublicKey();

// Initialize offchain service

const zkdb = await ZKDatabaseStorage.getInstance('zkdb-test', {
storageEngine: 'local',
merkleHeight,
storageEngineCfg: {
location: './data',
},
});

const offchainContract = new DatabaseContract(zkappAddress);
const rollUpService = await RollupService.activate(offchainContract, zkdb, new Credentials(feePayerKey, zkappKey));

if (doProofs) {
await DatabaseContract.compile();
}

console.log('Deploying Database Smart Contract...');
let tx = await Mina.transaction(feePayer, () => {
AccountUpdate.fundNewAccount(feePayer).send({
to: zkappAddress,
amount: initialBalance,
});
offchainContract.deploy();
});
await tx.prove();
await tx.sign([feePayerKey, zkappKey]).send();

Provable.log('on chain commitment', offchainContract.getState());
Provable.log('off chain commitment', await zkdb.getMerkleRoot());

// Feed
console.log('Update Database Smart Contract...');
for (let i = 0; i < 2; i++) {
const newUser = new User({
accountName: CircuitString.fromString(`user ${i}`),
ticketAmount: UInt32.from(i),
});


console.log(`Saving user ${i}`);
const index = await zkdb.add(newUser);

const tx = await Mina.transaction(feePayer, () => {
offchainContract.insert(UInt64.from(index), newUser.hash());
});

await tx.prove();
await tx.sign([feePayerKey, zkappKey,]).send();
}

const batchSize = 5;
await rollUpService.rollUp(batchSize);

Provable.log('on chain commitment', offchainContract.getState());
Provable.log('off chain commitment', await zkdb.getMerkleRoot());
})();
24 changes: 24 additions & 0 deletions packages/zkdb/src/examples/rollup/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Schema } from "../../core/schema.js";
import { CircuitString, UInt32 } from "o1js";

export class User extends Schema({
accountName: CircuitString,
ticketAmount: UInt32,
}) {
static deserialize(data: Uint8Array): User {
return new User(User.decode(data));
}

index(): { accountName: string } {
return {
accountName: this.accountName.toString(),
};
}

json(): { accountName: string; ticketAmount: string } {
return {
accountName: this.accountName.toString(),
ticketAmount: this.ticketAmount.toString(),
};
}
}
20 changes: 19 additions & 1 deletion packages/zkdb/src/merkle-tree/merkle-tree-base.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BSON } from 'bson';
import { Field, Poseidon } from 'o1js';
import { Field, MerkleTree, Poseidon } from 'o1js';
import { MerkleProof } from './common.js';
import { createExtendedMerkleWitness } from './merkle-tree-extended.js';

Expand Down Expand Up @@ -249,6 +249,24 @@ export abstract class BaseMerkleTree {
return 2n ** BigInt(this.height - 1);
}

public toMerkleTree(): MerkleTree {
if (this.needsUpdate) {
throw Error('Update Merkle Tree before calling this method');
}

const localMerkleTree = new MerkleTree(this.height);

if (this.nodesMap['0']) {
const nodesAtLevel = this.nodesMap['0'];

for (const [nodeIndex, nodeValue] of Object.entries(nodesAtLevel)) {
localMerkleTree.setLeaf(BigInt(nodeIndex), nodeValue);
}
}

return localMerkleTree;
}

/**
* @todo In the future we can think about byte stream
* we can deal with gigabytes of Merkle tree and use storage
Expand Down
27 changes: 27 additions & 0 deletions packages/zkdb/src/models/credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { PrivateKey, PublicKey } from "o1js";

export class Credentials {
private feePayerKey: PrivateKey;
private zkAppKey: PrivateKey;

constructor(feePayerKey: PrivateKey, zkAppKey: PrivateKey) {
this.feePayerKey = feePayerKey;
this.zkAppKey = zkAppKey;
}

public getZkAppKey(): PrivateKey {
return this.zkAppKey;
}

public getFeePayerKey(): PrivateKey {
return this.feePayerKey;
}

public getZkApp(): PublicKey {
return this.zkAppKey.toPublicKey();
}

public getFeePayer(): PublicKey {
return this.feePayerKey.toPublicKey();
}
}
Loading

0 comments on commit 54bfc79

Please sign in to comment.