Skip to content

Overview

Stuart Farmer edited this page Aug 1, 2018 · 3 revisions

The Seneca Language

Seneca is a subset of Python designed for authoring smart contracts. It’s syntactically valid Python and it can be parsed by the standard CPython interpreter. Differences and limitations:

  • We heavily restrict what libraries can be imported. The only allowed imports are:
    • The Seneca smart contract user libs. This is a set of useful functions made available to smart contract authors. We intentionally omit non-deterministic functions like random number generation and datetime.now().
    • Other smart contracts already present on the blockchain. The current methodology for import is to remove these nodes from the AST before execution, run a custom module loader, and bind them to the execution namespace.
  • Many language constructs are disallowed, in fact only a small subset are permitted. This is enforced by parsing smart contracts, generating an AST and only allowing certain AST node types, see: https://github.com/Lamden/seneca/blob/master/seneca/seneca_internal/parser/basic_ast_whitelist.py
  • Data access
    • Sandboxed DB
    • Seneca data storage modules are already signed in.
    • All or nothing (i.e. atomic) execution. Any failure on the Python side backs out all data writes
  • Etc. (TBD)

Security model

  • Data storage sandboxing
  • All execution in same process, not secure. (TODO)
  • Database executer in same process as interpreter, not secure. (TODO)

Performance Optimization

TBA

Description of Execution

Currently there’s one entrance point into the Seneca interpreter, the execute contract function: https://github.com/Lamden/seneca/blob/85a4d617a40fd2f6cc47ea317bee2ebdd24df321/seneca/execute_sc.py#L247,L344 execute_contract() is a convenience wrapper function around the recursive _execute_contract() function. When a smart contract is passed to _execute_contract(), which imports another smart contract as a dependency, the function calls itself with that dependency as an argument. The base case is a contract with no (smart contract) dependencies.

The _execute_contract() function has the following parameters:

  • global_run_data - this dictionary has metadata about the main smart contract (submitted by a user):
    • Who submitted it (i.e. what public key)
    • What the contract’s id is (determined by Cilantro)
    • Important: If the executed contract imports other contracts, _execute_contract() will recurse and global_run_data will be passed to those dependencies unchanged.
  • this_contract_run_data - this includes context about the current smart contract:
    • Similar to global_run_data, for the main contract it will be the exact same data.
    • If the executed contract imports other contracts, _execute_contract() will recurse and this_contract_run_data will have metadata for the dependency, it won’t be the same as global_run_data.
  • contract_str
    • A string containing the smart contract code.
  • module_loader
    • A function provided by Cilantro. It has one parameter, a contract_id and returns a pair: metadata about the contract and the contract’s code as a string.
  • db_executer
    • A wrapper around a mysql connection that lets Seneca execute queries
  • is_main
    • Already deprecated from the convenience wrapper function. This indicates whether the called contract is the main one (submitted by a user), and not a dependency of a main contract.

The steps of _execute_contract() function execution:

  • Use the CPython parser to parse the contract_str and create an AST.
  • Validate the AST against the AST node-type whitelist, throw exception if a disallowed language construct are present.
  • Create a new empty module execution scope.
  • Traverse the AST looking for imports:
    • If a Seneca user lib import is found, load the Seneca module and attach it to the module execution.
      • If the Seneca module is database related, inject database connection into module.
    • If a smart contract import is found:
      • Use the module loader to retrieve that smart contracts code and metadata.
      • Recursively execute _execute_contract() with that contract and attach that contract’s exports to the upstream module’s execution scope.
    • If the import is neither a seneca module or a smart contract, throw an exception.
  • Remove import nodes from the AST
  • Execute the AST with the constructed scope.
    • This actually executes the contract.

Clone this wiki locally