-
Notifications
You must be signed in to change notification settings - Fork 134
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
save progress, demo lightnet fetch error
- Loading branch information
1 parent
7e49a4e
commit f4fbc4c
Showing
9 changed files
with
464 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
src/lib/mina/actions/offchain-contract-tests/LilyPadContract.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// offchain state object, this is where the data is actually exposed | ||
import { | ||
AccountUpdate, | ||
Experimental, | ||
fetchAccount, | ||
Lightnet, | ||
method, | ||
Mina, | ||
PrivateKey, | ||
PublicKey, | ||
SmartContract, | ||
state, | ||
} from 'o1js'; | ||
import OffchainState = Experimental.OffchainState; | ||
|
||
export const LilypadState = OffchainState( | ||
{ currentOccupant: OffchainState.Field(PublicKey) }, | ||
{ logTotalCapacity: 4, maxActionsPerUpdate: 2, maxActionsPerProof: 2 } | ||
); | ||
export class LilyPadStateProof extends LilypadState.Proof {} | ||
|
||
export class OffchainStorageLilyPad extends SmartContract { | ||
@state(Experimental.OffchainState.Commitments) | ||
offchainStateCommitments = LilypadState.emptyCommitments(); | ||
offchainState = LilypadState.init(this); | ||
|
||
@method | ||
async visit() { | ||
const senderPublicKey = this.sender.getAndRequireSignature(); | ||
const currentOccupantOption = await this.offchainState.fields.currentOccupant.get(); | ||
this.offchainState.fields.currentOccupant.update({ | ||
from: currentOccupantOption, | ||
to: senderPublicKey, | ||
}); | ||
} | ||
|
||
@method | ||
async settle(proof: LilyPadStateProof) { | ||
await this.offchainState.settle(proof); | ||
} | ||
} |
135 changes: 135 additions & 0 deletions
135
src/lib/mina/actions/offchain-contract-tests/actionStateErrorDemo.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
// offchain state object, this is where the data is actually exposed | ||
import { | ||
AccountUpdate, | ||
fetchAccount, | ||
Lightnet, | ||
Mina, | ||
PrivateKey, | ||
PublicKey, | ||
} from 'o1js'; | ||
import {LilypadState, OffchainStorageLilyPad} from "./LilyPadContract.js"; | ||
|
||
/** | ||
* Set this address before running the fetch state process | ||
* */ | ||
let deployedZkAppAddress = ''; | ||
|
||
// configure lightnet and retrieve keys | ||
Mina.setActiveInstance( | ||
Mina.Network({ | ||
mina: 'http://127.0.0.1:8080/graphql', | ||
archive: 'http://127.0.0.1:8282', | ||
lightnetAccountManager: 'http://127.0.0.1:8181', | ||
}) | ||
); | ||
const senderPrivateKey = (await Lightnet.acquireKeyPair()).privateKey; | ||
|
||
// compile zkprograms | ||
await LilypadState.compile(); | ||
await OffchainStorageLilyPad.compile(); | ||
|
||
let zkApp: OffchainStorageLilyPad; | ||
if (!deployedZkAppAddress) { | ||
// deploy OffchainStorageLilyPad if it is not already deployed | ||
const zkAppPrivateKey: PrivateKey = PrivateKey.random(); | ||
zkApp = new OffchainStorageLilyPad(zkAppPrivateKey.toPublicKey()); | ||
const deployTx = await Mina.transaction( | ||
{ fee: 1e9, sender: senderPrivateKey.toPublicKey() }, | ||
async () => { | ||
AccountUpdate.fundNewAccount(senderPrivateKey.toPublicKey()); | ||
await zkApp.deploy(); | ||
} | ||
); | ||
await deployTx.prove(); | ||
const deployTxPromise = await deployTx | ||
.sign([senderPrivateKey, zkAppPrivateKey]) | ||
.send(); | ||
await deployTxPromise.wait(); | ||
console.log( | ||
`Deployed OffchainStorageLilyPad to address ${zkAppPrivateKey | ||
.toPublicKey() | ||
.toBase58()}` | ||
); | ||
} else { | ||
// init OffchainStorageLilyPad at deployedZkAppAddress | ||
zkApp = new OffchainStorageLilyPad( | ||
PublicKey.fromBase58(deployedZkAppAddress) | ||
); | ||
await fetchAccount({ publicKey: zkApp.address }); | ||
console.log( | ||
`Interacting with deployed OffchainStorageLilyPad at address ${deployedZkAppAddress}` | ||
); | ||
} | ||
|
||
zkApp.offchainState.setContractInstance(zkApp); | ||
console.log( | ||
'fetchAccount', | ||
( | ||
await fetchAccount({ publicKey: zkApp.address }) | ||
)?.account?.publicKey.toBase58() | ||
); | ||
console.log('OffchainStorageLilyPad starting state:'); | ||
await logAppState(); | ||
// stop settle process here, copy address from logs into deployedZkAppAddress, trigger fetch process and allow to run up to this point | ||
// after fetch process hits this point, execute settle process to run state updates and settlement | ||
// after state updates and settlement, execute fetch process | ||
await zkApp.offchainState.fetchInternalState(); | ||
await logAppState(); | ||
|
||
// call visit on the contract which will dispatch state updates but will not directly update the OffchainStateInstance | ||
await (await visit(senderPrivateKey)).wait(); | ||
console.log('Executed visits, app state should be unchanged: '); | ||
|
||
// Create a settlement proof | ||
console.log('\nSettling visits on chain'); | ||
const settlementProof = await zkApp.offchainState.createSettlementProof(); | ||
// await logAppState(); // todo: logging the state here gives a root mismatch error because the internal state map is updated by createSettlementProof before the on chain value is changed | ||
const settleTx = await Mina.transaction( | ||
{ fee: 1e9, sender: senderPrivateKey.toPublicKey() }, | ||
async () => { | ||
await zkApp.settle(settlementProof); | ||
} | ||
); | ||
await settleTx.prove(); | ||
const settleTxPromise = await settleTx.sign([senderPrivateKey]).send(); | ||
await settleTxPromise.wait(); | ||
|
||
console.log( | ||
'Executed OffchainStorageLilyPad.settle(), on chain state has been updated with the effects of the dispatched visits: ' | ||
); | ||
|
||
// must call fetchAccount after executing settle transaction in order to retrieve the most up to date on chain commitments | ||
await logAppState(); | ||
|
||
|
||
/************************************************************************** | ||
* Helpers | ||
***************************************************************************/ | ||
async function visit(sender: PrivateKey) { | ||
const tx = await Mina.transaction( | ||
{ fee: 1e9, sender: senderPrivateKey.toPublicKey() }, | ||
async () => { | ||
await zkApp.visit(); | ||
} | ||
); | ||
await tx.prove(); | ||
const txPromise = await tx.sign([sender]).send(); | ||
|
||
console.log( | ||
`${sender.toPublicKey().toBase58()} called OffchainStorageLilyPad.visit()` | ||
); | ||
return txPromise; | ||
} | ||
|
||
async function logAppState() { | ||
await fetchAccount({ publicKey: zkApp.address }); | ||
const onchainStateCommitment = zkApp.offchainStateCommitments.get(); | ||
|
||
console.log( | ||
`${process.pid} onchainStateCommitment.root=${onchainStateCommitment.root.toString()} ` | ||
); | ||
|
||
const currentOccupant = | ||
await zkApp.offchainState.fields.currentOccupant.get(); | ||
console.log(`currentOccupant: ${currentOccupant.value.toBase58()}`); | ||
} |
54 changes: 54 additions & 0 deletions
54
src/lib/mina/actions/offchain-contract-tests/fetch-state-process.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { Message } from './multi-thread-lightnet.js'; | ||
import { fetchAccount, Mina, PublicKey } from 'o1js'; | ||
import {LilypadState, OffchainStorageLilyPad} from "./LilyPadContract.js"; | ||
|
||
process.send?.(`Starting fetch state process: ${process.pid}`); | ||
/** | ||
* Configure lightnet and retrieve signing keys | ||
* */ | ||
const network = Mina.Network({ | ||
mina: 'http://localhost:8080/graphql', | ||
archive: 'http://127.0.0.1:8282', | ||
lightnetAccountManager: 'http://localhost:8181', | ||
}); | ||
Mina.setActiveInstance(network); | ||
let zkApp: OffchainStorageLilyPad; | ||
|
||
await LilypadState.compile(); | ||
await OffchainStorageLilyPad.compile(); | ||
// notify main process that this process is ready to begin work | ||
process.send?.({ type: 'READY' }); | ||
|
||
process.on('message', async (msg: Message) => { | ||
console.log( | ||
`Fetch state process received message from root: ${JSON.stringify(msg)}` | ||
); | ||
|
||
/** | ||
* Compile offchain state zkprogram and contract, deploy contract | ||
* */ | ||
if (msg.type === 'DEPLOYED') { | ||
// account = PublicKey.fromBase58(msg.account); | ||
zkApp = new OffchainStorageLilyPad(PublicKey.fromBase58(msg.address)); | ||
zkApp.offchainState.setContractInstance(zkApp); | ||
await logState(); | ||
process.send?.({ type: 'INSTANTIATED' }); | ||
} else if (msg.type === 'FETCH_STATE') { | ||
// todo: expect root mismatch | ||
await logState(); | ||
|
||
// todo: this is erroring on the other test | ||
await zkApp.offchainState.fetchInternalState(); | ||
await logState(); | ||
} | ||
}); | ||
|
||
async function logState() { | ||
await fetchAccount({ publicKey: zkApp.address }); | ||
const root = zkApp.offchainStateCommitments.get().root.toString(); | ||
const currentOccupant = ( | ||
await zkApp.offchainState.fields.currentOccupant.get() | ||
).value.toString(); | ||
const message = `offchainState: (currentOccupant => ${currentOccupant}),(root => ${root})`; | ||
process.send?.(message); | ||
} |
98 changes: 98 additions & 0 deletions
98
src/lib/mina/actions/offchain-contract-tests/multi-thread-lightnet.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import {fork} from 'child_process'; | ||
|
||
/** | ||
* Multi process offchain state synchronization test | ||
* | ||
* Launch two processes - a settling process and a fetch state process | ||
* Settling process deploys the contract | ||
* Offchain state process instantiates the contract at the deployed address | ||
* Settling process updates the state and calls settle | ||
* Offchain state process reads the state and gets a root mismatch error | ||
* Offchain state process calls fetchInternalState, reads the state, and sees the updated state | ||
* */ | ||
|
||
export type Message = | ||
| { type: 'READY' } | ||
| { type: 'DEPLOY' } | ||
| { type: 'DEPLOYED'; address: string; account: string } | ||
| { type: 'INSTANTIATED' } | ||
| { type: 'UPDATE_STATE' } | ||
| { type: 'STATE_UPDATED' } | ||
| { type: 'FETCH_STATE' }; | ||
|
||
let settlingReady = false; | ||
let fetchStateReady = false; | ||
|
||
// Launch the processes | ||
const settlingProcess = fork( | ||
'dist/node/lib/mina/actions/offchain-contract-tests/settling-process.js' | ||
); | ||
const fetchStateProcess = fork( | ||
'dist/node/lib/mina/actions/offchain-contract-tests/fetch-state-process.js' | ||
); | ||
|
||
// Listen for messages from the child processes | ||
settlingProcess.on('message', (msg: Message) => { | ||
console.log( | ||
`Settling process dispatched message to root: ${JSON.stringify(msg)}` | ||
); | ||
|
||
if (msg.type === 'READY') { | ||
settlingReady = true; | ||
// both processes are ready, dispatch DEPLOY to settling process to deploy contract | ||
if (settlingReady && fetchStateReady) { | ||
console.log('Both processes are ready. Starting the test...'); | ||
settlingProcess.send({ type: 'DEPLOY' }); | ||
} | ||
} else if (msg.type === 'DEPLOYED') { | ||
// settling process finished deploying contract, tell fetchState process to instantiate the contract and offchain state | ||
fetchStateProcess.send({ | ||
type: 'DEPLOYED', | ||
address: msg.address, | ||
account: msg.account, | ||
}); | ||
} else if (msg.type === 'STATE_UPDATED') { | ||
// settle state process has updated and settled state, tell fetch state process to try to retrieve the new state which lets us test synchronization | ||
fetchStateProcess.send({ type: 'FETCH_STATE' }); | ||
} | ||
}); | ||
fetchStateProcess.on('message', (msg: Message) => { | ||
console.log( | ||
`Fetch state process dispatched message to root: ${JSON.stringify(msg)}` | ||
); | ||
|
||
if (msg.type === 'READY') { | ||
fetchStateReady = true; | ||
// both processes are ready, dispatch DEPLOY to settling process to deploy contract | ||
if (settlingReady && fetchStateReady) { | ||
console.log('Both processes are ready. Starting the test...'); | ||
settlingProcess.send({ type: 'DEPLOY' }); | ||
} | ||
} else if (msg.type === 'INSTANTIATED') { | ||
// fetch state process instantiated contract, tell settle state process to update the state and settle it | ||
settlingProcess.send({ type: 'UPDATE_STATE' }); | ||
} | ||
}); | ||
|
||
function cleanup() { | ||
settlingProcess.kill(); | ||
fetchStateProcess.kill(); | ||
console.log('Child processes terminated.'); | ||
} | ||
|
||
function handleProcessEvents(processName: string, processInstance: any) { | ||
processInstance.on('error', (err: Error) => { | ||
console.error(`${processName} threw an error: ${err.message}`); | ||
}); | ||
|
||
processInstance.on('exit', (code: number) => { | ||
if (code !== 0) { | ||
console.error(`${processName} exited with code ${code}`); | ||
} else { | ||
console.log(`${processName} exited successfully.`); | ||
} | ||
}); | ||
} | ||
|
||
handleProcessEvents('Settling process', settlingProcess); | ||
handleProcessEvents('Fetch state process', fetchStateProcess); |
Oops, something went wrong.