diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 908f834..f24f4f3 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -196,7 +196,7 @@ pub fn conclude_tx_info(data: &[u8]) -> [u64;4] { macro_rules! create_zkwasm_apis { ($T: ident, $S: ident, $C: ident) => { #[wasm_bindgen] - pub fn handle_tx(params: Vec) -> u32 { + pub fn handle_tx(params: Vec) -> Vec { let user_address = [params[0], params[1], params[2], params[3]]; let sig_r = [params[16], params[17], params[18], params[19]]; let command = ¶ms[20..]; @@ -259,8 +259,7 @@ macro_rules! create_zkwasm_apis { } } - - #[wasm_bindgen] + #[wasm_bindgen] pub fn zkmain() { use zkwasm_rust_sdk::wasm_input; use zkwasm_rust_sdk::wasm_output; @@ -283,21 +282,17 @@ macro_rules! create_zkwasm_apis { } let command = unsafe {wasm_input(0)}; let command_length = ((command & 0xff00) >> 8) as usize; - zkwasm_rust_sdk::dbg!("cmd length: {}\n", command_length); unsafe { zkwasm_rust_sdk::require(command_length < 16) }; params.push(command); for _ in 0..command_length - 1 { params.push(unsafe {wasm_input(0)}); } - zkwasm_rust_sdk::dbg!("sig verify\n"); verify_tx_signature(params.clone()); - zkwasm_rust_sdk::dbg!("success\n"); handle_tx(params); let trace = unsafe {wasm_trace_size()}; zkwasm_rust_sdk::dbg!("trace track: {}\n", trace); } - zkwasm_rust_sdk::dbg!("trace after tx handlers\n"); unsafe { zkwasm_rust_sdk::require(preempt()) }; let bytes = finalize(); diff --git a/example/Makefile b/example/Makefile index 120114c..b7d933a 100644 --- a/example/Makefile +++ b/example/Makefile @@ -12,3 +12,6 @@ clean: rm -rf ../ts/src/application/application.d.ts rm -rf ../ts/src/application/application_bg.js rm -rf ../ts/src/application/application_bg.wasm.d.ts + +run: + node ../ts/src/run.js diff --git a/example/src/state.rs b/example/src/state.rs index 75935fc..6ba1d53 100644 --- a/example/src/state.rs +++ b/example/src/state.rs @@ -31,7 +31,9 @@ impl StorageData for PlayerData { pub type HelloWorldPlayer = Player; #[derive(Serialize, Default)] -pub struct State {} +pub struct State { + tick: u64 +} pub struct SafeState(pub RefCell); unsafe impl Sync for SafeState {} @@ -52,10 +54,14 @@ impl CommonState for State { } impl StorageData for State { - fn from_data(_u64data: &mut IterMut) -> Self { - State {} + fn from_data(u64data: &mut IterMut) -> Self { + State { + tick: *u64data.next().unwrap() + } + } + fn to_data(&self, data: &mut Vec) { + data.push(self.tick); } - fn to_data(&self, _data: &mut Vec) {} } impl State { @@ -64,21 +70,25 @@ impl State { data } pub fn new() -> Self { - State {} + State { + tick: 0 + } + } + pub fn preempt() -> bool { + return Self::get_global().tick % 5 == 0 } pub fn store() { - unsafe { STATE.store() }; + unsafe { Self::get_global().store() }; } } -pub static mut STATE: State = State {}; - pub struct Transaction { pub command: u64, pub data: Vec, } +const TICK: u64 = 0; const INSTALL_PLAYER: u64 = 1; const INC_COUNTER: u64 = 2; @@ -118,14 +128,18 @@ impl Transaction { todo!() } - pub fn process(&self, pkey: &[u64; 4], _rand: &[u64; 4]) -> u32 { + pub fn process(&self, pkey: &[u64; 4], _rand: &[u64; 4]) -> Vec { let b = match self.command { + TICK => { + State::get_global_mut().tick += 1; + 0 + }, INSTALL_PLAYER => self.install_player(pkey), INC_COUNTER => self.inc_counter(pkey), _ => 0, }; let kvpair = unsafe { &mut MERKLE_MAP.merkle.root }; zkwasm_rust_sdk::dbg!("root after process {:?}\n", kvpair); - b + vec![b as u64] } } diff --git a/ts/package-lock.json b/ts/package-lock.json index 36ef76a..d330e66 100644 --- a/ts/package-lock.json +++ b/ts/package-lock.json @@ -5120,7 +5120,7 @@ } }, "node_modules/zkwasm-minirollup-rpc": { - "resolved": "git+ssh://git@github.com/DelphinusLab/zkWasm-minirollup-rpc.git#795ac4aad9ec87a572d69ff80fd8475da41a7351", + "resolved": "git+ssh://git@github.com/DelphinusLab/zkWasm-minirollup-rpc.git#b9ebe2f425ee1f4d3a8232a75ff9428115f6c535", "hasInstallScript": true, "dependencies": { "@reduxjs/toolkit": "^1.5.1", diff --git a/ts/src/application/application.d.ts b/ts/src/application/application.d.ts index 9f33600..47d2485 100644 --- a/ts/src/application/application.d.ts +++ b/ts/src/application/application.d.ts @@ -2,9 +2,9 @@ /* eslint-disable */ /** * @param {BigUint64Array} params -* @returns {number} +* @returns {BigUint64Array} */ -export function handle_tx(params: BigUint64Array): number; +export function handle_tx(params: BigUint64Array): BigUint64Array; /** * @param {BigUint64Array} pid * @returns {string} diff --git a/ts/src/application/application_bg.js b/ts/src/application/application_bg.js index 2249f5e..dd472b7 100644 --- a/ts/src/application/application_bg.js +++ b/ts/src/application/application_bg.js @@ -21,16 +21,6 @@ function passArray64ToWasm0(arg, malloc) { WASM_VECTOR_LEN = arg.length; return ptr; } -/** -* @param {BigUint64Array} params -* @returns {number} -*/ -export function handle_tx(params) { - const ptr0 = passArray64ToWasm0(params, wasm.__wbindgen_malloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.handle_tx(ptr0, len0); - return ret >>> 0; -} let cachedInt32Memory0 = null; @@ -41,6 +31,30 @@ function getInt32Memory0() { return cachedInt32Memory0; } +function getArrayU64FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getBigUint64Memory0().subarray(ptr / 8, ptr / 8 + len); +} +/** +* @param {BigUint64Array} params +* @returns {BigUint64Array} +*/ +export function handle_tx(params) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray64ToWasm0(params, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.handle_tx(retptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var v2 = getArrayU64FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 8, 8); + return v2; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); @@ -204,10 +218,6 @@ export function zkmain() { wasm.zkmain(); } -function getArrayU64FromWasm0(ptr, len) { - ptr = ptr >>> 0; - return getBigUint64Memory0().subarray(ptr / 8, ptr / 8 + len); -} /** * @returns {BigUint64Array} */ diff --git a/ts/src/application/application_bg.wasm b/ts/src/application/application_bg.wasm index aeb72d0..426ad6b 100644 Binary files a/ts/src/application/application_bg.wasm and b/ts/src/application/application_bg.wasm differ diff --git a/ts/src/application/application_bg.wasm.d.ts b/ts/src/application/application_bg.wasm.d.ts index bc7037e..5570a79 100644 --- a/ts/src/application/application_bg.wasm.d.ts +++ b/ts/src/application/application_bg.wasm.d.ts @@ -1,7 +1,7 @@ /* tslint:disable */ /* eslint-disable */ export const memory: WebAssembly.Memory; -export function handle_tx(a: number, b: number): number; +export function handle_tx(a: number, b: number, c: number): void; export function get_state(a: number, b: number, c: number): void; export function snapshot(a: number): void; export function decode_error(a: number, b: number): void; @@ -15,6 +15,6 @@ export function zkmain(): void; export function query_root(a: number): void; export function verify_tx_signature(a: number, b: number): void; export function test_merkle(): void; -export function __wbindgen_malloc(a: number, b: number): number; export function __wbindgen_add_to_stack_pointer(a: number): number; +export function __wbindgen_malloc(a: number, b: number): number; export function __wbindgen_free(a: number, b: number, c: number): void; diff --git a/ts/src/application/application_loader.js b/ts/src/application/application_loader.js index 2d3e647..1d04ae2 100644 --- a/ts/src/application/application_loader.js +++ b/ts/src/application/application_loader.js @@ -24,15 +24,25 @@ function passArray64ToWasm0(arg, malloc) { WASM_VECTOR_LEN = arg.length; return ptr; } + /** * @param {BigUint64Array} params -* @returns {number} +* @returns {BigUint64Array} */ export function handle_tx(params) { - const ptr0 = passArray64ToWasm0(params, wasm.__wbindgen_malloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.handle_tx(ptr0, len0); - return ret >>> 0; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray64ToWasm0(params, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.handle_tx(retptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var v2 = getArrayU64FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 8, 8); + return v2; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } } let cachedInt32Memory0 = null; diff --git a/ts/src/config.ts b/ts/src/config.ts index 0b89514..1dfdd5a 100644 --- a/ts/src/config.ts +++ b/ts/src/config.ts @@ -120,13 +120,21 @@ export const jobSchema = new mongoose.Schema({ export const bundleSchema = new mongoose.Schema({ merkleRoot: { - type: String, - required: true, - unique: true, + type: String, + required: true, + unique: true, + }, + preMerkleRoot: { + type: String, + default: '', + }, + postMerkleRoot: { + type: String, + default: '', }, taskId: { - type: String, - default: '', + type: String, + default: '', }, withdrawArray: [{ address: { type: String, default:'' }, @@ -140,6 +148,10 @@ export const bundleSchema = new mongoose.Schema({ type: String, default: '', }, + bundleIndex: { + type: Number, + default: 0, + } }); export const randSchema = new mongoose.Schema({ diff --git a/ts/src/run.ts b/ts/src/run.ts index 0a12864..ce14565 100644 --- a/ts/src/run.ts +++ b/ts/src/run.ts @@ -1,6 +1,6 @@ import { Service } from "./service.js"; -const service = new Service(()=>{return;}); +const service = new Service(()=>{return;}, ()=>{return;}); service.initialize(); service.serve(); diff --git a/ts/src/service.ts b/ts/src/service.ts index 9bd1405..92573af 100644 --- a/ts/src/service.ts +++ b/ts/src/service.ts @@ -50,13 +50,6 @@ if (process.env.TASKID) { let transactions_witness = new Array(); -let merkle_root = new BigUint64Array([ - 14789582351289948625n, - 10919489180071018470n, - 10309858136294505219n, - 2839580074036780766n, - ]); - let snapshot = JSON.parse("{}"); function randByte() { @@ -85,17 +78,49 @@ async function generateRandomSeed() { export class Service { worker: null | Worker; queue: null | Queue; - txCallback: (arg: TxWitness) => void; - constructor(cb: (arg: TxWitness) => void) { + txCallback: (arg: TxWitness, events: BigUint64Array) => void; + txBatched: (arg: TxWitness, task_id: string) => void; + merkleRoot: BigUint64Array; + bundleIndex: number; + preMerkleRoot: BigUint64Array | null; + + constructor(cb: (arg: TxWitness) => void, txBatched: (arg: TxWitness, task_id: string)=> void) { this.worker = null; this.queue = null; this.txCallback = cb; + this.txBatched = txBatched; + this.merkleRoot = new BigUint64Array([ + 14789582351289948625n, + 10919489180071018470n, + 10309858136294505219n, + 2839580074036780766n, + ]); + this.bundleIndex = 0; + this.preMerkleRoot = null; + } + + async findBundleIndex(merkleRoot: BigUint64Array) { + try { + const prevBundle = await modelBundle.findOne( + { + merkleRoot: merkleRootToBeHexString(merkleRoot), + }, + ); + if (prevBundle != null) { + return prevBundle!.bundleIndex; + } else { + throw Error("BundleNotFound"); + } + } catch (e) { + console.log(`fatal: bundle for ${merkleRoot} is not recorded`); + process.exit(); + } } - async install_transactions(tx: TxWitness, jobid: string | undefined) { + async install_transactions(tx: TxWitness, jobid: string | undefined, events: BigUint64Array) { console.log("installing transaction into rollup ..."); transactions_witness.push(tx); - this.txCallback(tx); + this.txCallback(tx, events); snapshot = JSON.parse(application.snapshot()); console.log("transaction installed, rollup pool length is:", transactions_witness.length); try { @@ -109,40 +134,93 @@ export class Service { let txdata = application.finalize(); console.log("txdata is:", txdata); try { + let task_id = null; if (deploymode) { - let task_id = await submitProofWithRetry(merkle_root, transactions_witness, txdata); + task_id = await submitProofWithRetry(this.merkleRoot, transactions_witness, txdata); console.log("proving task submitted at:", task_id); - console.log("tracking task in db ...", merkle_root); + console.log("tracking task in db current ...", merkleRootToBeHexString(this.merkleRoot)); + let preMerkleRootStr = ""; + if (this.preMerkleRoot != null) { + preMerkleRootStr = merkleRootToBeHexString(this.preMerkleRoot!) + }; + + if (this.preMerkleRoot != null) { + console.log("update merkle chain ...", merkleRootToBeHexString(this.preMerkleRoot)); + try { + const prevBundle = await modelBundle.findOneAndUpdate( + { + merkleRoot: merkleRootToBeHexString(this.preMerkleRoot), + }, + { + taskId: task_id, + postMerkleRoot: merkleRootToBeHexString(this.merkleRoot), + }, + {} + ); + if (this.bundleIndex != prevBundle!.bundleIndex) { + console.log(`fatal: bundleIndex does not match: ${this.bundleIndex}, ${prevBundle!.bundleIndex}`); + } + console.log("merkle chain prev is", prevBundle); + } catch (e) { + console.log(`fatal: can not find bundle for previous MerkleRoot: ${merkleRootToBeHexString(this.preMerkleRoot)}`); + //throw e + } + } + + this.bundleIndex += 1; + + console.log("add transaction bundle:", this.bundleIndex); const bundleRecord = new modelBundle({ - merkleRoot: merkleRootToBeHexString(merkle_root), + merkleRoot: merkleRootToBeHexString(this.merkleRoot), + preMerkleRoot: preMerkleRootStr, taskId: task_id, + bundleIndeX: this.bundleIndex, }); + try { await bundleRecord.save(); - console.log(`task recorded with key: ${merkleRootToBeHexString(merkle_root)}`); + console.log(`task recorded with key: ${merkleRootToBeHexString(this.merkleRoot)}`); } catch (e) { - let record = await modelBundle.find({ - merkleRoot: merkleRootToBeHexString(merkle_root), - }); + let record = await modelBundle.findOneAndUpdate( + { + merkleRoot: merkleRootToBeHexString(this.merkleRoot), + }, + { + taskId: task_id, + postMerkleRoot: "", + preMerkleRoot: preMerkleRootStr, + bundleIndex: this.bundleIndex, + }, + {} + ); console.log("fatal: conflict db merkle"); + // TODO: do we need to trim the corrputed branch? console.log(record); //throw e } + // update the merkel chain if necessary + } + for (let tx of transactions_witness) { + this.txBatched(tx, task_id); } + + // clear witness queue and set preMerkleRoot transactions_witness = new Array(); + this.preMerkleRoot = this.merkleRoot; + // need to update merkle_root as the input of next proof - merkle_root = application.query_root(); + this.merkleRoot = application.query_root(); // reset application here - console.log("restore root:", merkle_root); + console.log("restore root:", this.merkleRoot); await (initApplication as any)(bootstrap); - application.initialize(merkle_root); + application.initialize(this.merkleRoot); } catch (e) { console.log(e); process.exit(1); // this should never happen and we stop the whole process } } let current_merkle_root = application.query_root(); - console.log("last root:", current_merkle_root); + console.log("transaction installed with last root:", current_merkle_root); } async initialize() { @@ -193,8 +271,8 @@ export class Service { if (migrate) { if (remote) {throw Error("Can't migrate in remote mode");} - merkle_root = await getMerkleArray(); - console.log("Migrate: updated merkle root", merkle_root); + this.merkleRoot = await getMerkleArray(); + console.log("Migrate: updated merkle root", this.merkleRoot); } //initialize merkle_root based on the latest task if (remote) { @@ -214,13 +292,14 @@ export class Service { console.log("latest task", task?.instances); if (task) { const instances = ZkWasmUtil.bytesToBN(task?.instances); - merkle_root = new BigUint64Array([ + this.merkleRoot = new BigUint64Array([ BigInt(instances[4].toString()), BigInt(instances[5].toString()), BigInt(instances[6].toString()), BigInt(instances[7].toString()), ]); - console.log("updated merkle root", merkle_root); + this.bundleIndex = await this.findBundleIndex(this.merkleRoot); + console.log("updated merkle root", this.merkleRoot, this.bundleIndex); } } @@ -234,10 +313,10 @@ export class Service { this.queue = myQueue; console.log("initialize application merkle db ..."); - application.initialize(merkle_root); + application.initialize(this.merkleRoot); // update the merkle root variable - merkle_root = application.query_root(); + this.merkleRoot = application.query_root(); // Automatically add a job to the queue every few seconds if (application.autotick()) { @@ -271,7 +350,7 @@ export class Service { let u64array = signature_to_u64array(signature); application.verify_tx_signature(u64array); application.handle_tx(u64array); - await this.install_transactions(signature, job.id); + await this.install_transactions(signature, job.id, new BigUint64Array([])); } catch (error) { console.log("fatal: handling auto tick error, process will terminate.", error); process.exit(1); @@ -283,10 +362,11 @@ export class Service { let u64array = signature_to_u64array(signature); console.log("tx data", signature); application.verify_tx_signature(u64array); - let error = application.handle_tx(u64array); - if (error == 0) { + let txResult = application.handle_tx(u64array); + let errorCode = txResult[0]; + if (errorCode == 0n) { // make sure install transaction will succeed - await this.install_transactions(signature, job.id); + await this.install_transactions(signature, job.id, txResult); try { const jobRecord = new modelJob({ jobId: signature.sigx, @@ -299,7 +379,7 @@ export class Service { throw e } } else { - let errorMsg = application.decode_error(error); + let errorMsg = application.decode_error(Number(errorCode)); throw Error(errorMsg) } console.log("done"); @@ -418,7 +498,6 @@ export class Service { } - function signature_to_u64array(value: any) { const msg = new LeHexBN(value.msg).toU64Array(value.msg.length/16); const pkx = new LeHexBN(value.pkx).toU64Array();