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

Improved Optimism Bedrock Compatibility #2713

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

holgerd77
Copy link
Member

@holgerd77 holgerd77 commented May 20, 2023

This PR aims to improve compatibility with the upcoming Optimism Bedrock release, using the Bedrock Goerli deployment as a testbed.

@codecov
Copy link

codecov bot commented May 20, 2023

Codecov Report

Merging #2713 (910449b) into master (d1ba362) will increase coverage by 0.83%.
The diff coverage is 87.14%.

Additional details and impacted files

Impacted file tree graph

Flag Coverage Δ
block 90.88% <79.54%> (-0.21%) ⬇️
blockchain 90.72% <ø> (ø)
client 86.78% <ø> (ø)
common 97.01% <100.00%> (+0.01%) ⬆️
devp2p 89.29% <ø> (?)
ethash ∅ <ø> (∅)
evm 79.99% <100.00%> (+<0.01%) ⬆️
rlp ∅ <ø> (?)
statemanager 86.28% <ø> (ø)
trie 89.92% <ø> (ø)
tx 95.74% <ø> (?)
util 81.13% <ø> (ø)
vm 83.01% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Copy link
Contributor

@g11tech g11tech left a comment

Choose a reason for hiding this comment

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

looks amazing ! ❤️

@holgerd77 holgerd77 mentioned this pull request May 23, 2023
12 tasks
@holgerd77
Copy link
Member Author

Just dropping this link list here from some internal chat discussion so that it won't get lost:

(guess most of this should be retrievable by the now excellently structured official docs though as well)

Will also mention @kchojn here since he might be interested in this work (we had a chat exchange on Discord).

@kchojn
Copy link
Contributor

kchojn commented May 23, 2023

Thanks @holgerd77. Let me know if I should open an issue, mainly because these problems are more related to Optimism and not Bedrock in detail.
In connection with the reprocessing of the entire Optimism story, using ethereumjs, I encountered several problems
that I would like to share (I also applied some temporary solutions):

Before Berlin Hardfork:

EIIP-2929

Berlin Hardfork Block Number: 3950000;

In general, a minimal version of EIP-2929 is needed:

for reprocessing purposes, I added my JSON file with eip2929-minimal, this allowed the blocks to be reprocessed
correctly.

Btw, before the Berlin hardfork, basically everything was treated as a new storage slot.

L1BLOCK_NUMBER

This opcode is missing for Optimism.

After Berlin Hardfork:

GasRefund

0 address

Optimism has some system transactions that don't quite work like legacy transactions.
Example:

The transaction is a system transaction, but it has v=0x0 and r=0x0 and s=0x0. This is not a valid signature. To
fix this, we need to do 2 things:

  • replace 0x0 with undefined in the v, r, and s fields (this is done in my own
    rpcTransactionToEthereumjsTransaction0x0 function)
  const v = (rpcTransaction as any).v === "0x0" ? undefined : (rpcTransaction as any).v;
const r = (rpcTransaction as any).r === "0x0" ? undefined : (rpcTransaction as any).r;
const s = (rpcTransaction as any).s === "0x0" ? undefined : (rpcTransaction as any).s;
  • transaction should always be signed, so we need to mock isSigned to always return true in baseTransaction.js in
    @ethereumjs/tx/dist:
    isSigned();
{
  // const { v, r, s } = this;
  // if (v === undefined || r === undefined || s === undefined) {
  //     return false;
  // }
  // else {
  //     return true;
  // }
  return true;
}

Sometimes When does a transaction have a zero address as the sender, it
uses 0x36bde71c97b33cc4729cf772ae268934f7ab70b2 (l1TxOrigin).

To fix it, we need:

  • legacyTransaction.js in @ethereumjs/tx/dist:
  getSenderPublicKey();
{
  const isOptimismNetwork = this.common.chainName().toLowerCase().includes("optimistic");

  const msgHash = this.getMessageToVerifySignature();
  const { v, r, s } = this;
  this._validateHighS();
  try {
    return (0, util_1.ecrecover)(msgHash, v, (0, util_1.bigIntToUnpaddedBuffer)(r), (0, util_1.bigIntToUnpaddedBuffer)(s), this.supports(types_1.Capability.EIP155ReplayProtection) ? this.common.chainId() : undefined);
  } catch (e) {
    const msg = this._errorMsg("Invalid Signature");

    // Optimism network zero address handler
    if (isOptimismNetwork) {
      return null;
    }

    throw new Error(msg);
  }
}

