Compute task status 3 during "discovery test"

I am working on a project that’s very similar to enigma-secret-access-control and get the following output after running “discovery test”.

root@baremetal01:/home/timecapsule# discovery test
Smart Contracts:Compiling Secret Contract “timecapsule”…
Finished release [optimized] target(s) in 0.02s
Using network ‘development’.

Compiling your contracts…

Everything is up to date, there is nothing to compile.

secp256k1 unavailable, reverting to browser version
Deploying Secret Contract “timecapsule.wasm”…
Completed. Final Task Status is 2
Secret Contract “timecapsule.wasm” deployed at Enigma address: 0x3f130d8f687e147e47d26cb0aee43528511ede5d923da8bf0daedd72586f6540

Contract: Timecapsule
✓ should execute compute task to add secret #1 (185ms)
✓ should get the pending task
Completed. Final Task Status is 2
✓ should get the confirmed task (2028ms)
✓ should execute compute task to add secret #2 (161ms)
✓ should get the pending task
Completed. Final Task Status is 2
✓ should get the confirmed task (2027ms)
✓ should execute compute task to get secret (119ms)
✓ should get the pending task
1) should get the confirmed task

Events emitted during test:
---------------------------


---------------------------
2) should get the result and verify the computation is correct
> No events were emitted

8 passing (5m)
2 failing

  1. Contract: Timecapsule
    should get the confirmed task:
    Error: Timeout of 300000ms exceeded. For async tests and hooks, ensure “done()” is called; if returning a Promise, ensure it resolves. (/home/timecapsule/test/test_timecapsule.js)
    at listOnTimeout (internal/timers.js:535:17)
    at processTimers (internal/timers.js:479:7)

  2. Contract: Timecapsule
    should get the result and verify the computation is correct:

    AssertionError: expected ‘FAILED’ to equal ‘SUCCESS’

    • expected - actual

    -FAILED
    +SUCCESS

    at Context.it (test/test_timecapsule.js:162:31)
    at processTicksAndRejections (internal/process/task_queues.js:86:5)

Below is the output from my “discovery start” process for the task that’s failing:

p2p_1 | [Mon Jan 20 2020 01:32:31 GMT+0000 (Coordinated Universal Time)] DEBUG [VERIFY_NEW_TASK] successful verification of task e3373ef16992060636c7888820dc9fab28338cf8b824772c1bbb6ab17c77b868
p2p_1 | [Mon Jan 20 2020 01:32:31 GMT+0000 (Coordinated Universal Time)] DEBUG [onVerifyTask] saved to db task e3373ef16992060636c7888820dc9fab28338cf8b824772c1bbb6ab17c77b868
contract_1 | eth_getBlockByNumber
contract_1 | eth_blockNumber
km_1 | [? ] Blocks @ previous: 159, current: 165, next: 169 [? ]
km_1 | [? ] Epoch still active [? ]
contract_1 | eth_getBlockByNumber
contract_1 | eth_getBlockByNumber
contract_1 | eth_getBlockByNumber
contract_1 | eth_getBlockByNumber
contract_1 | eth_getBlockByNumber
contract_1 | eth_call
core_1 | Error in execution of smart contract function: Error in execution of WASM code: unreachable
core_1 | 01:32:33 [INFO] compute_task() => Ok(FailedTask { result: FailedTask { output: “b8fc4394613db48efaa347328b960b72420d1c3d81296c0c3ed1f54c51782d075b4f24b432d06df5dca2dcad9c12a80297888da010083cf041e9f37f916a8970e5a36b6e66f5c9d2”, used_gas: 93275, signature: “ff5929c39e6bb3d97108800b6c3c0129ada7dcc8a454ef1417557477ee054fbe18bc13c6ea7be8a7a6f4cdc6817184e37f67234ff9a02122eda69ababf84450f1c” } })
p2p_1 | [Mon Jan 20 2020 01:32:33 GMT+0000 (Coordinated Universal Time)] DEBUG received failed result
p2p_1 | [Mon Jan 20 2020 01:32:33 GMT+0000 (Coordinated Universal Time)] INFO [TASK_FINISHED] status = [FAILED] id: e3373ef16992060636c7888820dc9fab28338cf8b824772c1bbb6ab17c77b868
p2p_1 | [Mon Jan 20 2020 01:32:33 GMT+0000 (Coordinated Universal Time)] DEBUG published [/taskresults/0.1]
contract_1 | eth_sendTransaction
contract_1 |
contract_1 | Transaction: 0xde67c279652a6a8263e7fb6e34c7e99ac51e7df377b0fbb18d3ae01e2c8e99ee
contract_1 | Gas usage: 233890
contract_1 | Block Number: 166
contract_1 | Block Time: Mon Jan 20 2020 01:32:33 GMT+0000 (Coordinated Universal Time)
contract_1 |
contract_1 | eth_getTransactionReceipt
contract_1 | eth_getBlockByNumber
contract_1 | eth_blockNumber
km_1 | [? ] Blocks @ previous: 159, current: 166, next: 169 [? ]
km_1 | [? ] Epoch still active [? ]

Below is the test script:

root@baremetal01:/home/timecapsule/test# cat test_timecapsule.js
const fs = require(‘fs’);
const path = require(‘path’);
const dotenv = require(‘dotenv’);
const TimecapsuleContract = artifacts.require(“Timecapsule”);
const { Enigma, utils, eeConstants } = require(‘enigma-js/node’);

var EnigmaContract;
if (typeof process.env.SGX_MODE === ‘undefined’ || (process.env.SGX_MODE != ‘SW’ && process.env.SGX_MODE != ‘HW’)) {
console.log(Error reading ".env" file, aborting....);
process.exit();
} else if (process.env.SGX_MODE == ‘SW’) {
EnigmaContract = require(’…/build/enigma_contracts/EnigmaSimulation.json’);
} else {
EnigmaContract = require(’…/build/enigma_contracts/Enigma.json’);
}
const EnigmaTokenContract = require(’…/build/enigma_contracts/EnigmaToken.json’);

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

const splitMessages = decryptedOutput => {
const decodedParameters = web3.eth.abi.decodeParameters(
[
{
type: ‘string’,
name: ‘concatenatedMessages’,
},
],
decryptedOutput
)
const concatenatedMessages = decodedParameters.concatenatedMessages
// Return empty array of messages if decrypted output string is empty.
if (concatenatedMessages === ‘’) {
return
}
// Otherwise return messages.
const separator = ‘|’
return decodedParameters.concatenatedMessages.split(separator)
}

let enigma = null;

contract(“Timecapsule”, accounts => {
let owner1 = accounts[0];
let task;

before(function() {
    enigma = new Enigma(
        web3,
        EnigmaContract.networks['4447'].address,
        EnigmaTokenContract.networks['4447'].address,
        'http://localhost:3333', {
            gas: 4712388,
            gasPrice: 100000000000,
            from: accounts[0],
        },
    );
    enigma.admin();
    enigma.setTaskKeyPair('cupcake');
    contractAddr = fs.readFileSync('test/timecapsule.txt', 'utf-8');
})

it('should execute compute task to add secret #1', async() => {
    let taskFn = 'add_secret(address,string,int64)';
    let taskArgs = [
        [owner1, 'address'],
        ["Hello world 1", 'string'],
    [1579378831, 'int64'],
    ];
    let taskGasLimit = 500000;
    let taskGasPx = utils.toGrains(1);
    task = await new Promise((resolve, reject) => {
        enigma.computeTask(taskFn, taskArgs, taskGasLimit, taskGasPx, accounts[0], contractAddr)
            .on(eeConstants.SEND_TASK_INPUT_RESULT, (result) => resolve(result))
            .on(eeConstants.ERROR, (error) => reject(error))
    });
});

it('should get the pending task', async () => {
  task = await enigma.getTaskRecordStatus(task);
  expect(task.ethStatus).to.equal(1);
});

it('should get the confirmed task', async () => {
  do {
    await sleep(1000);
    task = await enigma.getTaskRecordStatus(task);
    process.stdout.write('Waiting. Current Task Status is '+task.ethStatus+'\r');
  } while (task.ethStatus !== 2);
  expect(task.ethStatus).to.equal(2);
  process.stdout.write('Completed. Final Task Status is '+task.ethStatus+'\n');
}, 10000);

it('should execute compute task to add secret #2', async () => {
    let taskFn = 'add_secret(address,string,int64)';
    let taskArgs = [
        [owner1, 'address'],
        ["Hello world 2", 'string'],
    [1579378831, 'int64'],
    ];
    let taskGasLimit = 500000;
    let taskGasPx = utils.toGrains(1);
    task = await new Promise((resolve, reject) => {
        enigma.computeTask(taskFn, taskArgs, taskGasLimit, taskGasPx, accounts[0], contractAddr)
            .on(eeConstants.SEND_TASK_INPUT_RESULT, (result) => resolve(result))
            .on(eeConstants.ERROR, (error) => reject(error))
    });
});

it('should get the pending task', async () => {
  task = await enigma.getTaskRecordStatus(task);
  expect(task.ethStatus).to.equal(1);
});

it('should get the confirmed task', async () => {
  do {
      await sleep(1000);
      task = await enigma.getTaskRecordStatus(task);
      process.stdout.write('Waiting. Current Task Status is '+task.ethStatus+'\r');
  } while (task.ethStatus !== 2);
  expect(task.ethStatus).to.equal(2);
  process.stdout.write('Completed. Final Task Status is '+task.ethStatus+'\n');
}, 10000);

it('should execute compute task to get secret', async () => {
    let taskFn = 'reveal_expired_secrets(address)';
    let taskArgs = [
    [owner1, 'address'],
];
    let taskGasLimit = 500000;
    let taskGasPx = utils.toGrains(1);
    task = await new Promise((resolve, reject) => {
        enigma.computeTask(taskFn, taskArgs, taskGasLimit, taskGasPx, accounts[0], contractAddr)
            .on(eeConstants.SEND_TASK_INPUT_RESULT, (result) => resolve(result))
            .on(eeConstants.ERROR, (error) => reject(error))
    });
});

it('should get the pending task', async () => {
task = await enigma.getTaskRecordStatus(task);
expect(task.ethStatus).to.equal(1);
});

it('should get the confirmed task', async () => {
do {
    await sleep(1000);
    task = await enigma.getTaskRecordStatus(task);
      process.stdout.write('Waiting. Current Task Status is '+task.ethStatus+'\r');
  } while (task.ethStatus !== 2);
  expect(task.ethStatus).to.equal(2);
  process.stdout.write('Completed. Final Task Status is '+task.ethStatus+'\n');
}, 10000);

it('should get the result and verify the computation is correct', async () => {
  task = await new Promise((resolve, reject) => {
  enigma.getTaskResult(task)
    .on(eeConstants.GET_TASK_RESULT_RESULT, (result) => resolve(result))
    .on(eeConstants.ERROR, (error) => reject(error))
});
expect(task.engStatus).to.equal('SUCCESS');
task = await enigma.decryptTaskResult(task);

Below is the secret contract:

root@baremetal01:/home/timecapsule/secret_contracts/timecapsule/src# cat lib.rs
// Built-In Attributes
#![no_std]

// Imports
extern crate eng_wasm;
extern crate eng_wasm_derive;
extern crate serde;
extern crate chrono;

use eng_wasm::*;
use eng_wasm_derive::pub_interface;
use serde::{Serialize, Deserialize};

// Encrypted state keys
static OWNER: &str = “owner”;
static SECRETS: &str = “secrets”;

// Structs
#[derive(Serialize, Deserialize)]
pub struct Secret {
secret: String,
timestamp: i64,
}

// Public struct Contract which will consist of private and public-facing secret contract functions
pub struct Contract;

impl Contract {
fn get_secrets() -> Vec {
read_state!(SECRETS).unwrap_or_default()
}
}

#[pub_interface]
pub trait ContractInterface{
fn construct(owner: H160);
fn add_secret(sender: H160, secret: String, timestamp: i64);
fn reveal_expired_secrets(sender: H160) -> String;
}

// Private functions accessible only by the secret contract
impl ContractInterface for Contract {
fn construct(owner: H160) {
write_state!(OWNER => owner);
}

#[no_mangle]
fn add_secret(sender: H160, secret: String, timestamp: i64) {
    let owner: H160 = read_state!(OWNER).unwrap();
    assert_eq!(sender, owner);
    let mut _secrets = Self::get_secrets();
    _secrets.push(Secret {
    secret,
        timestamp,
    });
    write_state!(SECRETS => _secrets);
}

#[no_mangle]
fn reveal_expired_secrets(sender: H160) -> String {
let owner: H160 = read_state!(OWNER).unwrap();
assert_eq!(sender, owner);
let now: i64 = chrono::offset::Utc::now().timestamp();
let mut revealed_secrets: String = String::new();
let separator = String::from("|");
let all_secrets = Self::get_secrets();
for one_secret in all_secrets {
	if now > one_secret.timestamp {
		revealed_secrets.push_str(&one_secret.secret);
		revealed_secrets.push_str(&separator);
	}
}
revealed_secrets.pop();
return revealed_secrets;
}

}

What am I doing wrong? I’ve been dealing with this for the past day and have been trial-n-erroring it by altering the secret contract’s reveal_expired_secrets function and the test script and have had no luck.

1 Like

HI and welcome!

This bit bugs me but I can’t tell why yet, it may help if I can run the code too, can you push your project to github?

https://github.com/realjohnward/timecapsule

Thanks for this @realjohnward

Good news is I’ve isolated your problem to chrono, this line of code doesn’t seem to work in wasm. You can read more about wasm limitations here

There are also some possible solutions for getting UTC in wasm on chrono’s github

I haven’t tried the solutions yet but have a go, if you don’t succeed I’ll have another look in the coming days.

Hope that helps.

1 Like

I appreciate your reply @taariq

Over the past week I’ve been looking at the links you provided and they seem to all lead to dead ends, as I’ve tried implementing every solution. After a week of googling I haven’t found one single “no_std”-compatible crate w/ a function that outputs timestamps without the need for input.

I believe the value of Enigma would increase tremendously if timestamps could be produced inside of enclaves, as it would allow for “smart-private” computation.

Do you think it would be possible to have a “now()” preprocessor? It would work in a similar nature to the “rand()” preprocessor in that it would require no input parameters.

1 Like

That’s quite interesting John, have raised it with the team as well and look forward to their response.

1 Like

Hi John, I’ve got some insight from @reuven about why this doesn’t work, as well as a possible solution to consider.

Despite the fact we do not use their SDK, fortanix puts this quite succinctly in their documentation:

Timekeeping in secure enclaves is an unsolved problem.
https://edp.fortanix.com/docs/concepts/rust-std/#codestdtimecode

(Fortanix provides an SGX enclave SDK in rust which is analogous to the one we’re using. The linked page explains various limitations that implementations of standard libraries face when trying to work from within enclaves, and apply to the functions available to developers of secret contracts.)

Our enclaves use the standard library implemented by the Baidu team, documented here:
https://github.com/apache/incubator-teaclave-sgx-sdk
But the secret contracts themselves are running inside a WASM runtime running inside the enclave runtime, and thus are subject to even more limitations.

They are compiled to target rust’s wasm32-unknown-unknown target, and use the standard library that it provides.
As the target name suggests, it does not (and in our case can not ) assume it has any OS interfaces available to it, and thus panics or aborts when it sees an attempt to use them (as this user observed).

All the I/O services that are available to secret contracts (secret state, randomness, etc) are exposed through custom functions available in the eng_wasm crate.

We could add a method which tries to fetch the time from the environment and return it into the WASM runtime, but we will not have a real way of implementing it in a trusted way. (at least not that i can think of)

The user can perhaps initialize their contract with the public key of a trusted “time service”, and expose a secret contract method that accepts a timestamp (encoded in some format) and a signature of that timestamp made by the trusted service.
This timestamp can be stored in secret storage, and the service could periodically let the smart contract know what time it is. The contract can then assume any other function is invoked within an interval after the “last reported time”.

1 Like

Brilliant idea! Thank you @taariq @reuven

2 Likes