Skip to content

Commit

Permalink
offchain jobs working
Browse files Browse the repository at this point in the history
  • Loading branch information
MaanavKhaitan committed Oct 15, 2024
1 parent 141d4b9 commit 81d4ef6
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 43 deletions.
2 changes: 1 addition & 1 deletion contracts/script/Deployer.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ contract Deployer is Script, Utils {

// Set ELF paths
jobManager.setElfPath(
bytes32(0x8a41c073bee555bc945c763ab860d8f9820fdaeefdb8c2f73186663f0c3ec947),
bytes32(0xe4d0f22c6442afa293bf7f102dcd1954c1902936e67c756051a1d35e5765a953),
"target/riscv-guest/riscv32im-risc0-zkvm-elf/release/square-root"
);

Expand Down
2 changes: 1 addition & 1 deletion contracts/src/ProgramID.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pragma solidity ^0.8.13;

library ProgramID {
bytes32 public constant SQUARE_ROOT_ID = bytes32(0x8a41c073bee555bc945c763ab860d8f9820fdaeefdb8c2f73186663f0c3ec947);
bytes32 public constant SQUARE_ROOT_ID = bytes32(0xe4d0f22c6442afa293bf7f102dcd1954c1902936e67c756051a1d35e5765a953);
}
16 changes: 8 additions & 8 deletions contracts/src/coprocessor/JobManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ contract JobManager is

string memory elfPath = getElfPath(programID);
// This would normally be a separate call by relayer, but for tests we call it here
(bytes memory resultWithMetadata, bytes memory signature) = execute(elfPath, onchainInput, jobID, maxCycles);
(bytes memory resultWithMetadata, bytes memory signature) = executeOnchainJob(elfPath, onchainInput, jobID, maxCycles);(elfPath, onchainInput, jobID, maxCycles);
submitResult(resultWithMetadata, signature);

return jobID;
Expand All @@ -104,7 +104,7 @@ contract JobManager is
}

function requestOffchainJob(OffchainJobRequest calldata request, bytes calldata offchainInput, bytes calldata state, string calldata privateKey) public {
(bytes memory resultWithMetadata, bytes memory resultSignature, bytes memory jobRequest, bytes memory requestSignature) = executeOffchainJob(request, privateKey);
(bytes memory resultWithMetadata, bytes memory resultSignature, bytes memory jobRequest, bytes memory requestSignature) = executeOffchainJob(request, offchainInput, state, privateKey);

submitResultForOffchainJob(resultWithMetadata, resultSignature, jobRequest, requestSignature);
}
Expand Down Expand Up @@ -198,7 +198,7 @@ contract JobManager is
Consumer(job.consumer).receiveResult(jobID, result);
}

