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

Add EIP-1898 support needed for The Graph compatibility #10815

Merged
merged 3 commits into from
Jun 23, 2023

Conversation

fridrik01
Copy link
Contributor

@fridrik01 fridrik01 commented May 3, 2023

Fixes: #10814
See also: https://github.com/filecoin-project/fvm-pm/issues/389

This PR updates the following RPC methods according to EIP-1898 specs.

The following RPC methods are affected:

  • eth_getBalance
  • eth_getStorageAt
  • eth_getTransactionCount
  • eth_getCode
  • eth_call

Note that eth_getBlockByNumber was not included in this list in the spec although it seems it should be affected also?

Currently these methods all accept a blkParam string which can be one of "latest", "earliest", "pending", or a block number (decimal or hex). The spec enables caller to additionally specify a json hash which can include the following fields:

  • blockNumber EthUint64: A block number (decimal or hex) which is similar to the original use of the blkParam string
  • blockHash EthHash: The block hash
  • requireCanonical bool) If true we should make sure the block is in the canonical chain

Since the blkParam needs to support both being a number/string and a json hash then this to properly work we need to introduce a new struct with pointer fields to check if they exist. This is done in the EthBlockParamByNumberOrHash struct which first tries to unmarshal as a json hash (according to eip-1898) and then fallback to unmarshal as string/number.

Test plan

Check that specifying by predefined blocks works:

curl  http://localhost:1234/rpc/v1  -X POST -H "Content-Type: application/json" --data '{"method":"eth_getTransactionCount","params":["0xd4c70007F3F502f212c7e6794b94C06F36173B36", "latest"],"id":1,"jsonrpc":"2.0"}'
{"jsonrpc":"2.0","result":"0x2b","id":1}

Check that specifying by number works:

curl -s http://localhost:1234/rpc/v1  -X POST -H "Content-Type: application/json" --data '{"method":"eth_getTransactionCount","params":["0xd4c70007F3F502f212c7e6794b94C06F36173B36", "0x2B229E"],"id":1,"jsonrpc":"2.0"}'
{"jsonrpc":"2.0","result":"0x2b","id":1}

Check that specifying by blockNumber works:

curl -s http://localhost:1234/rpc/v1  -X POST -H "Content-Type: application/json" --data '{"method":"eth_getTransactionCount","params":["0xd4c70007F3F502f212c7e6794b94C06F36173B36", {"blockNumber": "0x2B229E"}],"id":1,"jsonrpc":"2.0"}'
{"jsonrpc":"2.0","result":"0x2b","id":1}

Check that specifying by blockHash works:

curl -s http://localhost:1234/rpc/v1  -X POST -H "Content-Type: application/json" --data '{"method":"eth_getTransactionCount","params":["0xd4c70007F3F502f212c7e6794b94C06F36173B36", {"blockHash": "0x7f695f396c2782d772a46af38428ef8f27918ba8158b36854d547ff8e3b994b9"}],"id":1,"jsonrpc":"2.0"}'
{"jsonrpc":"2.0","result":"0x2b","id":1}

Check that specifying by blockHash works and when requireCanonical is set:

curl -s http://localhost:1234/rpc/v1  -X POST -H "Content-Type: application/json" --data '{"method":"eth_getTransactionCount","params":["0xd4c70007F3F502f212c7e6794b94C06F36173B36", {"blockHash": "0x7f695f396c2782d772a46af38428ef8f27918ba8158b36854d547ff8e3b994b9", "requireCanonical": true}],"id":1,"jsonrpc":"2.0"}'
{"jsonrpc":"2.0","result":"0x2b","id":1}

Check that specifying both blockNumber and blochHash should fail:

curl -s http://localhost:1234/rpc/v1  -X POST -H "Content-Type: application/json" --data '{"method":"eth_getTransactionCount","params":["0xd4c70007F3F502f212c7e6794b94C06F36173B36", {"blockNumber": "0x2B229E", "blockHash": "0x7f695f396c2782d772a46af38428ef8f27918ba8158b36854d547ff8e3b994b9", "requireCanonical": true}],"id":1,"jsonrpc":"2.0"}'
{"jsonrpc":"2.0","id":1,"error":{"code":-32700,"message":"unmarshaling params for 'eth_getTransactionCount' (param: *ethtypes.EthBlockParamByNumberOrHash): cannot specify both blockNumber and blockHash"}}

