Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Allow indetermistic instructions off-chain
Browse files Browse the repository at this point in the history
  • Loading branch information
athei committed Oct 16, 2022
1 parent 0ee0327 commit 2e5e9b0
Show file tree
Hide file tree
Showing 15 changed files with 925 additions and 169 deletions.
35 changes: 31 additions & 4 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1959,7 +1959,16 @@ impl_runtime_apis! {
input_data: Vec<u8>,
) -> pallet_contracts_primitives::ContractExecResult<Balance> {
let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block);
Contracts::bare_call(origin, dest, value, gas_limit, storage_deposit_limit, input_data, true)
Contracts::bare_call(
origin,
dest,
value,
gas_limit,
storage_deposit_limit,
input_data,
true,
pallet_contracts::Determinism::Deterministic,
)
}

fn instantiate(
Expand All @@ -1973,23 +1982,41 @@ impl_runtime_apis! {
) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId, Balance>
{
let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block);
Contracts::bare_instantiate(origin, value, gas_limit, storage_deposit_limit, code, data, salt, true)
Contracts::bare_instantiate(
origin,
value,
gas_limit,
storage_deposit_limit,
code,
data,
salt,
true
)
}

fn upload_code(
origin: AccountId,
code: Vec<u8>,
storage_deposit_limit: Option<Balance>,
determinism: pallet_contracts::Determinism,
) -> pallet_contracts_primitives::CodeUploadResult<Hash, Balance>
{
Contracts::bare_upload_code(origin, code, storage_deposit_limit)
Contracts::bare_upload_code(
origin,
code,
storage_deposit_limit,
determinism,
)
}

fn get_storage(
address: AccountId,
key: Vec<u8>,
) -> pallet_contracts_primitives::GetStorageResult {
Contracts::get_storage(address, key)
Contracts::get_storage(
address,
key
)
}
}

Expand Down
29 changes: 27 additions & 2 deletions frame/contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,37 @@ changes still persist.
One gas is equivalent to one [weight](https://docs.substrate.io/v3/runtime/weights-and-fees)
which is defined as one picosecond of execution time on the runtime's reference machine.

### Notable Scenarios
### Revert Behaviour

Contract call failures are not always cascading. When failures occur in a sub-call, they do not "bubble up",
Contract call failures are not cascading. When failures occur in a sub-call, they do not "bubble up",
and the call will only revert at the specific contract level. For example, if contract A calls contract B, and B
fails, A can decide how to handle that failure, either proceeding or reverting A's changes.

### Offchain Execution

In general, a contract execution needs to be deterministic so that all nodes come to the same
conclusion when executing it. To that end we disallow any instructions that could cause
indeterminism. Most notable are any floating point arithmetic. That said, sometimes contracts
are executed off-chain and hence are not subject to consensus. If code is only executed by a
single node and implicitly trusted by other actors is such a case. Trusted execution environments
come to mind. To that end we allow the execution of indeterminstic code for offchain usages
with the following constraints:

1. No contract can ever be instantiated from an indeterministic code. The only way to execute
the code is to use a delegate call from a deterministic contract.
2. The code that wants to use this feature needs to depend on `pallet-contracts` and use `bare_call`
directly. This makes sure that by default `pallet-contracts` does not expose any indeterminism.

## How to use

When setting up the `Schedule` for your runtime make sure to set `InstructionWeights::fallback`
to a non zero value. The default is `0` and prevents the upload of any non deterministic code.

An indeterministic code can be deployed on-chain by passing `Determinism::AllowIndeterministic`
to `upload_code`. A determinstic contract can then delegate call into it if and only if it
is ran by using `bare_call` and passing `Determinism::AllowIndeterministic` to it. **Never use
this argument when the contract is called from an on-chain transaction.**

## Interface

### Dispatchable functions
Expand Down
50 changes: 50 additions & 0 deletions frame/contracts/fixtures/delegate_call_simple.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
;; Just delegate call into the passed code hash and assert success.
(module
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32)))
(import "env" "memory" (memory 3 3))

;; [0, 32) buffer where input is copied

;; [32, 36) size of the input buffer
(data (i32.const 32) "\20")

(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)

(func (export "call")
;; Reading "callee" code_hash
(call $seal_input (i32.const 0) (i32.const 32))

;; assert input size == 32
(call $assert
(i32.eq
(i32.load (i32.const 32))
(i32.const 32)
)
)

;; Delegate call into passed code hash
(call $assert
(i32.eq
(call $seal_delegate_call
(i32.const 0) ;; Set no call flags
(i32.const 0) ;; Pointer to "callee" code_hash.
(i32.const 0) ;; Input is ignored
(i32.const 0) ;; Length of the input
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this case
)
(i32.const 0)
)
)
)

(func (export "deploy"))
)
11 changes: 11 additions & 0 deletions frame/contracts/fixtures/float_instruction.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
;; Module that contains a float instruction which is illegal in deterministic mode
(module
(func (export "call")
f32.const 1
drop
)
(func (export "deploy")
f32.const 2
drop
)
)
4 changes: 2 additions & 2 deletions frame/contracts/src/benchmarking/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
//! we define this simple definition of a contract that can be passed to `create_code` that
//! compiles it down into a `WasmModule` that can be used as a contract's code.

use crate::Config;
use crate::{Config, Determinism};
use frame_support::traits::Get;
use sp_core::crypto::UncheckedFrom;
use sp_runtime::traits::Hash;
Expand Down Expand Up @@ -554,7 +554,7 @@ where

fn inject_gas_metering<T: Config>(module: Module) -> Module {
let schedule = T::Schedule::get();
let gas_rules = schedule.rules(&module);
let gas_rules = schedule.rules(&module, Determinism::Deterministic);
wasm_instrument::gas_metering::inject(module, &gas_rules, "seal0").unwrap()
}

Expand Down
6 changes: 4 additions & 2 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ benchmarks! {
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call);
let origin = RawOrigin::Signed(caller.clone());
}: _(origin, code, None)
}: _(origin, code, None, Determinism::Deterministic)
verify {
// uploading the code reserves some balance in the callers account
assert!(T::Currency::reserved_balance(&caller) > 0u32.into());
Expand All @@ -386,7 +386,7 @@ benchmarks! {
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy();
let origin = RawOrigin::Signed(caller.clone());
let uploaded = <Contracts<T>>::bare_upload_code(caller.clone(), code, None)?;
let uploaded = <Contracts<T>>::bare_upload_code(caller.clone(), code, None, Determinism::Deterministic)?;
assert_eq!(uploaded.code_hash, hash);
assert_eq!(uploaded.deposit, T::Currency::reserved_balance(&caller));
assert!(<Contract<T>>::code_exists(&hash));
Expand Down Expand Up @@ -2894,6 +2894,7 @@ benchmarks! {
None,
data,
false,
Determinism::Deterministic,
)
.result?;
}
Expand Down Expand Up @@ -2941,6 +2942,7 @@ benchmarks! {
None,
data,
false,
Determinism::Deterministic,
)
.result?;
}
Expand Down
Loading

0 comments on commit 2e5e9b0

Please sign in to comment.