function execute(string memory elfPath, bytes memory input, bytes32 jobID, uint64 maxCycles) internal returns (bytes memory, bytes memory) {
function executeOnchainJob(string memory elfPath, bytes memory onchainInput, bytes32 jobID, uint64 maxCycles) internal returns (bytes memory, bytes memory) {
string[] memory imageRunnerInput = new string[](12);
uint256 i = 0;
imageRunnerInput[i++] = "cargo";
Expand All @@ -208,17 +208,17 @@ contract JobManager is
imageRunnerInput[i++] = "--bin";
imageRunnerInput[i++] = "zkvm-utils";
imageRunnerInput[i++] = "-q";
imageRunnerInput[i++] = "execute";
imageRunnerInput[i++] = "execute-onchain-job";
imageRunnerInput[i++] = elfPath;
imageRunnerInput[i++] = input.toHexString();
imageRunnerInput[i++] = onchainInput.toHexString();
imageRunnerInput[i++] = jobID.toHexString();
imageRunnerInput[i++] = maxCycles.toString();
return abi.decode(vm.ffi(imageRunnerInput), (bytes, bytes));
}

function executeOffchainJob(OffchainJobRequest calldata request, bytes calldata offchainInput, bytes calldata state, string calldata privateKey) internal returns (bytes memory, bytes memory, bytes memory, bytes memory) {
string memory elfPath = getElfPath(request.programID);
string[] memory imageRunnerInput = new string[](14);
string[] memory imageRunnerInput = new string[](16);
uint256 i = 0;
imageRunnerInput[i++] = "cargo";
imageRunnerInput[i++] = "run";
Expand All @@ -230,8 +230,8 @@ contract JobManager is
imageRunnerInput[i++] = "execute-offchain-job";
imageRunnerInput[i++] = elfPath;
imageRunnerInput[i++] = request.onchainInput.toHexString();
imageRunnerInput[i++] = request.offchainInputHash.toHexString();
imageRunnerInput[i++] = request.stateHash.toHexString();
imageRunnerInput[i++] = offchainInput.toHexString();
imageRunnerInput[i++] = state.toHexString();
imageRunnerInput[i++] = request.maxCycles.toString();
imageRunnerInput[i++] = request.consumer.toHexString();
imageRunnerInput[i++] = request.nonce.toString();
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/coprocessor/OffchainRequester.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ abstract contract OffchainRequester {

// EIP-1271
function isValidSignature(bytes32 hash, bytes memory signature) public virtual view returns (bytes4);
}
}
57 changes: 32 additions & 25 deletions contracts/test/SquareRootConsumer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";
import {JobManager} from "../src/coprocessor/JobManager.sol";
import {IJobManager} from "../src/coprocessor/IJobManager.sol";
import {Consumer} from "../src/coprocessor/Consumer.sol";
import {SquareRootConsumer} from "../src/SquareRootConsumer.sol";
import {Deployer} from "../script/Deployer.s.sol";
Expand Down Expand Up @@ -38,36 +39,42 @@ contract SquareRootConsumerTest is Test, Deployer {
assertEq(consumer.getJobResult(DEFAULT_JOB_ID), abi.encode(9, 3));
}

// function test_Consumer_RequestOffchainJob() public {
// // Request offchain job from default offchain user
// jobManager.requestOffchainJob(
// ProgramID.SQUARE_ROOT_ID, // Program ID
// abi.encode(9), // Onchain input
// abi.encode(9), // Offchain input
// abi.encode(0), // State
// DEFAULT_MAX_CYCLES, // Max cycles
// address(consumer), // Consumer address to send result to
// DEFAULT_NONCE, // Nonce (should be unique for each offchain job request)
// DEFAULT_OFFCHAIN_SIGNER_PRIVATE_KEY // Private key of offchain request signer
// );
function test_Consumer_RequestOffchainJob() public {
// Request offchain job from default offchain user
IJobManager.OffchainJobRequest memory request = IJobManager.OffchainJobRequest(
DEFAULT_NONCE, // Nonce (should be unique for each offchain job request)
DEFAULT_MAX_CYCLES, // Max cycles
address(consumer), // Consumer address to send result to
ProgramID.SQUARE_ROOT_ID, // Program ID
abi.encode(9), // Onchain input
keccak256(""), // Offchain input
keccak256("") // State
);

// JobManager.JobMetadata memory jobMetadata = jobManager.getJobMetadata(DEFAULT_JOB_ID);
// assertEq(jobMetadata.programID, ProgramID.SQUARE_ROOT_ID);
jobManager.requestOffchainJob(
request,
"", // Offchain input
"", // State
DEFAULT_OFFCHAIN_SIGNER_PRIVATE_KEY // Private key of offchain request signer
);

// // Job status is COMPLETED since createJob in JobManager calls
// // submitResult in this Foundry template
// assertEq(jobMetadata.status, 3);
JobManager.JobMetadata memory jobMetadata = jobManager.getJobMetadata(DEFAULT_JOB_ID);
assertEq(jobMetadata.programID, ProgramID.SQUARE_ROOT_ID);

// // Check that state was correctly updated in Consumer contract
// assertEq(consumer.getSquareRoot(9), 3);
// assertEq(consumer.getJobResult(DEFAULT_JOB_ID), abi.encode(9, 3));
// Job status is COMPLETED since createJob in JobManager calls
// submitResult in this Foundry template
assertEq(jobMetadata.status, 3);

// // Check inputs are set correctly in consumer
// assertEq(consumer.getOnchainInputForJob(DEFAULT_JOB_ID), abi.encode(9));
// Check that state was correctly updated in Consumer contract
assertEq(consumer.getSquareRoot(9), 3);
assertEq(consumer.getJobResult(DEFAULT_JOB_ID), abi.encode(9, 3));

// // Check that nonce is correctly updated in Consumer contract
// assertEq(consumer.getNextNonce(), DEFAULT_NONCE + 1);
// }
// Check inputs are set correctly in consumer
assertEq(consumer.getOnchainInputForJob(DEFAULT_JOB_ID), abi.encode(9));

// Check that nonce is correctly updated in Consumer contract
assertEq(consumer.getNextNonce(), DEFAULT_NONCE + 1);
}

function testRevertWhen_Consumer_ReceiveResultUnauthorized() public {
test_Consumer_RequestJob();
Expand Down
6 changes: 4 additions & 2 deletions programs/app/src/square_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ type NumberWithSquareRoot = sol! {

fn main() {
// Read the input data for this application.
let mut input_bytes = Vec::<u8>::new();
env::stdin().read_to_end(&mut input_bytes).unwrap();
let onchain_input_len: u32 = env::read();
let mut input_bytes = vec![0; onchain_input_len as usize];
env::read_slice(&mut input_bytes);

// Decode and parse the input
let number = <U256>::abi_decode(&input_bytes, true).unwrap();

Expand Down
23 changes: 18 additions & 5 deletions zkvm-utils/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type K256LocalSigner = LocalSigner<SigningKey>;
#[clap(author, version, about, long_about = None)]
enum Command {
/// Execute the RISC-V ELF binary (returns the signed result)
Execute {
ExecuteOnchainJob {
/// The guest binary path
guest_binary_path: String,

Expand Down Expand Up @@ -68,7 +68,7 @@ pub async fn main() -> Result<()> {
let signer = create_signer(secret)?;

match Command::parse() {
Command::Execute { guest_binary_path, onchain_input, job_id, max_cycles } => {
Command::ExecuteOnchainJob { guest_binary_path, onchain_input, job_id, max_cycles } => {
let job_id_decoded: [u8; 32] =
hex::decode(job_id.strip_prefix("0x").unwrap_or(&job_id))?.try_into().unwrap();
execute_onchain_job_ffi(
Expand Down Expand Up @@ -193,8 +193,9 @@ async fn execute_offchain_job_ffi(

/// Generates journal for the given elf and input, for an onchain job.
fn execute_onchain_job(elf: &[u8], onchain_input: &[u8], max_cycles: u64) -> Result<Vec<u8>> {
// TODO (Maanav): Actually write onchain_input_len and then onchain_input etc. and modify the zkVM program to match this
let env = ExecutorEnv::builder().session_limit(Some(max_cycles)).write_slice(onchain_input).build()?;
let onchain_input_len = onchain_input.len() as u32;

let env = ExecutorEnv::builder().session_limit(Some(max_cycles)).write(&onchain_input_len)?.write_slice(onchain_input).build()?;

let prover = LocalProver::new("locals only");
let prove_info = prover.execute(env, elf)?;
Expand All @@ -204,7 +205,19 @@ fn execute_onchain_job(elf: &[u8], onchain_input: &[u8], max_cycles: u64) -> Res

/// Generates journal for the given elf and input, for an offchain job.
fn execute_offchain_job(elf: &[u8], onchain_input: &[u8], offchain_input: &[u8], state: &[u8], max_cycles: u64) -> Result<Vec<u8>> {
let env = ExecutorEnv::builder().session_limit(Some(max_cycles)).write_slice(onchain_input).write_slice(offchain_input).write_slice(state).build()?;
let onchain_input_len = onchain_input.len() as u32;
let offchain_input_len = offchain_input.len() as u32;
let state_len = state.len() as u32;

let env = ExecutorEnv::builder()
.session_limit(Some(max_cycles))
.write(&onchain_input_len)?
.write_slice(onchain_input)
.write(&offchain_input_len)?
.write_slice(offchain_input)
.write(&state_len)?
.write_slice(state)
.build()?;

let prover = LocalProver::new("locals only");
let prove_info = prover.execute(env, elf)?;
Expand Down

0 comments on commit 81d4ef6

Please sign in to comment.