Working with time

For a smart contract, I need to know the current time. In compact documentation, I saw only circuits that compares current block time with some value.

Attempt 1: Witness-less ledger shredder

Based on that, I tried to create a witness-less implementation of now circuit, something like this:

pragma language_version >= 0.18.0 && < 0.19.0;

module Time {
  import CompactStandardLibrary;

  circuit powersOfTwo(): Vector<64, Uint<64>> {
    return [
      1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288,
      1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824,
      2147483648, 4294967296, 8589934592, 17179869184, 34359738368, 68719476736, 137438953472, 274877906944,
      549755813888, 1099511627776, 2199023255552, 4398046511104, 8796093022208, 17592186044416, 35184372088832,
      70368744177664, 140737488355328, 281474976710656, 562949953421312, 1125899906842624, 2251799813685248,
      4503599627370496, 9007199254740992, 18014398509481984, 36028797018963968, 72057594037927936, 144115188075855872,
      288230376151711744, 576460752303423488, 1152921504606846976, 2305843009213693952, 4611686018427387904,
      9223372036854775808
    ];
  }

  circuit reverseIndexes(): Vector<64, Uint<0..63>> {
    return [
      63, 62, 61, 60, 59, 58, 57, 56,
      55, 54, 53, 52, 51, 50, 49, 48,
      47, 46, 45, 44, 43, 42, 41, 40,
      39, 38, 37, 36, 35, 34, 33, 32,
      31, 30, 29, 28, 27, 26, 25, 24,
      23, 22, 21, 20, 19, 18, 17, 16,
      15, 14, 13, 12, 11, 10, 9, 8,
      7, 6, 5, 4, 3, 2, 1, 0
     ];
  }

  export circuit now(): Uint<64> {
    const pows = powersOfTwo();

    return fold((currentNow, i) => {
      const candidateNow = (currentNow + pows[i]) as Uint<64>;
      if (blockTimeGte(candidateNow)) {
        return candidateNow;
      } else {
        return currentNow;
      }
    }, 0 as Uint<64>, reverseIndexes());
  }
}

import CompactStandardLibrary;
import Time prefix Time_;

export ledger lastNow: Maybe<Uint<64>>;

constructor() {
  lastNow = none<Uint<64>>();
}

export circuit getLastNow(): Maybe<Uint<64>> {
  return lastNow;
}

export circuit getNow(): Uint<64> {
  const now = Time_now();
  lastNow = some<Uint<64>>(now);
  return now;
}

This contract behaves extremely weirdly. Calling circuit getNow bricks the deployed smart contract. The TX calling this circuit fails with this error:

{
  “circuitId”: “getNow”,
  “tx”: {“__wbg_ptr”: 1516976},
  “status”: “FailEntirely”,
  “txId”: “00000000f58378d4d3d06667a2d100b52cb1546bf4f89c20364c2b5f70a0400c70a4e61b”,
  “txHash”: “1691da3c5a4a457e00dc98b73c906d6657591a92de3608dc37d0fb718d44f556”,
  “blockHeight”: 1506,
  “blockHash”: “156f6ff6b3dd8356d9d4236df157eecfb4ab6aee3f4861403e9194dd9b7c621e”
}
    at submitCallTx (./node_modules/@midnight-ntwrk/midnight-js-contracts/src/submit-call-tx.ts:67:11)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Object.interact (./apps/contract-cli/src/commands/interact.ts:41:14)

And any subsequent interaction (like calling getLastNow) with deployed contract fails on error:

