We propose to remove the limitation on the complexity of a callable function of a dApp script. For this purpose, we suggest adding a new transaction type: Continuation. If the complexity exceeds 4000, the execution of the callable function is split into several stages. The first stage of calculations is performed within the Invoke Script transaction; the further stages are performed within Continuation transactions created automatically by block generators. The sender of the Invoke Script transaction should specify a transaction fee sufficient for the estimated number of stages.
Motivation and purposes
At the moment, a single callable function can perform calculations with a complexity of up to 4000. To implement a more complex logic, a developer is forced to decompose it into several transactions manually.
The idea is to split a large execution into multiple transactions and blocks automatically by the blockchain’s execution environment. The computation cost in each transaction remains predictable, and heavy calculations do not lock the blockchain. A more detailed motivation is given in the Unlimited dApp script complexity via Continuations article.
A block generator that adds an Invoke Script transaction to a microblock performs calculations with a total complexity up to 4000 and saves intermediate results in the internal database. Further, the block generator, the same or another if a new key block is already created, detects an uncompleted calculation sequence, creates a Continuation transaction, and adds it to the microblock, performing the next stage of calculations. The process continues until the script is completely executed or fails.
Suspension of other transactions involving dApp
Until the calculation sequence is completed, transactions that can reduce the dApp balance can be added to the UTX pool but cannot be added to the block:
- Transactions that are sent on behalf of the dApp.
- Transactions that invoke the dApp.
- Exchange transactions in which the dApp is one of the order senders.
- Transactions with a fee in a sponsored asset issued by dApp.
Meanwhile, any transfers in favor of dApp, leases to dApp, and cancellations of leases to dApp are allowed.
Blockchain data usage
During all the stages of calculations, the dApp script operates the same blockchain data.
1) Entries of dApp’s own data storage
Due to the suspension of other transactions involving the dApp (see above), the dApp’s data storage entries do not change from the start of the script execution until the end. We add new built-in functions to Ride that allow the dApp to read data from its own data storage:
- getBinary(key: String): ByteVector|Unit
- getBinaryValue(key: String): ByteVector
- getBoolean(key: String): Boolean|Unit
- getBooleanValue(key: String): Boolean
- getInteger(key: String): Int|Unit
- getIntegerValue(key: String): Int
- getString(key: String): String|Unit
- getStringValue(key: String): String
The dApp script can call these functions at any stage, that is, in any transaction of the calculation sequence.
2) External data
All the blockchain data, except for the dApp’s data storage entries, are considered external. External blockchain data include:
- Entries of data storages of other accounts.
- Account balances, including the dApp account itself.
- Current blockchain height.
- Asset parameters, block headers, transactions, etc.
All external blockchain data used by the dApp script must be obtained at the first stage of calculations. The complexity of the script’s part from the beginning to the last function that reads external data, inclusive, should not exceed 4000.
If the script contains branches, it is not known in advance which branch will be executed. Therefore, the total complexity for all branches is taken into account. Consider the following scheme:
If the operations
op-b3 read the external blockchain data, then the total complexity of the operations
op-b3 must not exceed 4000, otherwise the dApp script cannot be assigned to an account (Set Script transaction would be rejected).
Continuation is a new transaction type designed to execute a callable function of a dApp script in several stages. A block generator creates the Continuation transaction if there is an uncompleted calculation sequence. A user cannot send a Continuation transaction, so it does not have the
New version of Invoke Script transaction; extraFeePerStep
Invoke Script transaction version 3 contains the
extraFeePerStep field that indicates the extra fee for each stage of calculations.
extraFeePerStep can be 0. The sender can specify
extraFeePerStep > 0 in order to raise the processing priority of transactions of the calculation sequence.
The sender should specify a fee taking into account the maximum possible number of calculation stages and script actions.
The minimum fee in WAVES for an Invoke Script transaction is calculated as follows:
Fee = (0.005 + E) × ⌈С / 4000⌉ + S + 0.004 × P + 0.004 × A + 1 × I,
E is the extra fee specified in the
С is the complexity of the callable function.
С/4000 rounded up to the nearest integer is the number of stages in the calculation sequence.
S = 0.004 if the transaction sender is a dApp or a smart account, otherwise 0.
P is the number of payments in smart assets attached to the transaction.
A is the number of script actions (transfers, reissues, burnings) with smart assets.
I is the number of issued assets that are not NFT.
The entire fee amount specified in the transaction is charged to the sender when the Invoke Script transaction is added to a microblock. If the fee is indicated in the sponsored asset, the sponsor will be charged the fee equivalent in WAVES.
The fee is distributed as follows:
- For the Invoke Script transaction: 0.005 +
- For each Continuation transaction except the last one: 0.005 +
- For the last transaction of the continuation: 0.005 +
E+ 0.004 ×
P+ 0.004 ×
A+ 1 ×
(By the Waves-NG protocol, the generator of the block to which the transaction is added receives 40% of the transaction fee, and the next block generator receives 60% of the fee.)
After the script is completely executed or fails, the fee’s unused portion (for stages and asset scripts whose execution was not started; see example below) is returned to the sender. If the fee is indicated in the sponsored asset, the WAVES equivalent of this portion of the fee is returned to the sponsor.
Note: the threshold for saving failed transactions is applicable only to Invoke Script transactions. If the script fails at one of the subsequent stages, the Continuation transaction is saved on the blockchain, and a fee is charged for it.
Script execution with continuation is only available if the dApp script uses Standard library version 5 and is called by the Invoke Script transaction version 3. In the earlier version of Standard library or Invoke Script transaction, the limit of 4000 complexity is applied.
Examples and implementation
Distribution of complexity between stages
Consider the complexity of the callable function is 10,000.
1st stage: the block generator that processes the Invoke Script transaction stops script execution before the total complexity exceeds 4000. For example, at complexity 3900, if the next operation complexity is 120.
2nd stage: the block generator that processes the Continuation transaction stops script execution before the total complexity exceeds 8000. For example, at complexity 7950, if the next operation complexity is 80. The complexity of the stage is 7950 – 3900 = 4050.
3rd stage: the block generator that processes the next Continuation transaction executes the remaining operations with a complexity of 10,000 – 7950 = 2050.
Distribution of fee
Consider the previous example, supposing that:
- The transaction sender is a smart account:
- The Invoke Script Transaction contains 1 payment in a smart asset:
- The callable function performs 2 smart asset transfers and 1 token issue:
A= 2 and
- The transaction sender specifies
The minimum fee for the Invoke Script transaction is (0.005 + 0.002) × 3 + 0.004 + 0.004 × 1+ 0.004 × 2 + 1 = 1.037 WAVES. If all transactions are successful, the fee will be distributed as follows:
- for the Invoke Script transaction: 0.005 + 0.002 + 0.004,
- for the first Continuation transaction: 0.005 + 0.002,
- for the last Continuation transaction: 0.005 + 0.002 + 0.004 × 1 + 0.004 × 2 + 1.
Note: if the sender specifies the transaction fee more than the minimum of 1.037 WAVES, the remainder will be returned to them even if all stages are successfully completed.
If the Invoke Script transaction fails after the calculations’ complexity exceeds the threshold for saving failed transactions, the sender pays 0.005 + 0.002 + 0.004 = 0.011 WAVES. The unused fee of 1.026 WAVES is returned to the sender.
If the first Continuation transaction fails, then the sender pays (0.005 + 0.002) × 2 + 0.004 = 0.018 WAVES. The unused fee of 1.019 WAVES is returned.
If the last Continuation transaction fails:
- If the callable function execution fails, then the sender pays (0.005 + 0.002) × 3 + 0.004 = 0.025 WAVES. The unused fee of 1.012 WAVES is returned.
- If the callable function result is successfully calculated, but one of the asset scripts denied the transaction, the sender also pays 0.004 for each asset script actually executed. For example, if the first asset script returned
true, the second asset script returned
falseor failed, and the third asset script has not started execution, then the sender pays (0.005 + 0.002) × 3 + 0.004 + 0.004 × 2 = 0.033 WAVES. The unused fee of 1.004 WAVES is returned.