Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement fine-grained control over time #87

Closed
p-offtermatt opened this issue Nov 7, 2023 · 2 comments · Fixed by #88
Closed

Implement fine-grained control over time #87

p-offtermatt opened this issue Nov 7, 2023 · 2 comments · Fixed by #88
Assignees
Labels
enhancement New feature or request prio:shouldhave For high priority issues that we *should* have because they are major outcomes

Comments

@p-offtermatt
Copy link
Member

p-offtermatt commented Nov 7, 2023

TL;DR Summary

To allow more control over time, I propose introducing some flags:
--starting-timestamp to set the timestamp of the first block
(--starting-timestamp-from-genesis to take the timestamp from the Genesis file instead)
--block-time to determine how timestamps in blocks increase from one block to the next
--block-production-interval to determine how often CometMock produces blocks automatically
--auto-tx to determine whether CometMock produces a new block when a tx is broadcasted or not

Goal

The goal is to enable use cases where time needs to be controlled precisely, i.e.
where we control precisely the block timestamps.

This can be split into separate features:

Feature 1: Decoupling the initial timestamp from the system time

This feature is straightforward: There should be a way to set the initial timestamp completely independently from the system time.

This will be implemented by adding a new CLI flag, --starting-timestamp.
The default value is time.Now() at startup of CometMock.

Alternatively, one can also opt to use the Genesis time for the first block
with the --starting-timestamp-from-genesis flag.

Feature 2: Decoupling the time advancement from the system clock

This feature is more complex. I see that we want three modes for timestamps:
Timestamps increase at the same rate as the system clock
With this mode, the exact block timestamps depend on how fast blocks are processed by the app, networking delays, ...
This is the mode that is currently in place, and where the interval for blocks can be set by using --block-time, e.g. --block-time 5 makes it so that CometMock sleeps for 5 seconds in between building blocks, and bases the timestamp on the current time (shifted by any time advancements that were made).

Timestamps increase at a fixed rate
This is similar to the last mode, but now block timestamps do not depend on how fast blocks are processed by the app, but rather, block timestamps are always advanced by the fixed rate, no matter how much the system clock advances between blocks (but the user may shift timestamps by advancing time).

Timestamps only increase based on user inputs
This is a subcase of Timestamps increasing at a fixed rate, namely the case where the rate is 0.
Timestamps only increase when the user tells them to, and this (together with decoupling the initial time)
gives complete, precise control over the timestamps of blocks.

Feature 3: (Give an option to) Decouple block production from transaction broadcasting

Currently, CometMock immediately produces a new block when a transaction is broadcast.

This does not work well when more precise control over blocks is needed.
In particular, it does not work well with fixed-rate increasing timestamps because when many transactions are submitted (and each produces a new block), the block time would rapidly increase, which could e.g. make ibc connections go out of sync.

Implementation

I propose the addition of three new flags for the cometmock start command, and to change the block-time flag:

--auto-tx {true | false}

If true, when a tx is set to be broadcasted, a new block is immediately created which contains just this transaction.

If false, all transactions that are broadcasted are kept in a queue, and whenever a new block is created for any reason (either by explicitly calling the advance_blocks endpoint, or with the appropriate flags, by system time passing), all transactions in the queue are included in the block.

(There might be good reason to not include all transactions from the queue, but rather to only include transactions up to a certain byte limit).

Defaults to false.

auto-tx=true is CometMocks current behaviour.

--block-time {time_in_milliseconds}

This flag already exists, but I would propose to change its semantics. Currently, block-time governs how long CometMock sleeps before creating a new block, and the block timestamps are taken from local time.

I would instead change this as follows: When a new block is created, its timestamp is

new_block_timestamp = last_block_timestamp + time_advancement_since_last_block + block-time

where last_block_timestamp is the timestamp of the last block created by CometMock, time_advancement_since_last_block is the sum of time that CometMock was told to advance time by with calls to advance_time.

This means block-time would not govern how often blocks are produced, but only the timestamps of blocks.

If block-time is below 0, the system clock will be used as a reference for block timestamps, so the above equation would become

new_block_timestamp = system_time_when_block_is_created + time_advancements + starting_offset

where starting_offset is the offset as calculated from the starting_timestamp.

Defaults to 1 second.

Current behaviour is block-time=0.

