-
Notifications
You must be signed in to change notification settings - Fork 679
fix: calling evm_mine
with a timestamp
argument should reflect the change of time in subsequent blocks
#3531
Changes from 4 commits
4acf45b
1e8d60c
81c6f8e
fdd6fac
de325f9
26ecffa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -357,10 +357,10 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> { | |
|
||
//#region automatic mining | ||
const nullResolved = Promise.resolve(null); | ||
const mineAll = (maxTransactions: Capacity, onlyOneBlock = false) => | ||
const mineAll = (maxTransactions: Capacity, onlyOneBlock?: boolean) => | ||
this.#isPaused() | ||
? nullResolved | ||
: this.mine(maxTransactions, null, onlyOneBlock); | ||
: this.mine(maxTransactions, onlyOneBlock); | ||
if (instamine) { | ||
// insta mining | ||
// whenever the transaction pool is drained mine the txs into blocks | ||
|
@@ -592,8 +592,8 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> { | |
|
||
mine = async ( | ||
maxTransactions: number | Capacity, | ||
timestamp?: number, | ||
onlyOneBlock: boolean = false | ||
onlyOneBlock: boolean = false, | ||
timestamp?: number | ||
) => { | ||
const nextBlock = this.#readyNextBlock(this.blocks.latest, timestamp); | ||
|
||
|
@@ -608,6 +608,15 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> { | |
onlyOneBlock | ||
); | ||
await this.#blockBeingSavedPromise; | ||
|
||
if ( | ||
timestamp !== undefined && | ||
this.#options.miner.timestampIncrement === "clock" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are two time-related blocks of code in this function that both check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call! |
||
) { | ||
// when miner.timestampIncrement is a number, the previous block timestamp is used as a reference | ||
// for the next block, so this call is not required. | ||
this.setTimeDiff(timestamp * 1000); | ||
} | ||
return { | ||
transactions, | ||
blockNumber: nextBlock.header.number.toArrayLike(Buffer) | ||
|
@@ -839,7 +848,8 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> { | |
} | ||
|
||
/** | ||
* @param newTime - the number of milliseconds to adjust the time by. Can be negative. | ||
davidmurdoch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* Adjusts the internal time adjustment such that the provided time is considered the "current" time. | ||
* @param newTime - the time (in milliseconds) that will be considered the "current" time | ||
* @returns the total time offset *in milliseconds* | ||
*/ | ||
public setTimeDiff(newTime: number) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -141,6 +141,83 @@ describe("provider", () => { | |
await provider.disconnect(); | ||
}); | ||
|
||
describe("uses timestamp adjustment in subsequent blocks after calling `evm_mine` with a `timestamp` argument", () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our Can you rephrase the We use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just flattened that describe down, and was more explicit in the test names. lmk what you think. |
||
const timestampIncrement = 5; // seconds | ||
const timeArgumentSeconds = 100; | ||
|
||
async function mineBlocksForTimestamps( | ||
provider: EthereumProvider | ||
): Promise<string[]> { | ||
// mine a block with a specified timestamp | ||
await provider.request({ | ||
method: "evm_mine", | ||
params: [timeArgumentSeconds] | ||
}); | ||
const specifiedBlock = await provider.request({ | ||
method: "eth_getBlockByNumber", | ||
params: ["latest", false] | ||
}); | ||
// mine a block without a specified timestamp | ||
await provider.request({ | ||
method: "evm_mine", | ||
params: [] | ||
}); | ||
const unspecifiedBlock = await provider.request({ | ||
method: "eth_getBlockByNumber", | ||
params: ["latest", false] | ||
}); | ||
|
||
return [specifiedBlock.timestamp, unspecifiedBlock.timestamp]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you coerce these to numbers here it'd make the tests slightly simpler. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I intentionally didn't do this, thinking that we should be asserting the exact returned value. Otherwise, there's a potential issue that could slip through where the return type isn't what's expected, but coerces to what's expected. Thinking about it in light of your comment, I think that we should be ensuring the above, but maybe in a specific test for that. I've updated my tests to what you've suggested, keen to hear your thoughts on that. |
||
} | ||
it("should work with `timestampIncrement` of `clock`", async () => { | ||
const provider = await getProvider({ | ||
miner: { timestampIncrement: "clock" } | ||
}); | ||
|
||
const [specifiedBlock, unspecifiedBlock] = | ||
await mineBlocksForTimestamps(provider); | ||
|
||
assert.strictEqual( | ||
specifiedBlock, | ||
`0x${timeArgumentSeconds.toString(16)}`, | ||
"Unexpected timestamp for block mined with specified timestamp" | ||
); | ||
|
||
assert( | ||
+unspecifiedBlock >= timeArgumentSeconds && | ||
+unspecifiedBlock <= timeArgumentSeconds + 1000, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If CI is running slowly, could this break? I don't know what the solution is, and I don't feel it's worth holding up this PR for, but just wanted to throw it out there in case you have a solution on-hand. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could do. Because we are specifying a timestamp in seconds, it would need to take an entire second to mine and fetch 2 blocks. I feel like I'm building a lot of pressure on the time refactor - but it'll let us inject a 🤞 |
||
`Unexpected timestamp for block mined without specified timestamp - expected a value between ${timeArgumentSeconds} and ${ | ||
timeArgumentSeconds + 1000 | ||
}, got ${+unspecifiedBlock}` | ||
); | ||
|
||
await provider.disconnect(); | ||
}); | ||
|
||
it("should work with a numeric `timestampIncrement`", async () => { | ||
const provider = await getProvider({ | ||
miner: { timestampIncrement } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this |
||
}); | ||
|
||
const [specifiedBlock, unspecifiedBlock] = | ||
await mineBlocksForTimestamps(provider); | ||
|
||
assert.strictEqual( | ||
specifiedBlock, | ||
`0x${timeArgumentSeconds.toString(16)}`, | ||
"Unexpected timestamp for block mined with specified timestamp" | ||
); | ||
|
||
assert.strictEqual( | ||
unspecifiedBlock, | ||
`0x${(timeArgumentSeconds + timestampIncrement).toString(16)}`, | ||
"Unexpected timestamp for block mined without specified timestamp" | ||
); | ||
|
||
await provider.disconnect(); | ||
}); | ||
}); | ||
|
||
it("applies the adjustment only once when `timestampIncrement` is used", async () => { | ||
const time = new Date("2019-01-01T00:00:00.000Z"); | ||
const timestampIncrement = 5; // seconds | ||
|
@@ -185,9 +262,9 @@ describe("provider", () => { | |
"unexpected timestamp for the second block mined" | ||
); | ||
await mineAndAssertTimestamp( | ||
startTimeSeconds + fastForwardSeconds + timestampIncrement * 3 | ||
), | ||
"unexpected timestamp for the third block mined"; | ||
startTimeSeconds + fastForwardSeconds + timestampIncrement * 3, | ||
"unexpected timestamp for the third block mined" | ||
); | ||
}); | ||
|
||
it("uses the `timestampIncrement` for the first block when forking", async () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A decent alternative would be to change
mineAll
to(maxTransactions: Capacity, onlyOneBlock: boolean) =>
and then all use of
mineAll
would need to pass the values it needs, basically just:This might make this code a bit clearer. Or not. I don't think I care either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left it as is because we set a default on the
mine
function, and don't feel that requiring it formineAll
, but not formine
feels nice - we might as well leverage the default that we have specified.