In this case, we added a check for the Optimism network and if it is, then we return null (not an error).

  • baseTransaction.js in @ethereumjs/tx/dist:
    getSenderAddress();
{
  const senderPublicKey = this.getSenderPublicKey();
  if (!senderPublicKey) {
    return new util_1.Address(Buffer.alloc(20));
  }
  return new util_1.Address((0, util_1.publicToAddress)(this.getSenderPublicKey()));
}

In this case, we added a check if the senderPublicKey is null, then we return the zero address.

  • runTx.js in @ethereumjs/vm/dist ( before beforeTx event)
  await this._emit("beforeTx", tx);
let caller = tx.getSenderAddress();
if (caller.equals(util_1.Address.zero()) && this._common.chainName().toLowerCase().includes("optimistic")) {
  const senderAddr = "36bde71c97b33cc4729cf772ae268934f7ab70b2";
  caller = new util_1.Address(Buffer.from(senderAddr, "hex"));
}

In this case, we added a check if the caller is the zero address and if it is, then we replace it with the special
address (l1TxOrigin).

CALL

gas.js

const warmStorageRead = common.param("gasPrices", "warmstorageread");
const coldAccountAccess = common.param("gasPrices", "coldaccountaccess");
const coldCost = coldAccountAccess - warmStorageRead;

I Had to do some changes in gas calculation in CALL instruction. I needed to add some gas after EIP2929

Summary

Most of these changes are temporary and some kind of hack. But it allowed me to reprocess more than 99.0% of blocks in
the whole history of the Optimism network.
If you need more examples or more explanation, let me know, I'll be happy to help

@holgerd77
Copy link
Member Author

Hi @kchojn Karol,
thanks a lot for all these detailed notes, that so so invaluable 🙏. I also already stumbled (and recognize) various points you made, your experiences here are really are great help to go forward here! 🤩

My current tendency would be to "only" make EthereumJS Bedrock compatible and do not focus too much on the old blocks (and e.g. not add the L1BLOCK_NUMBER opcode). Do you see some great advantage in also try to get these running? The Bedrock update is so much around the corner and from my feeling this will be the start of "the real" [tm] Optimism, with this technology (update) be the foundation of the whole OP stack with other rollups building on this Bedrock fundament.

Doesn't need to be decided ad hoc though, also not against to take the older stuff in. We just also do not have to decide now but can also slowly expand later on if needed.

For the system txs I guess it's likely worth to implement this as a dedicated tx type, not sure if would want to start with this now, but in general. For now I might also just skip them or something, or side-use some hacks.

One question on your technical setup: did you process the Optimism blocks with the EthersStateManager? 🤔 Or some other setup?