--block-production-interval {time_in_milliseconds}

This flag will take on the previous role of the block-time flag.

Thus, it governs how often blocks are produced without user intervention. Precisely, it is how long CometMock sleeps after it finished automatically producing one block, before starting to produce the next. This depends on system time.

Setting this to -1 means blocks will not be produced automatically, and will only be produced when advance_blocks is called, or (when auto-tx is true) a tx is broadcasted.

Defaults to 1 second.

Current behaviour is that this flag can be set, but is currently called block-time.

User stories

  1. Alice wants to have a test that is close to how a real, local testnet looks, but she wants to skip voting periods and be able to produce many empty blocks.

She can run with these settings:

cometmock [...] --block-time -1 --block-production-interval 1000 --auto-tx true

and her test run could look like this:

Block Number System time in ms since start Block timestamp in ms since initial timestamp Note
1 0 0
2 1006 1006
3 2102 2102 CometMock sleeps by 1s between producing blocks automatically, so duration between blocks can vary slightly
Alice calls advance_time to advance by 5500 ms
4 3106 8606 Block timestamp is increased, but no time passes in reality (system time just continues as normal
5 4156 9656 Still producing one block per real-time second
Alice broadcasts a transaction immediately after block 5 is produced, which produces a new block since auto-tx is true.
6 4164 9664
7 5167 10667 Block production continues as normal, 1 block per second
  1. Bob wants a reliable way to test his application, which hashes something based on the block timestamps. He needs blocks to have the same sequence of timestamps every time he runs his tests. He doesn't mind which exact block transactions go into, he cares just that the block timestamps are right, and that some transactions can occur.

Bob can run with these settings

cometmock [...] --block-time 1000 --block-production-interval 1000 --auto-tx true --starting-timestamp 0

then Bobs test run could look like this:

Block Number System time in ms since start Block timestamp in ms since initial timestamp Note
1 0 0
2 1006 1000
3 2102 2000 Block timestamps are regular, even if system time for each block can vary a bit
4 3106 3000
Bob broadcasts a transaction right after block 4 is procued
5 3109 4000 Because auto tx is true, a new block is produced immediately after block 4 in terms of system time, but because block-time is set, it still advances the block time precisely by 1 second
...
  1. Charlie wants to test some edge cases in the liquidation code of his lending smart contract. He needs to test what happens when a loan is liquidated precisely 11111 milliseconds after the loan was initiated, and afterwards in the same block someone else takes a loan.

He can run with settings like this:

cometmock [...] --block-time 0 --block-production-interval -1 --auto-tx false

--block-production-interval -1 meaning no blocks will be produced automatically.
--block-time 0 meaning timestamps will not increment automatically.

Then his test run can look like this:

Block Number System time in ms since start Block timestamp in ms since initial timestamp Note
1 0 0 The first block is still produced, even if block-production-interval is -1
Charlie broadcasts a transaction (Setup the lending contract), then calls advance_time to advance by 200 milliseconds, followed by advance_blocks 1 to produce 1 block
2 N/A 200 System time doesn't really matter, depends just on how quickly Charlie proceeds, not on CometMock
Charlie broadcasts a transaction (Take loan), calls advance_time to advance by 500 milliseconds, followed by advance_blocks 1
3 N/A 700
Charlie broadcasts transactions (Liquidate loan, Take loan), then calls advance_time to advance by 11111 ms, followed by. advance_blocks 1
4 N/A 11811
...
@p-offtermatt p-offtermatt added enhancement New feature or request prio:shouldhave For high priority issues that we *should* have because they are major outcomes labels Nov 16, 2023
@p-offtermatt p-offtermatt self-assigned this Nov 16, 2023
@mmulji-ic
Copy link

mmulji-ic commented Nov 22, 2023

With longer running tests would a jump-to-block api be useful?
A use case would be where we want to run multiple proposals that are spread out over time and inspect state between intervals.

@p-offtermatt
Copy link
Member Author

What would the semantics be?

I think there are endpoints that can do it already, namely advance_time and advance_blocks https://github.com/informalsystems/CometMock#cometmock-specific-rpc-endpoints

Let me know if you think those are not sufficient for a certain usecase

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request prio:shouldhave For high priority issues that we *should* have because they are major outcomes
Projects
None yet
2 participants