Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
add fixes and tests for setTime and increaseTime
Browse files Browse the repository at this point in the history
simplify
  • Loading branch information
davidmurdoch committed Jun 2, 2022
1 parent 72659c1 commit e31e0d4
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 42 deletions.
19 changes: 14 additions & 5 deletions src/chains/ethereum/ethereum/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,19 +551,28 @@ export default class EthereumApi implements Api {
*/
@assertArgLength(0, 1)
async evm_setTime(time: number | QUANTITY | Date) {
let t: number;
let timestamp: number;
switch (typeof time) {
case "object":
t = time.getTime();
timestamp = time.getTime();
break;
case "number":
t = time;
timestamp = time;
break;
default:
t = Quantity.from(time).toNumber();
timestamp = Quantity.from(time).toNumber();
break;
}
return Math.floor(this.#blockchain.setTime(t) / 1000);
const blockchain = this.#blockchain;
// when using clock time use Date.now(), otherwise use the timestamp of the
// current latest block
const currentTime =
this.#options.miner.timestampIncrement === "clock"
? Date.now()
: blockchain.blocks.latest.header.timestamp.toNumber() * 1000;
const offsetMilliseconds = blockchain.setTimeDiff(timestamp, currentTime);
// convert offsetMilliseconds to seconds:
return Math.floor(offsetMilliseconds / 1000);
}

/**
Expand Down
77 changes: 43 additions & 34 deletions src/chains/ethereum/ethereum/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,17 +294,17 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {

{
// create first block
let firstBlockTime: number;
if (options.chain.time != null) {
// If we were given a timestamp, use it instead of the current time
const time = options.chain.time.getTime();
firstBlockTime = Math.floor(time / 1000);
this.setTime(time);
} else {
options.chain.time = new Date();
firstBlockTime = Math.floor(options.chain.time.getTime() / 1000);
// we don't need to call `this.setTime(time)` in this case because the
// offset is always 0

// if we don't have a time form the user get one now
if (options.chain.time == null) options.chain.time = new Date();

const timestamp = options.chain.time.getTime();
const firstBlockTime = Math.floor(timestamp / 1000);

// if we are using clock time we need to record the time offset so
// other blocks can have timestamps relative to our initial time.
if (options.miner.timestampIncrement === "clock") {
this.setTimeDiff(timestamp, Date.now());
}

// if we don't already have a latest block, create a genesis block!
Expand Down Expand Up @@ -561,26 +561,17 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
const previousHeader = previousBlock.header;
const previousNumber = previousHeader.number.toBigInt() || 0n;
const minerOptions = this.#options.miner;
let qTimestamp: Quantity;
if (timestamp != null) {
qTimestamp = Quantity.from(timestamp);
} else {
const { timestampIncrement } = minerOptions;
if (timestampIncrement === "clock") {
qTimestamp = Quantity.from(this.#currentTime());
} else {
qTimestamp = Quantity.from(
previousHeader.timestamp.toBigInt() + timestampIncrement.toBigInt()
);
}
if (timestamp == null) {
timestamp = this.#adjustedTime(previousHeader.timestamp);
}

return new RuntimeBlock(
Quantity.from(previousNumber + 1n),
previousBlock.hash(),
this.coinbase,
minerOptions.blockGasLimit.toBuffer(),
BUFFER_ZERO,
qTimestamp,
Quantity.from(timestamp),
minerOptions.difficulty,
previousHeader.totalDifficulty,
Block.calcNextBaseFee(previousBlock)
Expand Down Expand Up @@ -794,32 +785,50 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
}));
};

/**
* The number of milliseconds time should be adjusted by when computing the
* "time" for a block.
*/
#timeAdjustment: number = 0;

/**
* Returns the timestamp, adjusted by the timeAdjustment offset, in seconds.
* @param precedingTimestamp - the timestamp of the block to be used as the
* time source if `timestampIncrement` is not "clock".
*/
#currentTime = () => {
return Math.floor((Date.now() + this.#timeAdjustment) / 1000);
#adjustedTime = (precedingTimestamp: Quantity) => {
const { #timeAdjustment: timeAdjustment } = this;
const timestampIncrement = this.#options.miner.timestampIncrement;
if (timestampIncrement === "clock") {
return Math.floor((Date.now() + timeAdjustment) / 1000);
} else {
return (
precedingTimestamp.toNumber() +
Math.floor(timeAdjustment / 1000) +
timestampIncrement.toNumber()
);
}
};

/**
* @param seconds -
* @param milliseconds - the number of milliseconds to adjust the time by.
* Negative numbers are treated as 0.
* @returns the total time offset *in milliseconds*
*/
public increaseTime(seconds: number) {
if (seconds < 0) {
seconds = 0;
public increaseTime(milliseconds: number) {
if (milliseconds < 0) {
milliseconds = 0;
}
return (this.#timeAdjustment += seconds);
return (this.#timeAdjustment += milliseconds);
}

/**
* @param seconds -
* @param newTime - the number of milliseconds to adjust the time by. Can be negative.
* @param sourceTime - the current time in milliseconds
* @returns the total time offset *in milliseconds*
*/
public setTime(timestamp: number) {
return (this.#timeAdjustment = timestamp - Date.now());
public setTimeDiff(newTime: number, sourceTime: number) {
return (this.#timeAdjustment = newTime - sourceTime);
}

#deleteBlockData = async (
Expand Down
59 changes: 57 additions & 2 deletions src/chains/ethereum/ethereum/tests/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("provider", () => {
it("uses the default 'clock' time for the `timestampIncrement` option", async () => {
const provider = await getProvider();
const options = provider.getOptions();
assert(options.miner.timestampIncrement, "clock");
assert.strictEqual(options.miner.timestampIncrement, "clock");

const timeBeforeMiningBlock = Math.floor(Date.now() / 1000);
await provider.request({
Expand Down Expand Up @@ -81,11 +81,66 @@ describe("provider", () => {
});
assert.strictEqual(
parseInt(block.timestamp),
+time / 1000 + timestampIncrement
Math.floor(+time / 1000) + timestampIncrement
);
await provider.disconnect();
});

it("uses time adjustment after evm_setTime when timestampIncrement is used", async () => {
const time = new Date("2019-01-01T00:00:00.000Z");
const timestampIncrement = 5;
const fastForward = 100 * 1000; // 100 seconds
const provider = await getProvider({
chain: { time },
miner: { timestampIncrement }
});
await provider.request({
method: "evm_setTime",
// fastForward into the future
params: [`0x${(fastForward + +time).toString(16)}`]
});
await provider.request({
method: "evm_mine",
params: []
});
const block = await provider.request({
method: "eth_getBlockByNumber",
params: ["latest", false]
});
const expectedTime =
Math.floor((fastForward + +time) / 1000) + timestampIncrement;
assert.strictEqual(parseInt(block.timestamp), expectedTime);

await provider.disconnect();
});

it("uses time adjustment after evm_increaseTime when timestampIncrement is used", async () => {
const time = new Date("2019-01-01T00:00:00.000Z");
const timestampIncrement = 5; // seconds
const fastForward = 100; // seconds
const provider = await getProvider({
chain: { time },
miner: { timestampIncrement }
});
await provider.request({
method: "evm_increaseTime",
// fastForward into the future, evm_increaseTime param is in seconds
params: [`0x${fastForward.toString(16)}`]
});
await provider.request({
method: "evm_mine",
params: []
});
const block = await provider.request({
method: "eth_getBlockByNumber",
params: ["latest", false]
});
const expectedTime =
Math.floor(+time / 1000) + fastForward + timestampIncrement;
assert.strictEqual(parseInt(block.timestamp), expectedTime);
await provider.disconnect();
});

it("uses the timestampIncrement for the first block when forking", async () => {
const time = new Date("2019-01-01T00:00:00.000Z");
const timestampIncrement = 5;
Expand Down
2 changes: 1 addition & 1 deletion src/chains/ethereum/options/src/chain-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export type ChainConfig = {
* The id of the network returned by the RPC method `net_version`.
*
* Defaults to the current timestamp, via JavaScript's `Date.now()` (the
* number of millisconds since the UNIX epoch).
* number of milliseconds since the UNIX epoch).
*
* @defaultValue Date.now()
*/
Expand Down

0 comments on commit e31e0d4

Please sign in to comment.