diff --git a/docs/cad/000_principles/README.md b/docs/cad/000_principles/README.md index 2bc1c30..0a2e7a8 100644 --- a/docs/cad/000_principles/README.md +++ b/docs/cad/000_principles/README.md @@ -1,25 +1,27 @@ # CAD000: Design Principles -This document details general design and engineering principles deployed in the implementation and documentation of Convex. +We are building a platform for global decentralised computation and data, based on lattice technology and as a shared public utility network. As such, we are developing a set of open standards based on sound principles and values that will serve the long term vision. + +This document details general design and engineering principles deployed in the implementation and documentation of Convex. All CADs should refer to and align with these principles. ## Technical Principles ### Values are Immutable -We adopt immutability as a standard principle for all values in Convex. Immutability is important for several reasons: +We adopt immutability for all values in Convex. Immutability is important for several reasons: - Enables hash codes to be used for value identity (value IDs) - essential for content-addressable storage -- Enables structural sharing in persistent data structures +- Enables structural sharing in persistent data structures, especially when shared on the lattice - Easier to reason about immutable values, especially with pure functions - Better suited for concurrency -Mutability in implementations is permitted (e.g. a mutable cached value for performance reasons), however such mutability should not be externally visible (e.g. should not affect the Encoding of Values). +Mutability may occur as an implementation detail (e.g. a mutable cached value for performance reasons), however such mutability should not be externally visible (e.g. should not affect the encoding of values or CVM behaviour). -An important presentation on the topic: https://www.infoq.com/presentations/Value-Values/ +A useful presentation on the topic: https://www.infoq.com/presentations/Value-Values/ ### Bounded Resources -We are building a system for distibuted computation and data in the context of a global internet where many parties with access to the Convex network may be untrusted. +We are building a system for distributed computation and data in the context of a global internet where many parties with access to the Convex network may be untrusted. It is therefore necessary to place a bound on the size of resources used. This is essential to ensure that the CVM does not ever attempt to process data of unbounded size, which could allow DoS attacks by adversaries constructing arbitrarily sized input. @@ -29,17 +31,17 @@ Where input to Convex may be effectively unbounded (e.g. the size of data struct ### Security First -Convex is a system intended to support high value economic transactions. As such, security issues should be automatically regarded as the highest priority. We MUST NOT release core software with known severe security defects. +Convex supports high value economic transactions. As such, security issues should be automatically regarded as the highest priority. We MUST NOT release core software with known severe security defects that might place digital assets at risk. -We SHOULD use existing, proven algorithms and cryptography wherever practically possible: there is no need to reinvent the wheel in crypto. +We SHOULD use existing, proven algorithms and cryptography wherever practical: there is no need to "reinvent the wheel" in crypto. ### Favour Simplicity -Especially in API design, there may be a tendency to want to add new features for user convenience, e.g. additional optional arguments for core functions. +Especially in API design, there is a tendency to want to add new features for user convenience, e.g. additional optional arguments for core functions. In such cases we SHOULD strongly resist the temptation to add additional complexity, and prefer the simplest possible implementation, especially within core Convex functionality. It is more important that core functionality is clean, simple and maintainable than superficially easy to use. Users have a powerful language with macro capabilities if they wish to implement more convenient programmatic interfaces appropriate for their own use case or design tastes. -An Excellent talk by Rich Hickey on this topic: https://www.infoq.com/presentations/Simple-Made-Easy-QCon-London-2012/ +An excellent talk by Rich Hickey on this topic: https://www.infoq.com/presentations/Simple-Made-Easy-QCon-London-2012/ ### Design for Composition @@ -78,14 +80,13 @@ Such features MAY make sense in P2P off-chain usage, e.g. the Data Lattice. We e ### Apply Judgement -Principles are rarely absolute. There are always tradeoffs in engineering decisions that must be considered. Discussion is encouraged to ensure relevant aspects are considered from a number of perspectives. +Principles are rarely absolute. There are always trade-offs in engineering decisions that must be considered. Discussion is encouraged to ensure relevant aspects are considered from a number of perspectives. ## CAD Style Conventions - Literal code (e.g. Convex Lisp forms) should be quoted in a fixed width font `(like this)` -Type names like Vector or the Java type Object should be capitalised. +Type names like the Convex Vector or the Java type Object should be capitalised. They SHOULD NOT be quoted as code unless they are intended as a code example. Where possible, follow RFC style MUST, SHOULD, MAY, SHOULD NOT etc. in formal specifications. diff --git a/docs/cad/011_errors/README.md b/docs/cad/011_errors/README.md index 0da77bc..8f23d84 100644 --- a/docs/cad/011_errors/README.md +++ b/docs/cad/011_errors/README.md @@ -221,12 +221,13 @@ Error sources indicate the region in the network where an error occurred. These | Source Code | Location of error | Example(s) | ------------ | -------------------------------- | ----------- -| `:CLIENT` | Client library code | Failed input validation -| `:COMM` | Client-Server communications | IO failure, connection failure or timeout -| `:PEER` | Peer handling of user request | Rejected for bad signature by peer -| `:NET` | Consensus network | Transaction failed to get into consensus -| `:CVM` | CVM state transition handling | Invalid sequence number, `:JUICE` error -| `:CODE` | CVM code execution | `:CAST` error in user code +| `:CLIENT` | Client library code | Failed input validation +| `:COMM` | Client-Server communications | IO failure, connection failure, timeout +| `:SERVER` | Server handling of request | Bad request format, server error, server load +| `:PEER` | Peer handling of user request | Rejected for bad signature by peer +| `:NET` | Consensus network | Transaction failed to get into consensus +| `:CVM` | CVM state transition handling | Invalid sequence number, `:JUICE` error +| `:CODE` | CVM code execution | `:CAST` error in user code Error sources are not formally part of the Convex Network / CVM specification, but are important additional information normally returned alongside transaction results. Be aware that a malicious peer could fabricate the error source, so it may be useful to independently validate results. diff --git a/docs/intro.md b/docs/intro.md index da70efe..8404a1d 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -4,3 +4,6 @@ sidebar_position: 1 # Convex Intro +Convex is an open platform for building decentralised applications that require a high performance, trusted global state without needing to depend on centralised services. + +It delivers the promise of the Internet of Value, offering flexibility and scalability far beyond the capabilities of traditional blockchains. In particular, it is supports high volume, interactive applications used directly by end users such as for mobile apps, payments and gaming. \ No newline at end of file diff --git a/docs/overview/convex-whitepaper.md b/docs/overview/convex-whitepaper.md index 71591ef..c129c46 100644 --- a/docs/overview/convex-whitepaper.md +++ b/docs/overview/convex-whitepaper.md @@ -1,7 +1,8 @@ --- -title: White Paper +title: Convex White Paper authors: [convex] tags: [convex, technology] +permalink: white-paper --- # Convex White Paper @@ -314,7 +315,7 @@ Convex eschews the idea of selecting a leader. We maintain the principle that ** - Blocks can be **independent of all previous Blocks** - they do not necessarily form a "chain" linked by cryptographic hashes. - It is possible for multiple Peers to propose Blocks **concurrently** for inclusion in consensus at the same time. This removes a major bottleneck compared to systems that require on some form of sequential leadership e.g. ??????. -An Ordering includes all Blocks up to the current Consensus Point. A Peer may propose a novel Block for consensus by appending it to the Ordering plus additional Blocks remaining to be confirmed in consensus. +An ordering includes all blocks up to the current consensus point. A peer may propose a novel block for consensus by appending it to the ordering (alongside any other additional blocks still to be confirmed in consensus). #### Convergent Consensus @@ -337,9 +338,9 @@ The Ordering of one or more other peers could be removed by from the Belief of a During the convergence process conflicts in proposed block Orderings from different Peers are resolved by a system of convergent stake-weighted voting. [A diagram would be really helpful here] At each belief merge step, peers compute the total share of stake voting for each proposed block in the next position after the current Consensus Point. Peers have a view of the Orderings proposed by all other Peers. -Stake-weighted voting is applied iteratively to future proposed blocks, but only counting the votes by peers that have supported the winning ordering up to this point. Supporting a minority block causes peers to be temporarily excluded from the considered vote in following blocks. Peers that vote for orderings inconsistent with the majority cannot influence the ordering of any subsequent blocks. +Stake-weighted voting is applied iteratively to future proposed blocks, but only counting the votes by peers that have supported the winning ordering up to this point. Supporting a minority block causes peers to be temporarily excluded from the vote on following blocks. Peers that vote for orderings inconsistent with the majority cannot influence the ordering of any subsequent blocks. There is therefore an incentive for peers to adopt an ordering consistent with the majority. -Once the overall winning ordering has been determined, any peer can append any new Blocks it wishes to propose, adopting this ordering as its own proposal. This ordering is signed and incorporated into the pPeer's own belief, which is then propagated onwards to other peers. +Once the overall winning ordering has been determined, any peer can append any new blocks it wishes to propose, adopting this ordering as its own proposal. This ordering is signed and incorporated into the peer's own belief, which is then propagated onwards to other peers. As an illustration, consider three Peers that are initially in consensus with respect to an ordering of blocks `XXXX` but peers `A` and `B` propose new blocks `Y` and `Z`: @@ -771,7 +772,7 @@ The CVM therefore constrains **time**, **space** and **depth**. Convex constrains time by placing a "juice cost" on each CVM operation performed. Any transaction executed has a "juice limit" that places a bound on the total amount of computational work that can be performed within the scope of the transaction. -The originating account for a transaction must reserve juice by paying an amount `[juice limit] x [juice price]` at the start of the transaction. Any unused juice at the end of the transaction is refunded at the same rate. The juice price a dynamically varying price that adjusts with amount of execution performed per unit time by the Convex network as a whole: this is a cryptoeconomic mechanism to disincentivise transactions from being submitted at peak periods, and as a protection from DoS attacks by making it prohibitively expensive to flood the compute capacity of the network for a sustained period of time. +The originating account for a transaction must reserve juice by paying an amount `[juice limit] x [juice price]` at the start of the transaction. Any unused juice at the end of the transaction is refunded at the same rate. The juice price is a dynamically varying price that adjusts with the amount of load on the Convex network as a whole: this is a cryptoeconomic mechanism to disincentivise transactions from being submitted at peak periods, and as a protection from DoS attacks by making it prohibitively expensive to flood the compute capacity of the network for a sustained period of time. If the juice limit has been exceeded, the CVM terminates transaction execution with an exception, and rolls back any state changes. No juice is refunded in such a situation - this penalises users who attempt excessive resource consumption either carelessly or maliciously. @@ -1053,15 +1054,15 @@ Memory Consumption is computed at the end of each transaction, and is defined as `Memory Consumption = [CVM State Size at end of Transaction] - [CVM State Size at start of Transaction]` -If a transaction has zero Memory Consumption, it will complete normally with no effect from the Memory Accounting subsystem +If a transaction has zero memory consumption, it will complete normally with no effect from the memory accounting subsystem -If a transaction would complete normally, but has a positive Memory Consumption, the following resolutions are attempted, in this order: +If a transaction would complete normally, but has a positive memory consumption, the following resolutions are attempted, in this order: -1. If the user has sufficient Allowance, the additional memory requirement will be deducted from the allowance, and the transaction will complete normally +1. If the user has sufficient allowance, the additional memory requirement will be deducted from the allowance, and the transaction will complete normally 2. If the transaction execution context has remaining juice, and attempt will be made to automatically purchase sufficient memory from the Memory Exchange. The maximum amount paid will be the current juice price multiplied by the remaining juice for the transaction. If this succeeds, the transaction will complete successfully with the additional memory purchase included in the total juice cost. 3. The transaction will fail with a MEMORY Error, and any state changes will be rolled back. The User will still be charged the juice cost of the transaction -If a transaction has negative Memory Consumption, the memory allowance of the user will be increased by the absolute size of this value. In effect, this is a refund granted for releasing storage requirements. +If a transaction has negative memory consumption, the memory allowance of the user will be increased by the absolute size of this value. In effect, this is a refund granted for releasing storage requirements. #### Allowance transfers @@ -1076,7 +1077,7 @@ It is permissible to make an allowance transfer directly between accounts. This #### Actor Considerations -All Accounts, including actors, have a memory allowance. However, in most cases actors have no use for a memory allowance: any memory consumed during interaction with an actor will be accounted for via the user account account that originated the transaction. +All Accounts, including actors, have a memory allowance. However, in most cases actors have no need for a memory allowance: any memory consumed during interaction with an actor will be accounted for via the user account account that originated the transaction. One exception to this is with scheduled execution, where an actor itself may be the origin for a transaction. diff --git a/docs/overview/faq.md b/docs/overview/faq.md index 00139db..2ba7292 100644 --- a/docs/overview/faq.md +++ b/docs/overview/faq.md @@ -6,20 +6,18 @@ tags: [convex] Some answers to common questions can be found here. - ## Is Convex Free? -Yes! Convex is free for anyone to use and build applications upon, and always will be. We are building Convex for everyone to support the Internet of Value. +Yes! Convex is free for anyone to use, and always will be. We are building Convex as an open public utility network for everyone to support the Internet of Value. -To make use of the resources of the network, small transaction fees are charged using Convex Coins, which is the native utility token of the network. This is important for several reasons: +When transacting on the network, small fees are charged using Convex Coins, which is the native utility token of the network. This is necessary for several reasons: -- Compensate those who provide important secure infrastructure to the network (i.e. peer operators) +- Compensate fairly those who provide important secure infrastructure to the network (i.e. peer operators) - Prevent denial of service attacks by people flooding the network with wasteful transactions. This makes it very expensive to launch such attacks. - Create an economic incentive to use the network as efficiently as possible (both for users and developers of smart contracts) Our goal is to keep transaction fees small, so that it is never a significant issue for legitimate network users. - ## How fast is Convex? Convex can comfortably process many thousands of complex transactions per second (e.g. transfers and smart contract calls). The CVM itself has been benchmarked at over 1,000,000 TPS on a modern desktop PC. And as we continue making performance improvements it is getting faster by the day. diff --git a/docs/overview/manifesto.md b/docs/overview/manifesto.md index d7ddd3e..d77e006 100644 --- a/docs/overview/manifesto.md +++ b/docs/overview/manifesto.md @@ -228,7 +228,7 @@ Initially, the governance role will be performed by the Convex Foundation, a non In the longer term, we will implement decentralised governance. This will happen once we have sufficient confidence that it is practical to implement, secure against plausible threats and fully serves the interest of Convex users and society as a whole. Key governance roles include: -- Authorising official updates to the peer network / protocol +- Authorising official updates to the peer network or protocol - Preventing forks in the global state: the entire point of a global state is to act as a single source of truth. As a public utility that records important economic information on a decentralised basis, the network must avoid forks since these present a significant risk of confusion and loss to participants in the ecosystem. Forks are not desirable in a system designed to act as a single source of truth for asset ownership, contract state and account balances etc. - Ensuring that issuance of Convex Coins is fair, secure and consistent with out principles of rewarding ecosystem contribution diff --git a/docs/overview/performance.md b/docs/overview/performance.md index c73eef6..69ee9c4 100644 --- a/docs/overview/performance.md +++ b/docs/overview/performance.md @@ -9,6 +9,9 @@ We care primarily about two different measurements of performance: Convex performance is based around a key idea: We implement consensus using a **CRDT** (conflict-free replicated data type) where the Peers achieve consensus by simply sharing a Belief data structure which is repeatedly merged with other Beliefs to form consensus. CRDTs are guaranteed to eventually converge to a consistent value under reasonable assumptions, which gives the desired properties of safety and liveness to the network. Peers, therefore, have a simple primary task: merge and propagate new beliefs to the network as quickly as possible. +Here are some performance figured validated in the EU's Next Generation Internet (NGI) OntoChain Project. +![CompareETH2](https://github.com/user-attachments/assets/0ed23d0b-85dc-4aa6-91f7-8fc6903bcf40) + ## Latency diff --git a/docs/products/convex-desktop/index.md b/docs/products/convex-desktop/index.md index 4e78e2b..6476564 100644 --- a/docs/products/convex-desktop/index.md +++ b/docs/products/convex-desktop/index.md @@ -1,3 +1,10 @@ +--- +title: Convex Desktop +authors: [convex] +tags: [convex] +sidebar_position: 0 +--- + # Convex Desktop Convex Desktop is a GUI tool for interacting with Convex. Designed for developers and power users, it puts all the capabilities of Convex at your fingertips. diff --git a/docs/tutorial/convex-lisp/lisp-cvm.md b/docs/tutorial/convex-lisp/lisp-cvm.md index 7708480..21d9d63 100644 --- a/docs/tutorial/convex-lisp/lisp-cvm.md +++ b/docs/tutorial/convex-lisp/lisp-cvm.md @@ -92,6 +92,18 @@ The significance of this capability cannot be understated: - You can store and manage arbitrary data - Everything can be done interactively with simple REPL commands - no other tools required! +## Special Symbols + +You might notice symbols like `*address*`, conventionally surrounded with asterisks. These are *special symbols* which get special treatment by the CVM. They are not static values, but dunamiocally calculated on demand by the CVM. Commonly used ones are: + +- `*address*` the address of the current account, e.g. `#15656` +- `*balance*` the Convex Coin balance of the account +- `*juice-price*` the price (in Convex coppers) of each unit of juice +- `*caller*` the address which made a `call` to the current address (may be `nil`) +- `*origin*` the address of the origin account for the transaction +- `*timestamp*` the unix timestamp of the current CVM state (= the time of the most recent block which started processing) +- `*controller*` an account (can be `nil`) with the authority to control this account + ## Actors So far, we've looked at accounts controlled by users. But accounts can also be CVM programs that are independent of any users. We call these actors in Convex because they act and respond autonomously in accordance with their code. @@ -109,11 +121,11 @@ To create an actor, you need to deploy some code to initialise the actor. The co (deploy '(def some-data "Hello")) => #1033 -;; This is undeclared, since some-data exists in the Actor's environment, not ours! +;; This is undeclared, since some-data is in the actor's environment some-data => UNDECLARED -;; However, we can look up the data in the new Actor's environment: +;; However, we can look up the data in another account: #1033/some-data => "Hello" ``` @@ -122,7 +134,7 @@ Your initialisation code *MUST* set up any capabilities you want the actor to ha ### Calling actor functions -Actors are more than just containers for data - they can be active participants in transactions. To create an Actor that exposes executable functionality to others, you need to make functions `:callable`. The following example is an actor that allows callers to get and set a value +Actors are more than just containers for data - they can be active participants in transactions. To create an actor that exposes executable functionality to others, you need to make functions `:callable`. The following example is an actor that allows callers to get and set a value ```clojure ;; define code for our Actor @@ -131,12 +143,12 @@ Actors are more than just containers for data - they can be active participants (def value :initial-value) ;; stateful data definition for this actor (defn ^:callable set [v] - (set! value v)) ;; for safety: set! fails if `value` is not defined + (set! value v)) ;; note: `set!` fails if `value` is not defined (defn ^:callable get [] value))) -;; Deploy the Actor and store the address as 'act' for convenient use later +;; Deploy the Actor and store the address as 'act' for convenient use later (def act (deploy actor-code)) ;; Call 'get' @@ -154,11 +166,11 @@ Actors are more than just containers for data - they can be active participants This actor is pretty simple, but it demonstrates the key ideas: - An actor is an autonomous program, with its own execution environment -- You can export functions to allow users to interact with an actor +- You can make callable functions to allow users to interact with an actor -Note you can also read the actor account's data directly by lookup. +Note you can also read the actor account's data directly by lookup: -``` +```clojure act/value => :new-value ``` @@ -167,7 +179,7 @@ This works, but is not recommended: you are making an assumption about how the a ### Sending funds to actors -Like users, actor accounts can have their own balance of funds. +Like users, actor accounts can have their own balance of Convex Coins. You can use the `transfer` function to transfer funds to an Actor. However, this causes a problem: what if the actor doesn't expect to receive funds, and there is no a facility to transfer the funds elsewhere? This can cause coins to be irrevocably lost. diff --git a/docs/tutorial/convex-lisp/lisp-guide-advanced.md b/docs/tutorial/convex-lisp/lisp-guide-advanced.md index 76d3832..092a5a7 100644 --- a/docs/tutorial/convex-lisp/lisp-guide-advanced.md +++ b/docs/tutorial/convex-lisp/lisp-guide-advanced.md @@ -6,11 +6,11 @@ authors: [mikera, helins] tags: [convex, developer, lisp] --- -If you've got this far, you may be interested in some of the more advanced features of Convex Lisp. This section is intended for people who want to know more about how Convex Lisp work, and how it integrated with the capabilities of the CVM. +If you've got this far, you may be interested in some of the more advanced features of Convex Lisp. This section is intended for people who want to know more about how Convex Lisp works. ## Compiler Phases -How does 'Code as Data' actually work? The secret is in understanding the phases of the Convex Lisp compiler +How does 'Code as Data' actually work? The secret is in understanding the phases of the Convex Lisp compiler. ### 1. Reading @@ -20,31 +20,33 @@ Reading is the first phase that parses source code as text into CVM data structu "(foo :bar :baz)" -> '(foo :bar :baz) ``` -The Convex Lisp parser is part of the Convex platform code but technically outside the scope of the CVM - there's no good reason for doing parsing on-chain when it can be performed easily and cheaply by clients. This means that you can't parse code from Strings on-chain, but this isn't a significant limitation: you can just parse off-chain and pass in the resulting data structure (skipping Phase 1) +The Convex Lisp reader is part of the Convex platform code but outside the scope of the CVM - there's no good reason for doing parsing on-chain when it can be performed easily and cheaply by clients. This means that you can't parse code from Strings on-chain, but this isn't a significant limitation: you can just parse off-chain and pass in the resulting data structure (skipping the reading phase) ### 2. Expansion -Expansion is the second phase of the compiler. Expansion takes the raw form data structures and translates them into Syntax Objects, which are a representation of the Convex Lisp Abstract Syntax Tree (AST) +Expansion is the second phase of the compiler. Expansion takes the raw form data structures and translates them into expanded forms, which are a representation of the Convex Lisp Abstract Syntax Tree (AST) ```clojure -'(foo :bar :baz) -> +;; `if` is a macro that gets expanded to `cond` +(expand '(if :bar :baz)) +=> (cond :bar :baz) ``` In this phase, any macros are applied to the forms analysed, which has the effect of replacing them with the macro expansion. This means that arbitrary CVM code in macros *can* be executed during expansion - which in turn can be sometimes useful, e.g. in smart contract code that wishes to generate code based on analysing the CVM state. -Phase 2 expansion can be performed either on-chain or off-chain. +Expansion can be performed either on-chain (with the `expand` core function) or off-chain. ### 3. Compilation -In the third phase, Syntax Objects are *compiled* into *Ops*, which are the low-level instructions that can be executed by the CVM. +In the third phase, forms are *compiled* into *Ops*, which are the low-level instructions that can be executed by the CVM. ```clojure - -> + -> Op ``` -There are only a small number of Op types on the CVM, which are roughly based on the fundamental operations required to implement the [Lambda Calculus](https://en.wikipedia.org/wiki/Lambda_calculus). Currently, these are: +There are only a small number of Op types on the CVM, which are roughly based on the fundamental operations required to implement the [Lambda Calculus](https://en.wikipedia.org/wiki/Lambda_calculus). Important ones are: - **Cond** - Performs a conditional branch - **Constant** - Loads a constant value @@ -57,7 +59,7 @@ There are only a small number of Op types on the CVM, which are roughly based on Ops can be nested, e.g. an Op of type **Do** may contain multiple child Ops. In this way, single Ops can be used to represent whole programs or algorithms. -Normally, users won't need to interact directly with Ops. +Normally, users don't need to interact directly with Ops. There are cases where it may be marginally more efficient to construct Ops off-chain and send them directly for execution, but this is an optimisation probably only worthwhile for applications doing very large numbers of transactions. ### 4. Execution @@ -67,18 +69,45 @@ The final phase is execution, where Ops are executed in the CVM context. The Op + => + ``` -Results from Op execution must be either: +Results from Op execution can be either: - A valid CVM data object - An exceptional result (e.g. an error or early return value) Convex Ops are technically a form of [p-code](https://en.wikipedia.org/wiki/P-code_machine), analogous in many ways to Java bytecode. Using Ops gives a few big advantages: -- Ops can be executed very efficiently many times (avoiding the more expensive phases of parsing, expansion and compilation). -- Ops are very compact in terms of memory used - making them ideal for network transmission and efficient usage of on-chain storage. +- Ops can be executed efficiently many times (avoiding the more expensive phases of parsing, expansion and compilation). +- Ops are very compact in terms of memory used - making them ideal for network transmission and efficient use of on-chain storage. - We can improve the underlying performance and implementation details of the CVM without breaking CVM code that has been compiled to Ops. - Ops are designed to match up with the runtime and security checks that the CVM must perform when executing code securely on-chain. +## Destructuring + +It is common that data is passed in a data structure, and you wish to access specific elements of the structure. Convex supports basic destructuring of sequential data structures: + +```clojure +(defn name [user] + (let [[name age attributes & flags] user] + name)) + +(name ["Bob" 67 {:favourite-colour "Green"} :some-extra-flag]) + => "Bob" +``` + +The `&` symbol can be user to indicate an arbitrary number of following elements. Such elements are bound as a single vector. + +```clojure +(defn restargs [_ & more] + more) + +(restargs 1 2 3 4) + => [2 3 4] +``` + +The `_` symbol is used to ignore an argument. Nothing is bound for the corresponding position. + +While often convenient, destructuring can make code harder to read, so use judiciously. + ## Macros We've actually used a couple of macros already in this guide: `if`, `undef` and `defn` are all examples of macros. @@ -102,47 +131,177 @@ What is happening here? The macro defines an expander function that takes three => (+ 1 3) ``` -Macros are powerful tools, but should only be used when they are needed - they are more complicated to use and understand than regular functions. The best use cases for macros are usually: +Macros are powerful tools, but should only be used when needed - they are more complicated to use and understand than regular functions. The best use cases for macros are usually: - Writing new syntax / language extensions that need to make use of arguments *without* evaluating them beforehand. If you are happy to use arguments after regular evaluation, then regular functions are probably a better fit. - Situations where you want code to be evaluated at compile-time, e.g. to avoid repeatedly performing the same expensive computation at runtime. +## Transactions and state rollback + +The CVM is a state machine, and the execution of ops often leads to changes in the CVM state. Usually this is desirable (because you want a digital asset transfer to be executed, for example) however sometimes you need a greater level of control for security or integrity reasons. + +### Atomic transactions + +A transaction on Convex either succeeds (with a result value) or fails (with an error code and optional message / metadata). + +If a transaction fails then *all state changes that happen within the transaction* are rolled back. Transactions are therefore **atomic** from the perspective of any external observer. + +This is important because it prevents a situation where a transaction is only partially completed. The only state changes that can happen in relation to a failed transaction are those external to the transaction itself (e.g. juice accounting). + +### Actor calls + +Calling an actor creates a new execution context (effectively a fork of the CVM). Like an overall transaction, this will either succeed or fail. Again, if there is any failure, all CVM changes made within the scope of the `call` are rolled back. + +```clojure +;; an actor with a callable function that changes state but cannot succeed +(def bad-actor (deploy + '(defn ^:callable will-fail [] + (def test 10) ;; make a state change first + (fail) ;; then fail + ))) + +;; the call fails as expected +(call bad-actor (will-fail) +=> Exception: :ASSERT nil + +;; this fails because the definition of `test` has been rolled back +bad-actor/test +=> Exception: :UNDECLARED test +``` + +### `rollback` instruction + +You can terminate a transaction or actor call with `(rollback :value)`, which causes the current execution context to stop with the given value as a result. + +This returns the transaction to the `*caller*` of the current actor, or terminates the transaction if there is no `*caller*` (i.e. we are in a top level transaction). This is typically used when unexpected has happened, or an attempt to perform some work failed, but you do not want to throw an error. Presumably the caller will know what they want to do in this situation. + +### `halt` instruction + +You can terminate a transaction or actor call with `(halt :value)`, which causes the current execution context to stop with the given value as a result, but keep any CVM state changes. + +This returns the transaction to the `*caller*` of the current actor, or terminates the transaction if there is no `*caller*` (i.e. we are in a top level transaction). This is typically used to exit processing early when everything is successfully completed and the desired result is known. + +### `query` expression + +Often you want to execute some code to determine the result, but do not want any CVM changes to occur. You can do this by wrapping any code in `(query ...)`. The example below + +```clojure +;; if I deployed a new actor, what address would I get? +(def next-actor-address (query (deploy :this-is-not-important))) +=> #68796 + +;; The new actor account wan't actually created! +(account next-actor-address) +=> nil +``` + +Note that code within queries may still fail, with the error propagated back to the calling code.# + +`query` is particularly useful when executing code that *shouldn't* make state changes but you don't entirely trust it. A typical example would be calling a 3rd party actor to read some data - it protects you against unintended or malicious CVM state changes in what should be a read-only operation. + +Such calls might also open up security vulnerabilities like reentrancy attacks if the actor calls back into your code, but again `query` protects you, because any such changes are rolled back and it doesn't matter even if an attacker does manage to compromise your code: they can't do anything. + +## Exception Handling + +**WARNING**: error handling on the CVM is a risky business. It is always safer to fail a transaction than to attempt to handle an error - so only do this if you really know what you are doing + +### `try` expressions + +The CVM supports a `try` expression similar to many general purpose languages that support exception handling. The semantics of `try` are: + +1. Attempt the first expression +2. If the expression succeeds, return its result and finish (including any CVM state changes) +3. If expression fails with a catchable error **roll back** any CVM state changes and proceed +4. If more expressions exist, continue to attempt each expression in turn as in 2. above +5. If all expressions fail, return the result of the last expression (which could be an error) + +Example: + +```clojure +(try + (+ :foo :bar) ;; this will fail due to bad argument types + :ALTERNATIVE)=> :ALTERNATIVE +``` + +### Rollback behaviour + +The atomic rollback feature of `try` is critical for smart contract safety. In the event of failure, the CVM state will be as it was before the failing expression. + +For example, the following code performs some digital asset transactions (which typically involved nested calls to actors that modify CVM state). If any one of these fails, the entire `do` block is atomically rolled back before `alternative-handling` is attempted. + +```clojure +(try + (do + (asset-transfer-1) + (asset-transfer-2) + (asset-transfer-3)) + (alternative-handling)) +``` + +This pattern of ensuring a set of actions either all succeed or are all rolled back is quite common in more sophisticated smart contract code. In effect, the `try` block lets you attempt an atomic sub-transaction. + +You can furthermore wrap code in `query` to discard CVM state changes even in the case of success. This enables speculative execution of arbitrary code, even with `eval` on untrusted code: + +```clojure +(defn would-code-succeed? [dangerous-code] + (try + (query + (eval dangerous-code) + true) + false)) + +;; assuming you have 1 gold at least +(would-code-succeed? '(transfer #11 1000000000)) +=> true + +;; A transfer of this size is impossible +(would-code-succeed? '(transfer #11 1000000000000000001)) +=> false +``` + +The most you can lose here is the value of your juice: the transfer (or any other state changes) won't actually happen. + +### Uncatchable errors + +Note: Some CVM errors are impossible to recover from: such errors are regarded as *uncatchable*. + +This usually isn't a concern because there is nothing you can do anyway to continue effectively. e.g. `:JUICE` failures are pointless to catch because any error handling code will immediately also fail due to `:JUICE`. + ## Upgradable Actors A key risk of developing smart contracts is that once they are live, significant losses may occur if bugs are found. Losses could be from theft by malicious actors that manage to exploit a security weakness, or a bug that causes assets to be permanently lost. -It is therefore *an option* to make Actors upgradable. This is no panacea but a trade-off: You can the ability to patch problems in the original smart contract, but also open up the possibility that this upgrade feature itself may be exploited by attackers. +It is therefore *an option* to make actors upgradable. This is a trade-off: You gain the ability to patch problems in the original smart contract, but also open up the possibility that this upgrade feature itself may be exploited by attackers or accidentally misused. -An upgradable actor can be implemented by providing an exported function that allows a trusted user to execute an `eval` operation in the context of the Actor. A minimal implementation of this that always trusts the user who deployed the Actor might look like: +An upgradable actor can, for example, be implemented by providing an exported function that allows a trusted user to execute an `eval` operation in the context of the actor's account. A minimal implementation of this that always trusts the user who deployed the actor might look like: ```clojure ;; deploy an upgradable Actor (def upgradable-actor (deploy '(do + ;; make the initial deployer be the owner (def owner *caller*) - (defn upgrade [code] - (assert (= *caller* owner)) - (eval code)) - - (export upgrade)))) + ;; upgrade function only callable by owner + (defn ^:callable upgrade [code] + (if (= *caller* owner) + (eval code) ;; the dangerous part! + (fail :TRUST "Not authorised to upgrade")))))) ;; Check we are the owner! (= *address* (call upgradable-actor (upgrade 'owner))) => true -;; Add a new function to the Actor and export it +;; Add a new callable function to the Actor (call upgradable-actor (upgrade - '(do - (defn foo [x] :foo-called) - (export foo)))) + '(defn ^:callable foo [x] :foo-called))) ;; Call the new function (call upgradable-actor (foo 12)) => :foo-called ``` -**SECURITY NOTE:** Adding a general-purpose upgrade feature like this lets you correct bugs or add new enhancements to Actors, but it opens the risk that the same mechanism could be used to compromise the Actor's behaviour if an attacker were able to impersonate the owner, and also creates the risk that the Actor may be permanently disabled by the owner by mistake. As always, you must perform your own security analysis to determine whether this trade-off is worthwhile for the Actors that you deploy. +**SECURITY NOTE:** Adding a general-purpose upgrade feature like this lets you correct bugs or add new enhancements to actors, but it opens the risk that the same mechanism could be used to compromise the actor's behaviour if an attacker were able to impersonate the owner, and also creates the risk that the actor may be permanently disabled by the owner by mistake. As always, you must perform your own security analysis to determine whether this trade-off is worthwhile for the actors that you deploy. diff --git a/docs/tutorial/convex-lisp/lisp-guide.md b/docs/tutorial/convex-lisp/lisp-guide.md index dcebf2d..9c751c3 100644 --- a/docs/tutorial/convex-lisp/lisp-guide.md +++ b/docs/tutorial/convex-lisp/lisp-guide.md @@ -18,9 +18,11 @@ Using the [Sandbox](/sandbox) is the easiest way to experience Convex Lisp. We r - You will see outputs from Convex in the output window. we use `=>` to indicate expected outputs in the examples below. --> -## Lisp expressions +## Expressions -All Lisp code is constructed from expressions, which can be evaluated to get a resulting value (or maybe an error, if something went wrong...). The classic Lisp expression is a list enclosed in parentheses `(...)` where the first element of the list is the function to be called and the following elemenst are the arguments. So to add two numbers with the `+` function you would do something like: +All Lisp code is constructed from expressions. Expressions are evaluated to get a result (or maybe an error, if something went wrong...). + +The classic Lisp expression is a list enclosed in parentheses `(...)` where the first element of the list is the function to be called and the following elements are the arguments. So to add two numbers with the `+` function you would do something like: ```clojure (+ 2 3) @@ -34,9 +36,9 @@ It's important to not that each element in the expression is itself an expressio => 75 ``` -So let's take a quick tour through the most common types of expressions, and the values that they produce. +There are many different types of expressions (many of which are introduced in this guide). But the syntax is of Lisp is ultimately just a tree of nested expressions. It is this simplicity and consistency which gives Lisp its power. -### Literals +## Literals The simplest type of expression is a constant literal data value, which evaluates directly to itself! If you type the number `1` in the REPL and execute it, the result is simply the number `1` itself: @@ -114,7 +116,7 @@ Finally, there is also support for byte data encoded in hexadecimal (we call the Blob literals are somewhat unusual as a data type, but are very convenient for many reasons in Convex: specifying addresses of users or smart contracts, validating cryptographic hashes against exact values etc. Using blob literals directly is also much more efficient than encoding/decoding binary data in some other format such as hex strings. -### Symbols +## Symbols Symbols are named references to value in the Convex programming environment. When used as expressions, they look up the value that they refer to in the current context. Usually, you would first use `def` to create a new value in the environment. @@ -148,11 +150,11 @@ Some *special symbols* are provided by Convex to make it easier to access specia => #123 ``` -### Functions +## Functions Functions in Convex Lisp are the fundamental objects that represent computation: algorithms that can be applied to transform input data into output data. -#### Function application syntax +### Function application Functions can be called in an expression by placing the function name in a list before the arguments to a function. Usually, the function is specified by a Symbol: @@ -174,7 +176,24 @@ inc(10) Why do we do this? It turns out that being able to express the whole function application expression as a list is extremely useful for more advanced techniques such as macros and code generation. A topic for later. -#### The Core library +### Variable arity + +Lisp function often allow variable arity, i.e. you can pass a variable number of arguments. This is often more concise and readable than nesting multiple functions. A good example is the core `str` function for assembling strings: + +```clojure +(str "Hello" " :: " "Bob" " :: " 42) +=> "Hello :: Bob :: 42" +``` + +If you pass an illegal number of arguments, you will get an `:ARITY` error: + +```clojure +(count [1 2 3] [5 6]) + Exception: :ARITY count requires arity 1 but called with: 2 +``` + + +### The Core library The Convex core runtime library provides a wide variety of useful functions that you can see in the [Reference](/documentation/reference). Some simple examples to try out: @@ -189,7 +208,7 @@ The Convex core runtime library provides a wide variety of useful functions that => true ``` -#### Defining functions +### Defining functions You can easily define your own functions with `defn`: @@ -209,7 +228,7 @@ You can also create anonymous functions and use them directly with the `fn` spec => 12321 ``` -### Data structures +## Data structures Convex provides a powerful set of data structures as part of the CVM. In fact, one of the reasons Convex performs so well is due to the power of the data structures. @@ -415,7 +434,7 @@ Some other ways of constructing a List: You should use vectors over lists for storing data in most cases. Lists are mainly be used for generating code - in macros, for example. -### Conditionals +## Conditionals General-purpose Turing complete languages need some way of controlling conditional execution of code, and Convex Lisp is no exception. @@ -462,6 +481,8 @@ The `cond` special form works like `if`, but allows multiple tests, and can opti Implementation note: `if` is actually a macro that expands to a `cond` special form. So technically, `cond` is the lower level special form. In practice, it may be more convenient and intuitive to use `if`. Your choice, as always! +## Execution flow + ### Sequential `do` blocks The `do` special form groups a number of expressions into a single expression and returns the value of the last expression (or `nil` if there are zero expressions). Results from earlier expressions are discarded. @@ -475,7 +496,7 @@ The `do` special form groups a number of expressions into a single expression an (do) => nil -;; Side effects from ealier expressions are visible in later expressions +;; Side effects from earlier expressions are visible in later expressions (do (def a 100) (+ a a)) => 200 ``` @@ -523,7 +544,7 @@ You can also use `set!` to modify the binding of a local variable. This value wi => 20 ``` -#### Def and the environment +### Def and the environment We've already seen the `def` special form in a couple of examples, where it was used to set the value of a symbol: @@ -555,7 +576,7 @@ If you want to define functions specifically, you can use `defn`: `defn` is actually a simple macro that converts `(defn f [x] ...)` into `(def f (fn [x] ...)`. So you never really need `defn`: it's just a convenient shortcut for defining functions and can make your code more readable. -#### Loop and recur +### Loop and recur When you want to iteratively re-evaluate an expression, you can use `loop` and `recur`. @@ -585,7 +606,7 @@ You can also use `recur` to repeat the evaluation of a function body: `recur` implements "tail call optimisation", i.e. it recurs without consuming any stack space. This is important if you want to perform many iterations: stack depth on the CVM is a limited resource and your transactions will fail if you consume too much. `recur` is your friend. -#### Quoting +## Quoting Sometimes, you want to use a symbol itself rather than the thing that the symbol refers to. In these cases, you can 'quote' the symbol. diff --git a/docusaurus.config.ts b/docusaurus.config.ts index b04e7fa..fb5cb05 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -67,7 +67,11 @@ const config: Config = { themeConfig: { // Replace with your project's social card - image: 'img/docusaurus-social-card.jpg', + image: 'img/Convex.png', + tableOfContents: { + minHeadingLevel: 2, + maxHeadingLevel: 3, + }, navbar: { title: 'Convex Docs', logo: { diff --git a/index.tsx b/index.tsx index 8f4cc9a..0a8205a 100644 --- a/index.tsx +++ b/index.tsx @@ -19,8 +19,8 @@ function HomepageHeader() {
- Convex Intro + to="/docs/overview/concepts"> + Convex Overview
diff --git a/papers/convex-whitepaper.pdf b/papers/convex-whitepaper.pdf new file mode 100644 index 0000000..1d1fd4b Binary files /dev/null and b/papers/convex-whitepaper.pdf differ diff --git a/static/img/docusaurus-social-card.jpg b/static/img/docusaurus-social-card.jpg deleted file mode 100644 index ffcb448..0000000 Binary files a/static/img/docusaurus-social-card.jpg and /dev/null differ