Blocks
There are several cases when testing CosmWasm smart contracts requires simulating delays on the blockchain, like:
- staking and unstaking processes,
- governance voting periods,
- validator set changes,
- token vesting schedules,
- incentive distribution, or
- IBC packet timeouts.
Operations on blocks
MultiTest
provides several operations to initialize and update current block properties, such
as height and timestamp, in order to test delays on the blockchain.
Although it is possible to modify only the height or only the timestamp of the block in
MultiTest
, this is not feasible in a real-life blockchain and may lead to unpredictable test
results that do not reflect the blockchain's real-life behavior.
Always increment the height and the timestamp of the block in tests.
In the following chapters, you will find practical examples of several operations on blocks, such as block initialization and generating a new block.
Initialization with the default block
In MultiTest
, the chain is initialized with a default block that has the following properties:
height
: 12345,time
: 1571797419879305533 (Wed Oct 23 2019 02:23:39 GMT+0000),chain_id
: cosmos-testnet-14002.
These properties constitute the BlockInfo
(opens in a new tab) structure, which is defined in the
CosmWasm library for representing block metadata. BlockInfo
(opens in a new tab) can be retrieved
at any point in a test by calling the block_info()
(opens in a new tab) function provided by
App
.
The following example illustrates this case in detail.
use cw_multi_test::App;
// create the default chain simulator
let app = App::default();
// get the initial block properties
let block = app.block_info();
// default block height is 12345
assert_eq!(12345, block.height);
// default block timestamp is Wed Oct 23 2019 02:23:39 GMT+0000
assert_eq!(1571797419879305533, block.time.nanos());
// default chain identifier is "cosmos-testnet-14002"
assert_eq!("cosmos-testnet-14002", block.chain_id);
In line 4, the chain is initialized and started with the default settings by calling the function
default()
on App
. In line 7, the current block metadata (of type
BlockInfo
(opens in a new tab)) is retrieved and assigned to variable block
. In the next
few lines, the default values are checked:
- line 10: block
height
is12345
, - line 13: block
time
is a Unix timestamp of value1571797419879305533
, which is the numeric representation of Wednesday, October 23, 2019 02:23:39 GMT, - line 16: block
chain_id
has a default value"cosmos-testnet-14002"
.
Initialization with the custom block
Although the chain initialization with the default block may be suitable for most test cases, it is
possible to initialize the chain with a custom BlockInfo
(opens in a new tab) using the
set_block()
(opens in a new tab) function provided by App
. The following example explains
this case in detail.
use cosmwasm_std::{BlockInfo, Timestamp};
use cw_multi_test::App;
// create the default chain simulator
let mut app = App::default();
// create and use a custom block
app.set_block(BlockInfo {
height: 1,
time: Timestamp::from_seconds(1723627489),
chain_id: "starship-testnet".to_string(),
});
// get the custom block properties
let block = app.block_info();
// now the block height is 1
assert_eq!(1, block.height);
// now the block timestamp is Wed Aug 14 2024 09:24:49 GMT+0000
assert_eq!(1723627489, block.time.seconds());
// now the chain identifier is "starship-testnet"
assert_eq!("starship-testnet", block.chain_id);
The chain is started with the default settings in line 5. Then in line 8, the
set_block()
(opens in a new tab) function is called with custom block properties provided as
BlockInfo
(opens in a new tab) structure. The current block metadata is retrieved in line 12 and in
the next few lines the detailed values are checked:
- line 18: block
height
is now1
, - line 21: new block
time
is a Unix timestamp of value1723627489
, which is the numeric representation (in seconds) of Wednesday, August 14, 2024 09:24:49 GMT, - line 24: block
chain_id
has now a value"starship-testnet"
.
This way, you can initialize the current block in tests to best suit your requirements.
Generating next block with the default step
To generate a new block in tests, you can use the update_block()
(opens in a new tab) function
provided by App
. In the following example, update_block()
(opens in a new tab) function
takes a function next_block()
(opens in a new tab) as an argument.
next_block()
(opens in a new tab) function - provided by MultiTest
- increases the block
height by 1
and advances the block time by 5
seconds, simulating the generation of a new block
every 5
seconds in a real-life blockchain. The chain_id
remains unchanged.
use cw_multi_test::{next_block, App};
// create the default chain simulator
let mut app = App::default();
// update the block by `generating` next block
app.update_block(next_block);
// get the current block properties
let block = app.block_info();
// now the block height is 1 after the initial value
assert_eq!(12346, block.height);
// now the block timestamp is 5 seconds after the initial value
// current value is: Wed Oct 23 2019 02:23:44 GMT+0000
// initial value was: Wed Oct 23 2019 02:23:39 GMT+0000
assert_eq!(1571797424879305533, block.time.nanos());
// chain identifier remains unchanged
assert_eq!("cosmos-testnet-14002", block.chain_id);
In line 7 the update_block()
(opens in a new tab) function is called and in the next few lines
the current block properties are checked. In line 10 the block metadata is retrieved and assigned to
variable block
. In line 13, we check if the block height has changed. The default block
height is 12345
, and we check it against the value 12346
. As this assertion
passes, you can see that the block height was indeed incremented by 1
. Similarly, the block's time
is checked against the Unix timestamp, which is 5 seconds later than the default value:
1571797424879305533
- 1571797419879305533
= 5000000000
= 5 seconds.
Generating next block with the custom step
Several events on the blockchain occur after a period longer than a few seconds. In such cases,
incrementing the block time multiple times until the desired time is reached would be impractical.
In MultiTest
, it is possible to advance the block using a specific block height and time. To
achieve this, pass a custom closure to the update_block()
(opens in a new tab) function. This
closure takes a mutable BlockInfo
(opens in a new tab) structure as an argument and modifies its
properties, as shown in the example below.
use cw_multi_test::App;
// create the default chain simulator
let mut app = App::default();
// 'generate' custom block
app.update_block(|block| {
block.time = block.time.plus_days(6);
block.height += 10000;
});
// get the block properties after updating
let block = app.block_info();
// now the block height is 10000 after the initial value
assert_eq!(22345, block.height);
// now the block timestamp is 6 days after the initial value
// current value is: Wed Oct 23 2019 02:23:44 GMT+0000
// initial value was: Tue Oct 29 2019 02:23:39 GMT+0000
assert_eq!(1572315819879305533, block.time.nanos());
// chain identifier remains unchanged
assert_eq!("cosmos-testnet-14002", block.chain_id);
In line 4, the default block is initialized during the chain start. Then in line 7, the
update_block()
(opens in a new tab) function takes a custom closure as an argument. This closure
increments the block height by 10000 and advances the block time by 6 days. Similarly like
in prevous examples, the new block metadata is retrieved and the current properties are checked. The
new block height should be 12345
+ 10000
= 22345
. The new block time
should be 1572315819879305533
-1571797419879305533
= 518400000000000
=
518400 seconds = 8640 minutes = 144 hours = 6 days. The chain identifier remains unchanged.