Developer Improvements in go-algorand 3.16
Alongside decreasing round times and improving Smart Contract capabilities , go-algorand 3.16 introduces several significant developer-facing improvements:
- The ability to control the block timestamp when in devmode.
- Two new options when running transactions using simulate:
- increase the amount of logging allowed for the smart contracts.
- increase the amount of opcode budget allowed for the smart contracts.
- New algod endpoints that return the changes to the ledger as a result of individual transaction groups.
Devmode Timestamp Control
Refresher on Devmode
Devmode is a staple of dapp testing on Algorand, and is turned on by default when running a local network using sandbox. When running a devmode-enabled network, a new block is produced every time a transaction is submitted to the network. Devmode allows tests that require many transactions in separate blocks to run nearly instantly.
Refresher on Block Timestamp
Timestamps on distributed networks cannot be perfectly accurate. On Algorand, a block’s timestamp can be at most 25 seconds further into the future than the previous block timestamp, and cannot be in the past from the previous block timestamp.
The block timestamp could theoretically be more than 25 seconds in the future compared to current time, if block proposers submit multiple blocks in a row with time off from current time.
Block timestamp is available from within a smart contract using block 1 (block BlkTimestamp).
A new API for block timestamp in devmode
We are now introducing a new feature to devmode-enabled networks: an API for controlling the block timestamp.
/v2/devmode/blocks/offset/{offset-seconds}
This will allow developers to easily test smart contracts with logic that depends on time. For example, a smart contract that calculates interest over time. Or, a lottery that ends at a certain date.
The new API works by setting the offset of time between all future consecutive blocks. For example, setting the offset to 500 means that timestamp(block x+1) = timestamp(block x) + 500 seconds.
When not using this API, the block timestamp defaults to current system time. This offset API will only work in devmode!
Example using curl
# Set a timestamp offset of 1000 seconds
> curl -X POST your-algod/v2/devmode/blocks/offset/1000
# Returns OK
# Get current offset
> curl your-algod/v2/devmode/blocks/offset
# Returns 1000
# ... send some blocks
# Query block X
> curl your-algod/v2/blocks/X | grep "ts"
"ts": 175589 # time.Now()
# Query block X+1, ts delta is 1000
> curl your-algod/v2/blocks/X+1 | grep "ts"
"ts": 176589 # previous ts + 1000
Example using the python SDK
algod_client = get_algod_client()
# Set and get offset (must be in devmode)
algod_client.set_timestamp_offset(10000)
print(f"Getting offset: {algod_client.get_timestamp_offset()}")
Simulate With More
More Logging with Simulate
When running a smart contract on Mainnet, there are limitations to how much can be logged. Specifically, one smart contract can make at most 32 separate log entries per app call, logging a total of at most 1024 bytes.
Logs are a useful debugging tool, and many developers would like to log a lot more than that during development and testing, as well as for large simulated (read-only) app calls. We thus introduce the ability to log more when using simulate (if you missed the recent release of simulate, get an introduction here).
For example, if submitting a transaction to the simulate endpoint using goal, use the allow-more-logging
flag:
goal clerk simulate --allow-more-logging -t example_transaction.tx
This will allow smart contracts to make up to 2048 log calls, and log a total of up to 65536 bytes.
Another example, using the Atomic Transaction Composer in the python SDK.
atc.simulate(client, SimulateRequest(txn_groups=[], allow_more_logs=true, extra_opcode_budget=15000)
Here we have enabled more logs and more opcode budget (see next section!) for this simulate call.
More Opcode Budget with Simulate
A single smart contract on Mainnet can use at most 700 opcode budget. However, the opcode budget is pooled across the transaction group, so it has become a common pattern to create “dummy” app calls just to increase opcode budget for the current smart contract being executed. We call this pattern “opup”.
This pattern is useful, but presents the challenge of knowing exactly how much opcode budget is needed, which can differ between calls to the smart contract if there is branching logic.
When running a transaction using simulate, developers can directly ask for the opcode budget they would like to use. They can either specify exactly how much, using the extra-opcode-budget
flag, or ask for the maximum allowed, allow-extra-opcode-budget
. The maximum is 320,000 per transaction group.
goal clerk simulate --allow-extra-opcode-budget -t example_transaction.tx
goal clerk simulate --extra-opcode-budget 2100 -t example_transaction.tx
These simulate calls will now also return, for each transaction in the group, how much opcode budget it consumed. This is the field app-budget-consumed
, reported at the transaction level and for the whole transaction group.
As a reminder, the increase in opcode budget can be observed from within a smart contract by using global 13
in TEAL (global OpcodeBudget
).
Optimizing opup usage
This new simulate option not only simplifies simulate testing workflows, but also presents a new production-time workflow to optimize fee usage:
- Given a transaction group you’d like to submit to the network:
- First, simulate your transaction group without any opup calls, with the max extra budget flag enabled. This simulate call will tell you exactly how much opcode budget you need for the transaction group to succeed on Mainnet.
- Then, add an app call transaction to the group with as many inner app calls as needed to cover the opcode budget.
- Submit this new transaction group to Mainnet.
With this method, you are likely to use the minimum number of opup calls necessary, thereby minimizing your transaction fees (for opcode budget needs, at least). The usual caveat with simulate applies: if the state of the chain changes between your simulate call and your execution on Mainnet, you may get a different result.
Transaction Group Updates
It’s currently straightforward to get state changes from one block to the next, but it can be tricky to isolate the effects of an individual transaction (or transaction group). Any dapp seeking to report on transactions involving an address and balances after each transaction (just like a bank account summary report) will need this information, and there has historically been no direct way to get this information.
Go-algorand 3.16 introduces two new algod endpoints for this purpose. This first endpoint takes as input the round number, and returns the set of all transaction groups ID’s in that round along with each of their state updates.
/v2/deltas/{round}/txn/group
The second endpoint takes as input a transaction ID or group ID and returns the state updates for the corresponding transaction group.
/v2/deltas/txn/group/{id}
Transaction group state updates are the set of updates made to the ledger as a result of a single transaction group. These include changes made to apps, accounts, key value stores, etc., and are meant to be comprehensive for all data stored in the ledger across past and future protocol versions.
Note that depending on node configurations, this endpoint will only be able to return the transaction group deltas for the last few rounds. You may consider configuring your node to use follower or archival mode if you will use this endpoint for production use cases. Another relevant configuration option is the MaxAccountLookback
, though this affects multiple cache sizes in the node.
As always, don’t hesitate to reach out on github or Discord with any questions or feedback!