-
Notifications
You must be signed in to change notification settings - Fork 153
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
Fix/eth_getBlockByNumber and eth_getUncle... nullable fields #341
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
418e29f
ethclient,internal/ethapi: add Nonce (null) to pending block JSON
meowsbits be15ecb
ethclient: add test case for expected non-null values ('number')
meowsbits b446973
ethclient: refactor test, unifying to map of expected value (null, ze…
meowsbits dc4e75c
ethclient: add missing fields to test and compare diff
meowsbits f0cd460
ethclient,internal/ethapi: add Hash (null) to pending block JSON
meowsbits b3140ce
ethclient: assert positive generic match on special case timestamp
meowsbits 0e6d656
ethclient,internal/ethapi: jsonrpc header marshaling assign totalDiff…
meowsbits f11d477
internal/ethapi: add Miner (null) to pending block JSON
meowsbits 6b3f223
ethclient,internal/ethapi: wip: totalDifficulty is fucked up; either …
meowsbits a6b943e
ethclient: move test to 'cg' namespaced file, allows testing @ethereu…
meowsbits 0b3cf0d
ethclient: refactor test to be generic using pattern matching
meowsbits 97e24db
ethclient: add Latest suffix for a more descriptive test name
meowsbits e52bcad
ethclient: extrapolate regex-based test to earliest,latest,pending
meowsbits cfcf223
ethclient: rename test to be more conventional and descriptive
meowsbits 14ec0c0
internal/ethapi: remove dead wip-commented code
meowsbits 4fd659c
.github/workflows,ethclient: init Github Actions CI workflow to test …
meowsbits 3b7de79
ethclient: use go subtest syntax for better descriptiveness and logging
meowsbits 904066f
ethclient: additional commentary
meowsbits 119b604
.github/workflows,ethclient: remove go-ethereum 1:1 comparative testing
meowsbits 78e602b
ethclient: assert hash len for block hash field
meowsbits 67ca5fc
internal/ethapi: omit the transactions field when marshaling uncle bl…
meowsbits 136e50f
ethclient: test that the transactions field is omitted for uncles res…
meowsbits File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
package ethclient | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"regexp" | ||
"testing" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/common/hexutil" | ||
"github.com/ethereum/go-ethereum/consensus/ethash" | ||
"github.com/ethereum/go-ethereum/core" | ||
"github.com/ethereum/go-ethereum/core/rawdb" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/ethereum/go-ethereum/eth" | ||
"github.com/ethereum/go-ethereum/node" | ||
"github.com/ethereum/go-ethereum/params" | ||
"github.com/ethereum/go-ethereum/params/types/genesisT" | ||
) | ||
|
||
// TestEthGetBlockByNumber_ValidJSONResponse tests that | ||
// JSON RPC API responses to eth_getBlockByNumber meet pattern-based expectations. | ||
// These validations include the null-ness of certain fields for the 'pending' block | ||
// as well existence of all expected keys and values. | ||
func TestEthGetBlockByNumber_ValidJSONResponse(t *testing.T) { | ||
backend, _ := newTestBackend(t) | ||
client, _ := backend.Attach() | ||
defer backend.Close() | ||
defer client.Close() | ||
|
||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) | ||
defer cancel() | ||
|
||
// Have to sleep a little to make sure miner has time to set pending. | ||
time.Sleep(time.Millisecond * 100) | ||
|
||
// Get a reference block. | ||
parent, err := NewClient(client).HeaderByNumber(ctx, nil) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if parent == nil { | ||
t.Fatal("bad test") | ||
} | ||
|
||
reNull := regexp.MustCompile(`^null$`) | ||
reHexAnyLen := regexp.MustCompile(`^"0x[a-zA-Z0-9]+"$`) | ||
reHexHashLen := regexp.MustCompile(fmt.Sprintf(`^"0x[a-zA-Z0-9]{%d}"$`, common.HashLength*2)) | ||
|
||
// completeBlockExpectations define expectations for 'earliest' and 'latest' blocks. | ||
completeBlockExpectations := map[string]*regexp.Regexp{ | ||
"nonce": reHexAnyLen, | ||
"hash": reHexHashLen, | ||
"miner": regexp.MustCompile(fmt.Sprintf(`^"0x[a-zA-Z0-9]{%d}"$`, common.AddressLength*2)), | ||
|
||
"totalDifficulty": reHexAnyLen, | ||
|
||
"mixHash": regexp.MustCompile(fmt.Sprintf(`^"0x[0]{%d}"$`, common.HashLength*2)), | ||
"logsBloom": regexp.MustCompile(fmt.Sprintf(`^"0x[0]{%d}"$`, types.BloomByteLength*2)), | ||
|
||
"number": reHexAnyLen, | ||
"difficulty": reHexAnyLen, | ||
"gasLimit": reHexAnyLen, | ||
"gasUsed": reHexAnyLen, | ||
"size": reHexAnyLen, | ||
"timestamp": reHexAnyLen, | ||
"extraData": reHexAnyLen, | ||
|
||
"parentHash": reHexHashLen, | ||
"transactionsRoot": reHexHashLen, | ||
"stateRoot": reHexHashLen, | ||
"receiptsRoot": reHexHashLen, | ||
"sha3Uncles": reHexHashLen, | ||
|
||
"uncles": regexp.MustCompile(`^\[\]$`), | ||
"transactions": regexp.MustCompile(`^\[\]$`), | ||
} | ||
|
||
// Construct the 'pending' block expectations as a copy of the concrete block | ||
// expectations. | ||
pendingBlockExpectations := map[string]*regexp.Regexp{} | ||
for k, v := range completeBlockExpectations { | ||
pendingBlockExpectations[k] = v | ||
} | ||
|
||
// Make 'pending' specific adjustments. | ||
pendingBlockExpectations["nonce"] = reNull | ||
pendingBlockExpectations["hash"] = reNull | ||
pendingBlockExpectations["miner"] = reNull | ||
pendingBlockExpectations["totalDifficulty"] = reNull | ||
|
||
for blockHeight, cases := range map[string]map[string]*regexp.Regexp{ | ||
"earliest": completeBlockExpectations, | ||
"latest": completeBlockExpectations, | ||
"pending": pendingBlockExpectations, | ||
} { | ||
for _, fullTxes := range []bool{true, false} { | ||
t.Run(fmt.Sprintf("eth_getBlockByNumber-%s-%v", blockHeight, fullTxes), func(t *testing.T) { | ||
gotPending := make(map[string]json.RawMessage) | ||
err := client.CallContext(ctx, &gotPending, "eth_getBlockByNumber", blockHeight, fullTxes) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
for key, re := range cases { | ||
gotVal, ok := gotPending[key] | ||
if !ok { | ||
t.Errorf("%s: missing key", key) | ||
} | ||
if !re.Match(gotVal) { | ||
t.Errorf("%s want: %v, got: %v", key, re, string(gotVal)) | ||
} | ||
} | ||
|
||
for k, v := range gotPending { | ||
if _, ok := cases[k]; !ok { | ||
t.Errorf("%s: missing key (value: %v)", k, string(v)) | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
} | ||
|
||
// newTestBackendWithUncles duplicates the logic of newTestBackend, except that it generates a backend | ||
// on top of a chain that has uncles. | ||
func newTestBackendWithUncles(t *testing.T) (*node.Node, []*types.Block) { | ||
// Generate test chain. | ||
genesis, blocks := generateTestChainWithUncles() | ||
// Create node | ||
n, err := node.New(&node.Config{}) | ||
if err != nil { | ||
t.Fatalf("can't create new node: %v", err) | ||
} | ||
// Create Ethereum Service | ||
config := ð.Config{Genesis: genesis} | ||
config.Ethash.PowMode = ethash.ModeFake | ||
ethservice, err := eth.New(n, config) | ||
if err != nil { | ||
t.Fatalf("can't create new ethereum service: %v", err) | ||
} | ||
// Import the test chain. | ||
if err := n.Start(); err != nil { | ||
t.Fatalf("can't start test node: %v", err) | ||
} | ||
if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil { | ||
t.Fatalf("can't import test blocks: %v", err) | ||
} | ||
return n, blocks | ||
} | ||
|
||
// generateTestChainWithUncles is a test helper function that essentially duplicates generateTestChain, | ||
// except that the chain generated includes block with uncles. | ||
func generateTestChainWithUncles() (*genesisT.Genesis, []*types.Block) { | ||
db := rawdb.NewMemoryDatabase() | ||
config := params.AllEthashProtocolChanges | ||
genesis := &genesisT.Genesis{ | ||
Config: config, | ||
Alloc: genesisT.GenesisAlloc{testAddr: {Balance: testBalance}}, | ||
ExtraData: []byte("test genesis"), | ||
Timestamp: 9000, | ||
} | ||
generate := func(i int, g *core.BlockGen) { | ||
g.OffsetTime(5) | ||
g.SetExtra([]byte("test")) | ||
if i >= 6 { | ||
b2 := g.PrevBlock(i - 3).Header() | ||
b2.Extra = []byte("foo") | ||
g.AddUncle(b2) | ||
} | ||
} | ||
gblock := core.GenesisToBlock(genesis, db) | ||
engine := ethash.NewFaker() | ||
blocks, _ := core.GenerateChain(config, gblock, engine, db, 8, generate) | ||
blocks = append([]*types.Block{gblock}, blocks...) | ||
|
||
return genesis, blocks | ||
} | ||
|
||
// TestUncleResponseEncoding tests the correctness of the JSON encoding of uncle-type responses. | ||
// These, different from canonical or side blocks, should NOT include the transactions field. | ||
func TestUncleResponseEncoding(t *testing.T) { | ||
backend, chain := newTestBackendWithUncles(t) | ||
client, _ := backend.Attach() | ||
defer backend.Close() | ||
defer client.Close() | ||
|
||
res := make(map[string]json.RawMessage) | ||
err := client.Call(&res, "eth_getUncleByBlockNumberAndIndex", hexutil.EncodeUint64(uint64(len(chain)-1)), "0x0") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if len(res) == 0 { | ||
t.Fatal("empty response") | ||
} | ||
|
||
if v, ok := res["uncles"]; !ok { | ||
t.Fatal("uncles: missing field") | ||
} else if !regexp.MustCompile(`^\[\]$`).Match(v) { | ||
t.Fatal("uncles: should be empty array") | ||
} | ||
|
||
if _, ok := res["transactions"]; ok { | ||
t.Fatal("transactions: field not omitted") | ||
} | ||
|
||
// Sanity check a few other fields | ||
reHexAnyLen := regexp.MustCompile(`^"0x[a-zA-Z0-9]+"$`) | ||
for _, field := range []string{"number", "hash", "nonce", "parentHash", "transactionsRoot"} { | ||
if v, ok := res[field]; !ok { | ||
t.Fatalf("%s: missing field", field) | ||
} else if !reHexAnyLen.Match(v) { | ||
t.Fatalf("%s: unexpected value: %s", field, string(v)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This now matches the ethereum/go-ethereum implementation.