And did you use our next-v7-releases code from the develop-v7 branch (now merged into master since two weeks or so?

Anyhow: again, thanks a lot!! 😀 ❤️ 🙏

@holgerd77
Copy link
Member Author

Update: ah, I will just start with implementing the Optimism system tx type today, then we'll have this clean and do not always need to route around this or do some hacky stuff and then we can just simply run the full blocks with e.g. VM.runBlock().

I will also introduce a high-level ChainType property for our underlying Common library with the options to switch between Ethereum mainnet (Ethereum) and Optimism compatible (Optimism) chains. Then we can introduce clean switches on this (e.g. throwing if a system tx is instantiated on ChainType.Ethereum).

@holgerd77 holgerd77 force-pushed the optimism-bedrock-compatibility branch from 0d49f20 to 33e8a26 Compare May 24, 2023 07:49
@kchojn
Copy link
Contributor

kchojn commented May 24, 2023

@holgerd77
Hi,
I understand, it is very useful, but I am able to modify the code to make it work, if you want to keep the start optimism from Bedrock (it is reasonable).
Dedicated types would probably be very useful, but on the other hand, it would be difficult to maintain for many chains. I am at the stage of PoC Arbitrum and there I have, for example, ~5-6 new transaction types.
I use a custom state manager (rpc/db) that is based on EVMStateAccess. Modified because, with this, I capture every storage/state change.
I have not used v7, blocks since about 90m I process on official release v6 latest 😀
Bedrock changes will be from v7 or still in v6? From my perspective I can start testing these changes as Bedrock happens, I don't know what your plans are for the release.
Anyway, any changes from your side, are very useful. Thanks!

@holgerd77
Copy link
Member Author

holgerd77 commented May 24, 2023

@kchojn Ah, just read your system tx section, I will be able to do this a lot cleaner with a dedicated tx type, your comments are super helpful though, can just go along one by one (so there are for sure (?) no Optimism 1559 system tx, right?).

I have also added the ChainType now in 14b2076. This is for sure not to support all kind of chains, it will e.g. quite for sure not be realistic that we support the Arbitrum stack due to the large change set - especially in the EVM. So this is only for selected switches and will relatively likely be "Optimism only" for quite some time (or also long term), or with eventually 1-2 additions (not sure about Polygon e.g., but maybe they are even in the ChainType.Ethereum realm? Anyhow, but opens the possibility for additional things like that).

Update: Ah, just re-read and realized that you meant "Dedicated Types" for "Transaction Types", yeah, this also needs to remain selective. For Optimism though this very much seems justified to me, this is at the end only one type and this system tx type is very prevalent. And the switches are there anyhow, I think it will rather be a bit cleaner with a type inheriting from I guess LegacyTransaction (will at least try, not 100% sure yet), think this won't be that much more code than inline switches.

I use a custom state manager (rpc/db) that is based on EVMStateAccess. Modified because, with this, I capture every storage/state change.

🤯

That's totally impressive!

So we on our side will work towards having the EthersStateManager ready to also be used towards Optimism, you can there pass in your Optimism-provider backed Ethers instance and then request state on the fly.

Both EthersStateManager/StateManager and generally all the EVM/VM state related stuff is under heavy rework right now for v7 (so this indicates our breaking release round for all EthereumJS libraries), but in the finishing stages, #2702 from @jochem-brouwer is finalizing the EVM/VM state refactoring, likely to be merged in the current or next week.

Bedrock changes will be from v7 or still in v6? From my perspective I can start testing these changes as Bedrock happens, I don't know what your plans are for the release.

Bedrock changes will be for v7, v6 is in maintenance mode now. We are relatively close, so there will be - minimally - 2 pre-releases during June and then likely the final releases in July this year! 😀

@kchojn
Copy link
Contributor

kchojn commented May 24, 2023

@holgerd77
Sounds like a very good change!
Yes, sorry, I write very chaotically. For Arbitrum, it's actually heavier, so I have some work ahead of me. 😄 But about six months ago I did a PoC for Polygon using ethereumjs. And it was very pleasant to process blocks, there were probably some system events that I missed that I didn't see in trace, at least I think so. In any case, Polygon seems to me to be very close to what it is. Well, I'm glad that Optimism will be so well supported!

Bedrock changes will be for v7, v6 is in maintenance mode now. We are relatively close, so there will be - minimally - 2 pre-releases during June and then likely the final releases in July this year! grinning

By the final release, I will have time to test your changes for Bedrock. 👀 Still fully focused on the history,

@holgerd77 holgerd77 force-pushed the optimism-bedrock-compatibility branch 2 times, most recently from bc6da15 to f37bde3 Compare June 1, 2023 07:21
@holgerd77 holgerd77 changed the base branch from master to cleanup-statemanager-v2 June 1, 2023 07:22
@holgerd77
Copy link
Member Author

holgerd77 commented Jun 1, 2023

Squashed some commits here and rebased and retargeted towards #2702 (EEI/EVM refactor work from Jochem)

Base automatically changed from cleanup-statemanager-v2 to master June 1, 2023 20:31
@holgerd77 holgerd77 force-pushed the optimism-bedrock-compatibility branch from 48c61b2 to 8d0e93d Compare June 2, 2023 08:35
@holgerd77
Copy link
Member Author

Cherry-picked this on top of master

@holgerd77 holgerd77 force-pushed the optimism-bedrock-compatibility branch from 8d0e93d to 16fa479 Compare June 2, 2023 09:01
@holgerd77 holgerd77 changed the title Optimism Bedrock Compatibility Improved Optimism Bedrock Compatibility Jun 2, 2023
@holgerd77 holgerd77 force-pushed the optimism-bedrock-compatibility branch from 16fa479 to f77d4e4 Compare June 2, 2023 09:27
@holgerd77 holgerd77 force-pushed the optimism-bedrock-compatibility branch from f77d4e4 to db919ab Compare June 2, 2023 09:46
Copy link
Contributor

@acolytec3 acolytec3 left a comment

Choose a reason for hiding this comment

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

Left a number of comments. The biggest concern I have is hardcoding non-mainnet hardforks in our code. That feels like we're entering a path of committing to support every L2 that comes along and wants a custom configuration (or else having to pick which ones are "worth" maintaining a configuration for).

transactions.push(tx)
let skip = false
if (opts?.skipTxTypes !== undefined && txData.type !== undefined) {
const txType = Number(bytesToInt(toBytes(txData.type)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this cast to Number if bytesToInt returns a Number?

@@ -51,6 +53,7 @@ export class Block {
public readonly withdrawals?: Withdrawal[]
public readonly txTrie = new Trie()
public readonly _common: Common
public readonly _skipTxTypes: number[]
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be slightly more efficient to use Bigint here so you don't need to conver the tx.type value to bytes and then to number down below?

@@ -153,6 +165,9 @@ export class Block {
* @param opts
*/
public static fromValuesArray(values: BlockBytes, opts?: BlockOptions) {
if (opts?.skipTxTypes !== undefined) {
throw new Error('constructor usage not possible in combination with skipTxTypes option')
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
throw new Error('constructor usage not possible in combination with skipTxTypes option')
throw new Error('cannot use fromValuesArray in combination with skipTxTypes option, use fromBlockData instead')

@@ -479,6 +502,9 @@ export class Block {
validateTransactions(stringError: false): boolean
validateTransactions(stringError: true): string[]
validateTransactions(stringError = false) {
if (this._skipTxTypes.length > 0) {
throw new Error('tx validation not possible when skipTxTypes option is used')
Copy link
Contributor

Choose a reason for hiding this comment

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

Why can't we validate transactions here? Couldn't you just skip validating the specific transaction down below in the loop where it validates each transaction?

* - Skip Optimism system txs (type: 126)
*
* Note that functionality of this library is limited when this option is used
* and validation- and serilization-related methods throw when called.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* and validation- and serilization-related methods throw when called.
* so validation and serialization-related methods throw when called.

@@ -175,6 +175,9 @@ export class EVM implements EVMInterface {
Hardfork.Paris,
Hardfork.Shanghai,
Hardfork.Cancun,
// Optimism (Bedrock) hardforks
Hardfork.Bedrock,
Hardfork.Regolith,
Copy link
Contributor

Choose a reason for hiding this comment

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

Per my comments above, rather than this hardcoded list of supported hardforks, I'd prefer to see a path where we take in a list of supported hardforks from common (and let people add arbitrary ones there). That way, it's not on us to maintain the list of hardforks for whatever L2 is popular next.

skip = true
}
}
if (!skip) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to include the system transaction in the block's transaction list and just not validate that specific transaction or are there further complications if you include a system transaction in a block unvalidated?

block.raw()
st.fail('should throw')
} catch (e) {
st.pass('should throw on raw() call')
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
st.pass('should throw on raw() call')
st.ok(e.message.includes('raw() method call not allowed when skipTxTypes option is used'), 'should throw on raw() call')

block.validateTransactions()
st.fail('should throw')
} catch (e) {
st.pass('should throw on validateTransactions() call')
Copy link
Contributor

Choose a reason for hiding this comment

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

As above, I like it when we check for the specific error message if we're checking to validate that a specific error is thrown (especially where we create the error message)

await block.validateTransactionsTrie()
st.fail('should throw')
} catch (e) {
st.pass('should throw on validateTransactionsTrie() call')
Copy link
Contributor

Choose a reason for hiding this comment

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

As above, check the error message to verify it's throwing the correct error as an additional safety check.

@kchojn
Copy link
Contributor

kchojn commented Jun 14, 2023

Gm,
Is this pr suitable for Bedrock testing? I will update the configuration for the mainnet ofc

@acolytec3
Copy link
Contributor

Gm, Is this pr suitable for Bedrock testing? I will update the configuration for the mainnet ofc

I think it's worth trying. I know @holgerd77 tested it with a couple of Optimism Bedrock blocks from goerli a while back. We haven't rebased it recently so not sure if anything is missing or needs changes but let us know your results!

@kchojn
Copy link
Contributor

kchojn commented Jul 6, 2023

@holgerd77 just started working on it. Is there any quick way to do a rebuild of this branch/pr in my project? I tried to do it manually, but tsc throws me errors every now and then

Edit: nvm, I found a way, thx!

@kchojn
Copy link
Contributor

kchojn commented Jul 11, 2023

@holgerd77,
Thanks for this PR. I did a lot of testing and it looks like everything is working. However, we would like to support optimism 100% (without skipping specific types).
Does implementing all the logic for tx 126 for optimism, involve only transactionFactory or are there other places where I need to add support?
I would like to undertake this, due to the fact that our solutions are based mostly on ethereumjs, and it will be a good exercise for upcoming chains (matic)

@@ -0,0 +1,11 @@
{
"name": "regolith",
Copy link
Member

Choose a reason for hiding this comment

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

In general maybe we should explicitly mark these hardforks as optimism? So we would use optimism/regolith as name (or something similar)?

Copy link
Member

Choose a reason for hiding this comment

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

And maybe also in Hardfork.Optimism.Regolith as enum?

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, that makes likely sense, we can give these kind of things a bit more thinking now that we have more time on this.

Copy link
Member Author

Choose a reason for hiding this comment

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

(the namespaced version might already be a good choice, good idea)

@holgerd77
Copy link
Member Author

@holgerd77, Thanks for this PR. I did a lot of testing and it looks like everything is working. However, we would like to support optimism 100% (without skipping specific types). Does implementing all the logic for tx 126 for optimism, involve only transactionFactory or are there other places where I need to add support? I would like to undertake this, due to the fact that our solutions are based mostly on ethereumjs, and it will be a good exercise for upcoming chains (matic)

Yeah, this work here turned out to get very generic, so the new flag is basically just a universal tool just for these kind of tx-type-unknown situation. 🙂 This will likely be helpful for various situations, but I already thought that this will likely not fit your use case.

It would be totally great if you would want to add broader Optimism support! ❤️ Did a quick consultation on this in our internal chat to confirm. We can promise you continued support on this with a close feedback loop!

So what we will short-term do is just to extract the skipTxTypes functionality from here and release this in separation.

What you can cherry-pick from here - if you want - is likely the deep Common integration (so the specific optimism.json chain file and such) as well as the added test data, that might already help as a start and we wouldn't directly add this chain file to master.

Yes, and then I would think the system tx type is the main thing. And for our "library thinking" we always want to have the EVM/VM included, so it would be good if this gets to a point (again) where an Optimism block can be instantiated and then run in the VM with `VM.runBlock(). Not sure if the VM is doing anything on system txs? (maybe change some state?).

So I guess that would be what we would call "library-compatible". If we can run this with VM.runBlock() and the state would be matching afterwards.

Does this help?

Really curious to see what will come out of this! 🤩

@kchojn
Copy link
Contributor

kchojn commented Nov 14, 2023

@holgerd77 small update,
I implemented everything and we can reprocess 100% of optimism blocks correctly with ethjs (Amazing!)! Announcement: full post-bedrock op data

As soon as I update ethereumjs to the latest versions, before the new hf on mainnet, I will open a PR here with my changes to Optimism:

  • support for tx 126 - we do not use skiptx option, we process these transactions as well 😎
  • all chainconfig
  • plus other small aspects (very very small changes in the current ejs code)

I guess, it will support Base as well

Edit: I know it took a long time, but I'm working on a lot of things 😵‍💫

@roninjin10
Copy link
Collaborator

This is amazing! I work at OP labs so let me know if I can help get this over finish line

@holgerd77
Copy link
Member Author

@kchojn Yes, can only agree: this is amazing! 🤩 Exciting to hear! ❤️

@scorbajio
Copy link
Contributor

scorbajio commented Apr 25, 2024

@holgerd77 small update, I implemented everything and we can reprocess 100% of optimism blocks correctly with ethjs (Amazing!)! Announcement: full post-bedrock op data

As soon as I update ethereumjs to the latest versions, before the new hf on mainnet, I will open a PR here with my changes to Optimism:

* support for tx 126 - we do not use skiptx option, we process these transactions as well 😎

* all chainconfig

* plus other small aspects (very very small changes in the current ejs code)

I guess, it will support Base as well

Edit: I know it took a long time, but I'm working on a lot of things 😵‍💫

Hey, thanks for the awesome work! Any updates on the PR? I'd be interested to get involved and can start after any updates have been pushed if there are any.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants