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

feat(sandbox): fast forward timestamp and epoch height #6211

Merged
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
f9aa23f
Added fast-forwarding to sandbox
ChaoticTempest Jan 21, 2022
232e901
Changed name to delta_height for better clarity
ChaoticTempest Jan 21, 2022
c9f9492
Remove print statements
ChaoticTempest Jan 21, 2022
85eb896
Cargo format
ChaoticTempest Jan 21, 2022
b70475a
Merge branch 'master' of https://github.com/near/nearcore into phuong…
ChaoticTempest Jan 21, 2022
9b0d8ed
Added sandbox fast forward test
ChaoticTempest Jan 21, 2022
f5b1eb1
Move import into inplaced location as full path
ChaoticTempest Jan 21, 2022
691a2dc
Move other sandbox imports into inplaced locations
ChaoticTempest Jan 22, 2022
f35d1a5
Added forwarded timestmaps for sandbox after fast-forwarding
ChaoticTempest Jan 27, 2022
31a9397
Change visibility of fastforward delta
ChaoticTempest Jan 27, 2022
0a4631b
Update epoch_height based on block info for sandbox
ChaoticTempest Jan 27, 2022
d7370d0
Correct timestamp after each block produced after forwarding
ChaoticTempest Jan 28, 2022
671350d
Added integration test for timestamp
ChaoticTempest Jan 28, 2022
a001fd2
Cargo fmt
ChaoticTempest Jan 28, 2022
6cb23f0
Added overflow panic for sandbox_delta_time
ChaoticTempest Jan 28, 2022
f6b18e6
Merge branch 'master' of github.com:near/nearcore into phuong/sandbox…
ChaoticTempest Feb 3, 2022
df6fa4e
Fix merge conflict typo
ChaoticTempest Feb 4, 2022
1eec9d2
Use accrued delta height to update timestamps
ChaoticTempest Feb 4, 2022
f8cc241
Update fast_forward pytest w/ time travel assertions
ChaoticTempest Feb 4, 2022
cc3872c
Merge branch 'master' of github.com:near/nearcore into phuong/sandbox…
ChaoticTempest Feb 4, 2022
61e168a
Fix formatting and another merge that slipped in
ChaoticTempest Feb 4, 2022
15836ad
Address comments
ChaoticTempest Feb 5, 2022
864a20d
Correct typo & use wait_for_blocks
ChaoticTempest Feb 7, 2022
f9a507c
Merge branch 'master' of github.com:near/nearcore into phuong/sandbox…
ChaoticTempest Feb 7, 2022
17a4518
Removed redundant test
ChaoticTempest Feb 8, 2022
b7d6fb4
Merge branch 'master' of github.com:near/nearcore into phuong/sandbox…
ChaoticTempest Feb 8, 2022
5d2de98
Update epoch height calculation & add unit test
ChaoticTempest Feb 11, 2022
b66850d
Merge branch 'master' of https://github.com/near/nearcore into phuong…
ChaoticTempest Feb 11, 2022
8adb77f
Added block hash fix and added in more pytest checks
ChaoticTempest Mar 16, 2022
0158ad1
Merge branch 'master' of https://github.com/near/nearcore into phuong…
ChaoticTempest Mar 16, 2022
3e4f833
Fix typo
ChaoticTempest Mar 16, 2022
79e1a25
Added new way to fast forward wrt epoch boundaries
ChaoticTempest Mar 24, 2022
427652f
Added timeout for fast-forward to complete
ChaoticTempest Mar 24, 2022
eab87db
Added epoch boundary test
ChaoticTempest Mar 24, 2022
07775d7
Merge branch 'master' of https://github.com/near/nearcore into phuong…
ChaoticTempest Mar 24, 2022
6a1903d
Remove sandbox feature from epoch_manager
ChaoticTempest Mar 24, 2022
39f429b
Remove outdated integration test
ChaoticTempest Mar 24, 2022
7bc2983
Merge branch 'master' of https://github.com/near/nearcore into phuong…
ChaoticTempest Mar 24, 2022
bf98f33
Cleanup sandbox related code in chain/client.rs
ChaoticTempest Mar 25, 2022
832a604
Added large constant for sandbox acceptable time diff
ChaoticTempest Mar 28, 2022
7c53fdc
Cleanup and added pre/post_block_production methods
ChaoticTempest Mar 28, 2022
d6f3261
Cleaned up block produce logic
ChaoticTempest Mar 28, 2022
e3634c6
Merge branch 'master' of https://github.com/near/nearcore into phuong…
ChaoticTempest Mar 28, 2022
625e036
Added extra produce block argument
ChaoticTempest Mar 28, 2022
d9e674c
Some more cleanup
ChaoticTempest Mar 29, 2022
3e6aa18
Merge branch 'master' of https://github.com/near/nearcore into phuong…
ChaoticTempest Mar 29, 2022
7e0761d
Merge branch 'master' of https://github.com/near/nearcore into phuong…
ChaoticTempest Mar 30, 2022
98d1d50
Merge branch 'phuong/sandbox-fast-forward-timestamp-and-epoch-height'…
ChaoticTempest Mar 30, 2022
2a1ca7f
Adjusted raw timestamp type for Duration type
ChaoticTempest Mar 31, 2022
8170d09
Changed fastforward_delta to not be an Option type
ChaoticTempest Mar 31, 2022
66adcad
Updated to Duration type
ChaoticTempest Mar 31, 2022
83203a0
Merge branch 'master' of https://github.com/near/nearcore into phuong…
ChaoticTempest Mar 31, 2022
0d90208
Merge branch 'master' into phuong/sandbox-fast-forward-timestamp-and-…
ChaoticTempest Apr 1, 2022
c1a31d6
Merge master into phuong/sandbox-fast-forward-timestamp-and-epoch-height
near-bulldozer[bot] Apr 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions chain/chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4193,8 +4193,11 @@ impl<'a> ChainUpdate<'a> {
provenance: &Provenance,
on_challenge: &mut dyn FnMut(ChallengeBody),
) -> Result<(), Error> {
// Refuse blocks from the too distant future.
if header.timestamp() > Clock::utc() + Duration::seconds(ACCEPTABLE_TIME_DIFFERENCE) {
// Refuse blocks from the too distant future. Only exception to this is while we're within
// sandbox, we're allowed to jump to the future via fast forwarding.
if cfg!(feature = "sandbox")
ChaoticTempest marked this conversation as resolved.
Show resolved Hide resolved
&& header.timestamp() > Clock::utc() + Duration::seconds(ACCEPTABLE_TIME_DIFFERENCE)
ChaoticTempest marked this conversation as resolved.
Show resolved Hide resolved
{
return Err(ErrorKind::InvalidBlockFutureTime(header.timestamp()).into());
}

Expand Down
39 changes: 38 additions & 1 deletion chain/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ pub struct Client {
#[cfg(feature = "test_features")]
pub adv_produce_blocks_only_valid: bool,

/// Fast Forward accrued delta height used to calculate fast forwarded timestamps for each block.
#[cfg(feature = "sandbox")]
pub(crate) accrued_fastforward_delta: near_primitives::types::BlockHeightDelta,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, if only we did the work for virtualizing time, than we'd be able to express this in a much cleaner way, by just making the time go forward faster.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this was definitely a hack to circumvent that. Maybe we can revisit this in the future, but for now, this was pretty simple to do just for contract testing side of things


pub config: ClientConfig,
pub sync_status: SyncStatus,
pub chain: Chain,
Expand Down Expand Up @@ -172,6 +176,8 @@ impl Client {
adv_produce_blocks: false,
#[cfg(feature = "test_features")]
adv_produce_blocks_only_valid: false,
#[cfg(feature = "sandbox")]
accrued_fastforward_delta: 0,
config,
sync_status,
chain,
Expand Down Expand Up @@ -494,7 +500,8 @@ impl Client {
let next_epoch_protocol_version =
self.runtime_adapter.get_epoch_protocol_version(&next_epoch_id)?;

let block = Block::produce(
#[allow(unused_mut)] // unused if not in sandbox
let mut block = Block::produce(
this_epoch_protocol_version,
next_epoch_protocol_version,
prev_header,
Expand All @@ -516,6 +523,24 @@ impl Client {
block_merkle_root,
);

// If sandbox is enabled, set the fast-forwarded block timestamp to the produced block.
// This is current time + total amount of time forwarded from fast-forwarding.
#[cfg(feature = "sandbox")]
{
let header = match block {
Block::BlockV1(ref mut block) => &mut block.header,
Block::BlockV2(ref mut block) => &mut block.header,
};
let mut inner_lite = match header {
BlockHeader::BlockHeaderV1(ref mut header) => &mut header.inner_lite,
BlockHeader::BlockHeaderV2(ref mut header) => &mut header.inner_lite,
BlockHeader::BlockHeaderV3(ref mut header) => &mut header.inner_lite,
};

// Set block timestamp to the actual fast forwarded timestamp:
inner_lite.timestamp = to_timestamp(Clock::utc()) + self.sandbox_delta_time();
mina86 marked this conversation as resolved.
Show resolved Hide resolved
}

// Update latest known even before returning block out, to prevent race conditions.
self.chain.mut_store().save_latest_known(LatestKnown {
height: next_height,
Expand Down Expand Up @@ -960,6 +985,18 @@ impl Client {
Ok(())
}

/// Gets the advanced timestamp delta in nanoseconds for sandbox once it has been fast-forwarded
#[cfg(feature = "sandbox")]
pub fn sandbox_delta_time(&self) -> u64 {
ChaoticTempest marked this conversation as resolved.
Show resolved Hide resolved
let avg_block_prod_time = (self.config.min_block_production_delay.as_nanos()
+ self.config.max_block_production_delay.as_nanos())
/ 2;
ChaoticTempest marked this conversation as resolved.
Show resolved Hide resolved
(self.accrued_fastforward_delta as u128 * avg_block_prod_time).try_into().expect(&format!(
"Too high of a delta_height {} to convert into u64",
self.accrued_fastforward_delta
))
}

pub fn send_approval(
&mut self,
parent_hash: &CryptoHash,
Expand Down
4 changes: 3 additions & 1 deletion chain/client/src/client_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,9 +777,11 @@ impl ClientActor {

#[cfg(feature = "sandbox")]
let latest_known = if let Some(delta_height) = self.fastforward_delta.take() {
self.client.accrued_fastforward_delta += delta_height;
let timestamp_delta_ns = self.client.sandbox_delta_time();
let new_latest_known = near_chain::types::LatestKnown {
height: latest_known.height + delta_height,
seen: near_primitives::utils::to_timestamp(Clock::utc()),
seen: near_primitives::utils::to_timestamp(Clock::utc()) + timestamp_delta_ns,
};

self.client.chain.mut_store().save_latest_known(new_latest_known.clone())?;
Expand Down
9 changes: 7 additions & 2 deletions chain/client/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ use near_primitives::utils::MaybeValidated;

pub type PeerManagerMock = Mocker<PeerManagerActor>;

/// min block production time in milliseconds
pub const MIN_BLOCK_PROD_TIME: u64 = 100;
ChaoticTempest marked this conversation as resolved.
Show resolved Hide resolved
/// max block production time in milliseconds
pub const MAX_BLOCK_PROD_TIME: u64 = 200;

const TEST_SEED: RngSeed = [3; 32];
/// Sets up ClientActor and ViewClientActor viewing the same store/runtime.
pub fn setup(
Expand Down Expand Up @@ -284,8 +289,8 @@ pub fn setup_mock_with_validity_period_and_no_epoch_sync(
5,
account_id,
skip_sync_wait,
100,
200,
MIN_BLOCK_PROD_TIME,
MAX_BLOCK_PROD_TIME,
enable_doomslug,
false,
false,
Expand Down
1 change: 1 addition & 0 deletions chain/epoch_manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ near-store = { path = "../../core/store" }
near-chain-configs = { path = "../../core/chain-configs" }

[features]
sandbox = []
expensive_tests = []
protocol_feature_chunk_only_producers = ["near-primitives/protocol_feature_chunk_only_producers", "near-chain-configs/protocol_feature_chunk_only_producers", "near-chain/protocol_feature_chunk_only_producers"]
protocol_feature_fix_staking_threshold = ["near-primitives/protocol_feature_fix_staking_threshold"]
Expand Down
15 changes: 14 additions & 1 deletion chain/epoch_manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,8 @@ impl EpochManager {
let validator_stake =
epoch_info.validators_iter().map(|r| r.account_and_stake()).collect::<HashMap<_, _>>();
let next_epoch_id = self.get_next_epoch_id_from_info(block_info)?;
let next_epoch_info = self.get_epoch_info(&next_epoch_id)?.clone();
#[allow(unused_mut)] // unused mut when not in sandbox
let mut next_epoch_info = self.get_epoch_info(&next_epoch_id)?.clone();
self.save_epoch_validator_info(store_update, block_info.epoch_id(), &epoch_summary)?;

let EpochSummary {
Expand Down Expand Up @@ -410,6 +411,12 @@ impl EpochManager {
)
};
let next_next_epoch_config = self.config.for_protocol_version(next_version);
#[cfg(feature = "sandbox")]
{
*next_epoch_info.epoch_height_mut() =
block_info.height() / next_next_epoch_config.epoch_length;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you describe why this change is necessary? In general the relationship described here does not always hold.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this updates the epoch height to after fast-forwarding the block height. This was the only way I could feasibly update it since epoch_manager is separate from everything else. Also, what cases does this not hold for? This is just for sandbox so it might not be necessary to account for them

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what cases does this not hold for?

While it is guaranteed that the block height of the end of an epoch is at least epoch_length + the height of the block in the beginning of the epoch, there is no guarantee on the upper bound. In fact, it is possible for a block height in one epoch to be arbitrarily large. Could you let me know the reason for modifying epoch height? What guarantees do you need to have here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no guarantee on the upper bound. In fact, it is possible for a block height in one epoch to be arbitrarily large

This should be fine since when we receive a fast-forward request, it happens in the middle of an epoch and the newly forward block height would trigger updating the epoch-height at an arbitrary block height.

The guarantees needed for epoch height updates would be what you mentioned at the end of an epoch:

  • block-height lower bound: epoch_length + height of block in beginning of the epoch
  • block-height upper bound: unbounded

Could you let me know the reason for modifying epoch height?

This is needed because what I mentioned earlier in the other post about producing blocks and simulating those. So after producing X amount of blocks (fast-forwarding), the user of a contract would intuitively assume that the epoch height also got updated to reflect X blocks being produced.

Also, correct me on any of these things, but that's my thinking at least

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So after producing X amount of blocks (fast-forwarding), the user of a contract would intuitively assume that the epoch height also got updated to reflect X blocks being produced.

Actually this should be done automatically as long as block height is more than epoch_length + height of block in the beginning of the epoch. I don't think we need custom logic here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm interesting. But wouldn't that mean the epoch-height is only incremented by 1? There was only logic I saw that incremented the epoch-height by 1 when the next epoch was in view:

prev_epoch_info.epoch_height() + 1,

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Do you require it to be incremented by more than 1? If so, what is the use case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not so much that it needs to be incremented more than 1 right there, but the custom logic is what I'm saying allows for us to skip to the designated epoch height after fast forwarding.

Actually this should be done automatically as long as block height is more than epoch_length + height of block in the beginning of the epoch. I don't think we need custom logic here.

But now I'm not sure what you're referring to then with the above statement. Is there some other logic that would allow us to jump to the new epoch height after fast forwarding? I didn't see anything and epoch height remained the same as if fast-forwarding the block height never happened

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you just need to produce three more blocks to end the epoch, which could be implemented as part of the sandbox

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't need to produce more blocks to end the epoch though, but regardless I tested it without the custom logic and it's not producing what's expected after fast forwarding. I'm getting epoch height of 3, when it should be 503, after waiting for roughly 50-60 blocks to be produced. You can see for yourself with the following diff and running python3 pytests/tests/sandbox/fast_forward.py:

diff --git a/chain/epoch_manager/src/lib.rs b/chain/epoch_manager/src/lib.rs
index b54af9e47..b1406c930 100644
--- a/chain/epoch_manager/src/lib.rs
+++ b/chain/epoch_manager/src/lib.rs
@@ -411,18 +411,6 @@ impl EpochManager {
             )
         };
         let next_next_epoch_config = self.config.for_protocol_version(next_version);
-        #[cfg(feature = "sandbox")]
-        {
-            // explaination on calculation:
-            // (block_height / length) gives us epoch_height, but we index from 1, so +1 at the end.
-            // Note, this is a retroactive update for sandbox since we can receive fast-forward
-            // requests in the middle of an epoch. block_height is different based on when it gets
-            // called. When fast-forwarded, we need to retroactively adjust the epoch height, but
-            // with a normal `finalize_epoch`, it's the start of the epoch's next block height, so
-            // (height - 1) is needed after adding +1 at the end.
-            *next_epoch_info.epoch_height_mut() =
-                ((block_info.height() - 1) / next_next_epoch_config.epoch_length) + 1;
-        }
 
         let next_next_epoch_info = match proposals_to_epoch_info(
             next_next_epoch_config,
@@ -460,12 +448,6 @@ impl EpochManager {
         // where epoch_id of it is the hash of last block in this epoch (T).
         self.save_epoch_info(store_update, &next_next_epoch_id, next_next_epoch_info)?;
 
-        // Store next_epoch info again since sandbox changes it. Need to retroactively update
-        // since a fast-forward request can happen in the middle of an epoch.
-        if cfg!(feature = "sandbox") {
-            self.save_epoch_info(store_update, &next_epoch_id, next_epoch_info)?;
-        }
-
         Ok(())
     }
 
diff --git a/pytest/tests/sandbox/fast_forward.py b/pytest/tests/sandbox/fast_forward.py
index 569981ab3..87287fd40 100644
--- a/pytest/tests/sandbox/fast_forward.py
+++ b/pytest/tests/sandbox/fast_forward.py
@@ -61,4 +61,9 @@ assert min_forwarded_time < latest < max_forwarded_time
 
 # Check to see that the epoch height has been updated correctly:
 epoch_height = nodes[0].get_validators()['result']['epoch_height']
-assert epoch_height > BLOCKS_TO_FASTFORWARD / EPOCH_LENGTH
+# assert epoch_height > BLOCKS_TO_FASTFORWARD / EPOCH_LENGTH
+print(f"-> epoch_height {epoch_height}")
+
+utils.wait_for_blocks(nodes[0], target=BLOCKS_TO_FASTFORWARD + 50)
+epoch_height = nodes[0].get_validators()['result']['epoch_height']
+print(f"-> epoch_height {epoch_height}")

And after including the custom logic again, the python tests show the correct epoch height of 503, so it's definitely needed here


let next_next_epoch_info = match proposals_to_epoch_info(
next_next_epoch_config,
rng_seed,
Expand Down Expand Up @@ -445,6 +452,12 @@ impl EpochManager {
// This epoch info is computed for the epoch after next (T+2),
// where epoch_id of it is the hash of last block in this epoch (T).
self.save_epoch_info(store_update, &next_next_epoch_id, next_next_epoch_info)?;

// Store next_epoch info again since sandbox changes it:
if cfg!(feature = "sandbox") {
self.save_epoch_info(store_update, &next_epoch_id, next_epoch_info)?;
ChaoticTempest marked this conversation as resolved.
Show resolved Hide resolved
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic looks quite dubious to me. Why do you need to change epoch info retroactively? Also we should have unit test for this.

Copy link
Member Author

@ChaoticTempest ChaoticTempest Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, there's a clone that happens when we grab next_epoch_info. This is why we need to update it again. I'll try to add a unit test for this. Also, need to mention there's one caveat to this epoch update: since we fast-forward the block height in the middle of an epoch, that epoch height needs to be retroactively adjusted to reflect the fast-forwarded block height as well. Added comments mentioning it too


Ok(())
}

Expand Down
52 changes: 42 additions & 10 deletions integration-tests/src/tests/client/sandbox.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;

use actix::System;

use near_actix_test_utils::run_actix;
use near_chain::{ChainGenesis, Provenance, RuntimeAdapter};
use near_chain_configs::Genesis;
use near_client::test_utils::{setup_mock, TestEnv};
use near_client::test_utils::{setup_mock, TestEnv, MAX_BLOCK_PROD_TIME, MIN_BLOCK_PROD_TIME};
use near_crypto::{InMemorySigner, KeyType};
use near_logger_utils::init_test_logger;
use near_network::types::{
Expand Down Expand Up @@ -119,36 +119,68 @@ fn test_patch_account() {

#[test]
fn test_fast_forward() {
const BLOCKS_TO_FASTFORWARD: BlockHeight = 10_000;
const BLOCKS_TO_PRODUCE: u64 = 20;

// the 1_000_000 is to convert milliseconds to nanoseconds, where MIN/MAX_BLOCK_PROD_TIME are in milliseconds:
const FAST_FORWARD_DELTA: u64 = (BLOCKS_TO_FASTFORWARD + BLOCKS_TO_PRODUCE) * 1_000_000;

init_test_logger();
run_actix(async {
let count = Arc::new(AtomicUsize::new(0));
let first_block_timestamp = Arc::new(AtomicU64::new(0));

// Produce 20 blocks
let (client, _view_client) = setup_mock(
let (_client, _view_client) = setup_mock(
vec!["test".parse().unwrap()],
"test".parse().unwrap(),
true,
false,
Box::new(move |msg, _ctx, _| {
Box::new(move |msg, _ctx, client| {
if let NetworkRequests::Block { block } = msg.as_network_requests_ref() {
let height = block.header().height();
let timestamp = block.header().raw_timestamp();

count.fetch_add(1, Ordering::Relaxed);
if count.load(Ordering::Relaxed) >= 20 {
let at = count.load(Ordering::Relaxed);

// Fast forward by 10,000 blocks after the first block produced:
if at == 1 {
first_block_timestamp.store(timestamp, Ordering::Relaxed);
client.do_send(NetworkClientMessages::Sandbox(
NetworkSandboxMessage::SandboxFastForward(BLOCKS_TO_FASTFORWARD),
));
}

// Check at final block if timestamp and block height matches up:
if at >= BLOCKS_TO_PRODUCE as usize {
let before_forward = first_block_timestamp.load(Ordering::Relaxed);
let min_forwarded_time =
before_forward + FAST_FORWARD_DELTA * MIN_BLOCK_PROD_TIME;
let max_forwarded_time =
before_forward + FAST_FORWARD_DELTA * MAX_BLOCK_PROD_TIME;
assert!(
height >= 10000,
height >= BLOCKS_TO_FASTFORWARD,
"Was not able to fast forward. Current height: {}",
height
);

// Check if final block timestamp is within fast forwarded time min and max:
assert!(
(min_forwarded_time..max_forwarded_time).contains(&timestamp),
"Forwarded timestamp {} is not within expected range ({},{})",
timestamp,
min_forwarded_time,
max_forwarded_time
);

ChaoticTempest marked this conversation as resolved.
Show resolved Hide resolved
System::current().stop();
}
}
PeerManagerMessageResponse::NetworkResponses(NetworkResponses::NoResponse)
}),
);

// Fast forward by 10,000 blocks:
client.do_send(NetworkClientMessages::Sandbox(NetworkSandboxMessage::SandboxFastForward(
10000,
)));
near_network::test_utils::wait_or_panic(5000);
});
}
2 changes: 1 addition & 1 deletion nearcore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,4 @@ force_wasmer2 = ["near-vm-runner/force_wasmer2"]
force_wasmer0 = ["near-vm-runner/force_wasmer0"]
force_wasmtime = ["near-vm-runner/force_wasmtime"]

sandbox = ["near-client/sandbox", "node-runtime/sandbox", "near-jsonrpc/sandbox"]
sandbox = ["near-client/sandbox", "node-runtime/sandbox", "near-jsonrpc/sandbox", "near-epoch-manager/sandbox"]
49 changes: 44 additions & 5 deletions pytest/tests/sandbox/fast_forward.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#!/usr/bin/env python3
# test fast fowarding by a specific block height within a sandbox node. This will
# fail if the block height is not past the forwarded height.
# fail if the block height is not past the forwarded height. Also we will test
# for the timestamps and epoch height being adjusted correctly after the block
# height is changed.

import datetime
import sys, time
import pathlib

Expand All @@ -11,17 +14,53 @@
from utils import figure_out_sandbox_binary

# startup a RPC node
BLOCKS_TO_FASTFORARD = 10000
MIN_BLOCK_PROD_TIME = 1 # seconds
MAX_BLOCK_PROD_TIME = 2 # seconds
EPOCH_LENGTH = 10
BLOCKS_TO_FASTFORWARD = 10000
CONFIG = figure_out_sandbox_binary()
nodes = start_cluster(1, 0, 1, CONFIG, [["epoch_length", 10]], {})
CONFIG.update({
"consensus": {
"min_block_production_delay": {
"secs": MIN_BLOCK_PROD_TIME,
"nanos": 0,
},
"max_block_production_delay": {
"secs": MAX_BLOCK_PROD_TIME,
"nanos": 0,
},
}
})

nodes = start_cluster(1, 0, 1, CONFIG, [["epoch_length", EPOCH_LENGTH]], {})

# request to fast forward
nodes[0].json_rpc('sandbox_fast_forward', {
"delta_height": BLOCKS_TO_FASTFORARD,
"delta_height": BLOCKS_TO_FASTFORWARD,
})

# wait a little for it to fast forward
time.sleep(3)
ChaoticTempest marked this conversation as resolved.
Show resolved Hide resolved

# Assert at the end that the node is past the amounts of blocks we specified
assert nodes[0].get_latest_block().height > BLOCKS_TO_FASTFORARD
assert nodes[0].get_latest_block().height > BLOCKS_TO_FASTFORWARD
ChaoticTempest marked this conversation as resolved.
Show resolved Hide resolved

# Assert that we're within the bounds of fast forward timestamp between range of min and max:
sync_info = nodes[0].get_status()['sync_info']
earliest = datetime.datetime.strptime(sync_info['earliest_block_time'][:-4],
'%Y-%m-%dT%H:%M:%S.%f')
latest = datetime.datetime.strptime(sync_info['latest_block_time'][:-4],
'%Y-%m-%dT%H:%M:%S.%f')

min_forwarded_secs = datetime.timedelta(
0, BLOCKS_TO_FASTFORWARD * MIN_BLOCK_PROD_TIME)
max_forwarded_secs = datetime.timedelta(
0, BLOCKS_TO_FASTFORWARD * MAX_BLOCK_PROD_TIME)
min_forwarded_time = earliest + min_forwarded_secs
max_forwarded_time = earliest + max_forwarded_secs

assert min_forwarded_time < latest < max_forwarded_time

# Check to see that the epoch height has been updated correctly:
epoch_height = nodes[0].get_validators()['result']['epoch_height']
assert epoch_height >= BLOCKS_TO_FASTFORWARD / EPOCH_LENGTH
2 changes: 1 addition & 1 deletion test-utils/state-viewer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ near-client = { path = "../../chain/client" }
testlib = { path = "../../test-utils/testlib" }

[features]
sandbox = ["node-runtime/sandbox", "near-chain/sandbox", "near-network/sandbox", "near-client/sandbox"]
sandbox = ["node-runtime/sandbox", "near-chain/sandbox", "near-network/sandbox", "near-client/sandbox", "near-epoch-manager/sandbox"]
nightly_protocol_features = ["nearcore/nightly_protocol_features"]
nightly_protocol = ["nearcore/nightly_protocol"]