Check specifying by blockHash with requireCanonical is set where blockHash is not in the canonical chain:

TODO: Still trying to find a way to get a orphaned blockHash

@fridrik01 fridrik01 force-pushed the 10814-eip-1891 branch 6 times, most recently from 32c4244 to 8a64b50 Compare May 3, 2023 21:27
@fridrik01 fridrik01 changed the title WIP Add EIP-1898 support to certain RPC methods needed for The Graph compatibility May 4, 2023
@fridrik01 fridrik01 changed the title Add EIP-1898 support to certain RPC methods needed for The Graph compatibility Add EIP-1898 support needed for The Graph compatibility May 4, 2023
@fridrik01 fridrik01 assigned fridrik01 and unassigned fridrik01 May 4, 2023
@fridrik01 fridrik01 force-pushed the 10814-eip-1891 branch 2 times, most recently from 9e18cbf to c9a6734 Compare May 4, 2023 11:54
@fridrik01 fridrik01 marked this pull request as ready for review May 4, 2023 12:12
@fridrik01 fridrik01 requested a review from a team as a code owner May 4, 2023 12:12
@@ -479,6 +479,9 @@ func ExampleValue(method string, t, parent reflect.Type) interface{} {
es := exampleStruct(method, t.Elem(), t)
ExampleValues[t] = es
return es
} else if t.Elem().Kind() == reflect.String {
Copy link
Member

Choose a reason for hiding this comment

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

Should probably be a switch statement now.

Comment on lines 820 to 845
PredefinedBlock *string
Number *EthUint64
Copy link
Member

Choose a reason for hiding this comment

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

  • Why do we have two block number fields?
  • These should both be ignored by json (`json:"-"`). Or private.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Stebalien I looked better into merging the Number/BlockNumber into one and I think it may not be such a great idea.

For context, the issue is that currently the blkParam can be one of "latest", "earliest", "pending", or a block number (decimal or hex) but with this PR we additionally need to support that blkParam to be able to be a json hash witch a certain named fields (blockNumber,blockHash,requireCanonical). To implement this, I introduced this EthBlockParamByNumberOrHash struct which has fields for both cases (normal as before + support for json hashes) and handles the al the construction/json deserialization.

It would be possible to merge the Number and BlockNumber in EthBlockParamByNumberOrHashinto one, but I would think that makes the code less readable since right now its easy to see from reading the code whether we are talking about a normal block number or a one we need to support for the EIP-1891. Also, it would make error handling cumbersome (like here).

@@ -815,3 +815,82 @@ func (e EthFeeHistoryParams) MarshalJSON() ([]byte, error) {
}
return json.Marshal([]interface{}{e.BlkCount, e.NewestBlkNum})
}

type EthBlockParamByNumberOrHash struct {
PredefinedBlock *string
Copy link
Member

Choose a reason for hiding this comment

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

I'd consider using negative constants to represent "latest", etc. Storing them in the BlockNumber field. I.e., we'd have three fields only:

const (
    EthBlockLatest EthUint64 = -iota
    EthBlockPending
    EthBlockEarliest
    // ...
)

type EthBlockParam struct {
    BlockNumber      *EthUint64
    BlockHash        *EthHash
    RequireCanonical bool
}

Copy link
Contributor Author

@fridrik01 fridrik01 May 9, 2023

Choose a reason for hiding this comment

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

@Stebalien I do recall considering that but EthUint64 is an unsigned type, do you think this would warrant a new type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would love feedback from @filecoin-project/lotus-maintainers team on Stebs comment here.

@jennijuju jennijuju requested a review from snissn May 19, 2023 16:05
@fridrik01 fridrik01 requested a review from a team May 27, 2023 11:00
Fixes: #10814

This PR updates the following RPC methods according to EIP-1898
specs.

The following RPC methods are affected:

- eth_getBalance
- eth_getStorageAt
- eth_getTransactionCount
- eth_getCode
- eth_call

Note that eth_getBlockByNumber was not included in this list in
the spec although it seems it should be affected also?

Currently these methods all accept a blkParam string which can be
one of "latest", "earliest", "pending", or a block number (decimal
or hex). The spec enables caller to additionally specify a json
hash which can include the following fields:

- blockNumber EthUint64: A block number (decimal or hex) which is
  similar to the original use of the blkParam string
- blockHash EthHash: The block hash
- requireCanonical bool) If true we should make sure the block is
  in the canonical chain

Since the blkParam needs to support both being a number/string and
a json hash then this to properly work we need to introduce a new
struct with pointer fields to check if they exist. This is done
in the EthBlockParamByNumberOrHash struct which first tries to
unmarshal as a json hash (according to eip-1898) and then fallback
to unmarshal as string/number.
Copy link
Member

@Stebalien Stebalien left a comment

Choose a reason for hiding this comment

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

Two small changes but otherwise lgtm.

@@ -839,3 +839,80 @@ func (e EthFeeHistoryParams) MarshalJSON() ([]byte, error) {
}
return json.Marshal([]interface{}{e.BlkCount, e.NewestBlkNum})
}

type EthBlockNumberOrHash struct {
PredefinedBlock *string
Copy link
Member

Choose a reason for hiding this comment

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

This field shouldn't be serialized to json.

Comment on lines 283 to 293
// walk back the current chain (our head) until we reach targetHeight and validate the block hash
currTs := head
for {
if currTs.Equals(ts) {
return ts, nil
} else if currTs.Height() < ts.Height() {
return nil, fmt.Errorf("could not find block hash %s in canonical chain", blkParam.BlockHash.ToCid())
}

currTs, err = a.Chain.LoadTipSet(ctx, currTs.Parents())
if err != nil {
return nil, fmt.Errorf("failed to load tipset: %v", err)
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Call ChainGetTipSetByHeight and let that walk for you. That'll use the index/skip-list.

Copy link
Member

Choose a reason for hiding this comment

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

Then, when you have the tipset at the target height:

  1. Make sure it's actually at the target height (nulls, etc.). Just in case.
  2. Make sure it equals the expected tipset.

@fridrik01 fridrik01 force-pushed the 10814-eip-1891 branch 2 times, most recently from f6136e0 to 07ac21c Compare June 21, 2023 18:12

predefined := blkParam.PredefinedBlock
if predefined != nil {
if *predefined == "earliest" {
Copy link
Member

Choose a reason for hiding this comment

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

This should probably be a switch, but it's not critical.

@Stebalien
Copy link
Member

Looks like the final issue is the linter not being happy about some error formatting.

@fridrik01
Copy link
Contributor Author

Looks like the final issue is the linter not being happy about some error formatting.

I need to look better into it tomorrow, but I think its because of the change of ignoring PredefinedBlock when marshaling json.

@Stebalien
Copy link
Member

Well, the linter is mad about an Errorf.

@fridrik01 fridrik01 force-pushed the 10814-eip-1891 branch 4 times, most recently from df2037e to b7d7d4a Compare June 22, 2023 11:43
@fridrik01
Copy link
Contributor Author

@Stebalien There were several failed tests that were due to the itest/testkit creating a client and setting the ThroughRPC() option. This causes the struct to always be serialized to JSON and in case of testing the predefined blocks (like here) then when we try to unmarshal the struct, we get an empty hash {} which results in a failure.

To address this we can either:

  1. Serialize this field as json
  2. Disable RPC in our tests
  3. Merge predefined block field with the BlockNumber but that would require creating a new EthInt64 type which would require a big refactor in the codebase as this is used everywhere.

I updated the PR with 1) in mind, but let me know if you would prefer other options

@Stebalien
Copy link
Member

I believe the correct thing to do is to marshal it into the blockNumber field.

@Stebalien
Copy link
Member

Well, it's unclear. That's what go-ethereum would do, but it's probably not correct.

IMO, the "most correct" thing to do would be to implement MarshalJSON and:

  1. If a predefined block is specified, marshal to just that as a string.
  2. Otherwise, marshal as an object.

@Stebalien
Copy link
Member

My concern with the current approach is that we're now exposing predefiendBlock as a field on the API, when it's supposed to just be an implementation detail.

@fridrik01
Copy link
Contributor Author

Well, it's unclear. That's what go-ethereum would do, but it's probably not correct.

IMO, the "most correct" thing to do would be to implement MarshalJSON and:

  1. If a predefined block is specified, marshal to just that as a string.
  2. Otherwise, marshal as an object.

That should work, just create a tmp struct like in Unmarshal, let me do that!

@fridrik01 fridrik01 merged commit a2431ff into master Jun 23, 2023
93 checks passed
@fridrik01 fridrik01 deleted the 10814-eip-1891 branch June 23, 2023 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

EIP-1898 support needed for The Graph compatibility
2 participants