Unexpected length of input
    at __wbindgen_error_new (file:///./node_modules/@midnight-ntwrk/ledger/midnight_ledger_wasm_bg.js:7821:17)
    at wasm://wasm/0152566a:wasm-function[4615]:0x4fea6e
    at wasm://wasm/0152566a:wasm-function[2684]:0x421cc8
    at wasm://wasm/0152566a:wasm-function[4285]:0x4c1604
    at ZswapChainState.deserialize (file:///./node_modules/@midnight-ntwrk/ledger/midnight_ledger_wasm_bg.js:7183:26)
    at deserializeZswapState (./node_modules/@midnight-ntwrk/midnight-js-indexer-public-data-provider/src/indexer-public-data-provider.ts:93:19)
    at Object.queryZSwapAndContractState (./node_modules/@midnight-ntwrk/midnight-js-indexer-public-data-provider/src/indexer-public-data-provider.ts:385:13)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async getPublicStates (./node_modules/@midnight-ntwrk/midnight-js-contracts/src/get-states.ts:48:5)
    at async getStates (./node_modules/@midnight-ntwrk/midnight-js-contracts/src/get-states.ts:73:32)

Attempt 2: Witness mess

This did not work, so I tried using a witness to get time from a user, and validating it with the comparison methods. I got such module

pragma language_version >= 0.18.0 && < 0.19.0;

module Time {
  import CompactStandardLibrary;

  ledger timePrecision: Uint<64>;

  /**
  * Witness from client, producing current timestamp. This is not validated in any way, and should not be trusted.
  * For this reason use `now`.
  */
  export witness secondsFromUnixEpoch_unsafe(): Uint<64>;

  /**
  * Initialization for the module. This *must* be called in contract constructor, which want to use the Time module.
  */
  export circuit init(timePrecision_: Uint<64>): [] {
    timePrecision = disclose(timePrecision_);
  }

  /**
  * Get current timestamps, represented as number of seconds from unix epoch. This method is validating and disclosing
  * value of `secondsFromUnixEpoch`, and is safe to use in other contracts.
  */
  export circuit now(): Uint<64> {
    const ts = disclose(secondsFromUnixEpoch_unsafe());

    if (ts + timePrecision > 18446744073709551615) {
      if (timePrecision > ts) {
        assertCurrentTimeInRange(ts, ts);
      } else {
        assertCurrentTimeInRange(ts - timePrecision, ts);
      }
    } else {
      if (timePrecision > ts) {
        assertCurrentTimeInRange(ts, (ts + timePrecision) as Uint<64>);
      } else {
        assertCurrentTimeInRange(ts - timePrecision, (ts + timePrecision) as Uint<64>);
      }
    }

    return ts;
  }

  //===
  // Helpers
  circuit assertCurrentTimeInRange(lowerBound: Uint<64>, upperBound: Uint<64>): [] {
    assert(blockTimeGte(lowerBound), "Time: Provided timestamp is older than set precision in comparison to current timestamp");
    assert(blockTimeLte(upperBound), "Time: Provided timestamp is newer than set precision in comparison to current timestamp");
  }
}

Used JS witness:

{
  secondsFromUnixEpoch_unsafe(context: WitnessContext<any, any>): [any, bigint] {
    return [context.privateState, BigInt(Math.ceil(new Date().getTime() / 1000))];
  }
}

As documentation says, that blockTime* methods are expecting ‘Returns true if the current block time is * than the given value.’, does not says what the current value format is supposed to be :person_shrugging: . Following docs, I found kernel.blockTimeGreaterThan method, which specifies that this expect value of seconds from the unix epoch, so I expect this to be also true for the circuits from CompactStandardLibrary. But no matter what, the transactions using these methods, always fails to prove, as time is never precise (used precision up to 8 hours long).

Can someone shed some light what am I doing wrong or whats might be going on?

3 Likes

:warning: Contract Bricking :warning:

After further experimentation, I pin-pointed, that the bug that causes the contract to brick up, is in the kernel.blockTimeLessThan. If the provided time is too close to the actual time, the bricking happens.

1 Like

:warning: The contracts are destructive :warning:

After further investigation. It is highly possible, that the bricking happening due to the kernel circuit is at chain state level or it is bug in a node. I setup script to print out the raw zswap chainState. This is how it looks like before the destructive call:

{
  zswapState: '00040003003d0000000000000000000000230000000220b97e12e9b1581600aab70f86b1e79d62097ff42e1011799ff65659e970a63953060000000023000000022054ed210db750224a2f052bc88c55752edd77920b579fb91d1f007fcb877c375605000000002300000002205ffda519e042cbbf8520574bc75398f3bab0c7544c81407d38108b1c67bcf96802000000000200000001020200000020000000ba60c72d5f49e54dce7765cf0432408521774d60a7dd18cd51341038e96c95d52000000081540ddc172ee212bc36b7e75f7b49cbd5feaf8686065cca7a3b0a49b7fa75c5230000000320e495a4df8ea5fac581c0dfe71d2eddbfe89ad54dcc8ea26243f75e509471864d0300000000020000000103020000002000000015474177f747d5c2a21e2adfdd853593afeff4959abbf61e9408c18de32c15702000000017aaf21c90c61e4a00dcb2918d7f041954dcdab4518289cf004a28992d6914522300000003201b1c80194d697509f7c0aae9ec50e6f6a5fb83e10eb21aa2d32d6c0342bd822904000000000200000001040200000020000000731ad8054f7f5753fbdf6ef913db72f9f0661c617b4e9f44aeab2e1b772c012820000000dd941aaa6ccb9e720039dab35be518871223333fb202ebc5d9f52cbdbe5d4d6923000000032039957e85473ad8d047acd585d09873066bb379f4acc336032b5a6227f07e611b05020000002000000032908e4d379ed8fa4df61217c3d6105df80b725d838962c4267c70e926683c192000000018503237379d0523d09691931fe171fbc99d34ae283353ea59f51d7645ae455e230000000320dece7f825adcd65e21e2688062c29212433ff9417e0975eb252a1f344d39fb430602000000200000000522ecfe6f90140ad8b2ff0d754538acacdfae190e1c3a029fd1feb2d618813f20000000d621206f4dabb6f2b88703ae5f30a367afadc0085fa7251cbf078a89fc6c93b223000000032099bddbcccbb228a74156d5f46588b96d08303719b7052ce09175b1f17727ce3c070000000002000000010702000000200000009d55faf5731d786b2bdac69fd12ae754e643e8ae1926afb44d3ca3f8aea2023c20000000bca9fa41ce356548f89d4c640a876ebd3dad078757b4ca470602361f0398bb2c23000000032027bcc8f13b5de539245907be245aab03f19b46876667f0a4f6bdd4b311aa835b08000000000200000001080200000020000000908d101e4c952d51fcdb11ef4f8ad4f78f89aadc3c2c4827862c0e7b358497a220000000ee2f1397df461f01d4dcd0bdf25c8578cc122a333c5dcfeb739e19f9003acdc4230000000320ec9095add657cf496a30c9b770ddecf9451b1fa209c40192e060552cf6d43d4c09000000000200000001090200000020000000cb2a9499f892cf9c2621ec284e56bc0d23ad7dc4df2ac8f76197f49c7b9680d520000000dc4271fb15a60a2f1b22560103a4da49a33d418e5d6c1282b86119bca6ae00f6230000000320fb83a945f0de5f2c421cff04013cd585405a17c68385dd5e407f6004136be3550a0000000002000000010a0200000020000000d88b4f1f4db716d5b1865f67d92c18562b0f22e32ee9340a4c3fa462e5aaf1a0200000002b5c2739f14f754263856759eee457022cb247d1ea9c07d568bf86902d5252f8230000000320e3f7646e16d8c9de5b98adc90c15cc6175451d0f7f8e38e1ff4760f9bec3a5010b0000000002000000010b0200000020000000d6e2a692017c4ad407d7cda0a39225625aeb13c6c0cbd62339ac169251f458b6200000009c067552d1eadd2689db8e49ceffc43e5460e0a31aacc39e23e8268e4c0ab52f230000000320201376ae53862d89dce5acbae0c79062836087f8eaeab30128ce7956e4353d2d0c0000000002000000010c020000002000000052e4b6f57cb6e6808f0986bc00ffae119ef5ba9c84871b514dd418f90f1972f4200000008b82106b530fc5c269b4b1cbcc7969e887bea081d3058b84ed688edbfd3930e6230000000320be23e39f76e36d27514c614376fb61d14ec03def0ab4ebd2faae4cec6054a6540d0000000002000000010d0200000020000000ec3dccb62dc1936a11535b3d3a08cb898180ba5750e38f9503acf0cdf187a83820000000e0e80869246080e5c667d4070cc1d1077643e90bdfcc8064608c0b8f5235338823000000032002c3debba80cbea0483eb77889e598753f5b5966d598f8c68603bca26b905e680e0000000002000000010e020000002000000055d7e1b2f3a1f596b53fb9e10d8bb316c0520e602c1359e1da4989d7b2ed25e220000000e423a843a7a250cedacb59a49c9085acdd1dd2f370327b5b84c1cd1acd915034230000000320b0c243c799289ca915763b0d9fec66832d66e7dc5ec900aae29441c8830a48650f0000000002000000010f020000002000000095c054e9704dc2bc19d60cd963f146743a03e6ce4c4815067d6f9a679eb6d4ea20000000ddc1b3cf242ca9397fff0b4e1675137ad620b44ee57b65771806e06bbc5ed98d23000000032037e57e68cc88f86875f0f3b5d979ea3d6ecd8dffd25638d31f26dc7a61e4c52110000000000200000001100200000020000000d0dd90ad6cb9802f86bed11a0151f109ba1ef89d3fcd8e7baf23eeeb6259e33120000000c2f1431314e3aa568965deda70850fe3c0dc481e25a6de230cc01d483024bc792300000003208ca7384332f949bedea00aae861a03849d9ea9e9c952f08949b869628c73f60311000000000200000001110200000020000000069be279fb1ef4255298b3e9b53fb1e26cd7d08164b91728df6ad842a61c144320000000e3a899273c6a1ba652c820ca016036dcdb935f2b77c9072e5d062dd26867c406230000000320269bc0a75b8b551090cc8be8fb26d78389d3ab0a722f0a6a38a7208c1569883f12000000000200000001120200000020000000840881b819b1c4f8adafa3f10422f037ce9d835a2a0ad67eee7188cc7eb59f0f20000000579a7a03f9e2a61dc7fdb55bfd70e8cfe294643aea9cfcb249573bece7deb9da230000000320bda976cd33bd6af267ea0aa21e1ae3922cf75f75ee4d91fe86f576bb4f0e8d3313000000000200000001130200000020000000201dec481fc8f1cef5bd2a12aaccdd97e0c60fed2b82f90972794a1a7c310876200000009e0bca7965a27d2041319bcf45e60b0b6e6fa1697ec627dc1ae7e18d85302b3c230000000320fefeb88c4118fce4f713e019ff02cd573c5d2b427513d88e36140c82120ab650140000000002000000011402000000200000005bf1bb0c0b4033033386c6250a57e069060950a2ccbc08c93f456f8eb3e34b4820000000f7bbb80e6123ee311dd47d32f2c6190b481ee756d84ff6182f955ff69dbef813230000000320e3f365867fe185ab75c0a03a5ce20ba9c3326695221c29dfaa05ded0d4102b47150000000002000000011502000000200000002b7ffc620aa84374b50b0b145d975f4ec82e1466be4b98af4f6ab3501a8d9091200000009678187f3f5b9f344579a2c32e8e8f16f5fa83c2e50078c3c2612a22ae119d6d23000000032052dc408468c79c82fbcb2611cbbe851db78cbcd38c5ca12c9515bcf3e8e6da66160000000002000000011602000000200000003bc6aba1a1c5aaa814091e2a6960b7f7390798b8e9a80c7dfd1cc3036a0a3dd92000000049a19ad7d1ede7401d582459923a9d30277703889ef14c9326b90939ec1fe293230000000320e0655ca2b76cf71ee3282bb76466f8192eec0227d97a0f73d2ff6a23f981dd0b17000000000200000001170200000020000000802117e07a8b36dc7fdae5a68fef60f1dee26f7b00e0caeb2252ef944ea8f26420000000d6fce1c378a689820db92774fa8612ff880910aab47232e2db0c1742eaa0f12623000000032095345b8d04b22767fbb08530267bbb8cf3c11101efbb98041026dfd5d9402e6918000000000200000001180200000020000000c32b0cbb7c231b6714657da7d0f65cfc262d906d5c0c5681be01eac9c8fae5dd20000000e765678d4a674e065b2b5b3e23a6d20ffb06ebe678aea84f585638d59e845101230000000320a34e20444b6e663cf192db991566f94a252e2ba181f57cea7d6f626bb69bfd2a19000000000200000001190200000020000000dddaf5dcb0e18e1338b79139d89ff575e994d170814b930591ca6e36b4cd81f4200000009d62169a6dbc22827b20be2691d81479ee3ff93966425ae0c6968d72caec48162300000003200fce729a2784eb872147af143437b4bd0aa16bfa1f1183411dbf50b8dd38fa031a0000000002000000011a0200000020000000b529d68bf2fb4895fc0df32feb9bc0ae0d5efd5fc37a6ae7e0f698fd9f9983f020000000a806b851939a1892d5e5c69eab4d3fa575bf23d82fbce9254a70559af7dc1309230000000320f1e37ee4aa6f61d20fe4f0138f90f2296acf75c2aa5351c5e672d91e0a9da14a1b0000000002000000011b020000002000000057e722488a59d50421b358c213c9157271b12c40b77d90b4b6f1c8ef765a63d8200000002ea05e45264f4caabb5440925d996f11a5e40e24d973c9fb218bee45bc40584e230000000320f4bd53be0a972d7b2e3fe825a5256a5f0c165f87020fd106d125cfec00b3ed251c0000000002000000011c02000000200000005b3edd39210d9893e9cb82bde8292a87769dca36b4bc9d23d2653c848e6e7dc2200000008c55ac946a4dd4b07cd2a50dcd70b0f00d4c15bee38965f2c3cd0093ecaff82c2300000003202bbaba595f513040346be9635705355b0757320718a1c0a721fcf9a99ab32a641d0000000002000000011d0200000020000000d76c5501c8c0322bf275b77c399748999b7e18d2c984a791eaf62a140868498420000000aadfd86a63a3890ade3a50f947d1d79e78e16d4fcf532d8f47890e67f3e7463623000000032047c064db3b87e571944a2e90ce1f31487ba5ac530af62f3c2c5753970aaffd671e0000000002000000011e0200000020000000baa863cd6d27e51286c3625f8b4223ac44df48fdff5dd74fd72a95a44023d7532000000034cb913006d36fa19bb58f4b59b3b824195994b1c0e8cba60a436cc1b365b3b5230000000320c62bd179a001d3a67b0efaf1541049019efa63b488f1c04105a73c3897f5e24b1f0000000002000000011f02000000200000004e37a609c0eefb0fe43cf8fc6cae99e5eab0f8e47b4ac169288f63dd6d0a549220000000dce3c1b054d9741da8a091f1f56a96d1f59f575272f8050f50e06db81b83974d230000000320ea89c85441d345c5f286e2efbc963175e801677df943d118bf33829b9ddd4554200101010000000000000000000000010000000000000000000000000101010000000000000000000000010000000001010100000000000000000000000100000000',
  contractState: 'ContractState (Array(5) [<[05]: b8>, Map {}, Map {}, <[-]: b8>, <[-]: b32>]{claim: <verifier key>, vestmentInfo: <verifier key>, createVestment: <verifier key>}ContractState )'
}

This is how it looks like after:

{
  zswapState: '',
  contractState: 'ContractState (Array(5) [<[05]: b8>, Map {}, Map {}, <[-]: b8>, <[-]: b32>]{claim: <verifier key>, vestmentInfo: <verifier key>, createVestment: <verifier key>}ContractState )'
}

I checked if it is not a problem with indexer. But after recreating the indexer container and waiting for it to catch up, it produces same data.

2 Likes