From 919229d63c9c138160816d007866bf3daeb6c6f0 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 12 Nov 2020 21:21:24 +0100 Subject: [PATCH 001/235] params: begin v1.9.25 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index c0f356889a..a2ea188170 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 24 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 25 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From a19b4235c71a1d440e585b13be90677e3572261e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 13 Nov 2020 09:27:57 +0100 Subject: [PATCH 002/235] crypto/bn256: improve bn256 fuzzer (#21815) * crypto/cloudflare: fix nil deref in random G1/G2 reading * crypto/bn256: improve fuzzer * crypto/bn256: fix some flaws in fuzzer --- crypto/bn256/bn256_fuzz.go | 117 ++++++++++++++----------------- crypto/bn256/cloudflare/bn256.go | 2 +- 2 files changed, 53 insertions(+), 66 deletions(-) diff --git a/crypto/bn256/bn256_fuzz.go b/crypto/bn256/bn256_fuzz.go index 6aa1421170..585d509bf4 100644 --- a/crypto/bn256/bn256_fuzz.go +++ b/crypto/bn256/bn256_fuzz.go @@ -8,42 +8,52 @@ package bn256 import ( "bytes" + "fmt" + "io" "math/big" cloudflare "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" google "github.com/ethereum/go-ethereum/crypto/bn256/google" ) -// FuzzAdd fuzzez bn256 addition between the Google and Cloudflare libraries. -func FuzzAdd(data []byte) int { - // Ensure we have enough data in the first place - if len(data) != 128 { - return 0 +func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1) { + _, xc, err := cloudflare.RandomG1(input) + if err != nil { + // insufficient input + return nil, nil } - // Ensure both libs can parse the first curve point - xc := new(cloudflare.G1) - _, errc := xc.Unmarshal(data[:64]) - xg := new(google.G1) - _, errg := xg.Unmarshal(data[:64]) - - if (errc == nil) != (errg == nil) { - panic("parse mismatch") - } else if errc != nil { - return 0 + if _, err := xg.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) } - // Ensure both libs can parse the second curve point - yc := new(cloudflare.G1) - _, errc = yc.Unmarshal(data[64:]) + return xc, xg +} - yg := new(google.G1) - _, errg = yg.Unmarshal(data[64:]) +func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2) { + _, xc, err := cloudflare.RandomG2(input) + if err != nil { + // insufficient input + return nil, nil + } + xg := new(google.G2) + if _, err := xg.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) + } + return xc, xg +} - if (errc == nil) != (errg == nil) { - panic("parse mismatch") - } else if errc != nil { +// FuzzAdd fuzzez bn256 addition between the Google and Cloudflare libraries. +func FuzzAdd(data []byte) int { + input := bytes.NewReader(data) + xc, xg := getG1Points(input) + if xc == nil { return 0 } + yc, yg := getG1Points(input) + if yc == nil { + return 0 + } + // Ensure both libs can parse the second curve point // Add the two points and ensure they result in the same output rc := new(cloudflare.G1) rc.Add(xc, yc) @@ -54,73 +64,50 @@ func FuzzAdd(data []byte) int { if !bytes.Equal(rc.Marshal(), rg.Marshal()) { panic("add mismatch") } - return 0 + return 1 } // FuzzMul fuzzez bn256 scalar multiplication between the Google and Cloudflare // libraries. func FuzzMul(data []byte) int { - // Ensure we have enough data in the first place - if len(data) != 96 { + input := bytes.NewReader(data) + pc, pg := getG1Points(input) + if pc == nil { return 0 } - // Ensure both libs can parse the curve point - pc := new(cloudflare.G1) - _, errc := pc.Unmarshal(data[:64]) - - pg := new(google.G1) - _, errg := pg.Unmarshal(data[:64]) - - if (errc == nil) != (errg == nil) { - panic("parse mismatch") - } else if errc != nil { + // Add the two points and ensure they result in the same output + remaining := input.Len() + if remaining == 0 { return 0 } - // Add the two points and ensure they result in the same output + buf := make([]byte, remaining) + input.Read(buf) + rc := new(cloudflare.G1) - rc.ScalarMult(pc, new(big.Int).SetBytes(data[64:])) + rc.ScalarMult(pc, new(big.Int).SetBytes(buf)) rg := new(google.G1) - rg.ScalarMult(pg, new(big.Int).SetBytes(data[64:])) + rg.ScalarMult(pg, new(big.Int).SetBytes(buf)) if !bytes.Equal(rc.Marshal(), rg.Marshal()) { panic("scalar mul mismatch") } - return 0 + return 1 } func FuzzPair(data []byte) int { - // Ensure we have enough data in the first place - if len(data) != 192 { + input := bytes.NewReader(data) + pc, pg := getG1Points(input) + if pc == nil { return 0 } - // Ensure both libs can parse the curve point - pc := new(cloudflare.G1) - _, errc := pc.Unmarshal(data[:64]) - - pg := new(google.G1) - _, errg := pg.Unmarshal(data[:64]) - - if (errc == nil) != (errg == nil) { - panic("parse mismatch") - } else if errc != nil { - return 0 - } - // Ensure both libs can parse the twist point - tc := new(cloudflare.G2) - _, errc = tc.Unmarshal(data[64:]) - - tg := new(google.G2) - _, errg = tg.Unmarshal(data[64:]) - - if (errc == nil) != (errg == nil) { - panic("parse mismatch") - } else if errc != nil { + tc, tg := getG2Points(input) + if tc == nil { return 0 } // Pair the two points and ensure thet result in the same output if cloudflare.PairingCheck([]*cloudflare.G1{pc}, []*cloudflare.G2{tc}) != google.PairingCheck([]*google.G1{pg}, []*google.G2{tg}) { panic("pair mismatch") } - return 0 + return 1 } diff --git a/crypto/bn256/cloudflare/bn256.go b/crypto/bn256/cloudflare/bn256.go index 38822a76bf..a6dd972ba8 100644 --- a/crypto/bn256/cloudflare/bn256.go +++ b/crypto/bn256/cloudflare/bn256.go @@ -23,7 +23,7 @@ import ( func randomK(r io.Reader) (k *big.Int, err error) { for { k, err = rand.Int(r, Order) - if k.Sign() > 0 || err != nil { + if err != nil || k.Sign() > 0 { return } } From 9ded4e33c5b6976657018b23ba4cc76939c165c7 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 13 Nov 2020 10:17:23 +0100 Subject: [PATCH 003/235] crypto/bn256: better comments for u, P and Order (#21836) --- crypto/bn256/google/constants.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crypto/bn256/google/constants.go b/crypto/bn256/google/constants.go index ab649d7f3f..0db432c9cf 100644 --- a/crypto/bn256/google/constants.go +++ b/crypto/bn256/google/constants.go @@ -13,13 +13,14 @@ func bigFromBase10(s string) *big.Int { return n } -// u is the BN parameter that determines the prime: 1868033³. +// u is the BN parameter that determines the prime. var u = bigFromBase10("4965661367192848881") -// p is a prime over which we form a basic field: 36u⁴+36u³+24u²+6u+1. +// P is a prime over which we form a basic field: 36u⁴+36u³+24u²+6u+1. var P = bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208583") // Order is the number of elements in both G₁ and G₂: 36u⁴+36u³+18u²+6u+1. +// Order - 1 = 2^28 * 3^2 * 13 * 29 * 983 * 11003 * 237073 * 405928799 * 1670836401704629 * 13818364434197438864469338081. var Order = bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617") // xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+9. From 0703c91fbad6653dc7aa809816e5698b0b868693 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 13 Nov 2020 12:36:38 +0100 Subject: [PATCH 004/235] tests/fuzzers: improve the fuzzers (#21829) * tests/fuzzers, common/bitutil: make fuzzers use correct returnvalues + remove output * tests/fuzzers/stacktrie: fix duplicate-key insertion in stacktrie (false positive) * tests/fuzzers/stacktrie: fix compilation error * tests/fuzzers: linter nits --- common/bitutil/compress_fuzz.go | 6 +++--- tests/fuzzers/keystore/keystore-fuzzer.go | 2 +- tests/fuzzers/rlp/rlp_fuzzer.go | 16 +++++++-------- tests/fuzzers/stacktrie/trie_fuzzer.go | 7 +++++++ tests/fuzzers/txfetcher/txfetcher_fuzzer.go | 22 +++++++++++++-------- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/common/bitutil/compress_fuzz.go b/common/bitutil/compress_fuzz.go index 1b87f50edc..714bbcd131 100644 --- a/common/bitutil/compress_fuzz.go +++ b/common/bitutil/compress_fuzz.go @@ -24,7 +24,7 @@ import "bytes" // invocations. func Fuzz(data []byte) int { if len(data) == 0 { - return -1 + return 0 } if data[0]%2 == 0 { return fuzzEncode(data[1:]) @@ -39,7 +39,7 @@ func fuzzEncode(data []byte) int { if !bytes.Equal(data, proc) { panic("content mismatch") } - return 0 + return 1 } // fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and @@ -52,5 +52,5 @@ func fuzzDecode(data []byte) int { if comp := bitsetEncodeBytes(blob); !bytes.Equal(comp, data) { panic("content mismatch") } - return 0 + return 1 } diff --git a/tests/fuzzers/keystore/keystore-fuzzer.go b/tests/fuzzers/keystore/keystore-fuzzer.go index 704f29dc48..e3bcae92e1 100644 --- a/tests/fuzzers/keystore/keystore-fuzzer.go +++ b/tests/fuzzers/keystore/keystore-fuzzer.go @@ -33,5 +33,5 @@ func Fuzz(input []byte) int { panic(err) } os.Remove(a.URL.Path) - return 0 + return 1 } diff --git a/tests/fuzzers/rlp/rlp_fuzzer.go b/tests/fuzzers/rlp/rlp_fuzzer.go index 534540476c..18b36287b5 100644 --- a/tests/fuzzers/rlp/rlp_fuzzer.go +++ b/tests/fuzzers/rlp/rlp_fuzzer.go @@ -37,17 +37,17 @@ func decodeEncode(input []byte, val interface{}, i int) { } func Fuzz(input []byte) int { + if len(input) == 0 { + return 0 + } + var i int { - if len(input) > 0 { - rlp.Split(input) - } + rlp.Split(input) } { - if len(input) > 0 { - if elems, _, err := rlp.SplitList(input); err == nil { - rlp.CountValues(elems) - } + if elems, _, err := rlp.SplitList(input); err == nil { + rlp.CountValues(elems) } } @@ -123,5 +123,5 @@ func Fuzz(input []byte) int { var rs types.Receipts decodeEncode(input, &rs, i) } - return 0 + return 1 } diff --git a/tests/fuzzers/stacktrie/trie_fuzzer.go b/tests/fuzzers/stacktrie/trie_fuzzer.go index a072ff772d..5cea7769c2 100644 --- a/tests/fuzzers/stacktrie/trie_fuzzer.go +++ b/tests/fuzzers/stacktrie/trie_fuzzer.go @@ -148,6 +148,8 @@ func (f *fuzzer) fuzz() int { vals kvs useful bool maxElements = 10000 + // operate on unique keys only + keys = make(map[string]struct{}) ) // Fill the trie with elements for i := 0; !f.exhausted && i < maxElements; i++ { @@ -158,6 +160,11 @@ func (f *fuzzer) fuzz() int { // thus 'deletion' which is not supported on stacktrie break } + if _, present := keys[string(k)]; present { + // This key is a duplicate, ignore it + continue + } + keys[string(k)] = struct{}{} vals = append(vals, kv{k: k, v: v}) trieA.Update(k, v) useful = true diff --git a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go index 10c7eb9424..d1d6fdc665 100644 --- a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go +++ b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go @@ -51,8 +51,9 @@ func init() { func Fuzz(input []byte) int { // Don't generate insanely large test cases, not much value in them if len(input) > 16*1024 { - return -1 + return 0 } + verbose := false r := bytes.NewReader(input) // Reduce the problem space for certain fuzz runs. Small tx space is better @@ -124,7 +125,9 @@ func Fuzz(input []byte) int { announceIdxs[i] = (int(annBuf[0])*256 + int(annBuf[1])) % len(txs) announces[i] = txs[announceIdxs[i]].Hash() } - fmt.Println("Notify", peer, announceIdxs) + if verbose { + fmt.Println("Notify", peer, announceIdxs) + } if err := f.Notify(peer, announces); err != nil { panic(err) } @@ -163,8 +166,9 @@ func Fuzz(input []byte) int { return 0 } direct := (directFlag % 2) == 0 - - fmt.Println("Enqueue", peer, deliverIdxs, direct) + if verbose { + fmt.Println("Enqueue", peer, deliverIdxs, direct) + } if err := f.Enqueue(peer, deliveries, direct); err != nil { panic(err) } @@ -177,8 +181,9 @@ func Fuzz(input []byte) int { return 0 } peer := peers[int(peerIdx)%len(peers)] - - fmt.Println("Drop", peer) + if verbose { + fmt.Println("Drop", peer) + } if err := f.Drop(peer); err != nil { panic(err) } @@ -191,8 +196,9 @@ func Fuzz(input []byte) int { return 0 } tick := time.Duration(tickCnt) * 100 * time.Millisecond - - fmt.Println("Sleep", tick) + if verbose { + fmt.Println("Sleep", tick) + } clock.Run(tick) } } From 6f4cccf8d27265e197261c717e95ae10e30d7abe Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 13 Nov 2020 13:39:59 +0100 Subject: [PATCH 005/235] core/vm, protocol_params: implement eip-2565 modexp repricing (#21607) * core/vm, protocol_params: implement eip-2565 modexp repricing * core/vm: fix review concerns --- core/vm/contracts.go | 80 +++++++++--- core/vm/contracts_test.go | 27 +++- .../testdata/precompiles/modexp_eip2565.json | 121 ++++++++++++++++++ params/protocol_params.go | 1 - 4 files changed, 209 insertions(+), 20 deletions(-) create mode 100644 core/vm/testdata/precompiles/modexp_eip2565.json diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 35faa7b83d..0c828b1cd9 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -58,7 +58,7 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, common.BytesToAddress([]byte{6}): &bn256AddByzantium{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{}, common.BytesToAddress([]byte{8}): &bn256PairingByzantium{}, @@ -71,7 +71,7 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, @@ -85,7 +85,7 @@ var PrecompiledContractsYoloV2 = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, @@ -222,14 +222,19 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) { } // bigModExp implements a native big integer exponential modular operation. -type bigModExp struct{} +type bigModExp struct { + eip2565 bool +} var ( big0 = big.NewInt(0) big1 = big.NewInt(1) + big3 = big.NewInt(3) big4 = big.NewInt(4) + big7 = big.NewInt(7) big8 = big.NewInt(8) big16 = big.NewInt(16) + big20 = big.NewInt(20) big32 = big.NewInt(32) big64 = big.NewInt(64) big96 = big.NewInt(96) @@ -239,6 +244,34 @@ var ( big199680 = big.NewInt(199680) ) +// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198 +// +// def mult_complexity(x): +// if x <= 64: return x ** 2 +// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072 +// else: return x ** 2 // 16 + 480 * x - 199680 +// +// where is x is max(length_of_MODULUS, length_of_BASE) +func modexpMultComplexity(x *big.Int) *big.Int { + switch { + case x.Cmp(big64) <= 0: + x.Mul(x, x) // x ** 2 + case x.Cmp(big1024) <= 0: + // (x ** 2 // 4 ) + ( 96 * x - 3072) + x = new(big.Int).Add( + new(big.Int).Div(new(big.Int).Mul(x, x), big4), + new(big.Int).Sub(new(big.Int).Mul(big96, x), big3072), + ) + default: + // (x ** 2 // 16) + (480 * x - 199680) + x = new(big.Int).Add( + new(big.Int).Div(new(big.Int).Mul(x, x), big16), + new(big.Int).Sub(new(big.Int).Mul(big480, x), big199680), + ) + } + return x +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bigModExp) RequiredGas(input []byte) uint64 { var ( @@ -273,25 +306,36 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { adjExpLen.Mul(big8, adjExpLen) } adjExpLen.Add(adjExpLen, big.NewInt(int64(msb))) - // Calculate the gas cost of the operation gas := new(big.Int).Set(math.BigMax(modLen, baseLen)) - switch { - case gas.Cmp(big64) <= 0: + if c.eip2565 { + // EIP-2565 has three changes + // 1. Different multComplexity (inlined here) + // in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565): + // + // def mult_complexity(x): + // ceiling(x/8)^2 + // + //where is x is max(length_of_MODULUS, length_of_BASE) + gas = gas.Add(gas, big7) + gas = gas.Div(gas, big8) gas.Mul(gas, gas) - case gas.Cmp(big1024) <= 0: - gas = new(big.Int).Add( - new(big.Int).Div(new(big.Int).Mul(gas, gas), big4), - new(big.Int).Sub(new(big.Int).Mul(big96, gas), big3072), - ) - default: - gas = new(big.Int).Add( - new(big.Int).Div(new(big.Int).Mul(gas, gas), big16), - new(big.Int).Sub(new(big.Int).Mul(big480, gas), big199680), - ) + + gas.Mul(gas, math.BigMax(adjExpLen, big1)) + // 2. Different divisor (`GQUADDIVISOR`) (3) + gas.Div(gas, big3) + if gas.BitLen() > 64 { + return math.MaxUint64 + } + // 3. Minimum price of 200 gas + if gas.Uint64() < 200 { + return 200 + } + return gas.Uint64() } + gas = modexpMultComplexity(gas) gas.Mul(gas, math.BigMax(adjExpLen, big1)) - gas.Div(gas, new(big.Int).SetUint64(params.ModExpQuadCoeffDiv)) + gas.Div(gas, big20) if gas.BitLen() > 64 { return math.MaxUint64 diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index ed0d675a69..30d9b49f71 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -43,7 +43,29 @@ type precompiledFailureTest struct { Name string } -var allPrecompiles = PrecompiledContractsYoloV2 +// allPrecompiles does not map to the actual set of precompiles, as it also contains +// repriced versions of precompiles at certain slots +var allPrecompiles = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, + common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, + common.BytesToAddress([]byte{10}): &bls12381G1Add{}, + common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, + common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, + common.BytesToAddress([]byte{13}): &bls12381G2Add{}, + common.BytesToAddress([]byte{14}): &bls12381G2Mul{}, + common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{}, + common.BytesToAddress([]byte{16}): &bls12381Pairing{}, + common.BytesToAddress([]byte{17}): &bls12381MapG1{}, + common.BytesToAddress([]byte{18}): &bls12381MapG2{}, +} // EIP-152 test vectors var blake2FMalformedInputTests = []precompiledFailureTest{ @@ -213,6 +235,9 @@ func BenchmarkPrecompiledIdentity(bench *testing.B) { func TestPrecompiledModExp(t *testing.T) { testJson("modexp", "05", t) } func BenchmarkPrecompiledModExp(b *testing.B) { benchJson("modexp", "05", b) } +func TestPrecompiledModExpEip2565(t *testing.T) { testJson("modexp_eip2565", "f5", t) } +func BenchmarkPrecompiledModExpEip2565(b *testing.B) { benchJson("modexp_eip2565", "f5", b) } + // Tests the sample inputs from the elliptic curve addition EIP 213. func TestPrecompiledBn256Add(t *testing.T) { testJson("bn256Add", "06", t) } func BenchmarkPrecompiledBn256Add(b *testing.B) { benchJson("bn256Add", "06", b) } diff --git a/core/vm/testdata/precompiles/modexp_eip2565.json b/core/vm/testdata/precompiles/modexp_eip2565.json new file mode 100644 index 0000000000..c55441439e --- /dev/null +++ b/core/vm/testdata/precompiles/modexp_eip2565.json @@ -0,0 +1,121 @@ +[ + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002003fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "eip_example1", + "Gas": 1360, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "eip_example2", + "Gas": 1360, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb502fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "60008f1614cc01dcfb6bfb09c625cf90b47d4468db81b5f8b7a39d42f332eab9b2da8f2d95311648a8f243f4bb13cfb3d8f7f2a3c014122ebb3ed41b02783adc", + "Name": "nagydani-1-square", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb503fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "4834a46ba565db27903b1c720c9d593e84e4cbd6ad2e64b31885d944f68cd801f92225a8961c952ddf2797fa4701b330c85c4b363798100b921a1a22a46a7fec", + "Name": "nagydani-1-qube", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5010001fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "c36d804180c35d4426b57b50c5bfcca5c01856d104564cd513b461d3c8b8409128a5573e416d0ebe38f5f736766d9dc27143e4da981dfa4d67f7dc474cbee6d2", + "Name": "nagydani-1-pow0x10001", + "Gas": 341, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5102e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "981dd99c3b113fae3e3eaa9435c0dc96779a23c12a53d1084b4f67b0b053a27560f627b873e3f16ad78f28c94f14b6392def26e4d8896c5e3c984e50fa0b3aa44f1da78b913187c6128baa9340b1e9c9a0fd02cb78885e72576da4a8f7e5a113e173a7a2889fde9d407bd9f06eb05bc8fc7b4229377a32941a02bf4edcc06d70", + "Name": "nagydani-2-square", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5103e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "d89ceb68c32da4f6364978d62aaa40d7b09b59ec61eb3c0159c87ec3a91037f7dc6967594e530a69d049b64adfa39c8fa208ea970cfe4b7bcd359d345744405afe1cbf761647e32b3184c7fbe87cee8c6c7ff3b378faba6c68b83b6889cb40f1603ee68c56b4c03d48c595c826c041112dc941878f8c5be828154afd4a16311f", + "Name": "nagydani-2-qube", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51010001e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "ad85e8ef13fd1dd46eae44af8b91ad1ccae5b7a1c92944f92a19f21b0b658139e0cabe9c1f679507c2de354bf2c91ebd965d1e633978a830d517d2f6f8dd5fd58065d58559de7e2334a878f8ec6992d9b9e77430d4764e863d77c0f87beede8f2f7f2ab2e7222f85cc9d98b8467f4bb72e87ef2882423ebdb6daf02dddac6db2", + "Name": "nagydani-2-pow0x10001", + "Gas": 1365, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb02d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "affc7507ea6d84751ec6b3f0d7b99dbcc263f33330e450d1b3ff0bc3d0874320bf4edd57debd587306988157958cb3cfd369cc0c9c198706f635c9e0f15d047df5cb44d03e2727f26b083c4ad8485080e1293f171c1ed52aef5993a5815c35108e848c951cf1e334490b4a539a139e57b68f44fee583306f5b85ffa57206b3ee5660458858534e5386b9584af3c7f67806e84c189d695e5eb96e1272d06ec2df5dc5fabc6e94b793718c60c36be0a4d031fc84cd658aa72294b2e16fc240aef70cb9e591248e38bd49c5a554d1afa01f38dab72733092f7555334bbef6c8c430119840492380aa95fa025dcf699f0a39669d812b0c6946b6091e6e235337b6f8", + "Name": "nagydani-3-square", + "Gas": 341, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb03d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "1b280ecd6a6bf906b806d527c2a831e23b238f89da48449003a88ac3ac7150d6a5e9e6b3be4054c7da11dd1e470ec29a606f5115801b5bf53bc1900271d7c3ff3cd5ed790d1c219a9800437a689f2388ba1a11d68f6a8e5b74e9a3b1fac6ee85fc6afbac599f93c391f5dc82a759e3c6c0ab45ce3f5d25d9b0c1bf94cf701ea6466fc9a478dacc5754e593172b5111eeba88557048bceae401337cd4c1182ad9f700852bc8c99933a193f0b94cf1aedbefc48be3bc93ef5cb276d7c2d5462ac8bb0c8fe8923a1db2afe1c6b90d59c534994a6a633f0ead1d638fdc293486bb634ff2c8ec9e7297c04241a61c37e3ae95b11d53343d4ba2b4cc33d2cfa7eb705e", + "Name": "nagydani-3-qube", + "Gas": 341, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb010001d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "37843d7c67920b5f177372fa56e2a09117df585f81df8b300fba245b1175f488c99476019857198ed459ed8d9799c377330e49f4180c4bf8e8f66240c64f65ede93d601f957b95b83efdee1e1bfde74169ff77002eaf078c71815a9220c80b2e3b3ff22c2f358111d816ebf83c2999026b6de50bfc711ff68705d2f40b753424aefc9f70f08d908b5a20276ad613b4ab4309a3ea72f0c17ea9df6b3367d44fb3acab11c333909e02e81ea2ed404a712d3ea96bba87461720e2d98723e7acd0520ac1a5212dbedcd8dc0c1abf61d4719e319ff4758a774790b8d463cdfe131d1b2dcfee52d002694e98e720cb6ae7ccea353bc503269ba35f0f63bf8d7b672a76", + "Name": "nagydani-3-pow0x10001", + "Gas": 5461, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8102df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "8a5aea5f50dcc03dc7a7a272b5aeebc040554dbc1ffe36753c4fc75f7ed5f6c2cc0de3a922bf96c78bf0643a73025ad21f45a4a5cadd717612c511ab2bff1190fe5f1ae05ba9f8fe3624de1de2a817da6072ddcdb933b50216811dbe6a9ca79d3a3c6b3a476b079fd0d05f04fb154e2dd3e5cb83b148a006f2bcbf0042efb2ae7b916ea81b27aac25c3bf9a8b6d35440062ad8eae34a83f3ffa2cc7b40346b62174a4422584f72f95316f6b2bee9ff232ba9739301c97c99a9ded26c45d72676eb856ad6ecc81d36a6de36d7f9dafafee11baa43a4b0d5e4ecffa7b9b7dcefd58c397dd373e6db4acd2b2c02717712e6289bed7c813b670c4a0c6735aa7f3b0f1ce556eae9fcc94b501b2c8781ba50a8c6220e8246371c3c7359fe4ef9da786ca7d98256754ca4e496be0a9174bedbecb384bdf470779186d6a833f068d2838a88d90ef3ad48ff963b67c39cc5a3ee123baf7bf3125f64e77af7f30e105d72c4b9b5b237ed251e4c122c6d8c1405e736299c3afd6db16a28c6a9cfa68241e53de4cd388271fe534a6a9b0dbea6171d170db1b89858468885d08fecbd54c8e471c3e25d48e97ba450b96d0d87e00ac732aaa0d3ce4309c1064bd8a4c0808a97e0143e43a24cfa847635125cd41c13e0574487963e9d725c01375db99c31da67b4cf65eff555f0c0ac416c727ff8d438ad7c42030551d68c2e7adda0abb1ca7c10", + "Name": "nagydani-4-square", + "Gas": 1365, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8103df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "5a2664252aba2d6e19d9600da582cdd1f09d7a890ac48e6b8da15ae7c6ff1856fc67a841ac2314d283ffa3ca81a0ecf7c27d89ef91a5a893297928f5da0245c99645676b481b7e20a566ee6a4f2481942bee191deec5544600bb2441fd0fb19e2ee7d801ad8911c6b7750affec367a4b29a22942c0f5f4744a4e77a8b654da2a82571037099e9c6d930794efe5cdca73c7b6c0844e386bdca8ea01b3d7807146bb81365e2cdc6475f8c23e0ff84463126189dc9789f72bbce2e3d2d114d728a272f1345122de23df54c922ec7a16e5c2a8f84da8871482bd258c20a7c09bbcd64c7a96a51029bbfe848736a6ba7bf9d931a9b7de0bcaf3635034d4958b20ae9ab3a95a147b0421dd5f7ebff46c971010ebfc4adbbe0ad94d5498c853e7142c450d8c71de4b2f84edbf8acd2e16d00c8115b150b1c30e553dbb82635e781379fe2a56360420ff7e9f70cc64c00aba7e26ed13c7c19622865ae07248daced36416080f35f8cc157a857ed70ea4f347f17d1bee80fa038abd6e39b1ba06b97264388b21364f7c56e192d4b62d9b161405f32ab1e2594e86243e56fcf2cb30d21adef15b9940f91af681da24328c883d892670c6aa47940867a81830a82b82716895db810df1b834640abefb7db2092dd92912cb9a735175bc447be40a503cf22dfe565b4ed7a3293ca0dfd63a507430b323ee248ec82e843b673c97ad730728cebc", + "Name": "nagydani-4-qube", + "Gas": 1365, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81010001df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "bed8b970c4a34849fc6926b08e40e20b21c15ed68d18f228904878d4370b56322d0da5789da0318768a374758e6375bfe4641fca5285ec7171828922160f48f5ca7efbfee4d5148612c38ad683ae4e3c3a053d2b7c098cf2b34f2cb19146eadd53c86b2d7ccf3d83b2c370bfb840913ee3879b1057a6b4e07e110b6bcd5e958bc71a14798c91d518cc70abee264b0d25a4110962a764b364ac0b0dd1ee8abc8426d775ec0f22b7e47b32576afaf1b5a48f64573ed1c5c29f50ab412188d9685307323d990802b81dacc06c6e05a1e901830ba9fcc67688dc29c5e27bde0a6e845ca925f5454b6fb3747edfaa2a5820838fb759eadf57f7cb5cec57fc213ddd8a4298fa079c3c0f472b07fb15aa6a7f0a3780bd296ff6a62e58ef443870b02260bd4fd2bbc98255674b8e1f1f9f8d33c7170b0ebbea4523b695911abbf26e41885344823bd0587115fdd83b721a4e8457a31c9a84b3d3520a07e0e35df7f48e5a9d534d0ec7feef1ff74de6a11e7f93eab95175b6ce22c68d78a642ad642837897ec11349205d8593ac19300207572c38d29ca5dfa03bc14cdbc32153c80e5cc3e739403d34c75915e49beb43094cc6dcafb3665b305ddec9286934ae66ec6b777ca528728c851318eb0f207b39f1caaf96db6eeead6b55ed08f451939314577d42bcc9f97c0b52d0234f88fd07e4c1d7780fdebc025cfffcb572cb27a8c33963", + "Name": "nagydani-4-pow0x10001", + "Gas": 21845, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf02e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "d61fe4e3f32ac260915b5b03b78a86d11bfc41d973fce5b0cc59035cf8289a8a2e3878ea15fa46565b0d806e2f85b53873ea20ed653869b688adf83f3ef444535bf91598ff7e80f334fb782539b92f39f55310cc4b35349ab7b278346eda9bc37c0d8acd3557fae38197f412f8d9e57ce6a76b7205c23564cab06e5615be7c6f05c3d05ec690cba91da5e89d55b152ff8dd2157dc5458190025cf94b1ad98f7cbe64e9482faba95e6b33844afc640892872b44a9932096508f4a782a4805323808f23e54b6ff9b841dbfa87db3505ae4f687972c18ea0f0d0af89d36c1c2a5b14560c153c3fee406f5cf15cfd1c0bb45d767426d465f2f14c158495069d0c5955a00150707862ecaae30624ebacdd8ac33e4e6aab3ff90b6ba445a84689386b9e945d01823a65874444316e83767290fcff630d2477f49d5d8ffdd200e08ee1274270f86ed14c687895f6caf5ce528bd970c20d2408a9ba66216324c6a011ac4999098362dbd98a038129a2d40c8da6ab88318aa3046cb660327cc44236d9e5d2163bd0959062195c51ed93d0088b6f92051fc99050ece2538749165976233697ab4b610385366e5ce0b02ad6b61c168ecfbedcdf74278a38de340fd7a5fead8e588e294795f9b011e2e60377a89e25c90e145397cdeabc60fd32444a6b7642a611a83c464d8b8976666351b4865c37b02e6dc21dbcdf5f930341707b618cc0f03c3122646b3385c9df9f2ec730eec9d49e7dfc9153b6e6289da8c4f0ebea9ccc1b751948e3bb7171c9e4d57423b0eeeb79095c030cb52677b3f7e0b45c30f645391f3f9c957afa549c4e0b2465b03c67993cd200b1af01035962edbc4c9e89b31c82ac121987d6529dafdeef67a132dc04b6dc68e77f22862040b75e2ceb9ff16da0fca534e6db7bd12fa7b7f51b6c08c1e23dfcdb7acbd2da0b51c87ffbced065a612e9b1c8bba9b7e2d8d7a2f04fcc4aaf355b60d764879a76b5e16762d5f2f55d585d0c8e82df6940960cddfb72c91dfa71f6b4e1c6ca25dfc39a878e998a663c04fe29d5e83b9586d047b4d7ff70a9f0d44f127e7d741685ca75f11629128d916a0ffef4be586a30c4b70389cc746e84ebf177c01ee8a4511cfbb9d1ecf7f7b33c7dd8177896e10bbc82f838dcd6db7ac67de62bf46b6a640fb580c5d1d2708f3862e3d2b645d0d18e49ef088053e3a220adc0e033c2afcfe61c90e32151152eb3caaf746c5e377d541cafc6cbb0cc0fa48b5caf1728f2e1957f5addfc234f1a9d89e40d49356c9172d0561a695fce6dab1d412321bbf407f63766ffd7b6b3d79bcfa07991c5a9709849c1008689e3b47c50d613980bec239fb64185249d055b30375ccb4354d71fe4d05648fbf6c80634dfc3575f2f24abb714c1e4c95e8896763bf4316e954c7ad19e5780ab7a040ca6fb9271f90a8b22ae738daf6cb", + "Name": "nagydani-5-square", + "Gas": 5461, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf03e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "5f9c70ec884926a89461056ad20ac4c30155e817f807e4d3f5bb743d789c83386762435c3627773fa77da5144451f2a8aad8adba88e0b669f5377c5e9bad70e45c86fe952b613f015a9953b8a5de5eaee4566acf98d41e327d93a35bd5cef4607d025e58951167957df4ff9b1627649d3943805472e5e293d3efb687cfd1e503faafeb2840a3e3b3f85d016051a58e1c9498aab72e63b748d834b31eb05d85dcde65e27834e266b85c75cc4ec0135135e0601cb93eeeb6e0010c8ceb65c4c319623c5e573a2c8c9fbbf7df68a930beb412d3f4dfd146175484f45d7afaa0d2e60684af9b34730f7c8438465ad3e1d0c3237336722f2aa51095bd5759f4b8ab4dda111b684aa3dac62a761722e7ae43495b7709933512c81c4e3c9133a51f7ce9f2b51fcec064f65779666960b4e45df3900f54311f5613e8012dd1b8efd359eda31a778264c72aa8bb419d862734d769076bce2810011989a45374e5c5d8729fec21427f0bf397eacbb4220f603cf463a4b0c94efd858ffd9768cd60d6ce68d755e0fbad007ce5c2223d70c7018345a102e4ab3c60a13a9e7794303156d4c2063e919f2153c13961fb324c80b240742f47773a7a8e25b3e3fb19b00ce839346c6eb3c732fbc6b888df0b1fe0a3d07b053a2e9402c267b2d62f794d8a2840526e3ade15ce2264496ccd7519571dfde47f7a4bb16292241c20b2be59f3f8fb4f6383f232d838c5a22d8c95b6834d9d2ca493f5a505ebe8899503b0e8f9b19e6e2dd81c1628b80016d02097e0134de51054c4e7674824d4d758760fc52377d2cad145e259aa2ffaf54139e1a66b1e0c1c191e32ac59474c6b526f5b3ba07d3e5ec286eddf531fcd5292869be58c9f22ef91026159f7cf9d05ef66b4299f4da48cc1635bf2243051d342d378a22c83390553e873713c0454ce5f3234397111ac3fe3207b86f0ed9fc025c81903e1748103692074f83824fda6341be4f95ff00b0a9a208c267e12fa01825054cc0513629bf3dbb56dc5b90d4316f87654a8be18227978ea0a8a522760cad620d0d14fd38920fb7321314062914275a5f99f677145a6979b156bd82ecd36f23f8e1273cc2759ecc0b2c69d94dad5211d1bed939dd87ed9e07b91d49713a6e16ade0a98aea789f04994e318e4ff2c8a188cd8d43aeb52c6daa3bc29b4af50ea82a247c5cd67b573b34cbadcc0a376d3bbd530d50367b42705d870f2e27a8197ef46070528bfe408360faa2ebb8bf76e9f388572842bcb119f4d84ee34ae31f5cc594f23705a49197b181fb78ed1ec99499c690f843a4d0cf2e226d118e9372271054fbabdcc5c92ae9fefaef0589cd0e722eaf30c1703ec4289c7fd81beaa8a455ccee5298e31e2080c10c366a6fcf56f7d13582ad0bcad037c612b710fc595b70fbefaaca23623b60c6c39b11beb8e5843b6b3dac60f", + "Name": "nagydani-5-qube", + "Gas": 5461, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf010001e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500", + "Name": "nagydani-5-pow0x10001", + "Gas": 87381, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/params/protocol_params.go b/params/protocol_params.go index fd5452bf15..ffb901ec3d 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -116,7 +116,6 @@ const ( Ripemd160PerWordGas uint64 = 120 // Per-word price for a RIPEMD160 operation IdentityBaseGas uint64 = 15 // Base price for a data copy operation IdentityPerWordGas uint64 = 3 // Per-work price for a data copy operation - ModExpQuadCoeffDiv uint64 = 20 // Divisor for the quadratic particle of the big int modular exponentiation Bn256AddGasByzantium uint64 = 500 // Byzantium gas needed for an elliptic curve addition Bn256AddGasIstanbul uint64 = 150 // Gas needed for an elliptic curve addition From 2045a2bba3cd2f93fd913c692be146adabd8940c Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 13 Nov 2020 13:42:19 +0100 Subject: [PATCH 006/235] core, all: split vm.Context into BlockContext and TxContext (#21672) * all: core: split vm.Config into BlockConfig and TxConfig * core: core/vm: reset EVM between tx in block instead of creating new * core/vm: added docs --- accounts/abi/bind/backends/simulated.go | 5 ++- cmd/evm/internal/t8ntool/execution.go | 10 ++--- cmd/geth/retesteth.go | 10 +++-- core/evm.go | 16 ++++--- core/state_prefetcher.go | 5 ++- core/state_processor.go | 48 ++++++++++++-------- core/state_transition.go | 8 ++-- core/vm/evm.go | 41 +++++++++++------ core/vm/gas_table_test.go | 4 +- core/vm/instructions.go | 16 +++---- core/vm/instructions_test.go | 14 +++--- core/vm/logger_test.go | 2 +- core/vm/runtime/env.go | 10 +++-- core/vm/runtime/runtime.go | 6 +-- eth/api_backend.go | 5 ++- eth/api_tracer.go | 60 ++++++++++++------------- eth/tracers/tracer.go | 2 +- eth/tracers/tracer_test.go | 4 +- eth/tracers/tracers_test.go | 21 +++++---- les/api_backend.go | 5 ++- les/odr_test.go | 10 +++-- light/odr_test.go | 5 ++- tests/state_test_util.go | 5 ++- tests/vm_test_util.go | 10 +++-- 24 files changed, 183 insertions(+), 139 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index c7efca440b..695d81bf03 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -542,10 +542,11 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM // Execute the call. msg := callMsg{call} - evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil) + txContext := core.NewEVMTxContext(msg) + evmContext := core.NewEVMBlockContext(block.Header(), b.blockchain, nil) // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. - vmEnv := vm.NewEVM(evmContext, stateDB, b.config, vm.Config{}) + vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{}) gasPool := new(core.GasPool).AddGas(math.MaxUint64) return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb() diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index d8b93d291a..171443e156 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -110,7 +110,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, txIndex = 0 ) gaspool.AddGas(pre.Env.GasLimit) - vmContext := vm.Context{ + vmContext := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, Coinbase: pre.Env.Coinbase, @@ -119,7 +119,6 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, Difficulty: pre.Env.Difficulty, GasLimit: pre.Env.GasLimit, GetHash: getHash, - // GasPrice and Origin needs to be set per transaction } // If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's // done in StateProcessor.Process(block, ...), right before transactions are applied. @@ -143,10 +142,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, vmConfig.Tracer = tracer vmConfig.Debug = (tracer != nil) statedb.Prepare(tx.Hash(), blockHash, txIndex) - vmContext.GasPrice = msg.GasPrice() - vmContext.Origin = msg.From() + txContext := core.NewEVMTxContext(msg) - evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig) + evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) if chainConfig.IsYoloV2(vmContext.BlockNumber) { statedb.AddAddressToAccessList(msg.From()) if dst := msg.To(); dst != nil { @@ -185,7 +183,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, receipt.GasUsed = msgResult.UsedGas // if the transaction created a contract, store the creation address in the receipt. if msg.To() == nil { - receipt.ContractAddress = crypto.CreateAddress(evm.Context.Origin, tx.Nonce()) + receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } // Set the receipt logs and create a bloom for filtering receipt.Logs = statedb.GetLogs(tx.Hash()) diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go index debee1182b..d3e824bd68 100644 --- a/cmd/geth/retesteth.go +++ b/cmd/geth/retesteth.go @@ -671,12 +671,13 @@ func (api *RetestethAPI) AccountRange(ctx context.Context, } // Recompute transactions up to the target index. signer := types.MakeSigner(api.blockchain.Config(), block.Number()) + context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil) for idx, tx := range block.Transactions() { // Assemble the transaction call message and return if the requested offset msg, _ := tx.AsMessage(signer) - context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) + txContext := core.NewEVMTxContext(msg) // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(context, txContext, statedb, api.blockchain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } @@ -781,12 +782,13 @@ func (api *RetestethAPI) StorageRangeAt(ctx context.Context, } // Recompute transactions up to the target index. signer := types.MakeSigner(api.blockchain.Config(), block.Number()) + context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil) for idx, tx := range block.Transactions() { // Assemble the transaction call message and return if the requested offset msg, _ := tx.AsMessage(signer) - context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) + txContext := core.NewEVMTxContext(msg) // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(context, txContext, statedb, api.blockchain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } diff --git a/core/evm.go b/core/evm.go index 8abe5a0477..8f69d51499 100644 --- a/core/evm.go +++ b/core/evm.go @@ -35,8 +35,8 @@ type ChainContext interface { GetHeader(common.Hash, uint64) *types.Header } -// NewEVMContext creates a new context for use in the EVM. -func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address) vm.Context { +// NewEVMBlockContext creates a new context for use in the EVM. +func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { // If we don't have an explicit author (i.e. not mining), extract from the header var beneficiary common.Address if author == nil { @@ -44,17 +44,23 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author } else { beneficiary = *author } - return vm.Context{ + return vm.BlockContext{ CanTransfer: CanTransfer, Transfer: Transfer, GetHash: GetHashFn(header, chain), - Origin: msg.From(), Coinbase: beneficiary, BlockNumber: new(big.Int).Set(header.Number), Time: new(big.Int).SetUint64(header.Time), Difficulty: new(big.Int).Set(header.Difficulty), GasLimit: header.GasLimit, - GasPrice: new(big.Int).Set(msg.GasPrice()), + } +} + +// NewEVMTxContext creates a new transaction context for a single transaction. +func NewEVMTxContext(msg Message) vm.TxContext { + return vm.TxContext{ + Origin: msg.From(), + GasPrice: new(big.Int).Set(msg.GasPrice()), } } diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 1c550fa8bc..6fa52c2d9b 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -86,8 +86,9 @@ func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *co return err } // Create the EVM and execute the transaction - context := NewEVMContext(msg, header, bc, author) - vm := vm.NewEVM(context, statedb, config, cfg) + context := NewEVMBlockContext(header, bc, author) + txContext := NewEVMTxContext(msg) + vm := vm.NewEVM(context, txContext, statedb, config, cfg) _, err = ApplyMessage(vm, msg, gaspool) return err diff --git a/core/state_processor.go b/core/state_processor.go index ac6046b717..d992dfb9cb 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -65,10 +65,16 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(statedb) } + blockContext := NewEVMBlockContext(header, p.bc, nil) + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) // Iterate over and process the individual transactions for i, tx := range block.Transactions() { + msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number)) + if err != nil { + return nil, nil, 0, err + } statedb.Prepare(tx.Hash(), block.Hash(), i) - receipt, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg) + receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, header, tx, usedGas, vmenv) if err != nil { return nil, nil, 0, err } @@ -81,34 +87,25 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return receipts, allLogs, *usedGas, nil } -// ApplyTransaction attempts to apply a transaction to the given state database -// and uses the input parameters for its environment. It returns the receipt -// for the transaction, gas used and an error if the transaction failed, -// indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { - msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) - if err != nil { - return nil, err - } +func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { // Create a new context to be used in the EVM environment - context := NewEVMContext(msg, header, bc, author) - // Create a new environment which holds all relevant information - // about the transaction and calling mechanisms. - vmenv := vm.NewEVM(context, statedb, config, cfg) - + txContext := NewEVMTxContext(msg) + // Add addresses to access list if applicable if config.IsYoloV2(header.Number) { statedb.AddAddressToAccessList(msg.From()) if dst := msg.To(); dst != nil { statedb.AddAddressToAccessList(*dst) // If it's a create-tx, the destination will be added inside evm.create } - for _, addr := range vmenv.ActivePrecompiles() { + for _, addr := range evm.ActivePrecompiles() { statedb.AddAddressToAccessList(addr) } } + // Update the evm with the new transaction context. + evm.Reset(txContext, statedb) // Apply the transaction to the current state (included in the env) - result, err := ApplyMessage(vmenv, msg, gp) + result, err := ApplyMessage(evm, msg, gp) if err != nil { return nil, err } @@ -128,7 +125,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo receipt.GasUsed = result.UsedGas // if the transaction created a contract, store the creation address in the receipt. if msg.To() == nil { - receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce()) + receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } // Set the receipt logs and create a bloom for filtering receipt.Logs = statedb.GetLogs(tx.Hash()) @@ -139,3 +136,18 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo return receipt, err } + +// ApplyTransaction attempts to apply a transaction to the given state database +// and uses the input parameters for its environment. It returns the receipt +// for the transaction, gas used and an error if the transaction failed, +// indicating the block was invalid. +func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { + msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) + if err != nil { + return nil, err + } + // Create a new context to be used in the EVM environment + blockContext := NewEVMBlockContext(header, bc, author) + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) + return applyTransaction(msg, config, bc, author, gp, statedb, header, tx, usedGas, vmenv) +} diff --git a/core/state_transition.go b/core/state_transition.go index 9a9bf475e9..72e8b02a1c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -230,8 +230,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } msg := st.msg sender := vm.AccountRef(msg.From()) - homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber) - istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber) + homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber) + istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber) contractCreation := msg.To() == nil // Check clauses 4-5, subtract intrinsic gas if everything is correct @@ -245,7 +245,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.gas -= gas // Check clause 6 - if msg.Value().Sign() > 0 && !st.evm.CanTransfer(st.state, msg.From(), msg.Value()) { + if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) { return nil, ErrInsufficientFundsForTransfer } var ( @@ -260,7 +260,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) } st.refundGas() - st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) + st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) return &ExecutionResult{ UsedGas: st.gasUsed(), diff --git a/core/vm/evm.go b/core/vm/evm.go index 8f6e603aee..6c7b2370a5 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -91,9 +91,9 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err return nil, errors.New("no compatible interpreter") } -// Context provides the EVM with auxiliary information. Once provided +// BlockContext provides the EVM with auxiliary information. Once provided // it shouldn't be modified. -type Context struct { +type BlockContext struct { // CanTransfer returns whether the account contains // sufficient ether to transfer the value CanTransfer CanTransferFunc @@ -102,10 +102,6 @@ type Context struct { // GetHash returns the hash corresponding to n GetHash GetHashFunc - // Message information - Origin common.Address // Provides information for ORIGIN - GasPrice *big.Int // Provides information for GASPRICE - // Block information Coinbase common.Address // Provides information for COINBASE GasLimit uint64 // Provides information for GASLIMIT @@ -114,6 +110,14 @@ type Context struct { Difficulty *big.Int // Provides information for DIFFICULTY } +// TxContext provides the EVM with information about a transaction. +// All fields can change between transactions. +type TxContext struct { + // Message information + Origin common.Address // Provides information for ORIGIN + GasPrice *big.Int // Provides information for GASPRICE +} + // EVM is the Ethereum Virtual Machine base object and provides // the necessary tools to run a contract on the given state with // the provided context. It should be noted that any error @@ -125,7 +129,8 @@ type Context struct { // The EVM should never be reused and is not thread safe. type EVM struct { // Context provides auxiliary blockchain related information - Context + Context BlockContext + TxContext // StateDB gives access to the underlying state StateDB StateDB // Depth is the current call stack @@ -153,17 +158,18 @@ type EVM struct { // NewEVM returns a new EVM. The returned EVM is not thread safe and should // only ever be used *once*. -func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { +func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { evm := &EVM{ - Context: ctx, + Context: blockCtx, + TxContext: txCtx, StateDB: statedb, vmConfig: vmConfig, chainConfig: chainConfig, - chainRules: chainConfig.Rules(ctx.BlockNumber), + chainRules: chainConfig.Rules(blockCtx.BlockNumber), interpreters: make([]Interpreter, 0, 1), } - if chainConfig.IsEWASM(ctx.BlockNumber) { + if chainConfig.IsEWASM(blockCtx.BlockNumber) { // to be implemented by EVM-C and Wagon PRs. // if vmConfig.EWASMInterpreter != "" { // extIntOpts := strings.Split(vmConfig.EWASMInterpreter, ":") @@ -187,6 +193,13 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon return evm } +// Reset resets the EVM with a new transaction context.Reset +// This is not threadsafe and should only be done very cautiously. +func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { + evm.TxContext = txCtx + evm.StateDB = statedb +} + // Cancel cancels any running EVM operation. This may be called concurrently and // it's safe to be called multiple times. func (evm *EVM) Cancel() { @@ -233,7 +246,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } evm.StateDB.CreateAccount(addr) } - evm.Transfer(evm.StateDB, caller.Address(), addr, value) + evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) // Capture the tracer start/end events in debug mode if evm.vmConfig.Debug && evm.depth == 0 { @@ -426,7 +439,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.depth > int(params.CallCreateDepth) { return nil, common.Address{}, gas, ErrDepth } - if !evm.CanTransfer(evm.StateDB, caller.Address(), value) { + if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance } nonce := evm.StateDB.GetNonce(caller.Address()) @@ -447,7 +460,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.chainRules.IsEIP158 { evm.StateDB.SetNonce(address, 1) } - evm.Transfer(evm.StateDB, caller.Address(), address, value) + evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index 419c903062..6cd126c9b4 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -87,11 +87,11 @@ func TestEIP2200(t *testing.T) { statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original})) statedb.Finalise(true) // Push the state into the "original" slot - vmctx := Context{ + vmctx := BlockContext{ CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true }, Transfer: func(StateDB, common.Address, common.Address, *big.Int) {}, } - vmenv := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) + vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) _, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int)) if err != tt.failure { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index adf44b7f48..4ded0239d9 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -438,14 +438,14 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) return nil, nil } var upper, lower uint64 - upper = interpreter.evm.BlockNumber.Uint64() + upper = interpreter.evm.Context.BlockNumber.Uint64() if upper < 257 { lower = 0 } else { lower = upper - 256 } if num64 >= lower && num64 < upper { - num.SetBytes(interpreter.evm.GetHash(num64).Bytes()) + num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) } else { num.Clear() } @@ -453,30 +453,30 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) } func opCoinbase(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Coinbase.Bytes())) + callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Context.Coinbase.Bytes())) return nil, nil } func opTimestamp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - v, _ := uint256.FromBig(interpreter.evm.Time) + v, _ := uint256.FromBig(interpreter.evm.Context.Time) callContext.stack.push(v) return nil, nil } func opNumber(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - v, _ := uint256.FromBig(interpreter.evm.BlockNumber) + v, _ := uint256.FromBig(interpreter.evm.Context.BlockNumber) callContext.stack.push(v) return nil, nil } func opDifficulty(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - v, _ := uint256.FromBig(interpreter.evm.Difficulty) + v, _ := uint256.FromBig(interpreter.evm.Context.Difficulty) callContext.stack.push(v) return nil, nil } func opGasLimit(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetUint64(interpreter.evm.GasLimit)) + callContext.stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit)) return nil, nil } @@ -842,7 +842,7 @@ func makeLog(size int) executionFunc { Data: d, // This is a non-consensus field, but assigned here because // core/state doesn't know the current block number. - BlockNumber: interpreter.evm.BlockNumber.Uint64(), + BlockNumber: interpreter.evm.Context.BlockNumber.Uint64(), }) return nil, nil diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 0b6fb1f486..985d5a5156 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -92,7 +92,7 @@ func init() { func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() rstack = newReturnStack() pc = uint64(0) @@ -192,7 +192,7 @@ func TestSAR(t *testing.T) { func TestAddMod(t *testing.T) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) pc = uint64(0) @@ -231,7 +231,7 @@ func TestAddMod(t *testing.T) { // getResult is a convenience function to generate the expected values func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() pc = uint64(0) interpreter = env.interpreter.(*EVMInterpreter) @@ -281,7 +281,7 @@ func TestJsonTestcases(t *testing.T) { func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) ) @@ -515,7 +515,7 @@ func BenchmarkOpIsZero(b *testing.B) { func TestOpMstore(t *testing.T) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) @@ -539,7 +539,7 @@ func TestOpMstore(t *testing.T) { func BenchmarkOpMstore(bench *testing.B) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) @@ -560,7 +560,7 @@ func BenchmarkOpMstore(bench *testing.B) { func BenchmarkOpSHA3(bench *testing.B) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index e287f0c7aa..bf7d5358f8 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -51,7 +51,7 @@ func (*dummyStatedb) GetRefund() uint64 { return 1337 } func TestStoreCapture(t *testing.T) { var ( - env = NewEVM(Context{}, &dummyStatedb{}, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{}) logger = NewStructLogger(nil) mem = NewMemory() stack = newstack() diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 38ee448904..6c4c72eeac 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -22,18 +22,20 @@ import ( ) func NewEnv(cfg *Config) *vm.EVM { - context := vm.Context{ + txContext := vm.TxContext{ + Origin: cfg.Origin, + GasPrice: cfg.GasPrice, + } + blockContext := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, GetHash: cfg.GetHashFn, - Origin: cfg.Origin, Coinbase: cfg.Coinbase, BlockNumber: cfg.BlockNumber, Time: cfg.Time, Difficulty: cfg.Difficulty, GasLimit: cfg.GasLimit, - GasPrice: cfg.GasPrice, } - return vm.NewEVM(context, cfg.State, cfg.ChainConfig, cfg.EVMConfig) + return vm.NewEVM(blockContext, txContext, cfg.State, cfg.ChainConfig, cfg.EVMConfig) } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index d99e8f3b2b..8abd378cef 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -113,7 +113,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) { + if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) cfg.State.AddAddressToAccessList(address) for _, addr := range vmenv.ActivePrecompiles() { @@ -150,7 +150,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) { + if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) for _, addr := range vmenv.ActivePrecompiles() { cfg.State.AddAddressToAccessList(addr) @@ -178,7 +178,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er vmenv := NewEnv(cfg) sender := cfg.State.GetOrNewStateObject(cfg.Origin) - if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) { + if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) cfg.State.AddAddressToAccessList(address) for _, addr := range vmenv.ActivePrecompiles() { diff --git a/eth/api_backend.go b/eth/api_backend.go index 0e91691d8f..e7f676f178 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -194,8 +194,9 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { vmError := func() error { return nil } - context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil) - return vm.NewEVM(context, state, b.eth.blockchain.Config(), *b.eth.blockchain.GetVMConfig()), vmError, nil + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) + return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *b.eth.blockchain.GetVMConfig()), vmError, nil } func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 90d4a95c14..1a8c405cf4 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -203,13 +203,11 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // Fetch and execute the next block trace tasks for task := range tasks { signer := types.MakeSigner(api.eth.blockchain.Config(), task.block.Number()) - + blockCtx := core.NewEVMBlockContext(task.block.Header(), api.eth.blockchain, nil) // Trace all the transactions contained within for i, tx := range task.block.Transactions() { msg, _ := tx.AsMessage(signer) - vmctx := core.NewEVMContext(msg, task.block.Header(), api.eth.blockchain, nil) - - res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config) + res, err := api.traceTx(ctx, msg, blockCtx, task.statedb, config) if err != nil { task.results[i] = &txTraceResult{Error: err.Error()} log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) @@ -473,17 +471,15 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, if threads > len(txs) { threads = len(txs) } + blockCtx := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) for th := 0; th < threads; th++ { pend.Add(1) go func() { defer pend.Done() - // Fetch and execute the next transaction trace tasks for task := range jobs { msg, _ := txs[task.index].AsMessage(signer) - vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) - - res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config) + res, err := api.traceTx(ctx, msg, blockCtx, task.statedb, config) if err != nil { results[task.index] = &txTraceResult{Error: err.Error()} continue @@ -500,9 +496,9 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, // Generate the next state snapshot fast without tracing msg, _ := tx.AsMessage(signer) - vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) + txContext := core.NewEVMTxContext(msg) - vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(blockCtx, txContext, statedb, api.eth.blockchain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { failed = err break @@ -565,6 +561,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) dumps []string chainConfig = api.eth.blockchain.Config() + vmctx = core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) canon = true ) // Check if there are any overrides: the caller may wish to enable a future @@ -587,13 +584,12 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block for i, tx := range block.Transactions() { // Prepare the trasaction for un-traced execution var ( - msg, _ = tx.AsMessage(signer) - vmctx = core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) - - vmConf vm.Config - dump *os.File - writer *bufio.Writer - err error + msg, _ = tx.AsMessage(signer) + txContext = core.NewEVMTxContext(msg) + vmConf vm.Config + dump *os.File + writer *bufio.Writer + err error ) // If the transaction needs tracing, swap out the configs if tx.Hash() == txHash || txHash == (common.Hash{}) { @@ -617,7 +613,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block } } // Execute the transaction and flush any traces to disk - vmenv := vm.NewEVM(vmctx, statedb, chainConfig, vmConf) + vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) if writer != nil { writer.Flush() @@ -776,18 +772,19 @@ func (api *PrivateDebugAPI) TraceCall(ctx context.Context, args ethapi.CallArgs, // Execute the trace msg := args.ToMessage(api.eth.APIBackend.RPCGasCap()) - vmctx := core.NewEVMContext(msg, header, api.eth.blockchain, nil) + vmctx := core.NewEVMBlockContext(header, api.eth.blockchain, nil) return api.traceTx(ctx, msg, vmctx, statedb, config) } // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( - tracer vm.Tracer - err error + tracer vm.Tracer + err error + txContext = core.NewEVMTxContext(message) ) switch { case config != nil && config.Tracer != nil: @@ -817,7 +814,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v tracer = vm.NewStructLogger(config.LogConfig) } // Run the transaction with tracing enabled. - vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) + vmenv := vm.NewEVM(vmctx, txContext, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { @@ -847,19 +844,19 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } // computeTxEnv returns the execution environment of a certain transaction. -func (api *PrivateDebugAPI) computeTxEnv(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) { +func (api *PrivateDebugAPI) computeTxEnv(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { // Create the parent state database parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) if parent == nil { - return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + return nil, vm.BlockContext{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) } statedb, err := api.computeStateDB(parent, reexec) if err != nil { - return nil, vm.Context{}, nil, err + return nil, vm.BlockContext{}, nil, err } if txIndex == 0 && len(block.Transactions()) == 0 { - return nil, vm.Context{}, statedb, nil + return nil, vm.BlockContext{}, statedb, nil } // Recompute transactions up to the target index. @@ -868,18 +865,19 @@ func (api *PrivateDebugAPI) computeTxEnv(block *types.Block, txIndex int, reexec for idx, tx := range block.Transactions() { // Assemble the transaction call message and return if the requested offset msg, _ := tx.AsMessage(signer) - context := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) if idx == txIndex { return msg, context, statedb, nil } // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(context, txContext, statedb, api.eth.blockchain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) } - return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 050fb05159..16a8c7698f 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -545,7 +545,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost if jst.err == nil { // Initialize the context if it wasn't done yet if !jst.inited { - jst.ctx["block"] = env.BlockNumber.Uint64() + jst.ctx["block"] = env.Context.BlockNumber.Uint64() jst.inited = true } // If tracing was interrupted, set the error and stop diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index b4de998651..554b2282f1 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -51,7 +51,7 @@ type dummyStatedb struct { func (*dummyStatedb) GetRefund() uint64 { return 1337 } func runTrace(tracer *Tracer) (json.RawMessage, error) { - env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} @@ -166,7 +166,7 @@ func TestHaltBetweenSteps(t *testing.T) { t.Fatal(err) } - env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 18f8eb12aa..b749d832b4 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -143,16 +143,18 @@ func TestPrestateTracerCreate2(t *testing.T) { result: 0x60f3f640a8508fC6a86d45DF051962668E1e8AC7 */ origin, _ := signer.Sender(tx) - context := vm.Context{ + txContext := vm.TxContext{ + Origin: origin, + GasPrice: big.NewInt(1), + } + context := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, - Origin: origin, Coinbase: common.Address{}, BlockNumber: new(big.Int).SetUint64(8000000), Time: new(big.Int).SetUint64(5), Difficulty: big.NewInt(0x30000), GasLimit: uint64(6000000), - GasPrice: big.NewInt(1), } alloc := core.GenesisAlloc{} @@ -175,7 +177,7 @@ func TestPrestateTracerCreate2(t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } - evm := vm.NewEVM(context, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) + evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) msg, err := tx.AsMessage(signer) if err != nil { @@ -230,17 +232,18 @@ func TestCallTracer(t *testing.T) { } signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) origin, _ := signer.Sender(tx) - - context := vm.Context{ + txContext := vm.TxContext{ + Origin: origin, + GasPrice: tx.GasPrice(), + } + context := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, - Origin: origin, Coinbase: test.Context.Miner, BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), Time: new(big.Int).SetUint64(uint64(test.Context.Time)), Difficulty: (*big.Int)(test.Context.Difficulty), GasLimit: uint64(test.Context.GasLimit), - GasPrice: tx.GasPrice(), } _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) @@ -249,7 +252,7 @@ func TestCallTracer(t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } - evm := vm.NewEVM(context, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) + evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) msg, err := tx.AsMessage(signer) if err != nil { diff --git a/les/api_backend.go b/les/api_backend.go index 75bea56da6..9fbc21f459 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -171,8 +171,9 @@ func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { } func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { - context := core.NewEVMContext(msg, header, b.eth.blockchain, nil) - return vm.NewEVM(context, state, b.eth.chainConfig, vm.Config{}), state.Error, nil + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(header, b.eth.blockchain, nil) + return vm.NewEVM(context, txContext, state, b.eth.chainConfig, vm.Config{}), state.Error, nil } func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { diff --git a/les/odr_test.go b/les/odr_test.go index ccd220d692..c157507dd2 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -130,8 +130,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, bc, nil) - vmenv := vm.NewEVM(context, statedb, config, vm.Config{}) + context := core.NewEVMBlockContext(header, bc, nil) + txContext := core.NewEVMTxContext(msg) + vmenv := vm.NewEVM(context, txContext, statedb, config, vm.Config{}) //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) @@ -143,8 +144,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai state := light.NewState(ctx, header, lc.Odr()) state.SetBalance(bankAddr, math.MaxBig256) msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, lc, nil) - vmenv := vm.NewEVM(context, state, config, vm.Config{}) + context := core.NewEVMBlockContext(header, lc, nil) + txContext := core.NewEVMTxContext(msg) + vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) result, _ := core.ApplyMessage(vmenv, msg, gp) if state.Error() == nil { diff --git a/light/odr_test.go b/light/odr_test.go index 5f7f4d96cb..cb22334fdc 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -195,8 +195,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain // Perform read-only call. st.SetBalance(testBankAddress, math.MaxBig256) msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, chain, nil) - vmenv := vm.NewEVM(context, st, config, vm.Config{}) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(header, chain, nil) + vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) result, _ := core.ApplyMessage(vmenv, msg, gp) res = append(res, result.Return()...) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 28a5313129..96c7316969 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -182,9 +182,10 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh if err != nil { return nil, nil, common.Hash{}, err } - context := core.NewEVMContext(msg, block.Header(), nil, &t.json.Env.Coinbase) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash - evm := vm.NewEVM(context, statedb, config, vmconfig) + evm := vm.NewEVM(context, txContext, statedb, config, vmconfig) if config.IsYoloV2(context.BlockNumber) { statedb.AddAddressToAccessList(msg.From()) diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index ad124b7b25..418cc67168 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -138,20 +138,22 @@ func (t *VMTest) newEVM(statedb *state.StateDB, vmconfig vm.Config) *vm.EVM { return core.CanTransfer(db, address, amount) } transfer := func(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {} - context := vm.Context{ + txContext := vm.TxContext{ + Origin: t.json.Exec.Origin, + GasPrice: t.json.Exec.GasPrice, + } + context := vm.BlockContext{ CanTransfer: canTransfer, Transfer: transfer, GetHash: vmTestBlockHash, - Origin: t.json.Exec.Origin, Coinbase: t.json.Env.Coinbase, BlockNumber: new(big.Int).SetUint64(t.json.Env.Number), Time: new(big.Int).SetUint64(t.json.Env.Timestamp), GasLimit: t.json.Env.GasLimit, Difficulty: t.json.Env.Difficulty, - GasPrice: t.json.Exec.GasPrice, } vmconfig.NoRecursion = true - return vm.NewEVM(context, statedb, params.MainnetChainConfig, vmconfig) + return vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vmconfig) } func vmTestBlockHash(n uint64) common.Hash { From cf856ea1ad96ac39ea477087822479b63417036a Mon Sep 17 00:00:00 2001 From: Nicolas Feignon Date: Fri, 13 Nov 2020 13:43:15 +0100 Subject: [PATCH 007/235] accounts/abi: template: set events Raw field in Parse methods (#21807) --- accounts/abi/bind/template.go | 1 + 1 file changed, 1 insertion(+) diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 5329b3ebc3..8dac11f79f 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -541,6 +541,7 @@ var ( if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil { return nil, err } + event.Raw = log return event, nil } From 92c56eb820eff839e062947cd6231cc8593823e5 Mon Sep 17 00:00:00 2001 From: Pascal Dierich Date: Mon, 16 Nov 2020 14:08:13 +0100 Subject: [PATCH 008/235] common: fix documentation of Address.SetBytes (#21814) --- common/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/types.go b/common/types.go index cdcc6c20ad..94cf622e8c 100644 --- a/common/types.go +++ b/common/types.go @@ -240,7 +240,7 @@ func (a Address) Format(s fmt.State, c rune) { } // SetBytes sets the address to the value of b. -// If b is larger than len(a) it will panic. +// If b is larger than len(a), b will be cropped from the left. func (a *Address) SetBytes(b []byte) { if len(b) > len(a) { b = b[len(b)-AddressLength:] From 1ea7537997d1c5444f78ec87f8b309b2f908c76e Mon Sep 17 00:00:00 2001 From: Sad Pencil Date: Tue, 17 Nov 2020 16:51:36 +0800 Subject: [PATCH 009/235] crypto/bn256: refine comments according to #19577, #21595, and #21836 (#21847) --- crypto/bn256/cloudflare/bn256.go | 9 +++++++-- crypto/bn256/google/bn256.go | 5 +++-- crypto/bn256/google/constants.go | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/crypto/bn256/cloudflare/bn256.go b/crypto/bn256/cloudflare/bn256.go index a6dd972ba8..4f607af2ad 100644 --- a/crypto/bn256/cloudflare/bn256.go +++ b/crypto/bn256/cloudflare/bn256.go @@ -9,8 +9,13 @@ // // This package specifically implements the Optimal Ate pairing over a 256-bit // Barreto-Naehrig curve as described in -// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible -// with the implementation described in that paper. +// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is not +// compatible with the implementation described in that paper, as different +// parameters are chosen. +// +// (This package previously claimed to operate at a 128-bit security level. +// However, recent improvements in attacks mean that is no longer true. See +// https://moderncrypto.org/mail-archive/curves/2016/000740.html.) package bn256 import ( diff --git a/crypto/bn256/google/bn256.go b/crypto/bn256/google/bn256.go index e0402e51f0..0a9d5cd35d 100644 --- a/crypto/bn256/google/bn256.go +++ b/crypto/bn256/google/bn256.go @@ -12,8 +12,9 @@ // // This package specifically implements the Optimal Ate pairing over a 256-bit // Barreto-Naehrig curve as described in -// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible -// with the implementation described in that paper. +// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is not +// compatible with the implementation described in that paper, as different +// parameters are chosen. // // (This package previously claimed to operate at a 128-bit security level. // However, recent improvements in attacks mean that is no longer true. See diff --git a/crypto/bn256/google/constants.go b/crypto/bn256/google/constants.go index 0db432c9cf..2990bd9512 100644 --- a/crypto/bn256/google/constants.go +++ b/crypto/bn256/google/constants.go @@ -20,7 +20,9 @@ var u = bigFromBase10("4965661367192848881") var P = bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208583") // Order is the number of elements in both G₁ and G₂: 36u⁴+36u³+18u²+6u+1. +// Needs to be highly 2-adic for efficient SNARK key and proof generation. // Order - 1 = 2^28 * 3^2 * 13 * 29 * 983 * 11003 * 237073 * 405928799 * 1670836401704629 * 13818364434197438864469338081. +// Refer to https://eprint.iacr.org/2013/879.pdf and https://eprint.iacr.org/2013/507.pdf for more information on these parameters. var Order = bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617") // xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+9. From 844485ec6aa2da15f203dda43b919f842c866750 Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Tue, 17 Nov 2020 01:35:58 -0800 Subject: [PATCH 010/235] consensus/ethash: fix usage of *reflect.SliceHeader (#21372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * consensus/ethash: only use *reflect.SliceHeader, not reflect.SliceHeader. See comment here: https://github.com/golang/go/issues/40397\#issuecomment-663748689 * consensus/ethash: pr feedback from @mdempsky, makes a copy of dest such that is not mutated * consensus/ethash: remove noop assign * consensus/ethash: apply same fix to another location Co-authored-by: Péter Szilágyi Co-authored-by: Martin Holst Swende --- consensus/ethash/algorithm.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/consensus/ethash/algorithm.go b/consensus/ethash/algorithm.go index 47d7e51b59..80379597e2 100644 --- a/consensus/ethash/algorithm.go +++ b/consensus/ethash/algorithm.go @@ -151,10 +151,12 @@ func generateCache(dest []uint32, epoch uint64, seed []byte) { logFn("Generated ethash verification cache", "elapsed", common.PrettyDuration(elapsed)) }() // Convert our destination slice to a byte buffer - header := *(*reflect.SliceHeader)(unsafe.Pointer(&dest)) - header.Len *= 4 - header.Cap *= 4 - cache := *(*[]byte)(unsafe.Pointer(&header)) + var cache []byte + cacheHdr := (*reflect.SliceHeader)(unsafe.Pointer(&cache)) + dstHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dest)) + cacheHdr.Data = dstHdr.Data + cacheHdr.Len = dstHdr.Len * 4 + cacheHdr.Cap = dstHdr.Cap * 4 // Calculate the number of theoretical rows (we'll store in one buffer nonetheless) size := uint64(len(cache)) @@ -283,10 +285,12 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) { swapped := !isLittleEndian() // Convert our destination slice to a byte buffer - header := *(*reflect.SliceHeader)(unsafe.Pointer(&dest)) - header.Len *= 4 - header.Cap *= 4 - dataset := *(*[]byte)(unsafe.Pointer(&header)) + var dataset []byte + datasetHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dataset)) + destHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dest)) + datasetHdr.Data = destHdr.Data + datasetHdr.Len = destHdr.Len * 4 + datasetHdr.Cap = destHdr.Cap * 4 // Generate the dataset on many goroutines since it takes a while threads := runtime.NumCPU() From d513584e5280096efb096a92d512f6dc8771a658 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 17 Nov 2020 11:44:38 +0100 Subject: [PATCH 011/235] cmd/geth: remove retesteth --- cmd/geth/main.go | 2 - cmd/geth/retesteth.go | 918 -------------------------------- cmd/geth/retesteth_copypaste.go | 148 ----- 3 files changed, 1068 deletions(-) delete mode 100644 cmd/geth/retesteth.go delete mode 100644 cmd/geth/retesteth_copypaste.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 38e48534dc..d12457176a 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -244,8 +244,6 @@ func init() { licenseCommand, // See config.go dumpConfigCommand, - // See retesteth.go - retestethCommand, // See cmd/utils/flags_legacy.go utils.ShowDeprecated, } diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go deleted file mode 100644 index d3e824bd68..0000000000 --- a/cmd/geth/retesteth.go +++ /dev/null @@ -1,918 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "context" - "fmt" - "math/big" - "os" - "os/signal" - "time" - - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/consensus/misc" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/trie" - - cli "gopkg.in/urfave/cli.v1" -) - -var ( - rpcPortFlag = cli.IntFlag{ - Name: "rpcport", - Usage: "HTTP-RPC server listening port", - Value: node.DefaultHTTPPort, - } - retestethCommand = cli.Command{ - Action: utils.MigrateFlags(retesteth), - Name: "retesteth", - Usage: "Launches geth in retesteth mode", - ArgsUsage: "", - Flags: []cli.Flag{rpcPortFlag}, - Category: "MISCELLANEOUS COMMANDS", - Description: `Launches geth in retesteth mode (no database, no network, only retesteth RPC interface)`, - } -) - -type RetestethTestAPI interface { - SetChainParams(ctx context.Context, chainParams ChainParams) (bool, error) - MineBlocks(ctx context.Context, number uint64) (bool, error) - ModifyTimestamp(ctx context.Context, interval uint64) (bool, error) - ImportRawBlock(ctx context.Context, rawBlock hexutil.Bytes) (common.Hash, error) - RewindToBlock(ctx context.Context, number uint64) (bool, error) - GetLogHash(ctx context.Context, txHash common.Hash) (common.Hash, error) -} - -type RetestethEthAPI interface { - SendRawTransaction(ctx context.Context, rawTx hexutil.Bytes) (common.Hash, error) - BlockNumber(ctx context.Context) (uint64, error) - GetBlockByNumber(ctx context.Context, blockNr math.HexOrDecimal64, fullTx bool) (map[string]interface{}, error) - GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error) - GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error) - GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error) - GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error) -} - -type RetestethDebugAPI interface { - AccountRange(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - addressHash *math.HexOrDecimal256, maxResults uint64, - ) (AccountRangeResult, error) - StorageRangeAt(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - address common.Address, - begin *math.HexOrDecimal256, maxResults uint64, - ) (StorageRangeResult, error) -} - -type RetestWeb3API interface { - ClientVersion(ctx context.Context) (string, error) -} - -type RetestethAPI struct { - ethDb ethdb.Database - db state.Database - chainConfig *params.ChainConfig - author common.Address - extraData []byte - genesisHash common.Hash - engine *NoRewardEngine - blockchain *core.BlockChain - txMap map[common.Address]map[uint64]*types.Transaction // Sender -> Nonce -> Transaction - txSenders map[common.Address]struct{} // Set of transaction senders - blockInterval uint64 -} - -type ChainParams struct { - SealEngine string `json:"sealEngine"` - Params CParamsParams `json:"params"` - Genesis CParamsGenesis `json:"genesis"` - Accounts map[common.Address]CParamsAccount `json:"accounts"` -} - -type CParamsParams struct { - AccountStartNonce math.HexOrDecimal64 `json:"accountStartNonce"` - HomesteadForkBlock *math.HexOrDecimal64 `json:"homesteadForkBlock"` - EIP150ForkBlock *math.HexOrDecimal64 `json:"EIP150ForkBlock"` - EIP158ForkBlock *math.HexOrDecimal64 `json:"EIP158ForkBlock"` - DaoHardforkBlock *math.HexOrDecimal64 `json:"daoHardforkBlock"` - ByzantiumForkBlock *math.HexOrDecimal64 `json:"byzantiumForkBlock"` - ConstantinopleForkBlock *math.HexOrDecimal64 `json:"constantinopleForkBlock"` - ConstantinopleFixForkBlock *math.HexOrDecimal64 `json:"constantinopleFixForkBlock"` - IstanbulBlock *math.HexOrDecimal64 `json:"istanbulForkBlock"` - ChainID *math.HexOrDecimal256 `json:"chainID"` - MaximumExtraDataSize math.HexOrDecimal64 `json:"maximumExtraDataSize"` - TieBreakingGas bool `json:"tieBreakingGas"` - MinGasLimit math.HexOrDecimal64 `json:"minGasLimit"` - MaxGasLimit math.HexOrDecimal64 `json:"maxGasLimit"` - GasLimitBoundDivisor math.HexOrDecimal64 `json:"gasLimitBoundDivisor"` - MinimumDifficulty math.HexOrDecimal256 `json:"minimumDifficulty"` - DifficultyBoundDivisor math.HexOrDecimal256 `json:"difficultyBoundDivisor"` - DurationLimit math.HexOrDecimal256 `json:"durationLimit"` - BlockReward math.HexOrDecimal256 `json:"blockReward"` - NetworkID math.HexOrDecimal256 `json:"networkID"` -} - -type CParamsGenesis struct { - Nonce math.HexOrDecimal64 `json:"nonce"` - Difficulty *math.HexOrDecimal256 `json:"difficulty"` - MixHash *math.HexOrDecimal256 `json:"mixHash"` - Author common.Address `json:"author"` - Timestamp math.HexOrDecimal64 `json:"timestamp"` - ParentHash common.Hash `json:"parentHash"` - ExtraData hexutil.Bytes `json:"extraData"` - GasLimit math.HexOrDecimal64 `json:"gasLimit"` -} - -type CParamsAccount struct { - Balance *math.HexOrDecimal256 `json:"balance"` - Precompiled *CPAccountPrecompiled `json:"precompiled"` - Code hexutil.Bytes `json:"code"` - Storage map[string]string `json:"storage"` - Nonce *math.HexOrDecimal64 `json:"nonce"` -} - -type CPAccountPrecompiled struct { - Name string `json:"name"` - StartingBlock math.HexOrDecimal64 `json:"startingBlock"` - Linear *CPAPrecompiledLinear `json:"linear"` -} - -type CPAPrecompiledLinear struct { - Base uint64 `json:"base"` - Word uint64 `json:"word"` -} - -type AccountRangeResult struct { - AddressMap map[common.Hash]common.Address `json:"addressMap"` - NextKey common.Hash `json:"nextKey"` -} - -type StorageRangeResult struct { - Complete bool `json:"complete"` - Storage map[common.Hash]SRItem `json:"storage"` -} - -type SRItem struct { - Key string `json:"key"` - Value string `json:"value"` -} - -type NoRewardEngine struct { - inner consensus.Engine - rewardsOn bool -} - -func (e *NoRewardEngine) Author(header *types.Header) (common.Address, error) { - return e.inner.Author(header) -} - -func (e *NoRewardEngine) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error { - return e.inner.VerifyHeader(chain, header, seal) -} - -func (e *NoRewardEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { - return e.inner.VerifyHeaders(chain, headers, seals) -} - -func (e *NoRewardEngine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { - return e.inner.VerifyUncles(chain, block) -} - -func (e *NoRewardEngine) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error { - return e.inner.VerifySeal(chain, header) -} - -func (e *NoRewardEngine) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { - return e.inner.Prepare(chain, header) -} - -func (e *NoRewardEngine) accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { - // Simply touch miner and uncle coinbase accounts - reward := big.NewInt(0) - for _, uncle := range uncles { - state.AddBalance(uncle.Coinbase, reward) - } - state.AddBalance(header.Coinbase, reward) -} - -func (e *NoRewardEngine) Finalize(chain consensus.ChainHeaderReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction, - uncles []*types.Header) { - if e.rewardsOn { - e.inner.Finalize(chain, header, statedb, txs, uncles) - } else { - e.accumulateRewards(chain.Config(), statedb, header, uncles) - header.Root = statedb.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - } -} - -func (e *NoRewardEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction, - uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { - if e.rewardsOn { - return e.inner.FinalizeAndAssemble(chain, header, statedb, txs, uncles, receipts) - } else { - e.accumulateRewards(chain.Config(), statedb, header, uncles) - header.Root = statedb.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - - // Header seems complete, assemble into a block and return - return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie)), nil - } -} - -func (e *NoRewardEngine) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { - return e.inner.Seal(chain, block, results, stop) -} - -func (e *NoRewardEngine) SealHash(header *types.Header) common.Hash { - return e.inner.SealHash(header) -} - -func (e *NoRewardEngine) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { - return e.inner.CalcDifficulty(chain, time, parent) -} - -func (e *NoRewardEngine) APIs(chain consensus.ChainHeaderReader) []rpc.API { - return e.inner.APIs(chain) -} - -func (e *NoRewardEngine) Close() error { - return e.inner.Close() -} - -func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainParams) (bool, error) { - // Clean up - if api.blockchain != nil { - api.blockchain.Stop() - } - if api.engine != nil { - api.engine.Close() - } - if api.ethDb != nil { - api.ethDb.Close() - } - ethDb := rawdb.NewMemoryDatabase() - accounts := make(core.GenesisAlloc) - for address, account := range chainParams.Accounts { - balance := big.NewInt(0) - if account.Balance != nil { - balance.Set((*big.Int)(account.Balance)) - } - var nonce uint64 - if account.Nonce != nil { - nonce = uint64(*account.Nonce) - } - if account.Precompiled == nil || account.Balance != nil { - storage := make(map[common.Hash]common.Hash) - for k, v := range account.Storage { - storage[common.HexToHash(k)] = common.HexToHash(v) - } - accounts[address] = core.GenesisAccount{ - Balance: balance, - Code: account.Code, - Nonce: nonce, - Storage: storage, - } - } - } - chainId := big.NewInt(1) - if chainParams.Params.ChainID != nil { - chainId.Set((*big.Int)(chainParams.Params.ChainID)) - } - var ( - homesteadBlock *big.Int - daoForkBlock *big.Int - eip150Block *big.Int - eip155Block *big.Int - eip158Block *big.Int - byzantiumBlock *big.Int - constantinopleBlock *big.Int - petersburgBlock *big.Int - istanbulBlock *big.Int - ) - if chainParams.Params.HomesteadForkBlock != nil { - homesteadBlock = big.NewInt(int64(*chainParams.Params.HomesteadForkBlock)) - } - if chainParams.Params.DaoHardforkBlock != nil { - daoForkBlock = big.NewInt(int64(*chainParams.Params.DaoHardforkBlock)) - } - if chainParams.Params.EIP150ForkBlock != nil { - eip150Block = big.NewInt(int64(*chainParams.Params.EIP150ForkBlock)) - } - if chainParams.Params.EIP158ForkBlock != nil { - eip158Block = big.NewInt(int64(*chainParams.Params.EIP158ForkBlock)) - eip155Block = eip158Block - } - if chainParams.Params.ByzantiumForkBlock != nil { - byzantiumBlock = big.NewInt(int64(*chainParams.Params.ByzantiumForkBlock)) - } - if chainParams.Params.ConstantinopleForkBlock != nil { - constantinopleBlock = big.NewInt(int64(*chainParams.Params.ConstantinopleForkBlock)) - } - if chainParams.Params.ConstantinopleFixForkBlock != nil { - petersburgBlock = big.NewInt(int64(*chainParams.Params.ConstantinopleFixForkBlock)) - } - if constantinopleBlock != nil && petersburgBlock == nil { - petersburgBlock = big.NewInt(100000000000) - } - if chainParams.Params.IstanbulBlock != nil { - istanbulBlock = big.NewInt(int64(*chainParams.Params.IstanbulBlock)) - } - - genesis := &core.Genesis{ - Config: ¶ms.ChainConfig{ - ChainID: chainId, - HomesteadBlock: homesteadBlock, - DAOForkBlock: daoForkBlock, - DAOForkSupport: true, - EIP150Block: eip150Block, - EIP155Block: eip155Block, - EIP158Block: eip158Block, - ByzantiumBlock: byzantiumBlock, - ConstantinopleBlock: constantinopleBlock, - PetersburgBlock: petersburgBlock, - IstanbulBlock: istanbulBlock, - }, - Nonce: uint64(chainParams.Genesis.Nonce), - Timestamp: uint64(chainParams.Genesis.Timestamp), - ExtraData: chainParams.Genesis.ExtraData, - GasLimit: uint64(chainParams.Genesis.GasLimit), - Difficulty: big.NewInt(0).Set((*big.Int)(chainParams.Genesis.Difficulty)), - Mixhash: common.BigToHash((*big.Int)(chainParams.Genesis.MixHash)), - Coinbase: chainParams.Genesis.Author, - ParentHash: chainParams.Genesis.ParentHash, - Alloc: accounts, - } - chainConfig, genesisHash, err := core.SetupGenesisBlock(ethDb, genesis) - if err != nil { - return false, err - } - fmt.Printf("Chain config: %v\n", chainConfig) - - var inner consensus.Engine - switch chainParams.SealEngine { - case "NoProof", "NoReward": - inner = ethash.NewFaker() - case "Ethash": - inner = ethash.New(ethash.Config{ - CacheDir: "ethash", - CachesInMem: 2, - CachesOnDisk: 3, - CachesLockMmap: false, - DatasetsInMem: 1, - DatasetsOnDisk: 2, - DatasetsLockMmap: false, - }, nil, false) - default: - return false, fmt.Errorf("unrecognised seal engine: %s", chainParams.SealEngine) - } - engine := &NoRewardEngine{inner: inner, rewardsOn: chainParams.SealEngine != "NoReward"} - - blockchain, err := core.NewBlockChain(ethDb, nil, chainConfig, engine, vm.Config{}, nil, nil) - if err != nil { - return false, err - } - - api.chainConfig = chainConfig - api.genesisHash = genesisHash - api.author = chainParams.Genesis.Author - api.extraData = chainParams.Genesis.ExtraData - api.ethDb = ethDb - api.engine = engine - api.blockchain = blockchain - api.db = state.NewDatabase(api.ethDb) - api.txMap = make(map[common.Address]map[uint64]*types.Transaction) - api.txSenders = make(map[common.Address]struct{}) - api.blockInterval = 0 - return true, nil -} - -func (api *RetestethAPI) SendRawTransaction(ctx context.Context, rawTx hexutil.Bytes) (common.Hash, error) { - tx := new(types.Transaction) - if err := rlp.DecodeBytes(rawTx, tx); err != nil { - // Return nil is not by mistake - some tests include sending transaction where gasLimit overflows uint64 - return common.Hash{}, nil - } - signer := types.MakeSigner(api.chainConfig, big.NewInt(int64(api.currentNumber()))) - sender, err := types.Sender(signer, tx) - if err != nil { - return common.Hash{}, err - } - if nonceMap, ok := api.txMap[sender]; ok { - nonceMap[tx.Nonce()] = tx - } else { - nonceMap = make(map[uint64]*types.Transaction) - nonceMap[tx.Nonce()] = tx - api.txMap[sender] = nonceMap - } - api.txSenders[sender] = struct{}{} - return tx.Hash(), nil -} - -func (api *RetestethAPI) MineBlocks(ctx context.Context, number uint64) (bool, error) { - for i := 0; i < int(number); i++ { - if err := api.mineBlock(); err != nil { - return false, err - } - } - fmt.Printf("Mined %d blocks\n", number) - return true, nil -} - -func (api *RetestethAPI) currentNumber() uint64 { - if current := api.blockchain.CurrentBlock(); current != nil { - return current.NumberU64() - } - return 0 -} - -func (api *RetestethAPI) mineBlock() error { - number := api.currentNumber() - parentHash := rawdb.ReadCanonicalHash(api.ethDb, number) - parent := rawdb.ReadBlock(api.ethDb, parentHash, number) - var timestamp uint64 - if api.blockInterval == 0 { - timestamp = uint64(time.Now().Unix()) - } else { - timestamp = parent.Time() + api.blockInterval - } - gasLimit := core.CalcGasLimit(parent, 9223372036854775807, 9223372036854775807) - header := &types.Header{ - ParentHash: parent.Hash(), - Number: big.NewInt(int64(number + 1)), - GasLimit: gasLimit, - Extra: api.extraData, - Time: timestamp, - } - header.Coinbase = api.author - if api.engine != nil { - api.engine.Prepare(api.blockchain, header) - } - // If we are care about TheDAO hard-fork check whether to override the extra-data or not - if daoBlock := api.chainConfig.DAOForkBlock; daoBlock != nil { - // Check whether the block is among the fork extra-override range - limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange) - if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 { - // Depending whether we support or oppose the fork, override differently - if api.chainConfig.DAOForkSupport { - header.Extra = common.CopyBytes(params.DAOForkBlockExtra) - } else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) { - header.Extra = []byte{} // If miner opposes, don't let it use the reserved extra-data - } - } - } - statedb, err := api.blockchain.StateAt(parent.Root()) - if err != nil { - return err - } - if api.chainConfig.DAOForkSupport && api.chainConfig.DAOForkBlock != nil && api.chainConfig.DAOForkBlock.Cmp(header.Number) == 0 { - misc.ApplyDAOHardFork(statedb) - } - gasPool := new(core.GasPool).AddGas(header.GasLimit) - txCount := 0 - var txs []*types.Transaction - var receipts []*types.Receipt - var blockFull = gasPool.Gas() < params.TxGas - for address := range api.txSenders { - if blockFull { - break - } - m := api.txMap[address] - for nonce := statedb.GetNonce(address); ; nonce++ { - if tx, ok := m[nonce]; ok { - // Try to apply transactions to the state - statedb.Prepare(tx.Hash(), common.Hash{}, txCount) - snap := statedb.Snapshot() - - receipt, err := core.ApplyTransaction( - api.chainConfig, - api.blockchain, - &api.author, - gasPool, - statedb, - header, tx, &header.GasUsed, *api.blockchain.GetVMConfig(), - ) - if err != nil { - statedb.RevertToSnapshot(snap) - break - } - txs = append(txs, tx) - receipts = append(receipts, receipt) - delete(m, nonce) - if len(m) == 0 { - // Last tx for the sender - delete(api.txMap, address) - delete(api.txSenders, address) - } - txCount++ - if gasPool.Gas() < params.TxGas { - blockFull = true - break - } - } else { - break // Gap in the nonces - } - } - } - block, err := api.engine.FinalizeAndAssemble(api.blockchain, header, statedb, txs, []*types.Header{}, receipts) - if err != nil { - return err - } - return api.importBlock(block) -} - -func (api *RetestethAPI) importBlock(block *types.Block) error { - if _, err := api.blockchain.InsertChain([]*types.Block{block}); err != nil { - return err - } - fmt.Printf("Imported block %d, head is %d\n", block.NumberU64(), api.currentNumber()) - return nil -} - -func (api *RetestethAPI) ModifyTimestamp(ctx context.Context, interval uint64) (bool, error) { - api.blockInterval = interval - return true, nil -} - -func (api *RetestethAPI) ImportRawBlock(ctx context.Context, rawBlock hexutil.Bytes) (common.Hash, error) { - block := new(types.Block) - if err := rlp.DecodeBytes(rawBlock, block); err != nil { - return common.Hash{}, err - } - fmt.Printf("Importing block %d with parent hash: %x, genesisHash: %x\n", block.NumberU64(), block.ParentHash(), api.genesisHash) - if err := api.importBlock(block); err != nil { - return common.Hash{}, err - } - return block.Hash(), nil -} - -func (api *RetestethAPI) RewindToBlock(ctx context.Context, newHead uint64) (bool, error) { - if err := api.blockchain.SetHead(newHead); err != nil { - return false, err - } - // When we rewind, the transaction pool should be cleaned out. - api.txMap = make(map[common.Address]map[uint64]*types.Transaction) - api.txSenders = make(map[common.Address]struct{}) - return true, nil -} - -var emptyListHash common.Hash = common.HexToHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") - -func (api *RetestethAPI) GetLogHash(ctx context.Context, txHash common.Hash) (common.Hash, error) { - receipt, _, _, _ := rawdb.ReadReceipt(api.ethDb, txHash, api.chainConfig) - if receipt == nil { - return emptyListHash, nil - } else { - if logListRlp, err := rlp.EncodeToBytes(receipt.Logs); err != nil { - return common.Hash{}, err - } else { - return common.BytesToHash(crypto.Keccak256(logListRlp)), nil - } - } -} - -func (api *RetestethAPI) BlockNumber(ctx context.Context) (uint64, error) { - return api.currentNumber(), nil -} - -func (api *RetestethAPI) GetBlockByNumber(ctx context.Context, blockNr math.HexOrDecimal64, fullTx bool) (map[string]interface{}, error) { - block := api.blockchain.GetBlockByNumber(uint64(blockNr)) - if block != nil { - response, err := RPCMarshalBlock(block, true, fullTx) - if err != nil { - return nil, err - } - response["author"] = response["miner"] - response["totalDifficulty"] = (*hexutil.Big)(api.blockchain.GetTd(block.Hash(), uint64(blockNr))) - return response, err - } - return nil, fmt.Errorf("block %d not found", blockNr) -} - -func (api *RetestethAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error) { - block := api.blockchain.GetBlockByHash(blockHash) - if block != nil { - response, err := RPCMarshalBlock(block, true, fullTx) - if err != nil { - return nil, err - } - response["author"] = response["miner"] - response["totalDifficulty"] = (*hexutil.Big)(api.blockchain.GetTd(block.Hash(), block.Number().Uint64())) - return response, err - } - return nil, fmt.Errorf("block 0x%x not found", blockHash) -} - -func (api *RetestethAPI) AccountRange(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - addressHash *math.HexOrDecimal256, maxResults uint64, -) (AccountRangeResult, error) { - var ( - header *types.Header - block *types.Block - ) - if (*big.Int)(blockHashOrNumber).Cmp(big.NewInt(math.MaxInt64)) > 0 { - blockHash := common.BigToHash((*big.Int)(blockHashOrNumber)) - header = api.blockchain.GetHeaderByHash(blockHash) - block = api.blockchain.GetBlockByHash(blockHash) - //fmt.Printf("Account range: %x, txIndex %d, start: %x, maxResults: %d\n", blockHash, txIndex, common.BigToHash((*big.Int)(addressHash)), maxResults) - } else { - blockNumber := (*big.Int)(blockHashOrNumber).Uint64() - header = api.blockchain.GetHeaderByNumber(blockNumber) - block = api.blockchain.GetBlockByNumber(blockNumber) - //fmt.Printf("Account range: %d, txIndex %d, start: %x, maxResults: %d\n", blockNumber, txIndex, common.BigToHash((*big.Int)(addressHash)), maxResults) - } - parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash) - var root common.Hash - var statedb *state.StateDB - var err error - if parentHeader == nil || int(txIndex) >= len(block.Transactions()) { - root = header.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return AccountRangeResult{}, err - } - } else { - root = parentHeader.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return AccountRangeResult{}, err - } - // Recompute transactions up to the target index. - signer := types.MakeSigner(api.blockchain.Config(), block.Number()) - context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil) - for idx, tx := range block.Transactions() { - // Assemble the transaction call message and return if the requested offset - msg, _ := tx.AsMessage(signer) - txContext := core.NewEVMTxContext(msg) - // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, txContext, statedb, api.blockchain.Config(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) - } - // Ensure any modifications are committed to the state - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - root = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number())) - if idx == int(txIndex) { - // This is to make sure root can be opened by OpenTrie - root, err = statedb.Commit(api.chainConfig.IsEIP158(block.Number())) - if err != nil { - return AccountRangeResult{}, err - } - break - } - } - } - accountTrie, err := statedb.Database().OpenTrie(root) - if err != nil { - return AccountRangeResult{}, err - } - it := trie.NewIterator(accountTrie.NodeIterator(common.BigToHash((*big.Int)(addressHash)).Bytes())) - result := AccountRangeResult{AddressMap: make(map[common.Hash]common.Address)} - for i := 0; i < int(maxResults) && it.Next(); i++ { - if preimage := accountTrie.GetKey(it.Key); preimage != nil { - result.AddressMap[common.BytesToHash(it.Key)] = common.BytesToAddress(preimage) - } - } - //fmt.Printf("Number of entries returned: %d\n", len(result.AddressMap)) - // Add the 'next key' so clients can continue downloading. - if it.Next() { - next := common.BytesToHash(it.Key) - result.NextKey = next - } - return result, nil -} - -func (api *RetestethAPI) GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error) { - //fmt.Printf("GetBalance %x, block %d\n", address, blockNr) - header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) - if err != nil { - return nil, err - } - return (*math.HexOrDecimal256)(statedb.GetBalance(address)), nil -} - -func (api *RetestethAPI) GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error) { - header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) - if err != nil { - return nil, err - } - return statedb.GetCode(address), nil -} - -func (api *RetestethAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error) { - header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) - if err != nil { - return 0, err - } - return statedb.GetNonce(address), nil -} - -func (api *RetestethAPI) StorageRangeAt(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - address common.Address, - begin *math.HexOrDecimal256, maxResults uint64, -) (StorageRangeResult, error) { - var ( - header *types.Header - block *types.Block - ) - if (*big.Int)(blockHashOrNumber).Cmp(big.NewInt(math.MaxInt64)) > 0 { - blockHash := common.BigToHash((*big.Int)(blockHashOrNumber)) - header = api.blockchain.GetHeaderByHash(blockHash) - block = api.blockchain.GetBlockByHash(blockHash) - //fmt.Printf("Storage range: %x, txIndex %d, addr: %x, start: %x, maxResults: %d\n", - // blockHash, txIndex, address, common.BigToHash((*big.Int)(begin)), maxResults) - } else { - blockNumber := (*big.Int)(blockHashOrNumber).Uint64() - header = api.blockchain.GetHeaderByNumber(blockNumber) - block = api.blockchain.GetBlockByNumber(blockNumber) - //fmt.Printf("Storage range: %d, txIndex %d, addr: %x, start: %x, maxResults: %d\n", - // blockNumber, txIndex, address, common.BigToHash((*big.Int)(begin)), maxResults) - } - parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash) - var root common.Hash - var statedb *state.StateDB - var err error - if parentHeader == nil || int(txIndex) >= len(block.Transactions()) { - root = header.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return StorageRangeResult{}, err - } - } else { - root = parentHeader.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return StorageRangeResult{}, err - } - // Recompute transactions up to the target index. - signer := types.MakeSigner(api.blockchain.Config(), block.Number()) - context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil) - for idx, tx := range block.Transactions() { - // Assemble the transaction call message and return if the requested offset - msg, _ := tx.AsMessage(signer) - txContext := core.NewEVMTxContext(msg) - // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, txContext, statedb, api.blockchain.Config(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) - } - // Ensure any modifications are committed to the state - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - _ = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number())) - if idx == int(txIndex) { - // This is to make sure root can be opened by OpenTrie - _, err = statedb.Commit(vmenv.ChainConfig().IsEIP158(block.Number())) - if err != nil { - return StorageRangeResult{}, err - } - } - } - } - storageTrie := statedb.StorageTrie(address) - it := trie.NewIterator(storageTrie.NodeIterator(common.BigToHash((*big.Int)(begin)).Bytes())) - result := StorageRangeResult{Storage: make(map[common.Hash]SRItem)} - for i := 0; /*i < int(maxResults) && */ it.Next(); i++ { - if preimage := storageTrie.GetKey(it.Key); preimage != nil { - key := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(preimage)) - v, _, err := rlp.SplitString(it.Value) - if err != nil { - return StorageRangeResult{}, err - } - value := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(v)) - ks, _ := key.MarshalText() - vs, _ := value.MarshalText() - if len(ks)%2 != 0 { - ks = append(append(append([]byte{}, ks[:2]...), byte('0')), ks[2:]...) - } - if len(vs)%2 != 0 { - vs = append(append(append([]byte{}, vs[:2]...), byte('0')), vs[2:]...) - } - result.Storage[common.BytesToHash(it.Key)] = SRItem{ - Key: string(ks), - Value: string(vs), - } - } - } - if it.Next() { - result.Complete = false - } else { - result.Complete = true - } - return result, nil -} - -func (api *RetestethAPI) ClientVersion(ctx context.Context) (string, error) { - return "Geth-" + params.VersionWithCommit(gitCommit, gitDate), nil -} - -func retesteth(ctx *cli.Context) error { - log.Info("Welcome to retesteth!") - // register signer API with server - var ( - extapiURL string - ) - apiImpl := &RetestethAPI{} - var testApi RetestethTestAPI = apiImpl - var ethApi RetestethEthAPI = apiImpl - var debugApi RetestethDebugAPI = apiImpl - var web3Api RetestWeb3API = apiImpl - rpcAPI := []rpc.API{ - { - Namespace: "test", - Public: true, - Service: testApi, - Version: "1.0", - }, - { - Namespace: "eth", - Public: true, - Service: ethApi, - Version: "1.0", - }, - { - Namespace: "debug", - Public: true, - Service: debugApi, - Version: "1.0", - }, - { - Namespace: "web3", - Public: true, - Service: web3Api, - Version: "1.0", - }, - } - vhosts := utils.SplitAndTrim(ctx.GlobalString(utils.HTTPVirtualHostsFlag.Name)) - cors := utils.SplitAndTrim(ctx.GlobalString(utils.HTTPCORSDomainFlag.Name)) - - // register apis and create handler stack - srv := rpc.NewServer() - err := node.RegisterApisFromWhitelist(rpcAPI, []string{"test", "eth", "debug", "web3"}, srv, false) - if err != nil { - utils.Fatalf("Could not register RPC apis: %w", err) - } - handler := node.NewHTTPHandlerStack(srv, cors, vhosts) - - // start http server - var RetestethHTTPTimeouts = rpc.HTTPTimeouts{ - ReadTimeout: 120 * time.Second, - WriteTimeout: 120 * time.Second, - IdleTimeout: 120 * time.Second, - } - httpEndpoint := fmt.Sprintf("%s:%d", ctx.GlobalString(utils.HTTPListenAddrFlag.Name), ctx.Int(rpcPortFlag.Name)) - httpServer, _, err := node.StartHTTPEndpoint(httpEndpoint, RetestethHTTPTimeouts, handler) - if err != nil { - utils.Fatalf("Could not start RPC api: %v", err) - } - extapiURL = fmt.Sprintf("http://%s", httpEndpoint) - log.Info("HTTP endpoint opened", "url", extapiURL) - - defer func() { - // Don't bother imposing a timeout here. - httpServer.Shutdown(context.Background()) - log.Info("HTTP endpoint closed", "url", httpEndpoint) - }() - - abortChan := make(chan os.Signal, 11) - signal.Notify(abortChan, os.Interrupt) - - sig := <-abortChan - log.Info("Exiting...", "signal", sig) - return nil -} diff --git a/cmd/geth/retesteth_copypaste.go b/cmd/geth/retesteth_copypaste.go deleted file mode 100644 index e2795af7f9..0000000000 --- a/cmd/geth/retesteth_copypaste.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" -) - -// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction -type RPCTransaction struct { - BlockHash common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From common.Address `json:"from"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Hash common.Hash `json:"hash"` - Input hexutil.Bytes `json:"input"` - Nonce hexutil.Uint64 `json:"nonce"` - To *common.Address `json:"to"` - TransactionIndex hexutil.Uint `json:"transactionIndex"` - Value *hexutil.Big `json:"value"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` -} - -// newRPCTransaction returns a transaction that will serialize to the RPC -// representation, with the given location metadata set (if available). -func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { - var signer types.Signer = types.FrontierSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } - from, _ := types.Sender(signer, tx) - v, r, s := tx.RawSignatureValues() - - result := &RPCTransaction{ - From: from, - Gas: hexutil.Uint64(tx.Gas()), - GasPrice: (*hexutil.Big)(tx.GasPrice()), - Hash: tx.Hash(), - Input: hexutil.Bytes(tx.Data()), - Nonce: hexutil.Uint64(tx.Nonce()), - To: tx.To(), - Value: (*hexutil.Big)(tx.Value()), - V: (*hexutil.Big)(v), - R: (*hexutil.Big)(r), - S: (*hexutil.Big)(s), - } - if blockHash != (common.Hash{}) { - result.BlockHash = blockHash - result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) - result.TransactionIndex = hexutil.Uint(index) - } - return result -} - -// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction { - txs := b.Transactions() - if index >= uint64(len(txs)) { - return nil - } - return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index) -} - -// newRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction { - for idx, tx := range b.Transactions() { - if tx.Hash() == hash { - return newRPCTransactionFromBlockIndex(b, uint64(idx)) - } - } - return nil -} - -// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are -// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain -// transaction hashes. -func RPCMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { - head := b.Header() // copies the header once - fields := map[string]interface{}{ - "number": (*hexutil.Big)(head.Number), - "hash": b.Hash(), - "parentHash": head.ParentHash, - "nonce": head.Nonce, - "mixHash": head.MixDigest, - "sha3Uncles": head.UncleHash, - "logsBloom": head.Bloom, - "stateRoot": head.Root, - "miner": head.Coinbase, - "difficulty": (*hexutil.Big)(head.Difficulty), - "extraData": hexutil.Bytes(head.Extra), - "size": hexutil.Uint64(b.Size()), - "gasLimit": hexutil.Uint64(head.GasLimit), - "gasUsed": hexutil.Uint64(head.GasUsed), - "timestamp": hexutil.Uint64(head.Time), - "transactionsRoot": head.TxHash, - "receiptsRoot": head.ReceiptHash, - } - - if inclTx { - formatTx := func(tx *types.Transaction) (interface{}, error) { - return tx.Hash(), nil - } - if fullTx { - formatTx = func(tx *types.Transaction) (interface{}, error) { - return newRPCTransactionFromBlockHash(b, tx.Hash()), nil - } - } - txs := b.Transactions() - transactions := make([]interface{}, len(txs)) - var err error - for i, tx := range txs { - if transactions[i], err = formatTx(tx); err != nil { - return nil, err - } - } - fields["transactions"] = transactions - } - - uncles := b.Uncles() - uncleHashes := make([]common.Hash, len(uncles)) - for i, uncle := range uncles { - uncleHashes[i] = uncle.Hash() - } - fields["uncles"] = uncleHashes - - return fields, nil -} From db87223269ca0986af312df78b84257ed9836dcf Mon Sep 17 00:00:00 2001 From: Abd ar-Rahman Hamidi Date: Tue, 17 Nov 2020 15:47:17 +0500 Subject: [PATCH 012/235] crypto/secp256k1: add checking z sign in affineFromJacobian (#18419) The z == 0 check is hit whenever we Add two points with the same x1/x2 coordinate. crypto/elliptic uses the same check in their affineFromJacobian function. This change does not affect block processing or tx signature verification in any way, because it does not use the Add or Double methods. --- crypto/secp256k1/curve.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crypto/secp256k1/curve.go b/crypto/secp256k1/curve.go index 5409ee1d2c..8f83cccad9 100644 --- a/crypto/secp256k1/curve.go +++ b/crypto/secp256k1/curve.go @@ -116,6 +116,10 @@ func (BitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool { // affineFromJacobian reverses the Jacobian transform. See the comment at the // top of the file. func (BitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) { + if z.Sign() == 0 { + return new(big.Int), new(big.Int) + } + zinv := new(big.Int).ModInverse(z, BitCurve.P) zinvsq := new(big.Int).Mul(zinv, zinv) From 6b9858085f8e10d50df3bf09d0a5dfd59b683d05 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 17 Nov 2020 12:01:19 +0100 Subject: [PATCH 013/235] cmd/geth: improve les test on windows (#21860) --- cmd/geth/les_test.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index 259d4a8067..2425646510 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -2,7 +2,10 @@ package main import ( "context" + "os" "path/filepath" + "runtime" + "strings" "testing" "time" @@ -95,6 +98,27 @@ func (g *gethrpc) waitSynced() { } } +// ipcEndpoint resolves an IPC endpoint based on a configured value, taking into +// account the set data folders as well as the designated platform we're currently +// running on. +func ipcEndpoint(ipcPath, datadir string) string { + // On windows we can only use plain top-level pipes + if runtime.GOOS == "windows" { + if strings.HasPrefix(ipcPath, `\\.\pipe\`) { + return ipcPath + } + return `\\.\pipe\` + ipcPath + } + // Resolve names into the data directory full paths otherwise + if filepath.Base(ipcPath) == ipcPath { + if datadir == "" { + return filepath.Join(os.TempDir(), ipcPath) + } + return filepath.Join(datadir, ipcPath) + } + return ipcPath +} + func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { g := &gethrpc{name: name} args = append([]string{"--networkid=42", "--port=0", "--nousb"}, args...) @@ -103,10 +127,10 @@ func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { // wait before we can attach to it. TODO: probe for it properly time.Sleep(1 * time.Second) var err error - ipcpath := filepath.Join(g.geth.Datadir, "geth.ipc") + ipcpath := ipcEndpoint("geth.ipc", g.geth.Datadir) g.rpc, err = rpc.Dial(ipcpath) if err != nil { - t.Fatalf("%v rpc connect: %v", name, err) + t.Fatalf("%v rpc connect to %v: %v", name, ipcpath, err) } return g } From 23524f8900bceb0c921b61cbf6f51f38325e2971 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 18 Nov 2020 17:51:33 +0800 Subject: [PATCH 014/235] all: disable recording preimage of trie keys (#21402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * cmd, core, eth, light, trie: disable recording preimage by default * core, eth: fix unit tests * core: fix import * all: change to nopreimage * cmd, core, eth, trie: use cache.preimages flag * cmd: enable preimages for archive node * cmd/utils, trie: simplify preimage tracking a bit * core: fix linter Co-authored-by: Péter Szilágyi --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 15 ++++++++ core/blockchain.go | 15 +++++--- core/genesis.go | 2 +- core/state/database.go | 10 ++--- core/state/state_test.go | 4 +- eth/api_test.go | 2 +- eth/api_tracer.go | 4 +- eth/backend.go | 1 + eth/config.go | 1 + eth/gen_config.go | 6 +++ light/postprocess.go | 4 +- trie/database.go | 81 +++++++++++++++++++++++++++------------- trie/secure_trie.go | 11 +++--- 15 files changed, 110 insertions(+), 48 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 38e48534dc..ca67859a91 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -113,6 +113,7 @@ var ( utils.CacheGCFlag, utils.CacheSnapshotFlag, utils.CacheNoPrefetchFlag, + utils.CachePreimagesFlag, utils.ListenPortFlag, utils.MaxPeersFlag, utils.MaxPendingPeersFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 237cb8d516..a9b6f53e72 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -114,6 +114,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.CacheGCFlag, utils.CacheSnapshotFlag, utils.CacheNoPrefetchFlag, + utils.CachePreimagesFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e5ccfd7435..1b86771db8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -383,6 +383,10 @@ var ( Name: "cache.noprefetch", Usage: "Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)", } + CachePreimagesFlag = cli.BoolTFlag{ + Name: "cache.preimages", + Usage: "Enable recording the SHA3/keccak preimages of trie keys (default: true)", + } // Miner settings MiningEnabledFlag = cli.BoolFlag{ Name: "mine", @@ -1526,6 +1530,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(CacheNoPrefetchFlag.Name) { cfg.NoPrefetch = ctx.GlobalBool(CacheNoPrefetchFlag.Name) } + // Read the value from the flag no matter if it's set or not. + cfg.Preimages = ctx.GlobalBool(CachePreimagesFlag.Name) + if cfg.NoPruning && !cfg.Preimages { + cfg.Preimages = true + log.Info("Enabling recording of key preimages since archive mode is used") + } if ctx.GlobalIsSet(TxLookupLimitFlag.Name) { cfg.TxLookupLimit = ctx.GlobalUint64(TxLookupLimitFlag.Name) } @@ -1835,6 +1845,11 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.B TrieDirtyDisabled: ctx.GlobalString(GCModeFlag.Name) == "archive", TrieTimeLimit: eth.DefaultConfig.TrieTimeout, SnapshotLimit: eth.DefaultConfig.SnapshotCache, + Preimages: ctx.GlobalBool(CachePreimagesFlag.Name), + } + if cache.TrieDirtyDisabled && !cache.Preimages { + cache.Preimages = true + log.Info("Enabling recording of key preimages since archive mode is used") } if !ctx.GlobalIsSet(SnapshotFlag.Name) { cache.SnapshotLimit = 0 // Disabled diff --git a/core/blockchain.go b/core/blockchain.go index 1c8a7fe60a..c52be68354 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -129,6 +129,7 @@ type CacheConfig struct { TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node) TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory + Preimages bool // Whether to store preimage of trie key to the disk SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it } @@ -229,11 +230,15 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par badBlocks, _ := lru.New(badBlockLimit) bc := &BlockChain{ - chainConfig: chainConfig, - cacheConfig: cacheConfig, - db: db, - triegc: prque.New(nil), - stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit, cacheConfig.TrieCleanJournal), + chainConfig: chainConfig, + cacheConfig: cacheConfig, + db: db, + triegc: prque.New(nil), + stateCache: state.NewDatabaseWithConfig(db, &trie.Config{ + Cache: cacheConfig.TrieCleanLimit, + Journal: cacheConfig.TrieCleanJournal, + Preimages: cacheConfig.Preimages, + }), quit: make(chan struct{}), shouldPreserve: shouldPreserve, bodyCache: bodyCache, diff --git a/core/genesis.go b/core/genesis.go index 0535d7ee3a..908a969afd 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -175,7 +175,7 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig // We have the genesis block in database(perhaps in ancient database) // but the corresponding state is missing. header := rawdb.ReadHeader(db, stored, 0) - if _, err := state.New(header.Root, state.NewDatabaseWithCache(db, 0, ""), nil); err != nil { + if _, err := state.New(header.Root, state.NewDatabaseWithConfig(db, nil), nil); err != nil { if genesis == nil { genesis = DefaultGenesisBlock() } diff --git a/core/state/database.go b/core/state/database.go index a9342f5179..83f7b2a839 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -104,18 +104,18 @@ type Trie interface { // NewDatabase creates a backing store for state. The returned database is safe for // concurrent use, but does not retain any recent trie nodes in memory. To keep some -// historical state in memory, use the NewDatabaseWithCache constructor. +// historical state in memory, use the NewDatabaseWithConfig constructor. func NewDatabase(db ethdb.Database) Database { - return NewDatabaseWithCache(db, 0, "") + return NewDatabaseWithConfig(db, nil) } -// NewDatabaseWithCache creates a backing store for state. The returned database +// NewDatabaseWithConfig creates a backing store for state. The returned database // is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a // large memory cache. -func NewDatabaseWithCache(db ethdb.Database, cache int, journal string) Database { +func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { csc, _ := lru.New(codeSizeCacheSize) return &cachingDB{ - db: trie.NewDatabaseWithCache(db, cache, journal), + db: trie.NewDatabaseWithConfig(db, config), codeSizeCache: csc, codeCache: fastcache.New(codeCacheSize), } diff --git a/core/state/state_test.go b/core/state/state_test.go index 0dc4c0ad63..526d7f8177 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -41,7 +41,9 @@ func newStateTest() *stateTest { } func TestDump(t *testing.T) { - s := newStateTest() + db := rawdb.NewMemoryDatabase() + sdb, _ := New(common.Hash{}, NewDatabaseWithConfig(db, nil), nil) + s := &stateTest{db: db, state: sdb} // generate a few entries obj1 := s.state.GetOrNewStateObject(toAddr([]byte{0x01})) diff --git a/eth/api_test.go b/eth/api_test.go index 42f71e261e..2c9a2e54e8 100644 --- a/eth/api_test.go +++ b/eth/api_test.go @@ -58,7 +58,7 @@ func (h resultHash) Less(i, j int) bool { return bytes.Compare(h[i].Bytes(), h[j func TestAccountRange(t *testing.T) { var ( - statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) + statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), nil) state, _ = state.New(common.Hash{}, statedb, nil) addrs = [AccountRangeMaxResults * 2]common.Address{} m = map[common.Address]bool{} diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 1a8c405cf4..804d26b0b9 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -148,7 +148,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // Ensure we have a valid starting state before doing any work origin := start.NumberU64() - database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16, "") // Chain tracing will probably start at genesis + database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) if number := start.NumberU64(); number > 0 { start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) @@ -659,7 +659,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* } // Otherwise try to reexec blocks until we find a state or reach our limit origin := block.NumberU64() - database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16, "") + database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) for i := uint64(0); i < reexec; i++ { block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) diff --git a/eth/backend.go b/eth/backend.go index 3fd027137c..01e6cadd1f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -169,6 +169,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { TrieDirtyDisabled: config.NoPruning, TrieTimeLimit: config.TrieTimeout, SnapshotLimit: config.SnapshotCache, + Preimages: config.Preimages, } ) eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) diff --git a/eth/config.go b/eth/config.go index 0d99c2a3f1..0d90376d94 100644 --- a/eth/config.go +++ b/eth/config.go @@ -149,6 +149,7 @@ type Config struct { TrieDirtyCache int TrieTimeout time.Duration SnapshotCache int + Preimages bool // Mining options Miner miner.Config diff --git a/eth/gen_config.go b/eth/gen_config.go index 0093439d14..b0674c7d77 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -43,6 +43,7 @@ func (c Config) MarshalTOML() (interface{}, error) { TrieDirtyCache int TrieTimeout time.Duration SnapshotCache int + Preimages bool Miner miner.Config Ethash ethash.Config TxPool core.TxPoolConfig @@ -83,6 +84,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.TrieDirtyCache = c.TrieDirtyCache enc.TrieTimeout = c.TrieTimeout enc.SnapshotCache = c.SnapshotCache + enc.Preimages = c.Preimages enc.Miner = c.Miner enc.Ethash = c.Ethash enc.TxPool = c.TxPool @@ -127,6 +129,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { TrieDirtyCache *int TrieTimeout *time.Duration SnapshotCache *int + Preimages *bool Miner *miner.Config Ethash *ethash.Config TxPool *core.TxPoolConfig @@ -222,6 +225,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.SnapshotCache != nil { c.SnapshotCache = *dec.SnapshotCache } + if dec.Preimages != nil { + c.Preimages = *dec.Preimages + } if dec.Miner != nil { c.Miner = *dec.Miner } diff --git a/light/postprocess.go b/light/postprocess.go index de207ad4a3..891c8a5869 100644 --- a/light/postprocess.go +++ b/light/postprocess.go @@ -147,7 +147,7 @@ func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, dis diskdb: db, odr: odr, trieTable: trieTable, - triedb: trie.NewDatabaseWithCache(trieTable, 1, ""), // Use a tiny cache only to keep memory down + triedb: trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down trieset: mapset.NewSet(), sectionSize: size, disablePruning: disablePruning, @@ -340,7 +340,7 @@ func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uin diskdb: db, odr: odr, trieTable: trieTable, - triedb: trie.NewDatabaseWithCache(trieTable, 1, ""), // Use a tiny cache only to keep memory down + triedb: trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down trieset: mapset.NewSet(), parentSize: parentSize, size: size, diff --git a/trie/database.go b/trie/database.go index c0c8870f8f..d8fe45f444 100644 --- a/trie/database.go +++ b/trie/database.go @@ -272,33 +272,43 @@ func expandNode(hash hashNode, n node) node { } } +// Config defines all necessary options for database. +type Config struct { + Cache int // Memory allowance (MB) to use for caching trie nodes in memory + Journal string // Journal of clean cache to survive node restarts + Preimages bool // Flag whether the preimage of trie key is recorded +} + // NewDatabase creates a new trie database to store ephemeral trie content before // its written out to disk or garbage collected. No read cache is created, so all // data retrievals will hit the underlying disk database. func NewDatabase(diskdb ethdb.KeyValueStore) *Database { - return NewDatabaseWithCache(diskdb, 0, "") + return NewDatabaseWithConfig(diskdb, nil) } -// NewDatabaseWithCache creates a new trie database to store ephemeral trie content +// NewDatabaseWithConfig creates a new trie database to store ephemeral trie content // before its written out to disk or garbage collected. It also acts as a read cache // for nodes loaded from disk. -func NewDatabaseWithCache(diskdb ethdb.KeyValueStore, cache int, journal string) *Database { +func NewDatabaseWithConfig(diskdb ethdb.KeyValueStore, config *Config) *Database { var cleans *fastcache.Cache - if cache > 0 { - if journal == "" { - cleans = fastcache.New(cache * 1024 * 1024) + if config != nil && config.Cache > 0 { + if config.Journal == "" { + cleans = fastcache.New(config.Cache * 1024 * 1024) } else { - cleans = fastcache.LoadFromFileOrNew(journal, cache*1024*1024) + cleans = fastcache.LoadFromFileOrNew(config.Journal, config.Cache*1024*1024) } } - return &Database{ + db := &Database{ diskdb: diskdb, cleans: cleans, dirties: map[common.Hash]*cachedNode{{}: { children: make(map[common.Hash]uint16), }}, - preimages: make(map[common.Hash][]byte), } + if config == nil || config.Preimages { // TODO(karalabe): Flip to default off in the future + db.preimages = make(map[common.Hash][]byte) + } + return db } // DiskDB retrieves the persistent storage backing the trie database. @@ -345,6 +355,11 @@ func (db *Database) insert(hash common.Hash, size int, node node) { // // Note, this method assumes that the database's lock is held! func (db *Database) insertPreimage(hash common.Hash, preimage []byte) { + // Short circuit if preimage collection is disabled + if db.preimages == nil { + return + } + // Track the preimage if a yet unknown one if _, ok := db.preimages[hash]; ok { return } @@ -431,6 +446,10 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) { // preimage retrieves a cached trie node pre-image from memory. If it cannot be // found cached, the method queries the persistent database for the content. func (db *Database) preimage(hash common.Hash) []byte { + // Short circuit if preimage collection is disabled + if db.preimages == nil { + return nil + } // Retrieve the node from cache if available db.lock.RLock() preimage := db.preimages[hash] @@ -588,12 +607,16 @@ func (db *Database) Cap(limit common.StorageSize) error { // leave for later to deduplicate writes. flushPreimages := db.preimagesSize > 4*1024*1024 if flushPreimages { - rawdb.WritePreimages(batch, db.preimages) - if batch.ValueSize() > ethdb.IdealBatchSize { - if err := batch.Write(); err != nil { - return err + if db.preimages == nil { + log.Error("Attempted to write preimages whilst disabled") + } else { + rawdb.WritePreimages(batch, db.preimages) + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + return err + } + batch.Reset() } - batch.Reset() } } // Keep committing nodes from the flush-list until we're below allowance @@ -630,7 +653,11 @@ func (db *Database) Cap(limit common.StorageSize) error { defer db.lock.Unlock() if flushPreimages { - db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 + if db.preimages == nil { + log.Error("Attempted to reset preimage cache whilst disabled") + } else { + db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 + } } for db.oldest != oldest { node := db.dirties[db.oldest] @@ -674,20 +701,21 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H batch := db.diskdb.NewBatch() // Move all of the accumulated preimages into a write batch - rawdb.WritePreimages(batch, db.preimages) - if batch.ValueSize() > ethdb.IdealBatchSize { + if db.preimages != nil { + rawdb.WritePreimages(batch, db.preimages) + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + return err + } + batch.Reset() + } + // Since we're going to replay trie node writes into the clean cache, flush out + // any batched pre-images before continuing. if err := batch.Write(); err != nil { return err } batch.Reset() } - // Since we're going to replay trie node writes into the clean cache, flush out - // any batched pre-images before continuing. - if err := batch.Write(); err != nil { - return err - } - batch.Reset() - // Move the trie itself into the batch, flushing if enough data is accumulated nodes, storage := len(db.dirties), db.dirtiesSize @@ -709,8 +737,9 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H batch.Reset() // Reset the storage counters and bumpd metrics - db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 - + if db.preimages != nil { + db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 + } memcacheCommitTimeTimer.Update(time.Since(start)) memcacheCommitSizeMeter.Mark(int64(storage - db.dirtiesSize)) memcacheCommitNodesMeter.Mark(int64(nodes - len(db.dirties))) diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 87b364fb1b..e38471c1b7 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -147,12 +147,13 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte { func (t *SecureTrie) Commit(onleaf LeafCallback) (root common.Hash, err error) { // Write all the pre-images to the actual disk database if len(t.getSecKeyCache()) > 0 { - t.trie.db.lock.Lock() - for hk, key := range t.secKeyCache { - t.trie.db.insertPreimage(common.BytesToHash([]byte(hk)), key) + if t.trie.db.preimages != nil { // Ugly direct check but avoids the below write lock + t.trie.db.lock.Lock() + for hk, key := range t.secKeyCache { + t.trie.db.insertPreimage(common.BytesToHash([]byte(hk)), key) + } + t.trie.db.lock.Unlock() } - t.trie.db.lock.Unlock() - t.secKeyCache = make(map[string][]byte) } // Commit the trie to its intermediate node database From b9ff57c59e3705eb963d39001192ab3a0ecd2d1e Mon Sep 17 00:00:00 2001 From: gary rong Date: Thu, 19 Nov 2020 04:50:11 +0800 Subject: [PATCH 015/235] metrics: fix the panic for reading empty cpu stats (#21864) --- metrics/cpu_enabled.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/metrics/cpu_enabled.go b/metrics/cpu_enabled.go index 52a3c2e966..02192928b7 100644 --- a/metrics/cpu_enabled.go +++ b/metrics/cpu_enabled.go @@ -31,6 +31,10 @@ func ReadCPUStats(stats *CPUStats) { log.Error("Could not read cpu stats", "err", err) return } + if len(timeStats) == 0 { + log.Error("Empty cpu stats") + return + } // requesting all cpu times will always return an array with only one time stats entry timeStat := timeStats[0] stats.GlobalTime = int64((timeStat.User + timeStat.Nice + timeStat.System) * cpu.ClocksPerSec) From f1e1d9f874eca8430ccf2fbfd1b899592a2fcdac Mon Sep 17 00:00:00 2001 From: wbt Date: Thu, 19 Nov 2020 08:54:49 -0500 Subject: [PATCH 016/235] node: support expressive origin rules in ws.origins (#21481) * Only compare hostnames in ws.origins Also using a helper function for ToLower consolidates all preparation steps in one function for more maintainable consistency. Spaces => tabs Remove a semicolon Add space at start of comment Remove parens around conditional Handle case wehre parsed hostname is empty When passing a single word like "localhost" the parsed hostname is an empty string. Handle this and the error-parsing case together as default, and the nonempty hostname case in the conditional. Refactor with new originIsAllowed functions Adds originIsAllowed() & ruleAllowsOrigin(); removes prepOriginForComparison Remove blank line Added tests for simple allowed-orign rule which does not specify a protocol or port, just a hostname Fix copy-paste: `:=` => `=` Remove parens around conditional Remove autoadded whitespace on blank lines Compare scheme, hostname, and port with rule if the rule specifies those portions. Remove one autoadded trailing whitespace Better handle case where only origin host is given e.g. "localhost" Remove parens around conditional Refactor: attemptWebsocketConnectionFromOrigin DRY Include return type on helper function Provide srv obj in helper fn Provide srv to helper fn Remove stray underscore Remove blank line parent 93e666b4c1e7e49b8406dc83ed93f4a02ea49ac1 author wbt 1598559718 -0400 committer Martin Holst Swende 1605602257 +0100 gpgsig -----BEGIN PGP SIGNATURE----- iQFFBAABCAAvFiEEypmrtbNuJK1doP1AaDtDjAWl3fAFAl+zi9ARHG1hcnRpbkBz d2VuZGUuc2UACgkQaDtDjAWl3fDRiwgAoMtzU8dwRV7Q9xkCwWEx9Wz2f3n6jUr2 VWBycDKGKwRkPPOER3oc9kzjGU/P1tFlK07PjfnAKZ9KWzxpDcJZwYM3xCBurG7A 16y4YsQnzgPNONv3xIkdi3RZtDBIiPFFEmdZFFvZ/jKexfI6JIYPngCAoqdTIFb9 On/aPvvVWQn1ExfmarsvvJ7kUDUG77tZipuacEH5FfFsfelBWOEYPe+I9ToUHskv +qO6rOkV1Ojk8eBc6o0R1PnApwCAlEhJs7aM/SEOg4B4ZJJneiFuEXBIG9+0yS2I NOicuDPLGucOB5nBsfIKI3USPeE+3jxdT8go2lN5Nrhm6MimoILDsQ== =sgUp -----END PGP SIGNATURE----- Refactor: drop err var for more concise test lines Add several tests for new WebSocket origin checks Remove autoadded whitespace on blank lines Restore TestWebsocketOrigins originally-named test and rename the others to be helpers rather than full tests Remove autoadded whitespace on blank line Temporarily comment out new test sets Uncomment test around origin rule with scheme Remove tests without scheme on browser origin per https://github.com/ethereum/go-ethereum/pull/21481/files#r479371498 Uncomment tests with port; remove some blank lines Handle when browser does not specify scheme/port Uncomment test for including scheme & port in rule Add IP tests * node: more tests + table-driven, ws origin changes Co-authored-by: Martin Holst Swende --- node/rpcstack_test.go | 125 ++++++++++++++++++++++++++++++++++++------ rpc/websocket.go | 65 +++++++++++++++++++++- 2 files changed, 170 insertions(+), 20 deletions(-) diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 0ee120efd7..8267fb2f1d 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -19,6 +19,7 @@ package node import ( "bytes" "net/http" + "strings" "testing" "github.com/ethereum/go-ethereum/internal/testlog" @@ -52,25 +53,104 @@ func TestVhosts(t *testing.T) { assert.Equal(t, resp2.StatusCode, http.StatusForbidden) } -// TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server. -func TestWebsocketOrigins(t *testing.T) { - srv := createAndStartServer(t, httpConfig{}, true, wsConfig{Origins: []string{"test"}}) - defer srv.stop() +type originTest struct { + spec string + expOk []string + expFail []string +} - dialer := websocket.DefaultDialer - _, _, err := dialer.Dial("ws://"+srv.listenAddr(), http.Header{ - "Content-type": []string{"application/json"}, - "Sec-WebSocket-Version": []string{"13"}, - "Origin": []string{"test"}, - }) - assert.NoError(t, err) +// splitAndTrim splits input separated by a comma +// and trims excessive white space from the substrings. +// Copied over from flags.go +func splitAndTrim(input string) (ret []string) { + l := strings.Split(input, ",") + for _, r := range l { + r = strings.TrimSpace(r) + if len(r) > 0 { + ret = append(ret, r) + } + } + return ret +} - _, _, err = dialer.Dial("ws://"+srv.listenAddr(), http.Header{ - "Content-type": []string{"application/json"}, - "Sec-WebSocket-Version": []string{"13"}, - "Origin": []string{"bad"}, - }) - assert.Error(t, err) +// TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server. +func TestWebsocketOrigins(t *testing.T) { + tests := []originTest{ + { + spec: "*", // allow all + expOk: []string{"", "http://test", "https://test", "http://test:8540", "https://test:8540", + "http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"}, + }, + { + spec: "test", + expOk: []string{"http://test", "https://test", "http://test:8540", "https://test:8540"}, + expFail: []string{"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"}, + }, + // scheme tests + { + spec: "https://test", + expOk: []string{"https://test", "https://test:9999"}, + expFail: []string{ + "test", // no scheme, required by spec + "http://test", // wrong scheme + "http://test.foo", "https://a.test.x", // subdomain variatoins + "http://testx:8540", "https://xtest:8540"}, + }, + // ip tests + { + spec: "https://12.34.56.78", + expOk: []string{"https://12.34.56.78", "https://12.34.56.78:8540"}, + expFail: []string{ + "http://12.34.56.78", // wrong scheme + "http://12.34.56.78:443", // wrong scheme + "http://1.12.34.56.78", // wrong 'domain name' + "http://12.34.56.78.a", // wrong 'domain name' + "https://87.65.43.21", "http://87.65.43.21:8540", "https://87.65.43.21:8540"}, + }, + // port tests + { + spec: "test:8540", + expOk: []string{"http://test:8540", "https://test:8540"}, + expFail: []string{ + "http://test", "https://test", // spec says port required + "http://test:8541", "https://test:8541", // wrong port + "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, + }, + // scheme and port + { + spec: "https://test:8540", + expOk: []string{"https://test:8540"}, + expFail: []string{ + "https://test", // missing port + "http://test", // missing port, + wrong scheme + "http://test:8540", // wrong scheme + "http://test:8541", "https://test:8541", // wrong port + "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, + }, + // several allowed origins + { + spec: "localhost,http://127.0.0.1", + expOk: []string{"localhost", "http://localhost", "https://localhost:8443", + "http://127.0.0.1", "http://127.0.0.1:8080"}, + expFail: []string{ + "https://127.0.0.1", // wrong scheme + "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, + }, + } + for _, tc := range tests { + srv := createAndStartServer(t, httpConfig{}, true, wsConfig{Origins: splitAndTrim(tc.spec)}) + for _, origin := range tc.expOk { + if err := attemptWebsocketConnectionFromOrigin(t, srv, origin); err != nil { + t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err) + } + } + for _, origin := range tc.expFail { + if err := attemptWebsocketConnectionFromOrigin(t, srv, origin); err == nil { + t.Errorf("spec '%v', origin '%v': expected not to allow, got ok", tc.spec, origin) + } + } + srv.stop() + } } // TestIsWebsocket tests if an incoming websocket upgrade request is handled properly. @@ -103,6 +183,17 @@ func createAndStartServer(t *testing.T, conf httpConfig, ws bool, wsConf wsConfi return srv } +func attemptWebsocketConnectionFromOrigin(t *testing.T, srv *httpServer, browserOrigin string) error { + t.Helper() + dialer := websocket.DefaultDialer + _, _, err := dialer.Dial("ws://"+srv.listenAddr(), http.Header{ + "Content-type": []string{"application/json"}, + "Sec-WebSocket-Version": []string{"13"}, + "Origin": []string{browserOrigin}, + }) + return err +} + func testRequest(t *testing.T, key, value, host string, srv *httpServer) *http.Response { t.Helper() diff --git a/rpc/websocket.go b/rpc/websocket.go index a716383be9..cd60eeb613 100644 --- a/rpc/websocket.go +++ b/rpc/websocket.go @@ -75,14 +75,14 @@ func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool { allowAllOrigins = true } if origin != "" { - origins.Add(strings.ToLower(origin)) + origins.Add(origin) } } // allow localhost if no allowedOrigins are specified. if len(origins.ToSlice()) == 0 { origins.Add("http://localhost") if hostname, err := os.Hostname(); err == nil { - origins.Add("http://" + strings.ToLower(hostname)) + origins.Add("http://" + hostname) } } log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v", origins.ToSlice())) @@ -97,7 +97,7 @@ func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool { } // Verify origin against whitelist. origin := strings.ToLower(req.Header.Get("Origin")) - if allowAllOrigins || origins.Contains(origin) { + if allowAllOrigins || originIsAllowed(origins, origin) { return true } log.Warn("Rejected WebSocket connection", "origin", origin) @@ -120,6 +120,65 @@ func (e wsHandshakeError) Error() string { return s } +func originIsAllowed(allowedOrigins mapset.Set, browserOrigin string) bool { + it := allowedOrigins.Iterator() + for origin := range it.C { + if ruleAllowsOrigin(origin.(string), browserOrigin) { + return true + } + } + return false +} + +func ruleAllowsOrigin(allowedOrigin string, browserOrigin string) bool { + var ( + allowedScheme, allowedHostname, allowedPort string + browserScheme, browserHostname, browserPort string + err error + ) + allowedScheme, allowedHostname, allowedPort, err = parseOriginURL(allowedOrigin) + if err != nil { + log.Warn("Error parsing allowed origin specification", "spec", allowedOrigin, "error", err) + return false + } + browserScheme, browserHostname, browserPort, err = parseOriginURL(browserOrigin) + if err != nil { + log.Warn("Error parsing browser 'Origin' field", "Origin", browserOrigin, "error", err) + return false + } + if allowedScheme != "" && allowedScheme != browserScheme { + return false + } + if allowedHostname != "" && allowedHostname != browserHostname { + return false + } + if allowedPort != "" && allowedPort != browserPort { + return false + } + return true +} + +func parseOriginURL(origin string) (string, string, string, error) { + parsedURL, err := url.Parse(strings.ToLower(origin)) + if err != nil { + return "", "", "", err + } + var scheme, hostname, port string + if strings.Contains(origin, "://") { + scheme = parsedURL.Scheme + hostname = parsedURL.Hostname() + port = parsedURL.Port() + } else { + scheme = "" + hostname = parsedURL.Scheme + port = parsedURL.Opaque + if hostname == "" { + hostname = origin + } + } + return scheme, hostname, port, nil +} + // DialWebsocketWithDialer creates a new RPC client that communicates with a JSON-RPC server // that is listening on the given endpoint using the provided dialer. func DialWebsocketWithDialer(ctx context.Context, endpoint, origin string, dialer websocket.Dialer) (*Client, error) { From 6f88d6530a819a3a65c4b95681e6c52115365622 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 19 Nov 2020 22:50:47 +0100 Subject: [PATCH 017/235] trie, rpc, cmd/geth: fix tests on 32-bit and windows + minor rpc fixes (#21871) * trie: fix tests to work on 32-bit systems * les: make test work on 32-bit platform * cmd/geth: fix windows-issues on tests * trie: improve balance * cmd/geth: make account tests less verbose + less mem intense * rpc: make debug-level log output less verbose * cmd/geth: lint --- cmd/geth/accountcmd_test.go | 29 +++++++++--------- cmd/geth/les_test.go | 35 +++++++++++++++------- cmd/geth/run_test.go | 8 ++--- internal/cmdtest/test_cmd.go | 15 ++++++---- node/rpcstack.go | 1 + rpc/endpoints.go | 14 +++++++-- trie/trie_test.go | 57 ++++++++++++++++++++---------------- 7 files changed, 98 insertions(+), 61 deletions(-) diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index 6213e5195d..2f15915b08 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -180,8 +180,8 @@ Fatal: could not decrypt key with given password func TestUnlockFlag(t *testing.T) { datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "256", "--ipcdisable", + "--datadir", datadir, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") geth.Expect(` Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 @@ -204,8 +204,8 @@ Password: {{.InputLine "foobar"}} func TestUnlockFlagWrongPassword(t *testing.T) { datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--datadir", datadir, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") defer geth.ExpectExit() geth.Expect(` Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 @@ -223,9 +223,8 @@ Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could func TestUnlockFlagMultiIndex(t *testing.T) { datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "0,2", - "js", "testdata/empty.js") + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--datadir", datadir, "--unlock", "0,2", "js", "testdata/empty.js") geth.Expect(` Unlocking account 0 | Attempt 1/3 !! Unsupported terminal, password will be echoed. @@ -250,8 +249,8 @@ Password: {{.InputLine "foobar"}} func TestUnlockFlagPasswordFile(t *testing.T) { datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--password", "testdata/passwords.txt", "--unlock", "0,2", + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--datadir", datadir, "--password", "testdata/passwords.txt", "--unlock", "0,2", "js", "testdata/empty.js") geth.ExpectExit() @@ -270,8 +269,8 @@ func TestUnlockFlagPasswordFile(t *testing.T) { func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) { datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--password", "testdata/wrong-passwords.txt", "--unlock", "0,2") + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--datadir", datadir, "--password", "testdata/wrong-passwords.txt", "--unlock", "0,2") defer geth.ExpectExit() geth.Expect(` Fatal: Failed to unlock account 0 (could not decrypt key with given password) @@ -281,8 +280,8 @@ Fatal: Failed to unlock account 0 (could not decrypt key with given password) func TestUnlockFlagAmbiguous(t *testing.T) { store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") geth := runGeth(t, - "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--keystore", store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") defer geth.ExpectExit() @@ -319,8 +318,8 @@ In order to avoid this warning, you need to remove the following duplicate key f func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) { store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") geth := runGeth(t, - "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--keystore", store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") defer geth.ExpectExit() // Helper for the expect template, returns absolute keystore path. diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index 2425646510..e4fc2d4d01 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -2,10 +2,12 @@ package main import ( "context" + "fmt" "os" "path/filepath" "runtime" "strings" + "sync/atomic" "testing" "time" @@ -119,24 +121,36 @@ func ipcEndpoint(ipcPath, datadir string) string { return ipcPath } +// nextIPC ensures that each ipc pipe gets a unique name. +// On linux, it works well to use ipc pipes all over the filesystem (in datadirs), +// but windows require pipes to sit in "\\.\pipe\". Therefore, to run several +// nodes simultaneously, we need to distinguish between them, which we do by +// the pipe filename instead of folder. +var nextIPC = uint32(0) + func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { - g := &gethrpc{name: name} - args = append([]string{"--networkid=42", "--port=0", "--nousb"}, args...) + ipcName := fmt.Sprintf("geth-%d.ipc", atomic.AddUint32(&nextIPC, 1)) + args = append([]string{"--networkid=42", "--port=0", "--nousb", "--ipcpath", ipcName}, args...) t.Logf("Starting %v with rpc: %v", name, args) - g.geth = runGeth(t, args...) + + g := &gethrpc{ + name: name, + geth: runGeth(t, args...), + } // wait before we can attach to it. TODO: probe for it properly time.Sleep(1 * time.Second) var err error - ipcpath := ipcEndpoint("geth.ipc", g.geth.Datadir) - g.rpc, err = rpc.Dial(ipcpath) - if err != nil { + ipcpath := ipcEndpoint(ipcName, g.geth.Datadir) + if g.rpc, err = rpc.Dial(ipcpath); err != nil { t.Fatalf("%v rpc connect to %v: %v", name, ipcpath, err) } return g } func initGeth(t *testing.T) string { - g := runGeth(t, "--nousb", "--networkid=42", "init", "./testdata/clique.json") + args := []string{"--nousb", "--networkid=42", "init", "./testdata/clique.json"} + t.Logf("Initializing geth: %v ", args) + g := runGeth(t, args...) datadir := g.Datadir g.WaitExit() return datadir @@ -144,15 +158,16 @@ func initGeth(t *testing.T) string { func startLightServer(t *testing.T) *gethrpc { datadir := initGeth(t) + t.Logf("Importing keys to geth") runGeth(t, "--nousb", "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv").WaitExit() account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105" - server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1") + server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1", "--verbosity=4") return server } func startClient(t *testing.T, name string) *gethrpc { datadir := initGeth(t) - return startGethWithIpc(t, name, "--datadir", datadir, "--nodiscover", "--syncmode=light", "--nat=extip:127.0.0.1") + return startGethWithIpc(t, name, "--datadir", datadir, "--nodiscover", "--syncmode=light", "--nat=extip:127.0.0.1", "--verbosity=4") } func TestPriorityClient(t *testing.T) { @@ -175,7 +190,7 @@ func TestPriorityClient(t *testing.T) { prioCli := startClient(t, "prioCli") defer prioCli.killAndWait() // 3_000_000_000 once we move to Go 1.13 - tokens := 3000000000 + tokens := uint64(3000000000) lightServer.callRPC(nil, "les_addBalance", prioCli.getNodeInfo().ID, tokens) prioCli.addPeer(lightServer) diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go index f7b735b84c..79b892c59b 100644 --- a/cmd/geth/run_test.go +++ b/cmd/geth/run_test.go @@ -70,12 +70,12 @@ func runGeth(t *testing.T, args ...string) *testgeth { tt := &testgeth{} tt.TestCmd = cmdtest.NewTestCmd(t, tt) for i, arg := range args { - switch { - case arg == "-datadir" || arg == "--datadir": + switch arg { + case "--datadir": if i < len(args)-1 { tt.Datadir = args[i+1] } - case arg == "-etherbase" || arg == "--etherbase": + case "--etherbase": if i < len(args)-1 { tt.Etherbase = args[i+1] } @@ -84,7 +84,7 @@ func runGeth(t *testing.T, args ...string) *testgeth { if tt.Datadir == "" { tt.Datadir = tmpdir(t) tt.Cleanup = func() { os.RemoveAll(tt.Datadir) } - args = append([]string{"-datadir", tt.Datadir}, args...) + args = append([]string{"--datadir", tt.Datadir}, args...) // Remove the temporary datadir if something fails below. defer func() { if t.Failed() { diff --git a/internal/cmdtest/test_cmd.go b/internal/cmdtest/test_cmd.go index 0edfccec5a..82ad9c15b6 100644 --- a/internal/cmdtest/test_cmd.go +++ b/internal/cmdtest/test_cmd.go @@ -27,6 +27,7 @@ import ( "regexp" "strings" "sync" + "sync/atomic" "syscall" "testing" "text/template" @@ -55,10 +56,13 @@ type TestCmd struct { Err error } +var id int32 + // Run exec's the current binary using name as argv[0] which will trigger the // reexec init function for that name (e.g. "geth-test" in cmd/geth/run_test.go) func (tt *TestCmd) Run(name string, args ...string) { - tt.stderr = &testlogger{t: tt.T} + id := atomic.AddInt32(&id, 1) + tt.stderr = &testlogger{t: tt.T, name: fmt.Sprintf("%d", id)} tt.cmd = &exec.Cmd{ Path: reexec.Self(), Args: append([]string{name}, args...), @@ -238,16 +242,17 @@ func (tt *TestCmd) withKillTimeout(fn func()) { // testlogger logs all written lines via t.Log and also // collects them for later inspection. type testlogger struct { - t *testing.T - mu sync.Mutex - buf bytes.Buffer + t *testing.T + mu sync.Mutex + buf bytes.Buffer + name string } func (tl *testlogger) Write(b []byte) (n int, err error) { lines := bytes.Split(b, []byte("\n")) for _, line := range lines { if len(line) > 0 { - tl.t.Logf("(stderr) %s", line) + tl.t.Logf("(stderr:%v) %s", tl.name, line) } } tl.mu.Lock() diff --git a/node/rpcstack.go b/node/rpcstack.go index 731e807aca..81e054ec99 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -448,6 +448,7 @@ func (is *ipcServer) start(apis []rpc.API) error { } listener, srv, err := rpc.StartIPCEndpoint(is.endpoint, apis) if err != nil { + is.log.Warn("IPC opening failed", "url", is.endpoint, "error", err) return err } is.log.Info("IPC endpoint opened", "url", is.endpoint) diff --git a/rpc/endpoints.go b/rpc/endpoints.go index 9fc0705172..d78ebe2858 100644 --- a/rpc/endpoints.go +++ b/rpc/endpoints.go @@ -18,6 +18,7 @@ package rpc import ( "net" + "strings" "github.com/ethereum/go-ethereum/log" ) @@ -25,13 +26,22 @@ import ( // StartIPCEndpoint starts an IPC endpoint. func StartIPCEndpoint(ipcEndpoint string, apis []API) (net.Listener, *Server, error) { // Register all the APIs exposed by the services. - handler := NewServer() + var ( + handler = NewServer() + regMap = make(map[string]struct{}) + registered []string + ) for _, api := range apis { if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + log.Info("IPC registration failed", "namespace", api.Namespace, "error", err) return nil, nil, err } - log.Debug("IPC registered", "namespace", api.Namespace) + if _, ok := regMap[api.Namespace]; !ok { + registered = append(registered, api.Namespace) + regMap[api.Namespace] = struct{}{} + } } + log.Debug("IPCs registered", "namespaces", strings.Join(registered, ",")) // All APIs registered, start the IPC listener. listener, err := ipcListen(ipcEndpoint) if err != nil { diff --git a/trie/trie_test.go b/trie/trie_test.go index 682dec157c..ddbdcbbd5b 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -594,21 +594,20 @@ func benchmarkCommitAfterHash(b *testing.B, onleaf LeafCallback) { func TestTinyTrie(t *testing.T) { // Create a realistic account trie to hash - _, accounts := makeAccounts(10000) + _, accounts := makeAccounts(5) trie := newEmpty() trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001337"), accounts[3]) - if exp, root := common.HexToHash("4fa6efd292cffa2db0083b8bedd23add2798ae73802442f52486e95c3df7111c"), trie.Hash(); exp != root { - t.Fatalf("1: got %x, exp %x", root, exp) + if exp, root := common.HexToHash("8c6a85a4d9fda98feff88450299e574e5378e32391f75a055d470ac0653f1005"), trie.Hash(); exp != root { + t.Errorf("1: got %x, exp %x", root, exp) } trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001338"), accounts[4]) - if exp, root := common.HexToHash("cb5fb1213826dad9e604f095f8ceb5258fe6b5c01805ce6ef019a50699d2d479"), trie.Hash(); exp != root { - t.Fatalf("2: got %x, exp %x", root, exp) + if exp, root := common.HexToHash("ec63b967e98a5720e7f720482151963982890d82c9093c0d486b7eb8883a66b1"), trie.Hash(); exp != root { + t.Errorf("2: got %x, exp %x", root, exp) } trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001339"), accounts[4]) - if exp, root := common.HexToHash("ed7e06b4010057d8703e7b9a160a6d42cf4021f9020da3c8891030349a646987"), trie.Hash(); exp != root { - t.Fatalf("3: got %x, exp %x", root, exp) + if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root { + t.Errorf("3: got %x, exp %x", root, exp) } - checktr, _ := New(common.Hash{}, trie.db) it := NewIterator(trie.NodeIterator(nil)) for it.Next() { @@ -630,7 +629,7 @@ func TestCommitAfterHash(t *testing.T) { trie.Hash() trie.Commit(nil) root := trie.Hash() - exp := common.HexToHash("e5e9c29bb50446a4081e6d1d748d2892c6101c1e883a1f77cf21d4094b697822") + exp := common.HexToHash("72f9d3f3fe1e1dd7b8936442e7642aef76371472d94319900790053c493f3fe6") if exp != root { t.Errorf("got %x, exp %x", root, exp) } @@ -646,19 +645,27 @@ func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) { // Create a realistic account trie to hash addresses = make([][20]byte, size) for i := 0; i < len(addresses); i++ { - for j := 0; j < len(addresses[i]); j++ { - addresses[i][j] = byte(random.Intn(256)) - } + data := make([]byte, 20) + random.Read(data) + copy(addresses[i][:], data) } accounts = make([][]byte, len(addresses)) for i := 0; i < len(accounts); i++ { var ( - nonce = uint64(random.Int63()) - balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil)) - root = emptyRoot - code = crypto.Keccak256(nil) + nonce = uint64(random.Int63()) + root = emptyRoot + code = crypto.Keccak256(nil) ) - accounts[i], _ = rlp.EncodeToBytes(&account{nonce, balance, root, code}) + // The big.Rand function is not deterministic with regards to 64 vs 32 bit systems, + // and will consume different amount of data from the rand source. + //balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil)) + // Therefore, we instead just read via byte buffer + numBytes := random.Uint32() % 33 // [0, 32] bytes + balanceBytes := make([]byte, numBytes) + random.Read(balanceBytes) + balance := new(big.Int).SetBytes(balanceBytes) + data, _ := rlp.EncodeToBytes(&account{nonce, balance, root, code}) + accounts[i] = data } return addresses, accounts } @@ -714,12 +721,12 @@ func TestCommitSequence(t *testing.T) { expWriteSeqHash []byte expCallbackSeqHash []byte }{ - {20, common.FromHex("68c495e45209e243eb7e4f4e8ca8f9f7be71003bd9cafb8061b4534373740193"), - common.FromHex("01783213033d6b7781a641ab499e680d959336d025ac16f44d02f4f0c021bbf5")}, - {200, common.FromHex("3b20d16c13c4bc3eb3b8d0ad7a169fef3b1600e056c0665895d03d3d2b2ff236"), - common.FromHex("fb8db0ec82e8f02729f11228940885b181c3047ab0d654ed0110291ca57111a8")}, - {2000, common.FromHex("34eff3d1048bebdf77e9ae8bd939f2e7c742edc3dcd1173cff1aad9dbd20451a"), - common.FromHex("1c981604b1a9f8ffa40e0ae66b14830a87f5a4ed8345146a3912e6b2dcb05e63")}, + {20, common.FromHex("873c78df73d60e59d4a2bcf3716e8bfe14554549fea2fc147cb54129382a8066"), + common.FromHex("ff00f91ac05df53b82d7f178d77ada54fd0dca64526f537034a5dbe41b17df2a")}, + {200, common.FromHex("ba03d891bb15408c940eea5ee3d54d419595102648d02774a0268d892add9c8e"), + common.FromHex("f3cd509064c8d319bbdd1c68f511850a902ad275e6ed5bea11547e23d492a926")}, + {2000, common.FromHex("f7a184f20df01c94f09537401d11e68d97ad0c00115233107f51b9c287ce60c7"), + common.FromHex("ff795ea898ba1e4cfed4a33b4cf5535a347a02cf931f88d88719faf810f9a1c9")}, } { addresses, accounts := makeAccounts(tc.count) // This spongeDb is used to check the sequence of disk-db-writes @@ -740,10 +747,10 @@ func TestCommitSequence(t *testing.T) { callbackSponge.Write(c[:]) }) if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) { - t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp) + t.Errorf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp) } if got, exp := callbackSponge.Sum(nil), tc.expCallbackSeqHash; !bytes.Equal(got, exp) { - t.Fatalf("test %d, call back sequence wrong:\ngot: %x exp %x\n", i, got, exp) + t.Errorf("test %d, call back sequence wrong:\ngot: %x exp %x\n", i, got, exp) } } } From ebb9591c4d860e85fe1a015e6c9d7e85b7500b9a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 20 Nov 2020 08:53:10 +0100 Subject: [PATCH 018/235] crypto/bn256: fix bn256Mul fuzzer to not hang on large input (#21872) * crypto/bn256: fix bn256Mul fuzzer to not hang on large input * Update crypto/bn256/bn256_fuzz.go Co-authored-by: ligi Co-authored-by: ligi --- crypto/bn256/bn256_fuzz.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crypto/bn256/bn256_fuzz.go b/crypto/bn256/bn256_fuzz.go index 585d509bf4..b34043487f 100644 --- a/crypto/bn256/bn256_fuzz.go +++ b/crypto/bn256/bn256_fuzz.go @@ -80,6 +80,12 @@ func FuzzMul(data []byte) int { if remaining == 0 { return 0 } + if remaining > 128 { + // The evm only ever uses 32 byte integers, we need to cap this otherwise + // we run into slow exec. A 236Kb byte integer cause oss-fuzz to report it as slow. + // 128 bytes should be fine though + return 0 + } buf := make([]byte, remaining) input.Read(buf) From 3ef52775c4756b7cbdc28b807eb4600127a9a873 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 20 Nov 2020 15:14:25 +0100 Subject: [PATCH 019/235] p2p: avoid spinning loop on out-of-handles (#21878) * p2p: avoid busy-loop on temporary errors * p2p: address review concerns --- p2p/server.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index dd52297f8a..275cb5ea5c 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -854,13 +854,18 @@ func (srv *Server) listenLoop() { <-slots var ( - fd net.Conn - err error + fd net.Conn + err error + lastLog time.Time ) for { fd, err = srv.listener.Accept() if netutil.IsTemporaryError(err) { - srv.log.Debug("Temporary read error", "err", err) + if time.Since(lastLog) > 1*time.Second { + srv.log.Debug("Temporary read error", "err", err) + lastLog = time.Now() + } + time.Sleep(time.Millisecond * 200) continue } else if err != nil { srv.log.Debug("Read error", "err", err) From bddf5aaa2f737569e5f96de3676c5123b82e60f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Mon, 23 Nov 2020 10:18:33 +0100 Subject: [PATCH 020/235] les/utils: protect against WeightedRandomSelect overflow (#21839) Also fixes a bug in les/flowcontrol that caused the overflow. --- les/flowcontrol/control.go | 4 ++++ les/utils/weighted_select.go | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/les/flowcontrol/control.go b/les/flowcontrol/control.go index 490013677c..4f0de82318 100644 --- a/les/flowcontrol/control.go +++ b/les/flowcontrol/control.go @@ -19,6 +19,7 @@ package flowcontrol import ( "fmt" + "math" "sync" "time" @@ -316,6 +317,9 @@ func (node *ServerNode) CanSend(maxCost uint64) (time.Duration, float64) { node.lock.RLock() defer node.lock.RUnlock() + if node.params.BufLimit == 0 { + return time.Duration(math.MaxInt64), 0 + } now := node.clock.Now() node.recalcBLE(now) maxCost += uint64(safetyMargin) * node.params.MinRecharge / uint64(fcTimeConst) diff --git a/les/utils/weighted_select.go b/les/utils/weighted_select.go index d6db3c0e65..9b4413afb5 100644 --- a/les/utils/weighted_select.go +++ b/les/utils/weighted_select.go @@ -17,7 +17,10 @@ package utils import ( + "math" "math/rand" + + "github.com/ethereum/go-ethereum/log" ) type ( @@ -54,6 +57,14 @@ func (w *WeightedRandomSelect) IsEmpty() bool { // setWeight sets an item's weight to a specific value (removes it if zero) func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) { + if weight > math.MaxInt64-w.root.sumWeight { + // old weight is still included in sumWeight, remove and check again + w.setWeight(item, 0) + if weight > math.MaxInt64-w.root.sumWeight { + log.Error("WeightedRandomSelect overflow", "sumWeight", w.root.sumWeight, "new weight", weight) + weight = math.MaxInt64 - w.root.sumWeight + } + } idx, ok := w.idx[item] if ok { w.root.setWeight(idx, weight) From f6e1aed504d70197884da3031ae003755ef8e4d5 Mon Sep 17 00:00:00 2001 From: ligi Date: Mon, 23 Nov 2020 13:12:42 +0100 Subject: [PATCH 021/235] github: Add new style of issue-templates closes #20024 --- .../bug.md} | 12 +++++++----- .github/ISSUE_TEMPLATE/feature.md | 17 +++++++++++++++++ .github/ISSUE_TEMPLATE/question.md | 9 +++++++++ .github/ISSUE_TEMPLATE/vulnerability.md | 13 +++++++++++++ 4 files changed, 46 insertions(+), 5 deletions(-) rename .github/{ISSUE_TEMPLATE.md => ISSUE_TEMPLATE/bug.md} (50%) create mode 100644 .github/ISSUE_TEMPLATE/feature.md create mode 100644 .github/ISSUE_TEMPLATE/question.md create mode 100644 .github/ISSUE_TEMPLATE/vulnerability.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug.md similarity index 50% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug.md index 59285e456d..c5a3654bde 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -1,8 +1,10 @@ -Hi there, - -Please note that this is an issue tracker reserved for bug reports and feature requests. - -For general questions please use [discord](https://discord.gg/nthXNEv) or the Ethereum stack exchange at https://ethereum.stackexchange.com. +--- +name: Report a bug +about: Something with go-ethereum is not working as expected +title: '' +labels: 'type:bug' +assignees: '' +--- #### System information diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000000..aacd885f9e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,17 @@ +--- +name: Request a feature +about: Report a missing feature - e.g. as a step before submitting a PR +title: '' +labels: 'type:feature' +assignees: '' +--- + +# Rationale + +Why should this feature exist? +What are the use-cases? + +# Implementation + +Do you have ideas regarding the implementation of this feature? +Are you willing to implement this feature? \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000000..8f460ab558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,9 @@ +--- +name: Ask a question +about: Something is unclear +title: '' +labels: 'type:docs' +assignees: '' +--- + +This should only be used in very rare cases e.g. if you are not 100% sure if something is a bug or asking a question that leads to improving the documentation. For general questions please use [discord](https://discord.gg/nthXNEv) or the Ethereum stack exchange at https://ethereum.stackexchange.com. diff --git a/.github/ISSUE_TEMPLATE/vulnerability.md b/.github/ISSUE_TEMPLATE/vulnerability.md new file mode 100644 index 0000000000..f6bfbe59c4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/vulnerability.md @@ -0,0 +1,13 @@ +--- +name: Report a vulnerability +about: There is a bug in go-ethereum that can be exploited +title: '' +labels: 'type:security' +assignees: '' +--- + +Please do not submit these in this public issue tracker! + +To find out how to disclose a vulnerability in Ethereum visit https://bounty.ethereum.org or email bounty@ethereum.org. + +Please read [Reporting a vulnerability](https://github.com/ethereum/go-ethereum/security/policy#reporting-a-vulnerability) for more information. From 6104ab6b6d201df9e9d6372d1c35835d10c8f6ff Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 23 Nov 2020 15:49:16 +0100 Subject: [PATCH 022/235] tests/fuzzers/bls1381: add bls fuzzer (#21796) * added bls fuzzer * crypto/bls12381: revert bls-changes, fixup fuzzer tests * fuzzers: split bls fuzzing into 8 different units * fuzzers/bls: remove (now stale) corpus * crypto/bls12381: added blsfuzz corpus * fuzzers/bls12381: fix the bls corpus * fuzzers: fix oss-fuzz script * tests/fuzzers: fixups on bls corpus * test/fuzzers: remove leftover corpus Co-authored-by: Marius van der Wijden --- crypto/bls12381/fp_test.go | 9 ++ oss-fuzz.sh | 19 +++- tests/fuzzers/bls12381/bls_fuzzer.go | 101 ++++++++++++++++++ .../testdata/fuzz_g1_add_seed_corpus.zip | Bin 0 -> 41620 bytes .../testdata/fuzz_g1_mul_seed_corpus.zip | Bin 0 -> 37687 bytes .../testdata/fuzz_g1_multiexp_seed_corpus.zip | Bin 0 -> 521055 bytes .../testdata/fuzz_g2_add_seed_corpus.zip | Bin 0 -> 65856 bytes .../testdata/fuzz_g2_mul_seed_corpus.zip | Bin 0 -> 48203 bytes .../testdata/fuzz_g2_multiexp_seed_corpus.zip | Bin 0 -> 1377268 bytes .../testdata/fuzz_map_g1_seed_corpus.zip | Bin 0 -> 28135 bytes .../testdata/fuzz_map_g2_seed_corpus.zip | Bin 0 -> 31393 bytes .../testdata/fuzz_pairing_seed_corpus.zip | Bin 0 -> 55784 bytes 12 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 tests/fuzzers/bls12381/bls_fuzzer.go create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g1_add_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g1_mul_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g1_multiexp_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g2_mul_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip diff --git a/crypto/bls12381/fp_test.go b/crypto/bls12381/fp_test.go index 14bb4d7d65..97528d9db3 100644 --- a/crypto/bls12381/fp_test.go +++ b/crypto/bls12381/fp_test.go @@ -1393,6 +1393,15 @@ func BenchmarkMultiplication(t *testing.B) { } } +func BenchmarkInverse(t *testing.B) { + a, _ := new(fe).rand(rand.Reader) + b, _ := new(fe).rand(rand.Reader) + t.ResetTimer() + for i := 0; i < t.N; i++ { + inverse(a, b) + } +} + func padBytes(in []byte, size int) []byte { out := make([]byte, size) if len(in) > size { diff --git a/oss-fuzz.sh b/oss-fuzz.sh index 23fb4dd412..e0a293a6d6 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -30,13 +30,20 @@ function compile_fuzzer { path=$SRC/go-ethereum/$1 func=$2 fuzzer=$3 - echo "Building $fuzzer" + corpusfile="${path}/testdata/${fuzzer}_seed_corpus.zip" + echo "Building $fuzzer (expecting corpus at $corpusfile)" (cd $path && \ go-fuzz -func $func -o $WORK/$fuzzer.a . && \ echo "First stage built OK" && \ $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $WORK/$fuzzer.a -o $OUT/$fuzzer && \ echo "Second stage built ok" ) + ## Check if there exists a seed corpus file + if [ -f $corpusfile ] + then + cp $corpusfile $OUT/ + echo "Found seed corpus: $corpusfile" + fi } compile_fuzzer common/bitutil Fuzz fuzzBitutilCompress @@ -51,6 +58,16 @@ compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie +compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add +compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul +compile_fuzzer tests/fuzzers/bls12381 FuzzG1MultiExp fuzz_g1_multiexp +compile_fuzzer tests/fuzzers/bls12381 FuzzG2Add fuzz_g2_add +compile_fuzzer tests/fuzzers/bls12381 FuzzG2Mul fuzz_g2_mul +compile_fuzzer tests/fuzzers/bls12381 FuzzG2MultiExp fuzz_g2_multiexp +compile_fuzzer tests/fuzzers/bls12381 FuzzPairing fuzz_pairing +compile_fuzzer tests/fuzzers/bls12381 FuzzMapG1 fuzz_map_g1 +compile_fuzzer tests/fuzzers/bls12381 FuzzMapG2 fuzz_map_g2 + # This doesn't work very well @TODO #compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi diff --git a/tests/fuzzers/bls12381/bls_fuzzer.go b/tests/fuzzers/bls12381/bls_fuzzer.go new file mode 100644 index 0000000000..7e3f94c2aa --- /dev/null +++ b/tests/fuzzers/bls12381/bls_fuzzer.go @@ -0,0 +1,101 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bls + +import ( + "bytes" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" +) + +const ( + blsG1Add = byte(10) + blsG1Mul = byte(11) + blsG1MultiExp = byte(12) + blsG2Add = byte(13) + blsG2Mul = byte(14) + blsG2MultiExp = byte(15) + blsPairing = byte(16) + blsMapG1 = byte(17) + blsMapG2 = byte(18) +) + +func FuzzG1Add(data []byte) int { return fuzz(blsG1Add, data) } +func FuzzG1Mul(data []byte) int { return fuzz(blsG1Mul, data) } +func FuzzG1MultiExp(data []byte) int { return fuzz(blsG1MultiExp, data) } +func FuzzG2Add(data []byte) int { return fuzz(blsG2Add, data) } +func FuzzG2Mul(data []byte) int { return fuzz(blsG2Mul, data) } +func FuzzG2MultiExp(data []byte) int { return fuzz(blsG2MultiExp, data) } +func FuzzPairing(data []byte) int { return fuzz(blsPairing, data) } +func FuzzMapG1(data []byte) int { return fuzz(blsMapG1, data) } +func FuzzMapG2(data []byte) int { return fuzz(blsMapG2, data) } + +func checkInput(id byte, inputLen int) bool { + switch id { + case blsG1Add: + return inputLen == 256 + case blsG1Mul: + return inputLen == 160 + case blsG1MultiExp: + return inputLen%160 == 0 + case blsG2Add: + return inputLen == 512 + case blsG2Mul: + return inputLen == 288 + case blsG2MultiExp: + return inputLen%288 == 0 + case blsPairing: + return inputLen%384 == 0 + case blsMapG1: + return inputLen == 64 + case blsMapG2: + return inputLen == 128 + } + panic("programmer error") +} + +// The fuzzer functions must return +// 1 if the fuzzer should increase priority of the +// given input during subsequent fuzzing (for example, the input is lexically +// correct and was parsed successfully); +// -1 if the input must not be added to corpus even if gives new coverage; and +// 0 otherwise +// other values are reserved for future use. +func fuzz(id byte, data []byte) int { + // Even on bad input, it should not crash, so we still test the gas calc + precompile := vm.PrecompiledContractsYoloV2[common.BytesToAddress([]byte{id})] + gas := precompile.RequiredGas(data) + if !checkInput(id, len(data)) { + return 0 + } + // If the gas cost is too large (25M), bail out + if gas > 25*1000*1000 { + return 0 + } + cpy := make([]byte, len(data)) + copy(cpy, data) + _, err := precompile.Run(cpy) + if !bytes.Equal(cpy, data) { + panic(fmt.Sprintf("input data modified, precompile %d: %x %x", id, data, cpy)) + } + if err != nil { + return 0 + } + return 1 +} diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g1_add_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g1_add_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..16498c1cba89a3c9944b3f8b8e70f6ffd88aac08 GIT binary patch literal 41620 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 Z`g%BdES%#UHOd}-U4dP=a<8F3{RhNC0y6*r literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g1_mul_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g1_mul_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..57f9d6696d8c3f73a31d8098ee337eebd7f39d26 GIT binary patch literal 37687 zcmd75cT|+;+C7}al30laiJ}lIB5it+me>$!Q3Rwr73tDJ+KGxpK#71NbyNhT3n;zV z5Rs-LAYDN~y7V%X@8bQ+nrD9Bta<)A>oqZ!a~6AD-0doR@A*q(#mcomEdQ7abUyt* zzxjXuv;K#jKiIn1SYR3Uju%~=6!gxn`C-MykzDiTpDym}e)w_aw-rD9u#yS?|G(g? zY=vKc`Jb=yYjerHf8kH};3t9at1xIxHi^Nc!LJk=4ntrPiA)L+%fQp=WHN4(`)8u{waUpH-8~w?ZdxJMO%;8u zB{ll4SH2sIjab}wB)@xtzu2O+luQ0S(N@uTW zc1=QIbmW)3Nk@FgmmN7$P7;Ob?<3A;Hk6s!ic&^3R&K+WCvz{uXLpA>c5$3 zx-M?MS(ZKDjEpAO*d4c5>g2okMRT6H=I4beBt&=K)Kw{yAE^!~aGfc%Ymu(qAbwQS zYs1Scs%5TyQBaD@j$?FsRnHVe0)wPdidq^ZWOKj{cuHMFug} z*-D;YP6RMNK1o0ErRe4gi721ynpTgwVdve`Ka16MpHc~2@OZxXZ!UgjGEgye1L5Y7 z_h@bLXz$zXx-w^rMOhJ_sh5dg`2;oOtxL=607(clUY8vcK4=>z3<+F@rKD_I^Z#?s|*ODg#3p}~>k#%AkX-{mso*tB z-fW-UN9){37+U|JOwD@SB=X};<cXjH^2S2go!@!(98AInC)sxMZqV3BcI&%aV#QC+xU#{zAKZdP80ThrF^mfnm*4J zxbU#jBqo_j#n31e7Lf`U9jr8yOrVl56fBiMVX)}18-y9BqopUnFR)5lGCPiqO*7=R`GWV1J*_P$#lx}KSQ3#%BC;_QB7;F?;VE<`1&77b zsdyHHLc!74@Yi^uPn~RS?A4*`9xAgxl*h9SoHhs9NL~t%DspS`a=g_i_EL${QN~p~ zu9=W(Ld7}=L+$$rOGjz)g=bBXG)KGKCWqvz8rqJnMiHh7+!HF**E)KE8q zFZ%Td&GrjgdI}a&8VrF(Bd}N)0z7&wJOLhi8U{zC6G$uyg@mJ1NO+-3A0+dz?3`W3 zIQ_kn`O<^D8(Onwde<{1Wupc{J3kFy^(;`%+Y@?Q)*(&B-TaTN{t%1H$Z z0}Cl1EIA}TL?RhSh68}Z;e?&}PA}uxRSh3Mp0t_q9iC%%#SWbCylUTW>h6@ay@OV{ zphDeE^c!=+PsF-iRq@Pwl>Sv}#o@cr&%c;HExMv+=kRH6d#!$nj{f`RFK0hp8rk19 zs;sHpks>xIf4IOxd0MV+seDGa-Bd+adS^=E;-}e-^`h5rw0EAd+E3fGs&dtm%7Oza zp!f6@Dqrl=JM?c0-1bB^JZX3`1w*F84NoBvC{zMmeHc28Orqo1I2@izWDzmKVh>Vu zGLV*i8#DG@*>f=M+wu9FTO~Qkk+S=yk6&1dRdl&jWoH9)0arJuNFR06A-u?7uni{;n^RJF!(~jI&qTM0u+ol|k z6b!r8p_fdp$dwF_jo|LL_nzzWw7Ps_jJ4CkuvXiQKe_*XV$E?u(bJezES^Eelkkw^ zVIh$xQ3zBTn@XqS$yhp(L8jp7coG&XEc)n<1Sheb-#%9^Jvhys`@7QrNuO8cQoHFk z*F|-OaRond(=D^drPAvQ>-Xn6iZ|jmj>N;EKj$jlpOSiaVCS}i^rh}scB@$U`|8p4 zRlSKG3pPWK4-=kG3|qS1-fNcJ5^U4{x-{pn-Jj>{6T%{!8EOX>T4cTv^k`T9JruYY zy|mj&^7X=B(XI!!^R2yg#7hrBhn`Kuk_jvllfmCocG52s2o&4?E*4#y`hPcPLgWQdb{)#C44T6~TgbxkWS=O_j}wW$s| zusW;A>s0g+-`g(vbE;m!oT(DMKkG`>3XP9Cmmj&V9zA$w=xK$ud5pT7)z(K-HWdAZ zrDRO$khJulYK{NN3Fi!}a;EuXzj$iBx}exGOahw)IW7|_SqvLXpfQ#sB`~RY9E(na zHxR>S(g-x6HEoE0JNx4$rrg(h@(p=byRX;i1H%oi@1H(6?i;J}-f+^>Vk}DJxb(sa zqaKZY8HqKU&95iK{H>7_Jl_BI*&4?&4TEArj+T1$*L6(`-!nbg$Ks#=X*?RH?Ju9( zd@k0r*Y|OtRoE``2`{5_&Bc=oY^gGaT*Ggr?4|@mn#t$1w{Ox~vL{cxT<6WV?$Sj0 z1VOP=uy`tk4%Z!xjHAQpC$pG18jejQQSk6PJcLdr;;L9lf=XH%#D}L7ad5feNGt-I0H+^IA!1l~ zGK0!w5{OXaGuRZNul$~Y@v5ANm4hlW&ZUglobC+H2_3To?eDm5d42D+pN8rL=~&g@ zZ8v+W-*e@KL0qF%y!CR1m)23IG!i*`HCTo#QL393>pL-@cF#X#O5LY#K|gRRc}gt6FRH+ZcO+*Q{zB z+gIBo?kkmS*0HeXPpgI0Y5jE?OR*Epw~M}n9V>NpX^6b*IR103r^>f=@Zg9T3+)?j)a%L1QPPVyr+t=lcS3Qk={N)h0d;Cn0TzyWRSNr!^*PuM-KccNg zpoy7D)-&dcrn@ahc^~L~*9P0UOut0YXT0f!n^%1$d!=f0F6o{_#qQeu30O;)!a9Yu zHc|9O=lP0!Ez+KFu9;xBhB$WXOY z*-ScvL7?KZ6FnP}LeFqYbow)oxBDM@FZV;`|wHNR?#Jzn6t;QJ!i{(;{&u6MO`F-}%@{msWC zob%pR-F1rvX&UK?5BZx}KlNKz2)guXcsv{OTQ;4JVbSSSI)lU_(MeP)7E2(Ks5Bx8 z!=y531fiGy{Gt}AR^@`5>gg>;b6)(5{}4XF`e^}?9j!+vdLsD6>2;j11B{0PFh>Ml7C{sC&JRje=s4X z4e4C-P;2sw+6epj*e8%Zkp0M-xPV{6j?zf%6H)3FO5gPv}o|fo)Ms+ zDkyd~g9@W62AfJ^GN@P>P_c*QLA$K`0~9_~Jy?JT0H+_e4MnTeUf8~1uOC{uHDTRQ1`K3;s! zzMPApT&eo&TjEr+k2%FzTAa7*#h!6b@qDOV>hZR3*m9$Faew35M0vBVdB>Co`@+SI z+GBTbS=F^i%eYO`s7uV$^Y+vG2mN;sO}|hW^*=H@X75wMl4*VNxSQtIp>$emQ?LJP zzJ(uZ#99gpp25P?h%lLeyoQA#LSBc5i=BlbGsq+cl}u*S*%UbO!XNsbo_M>TZSvF4AmF;!*voB?*}?WEqb5iNYdx3PyW3VP_6Og4dqVUclAz2jgPK#50% z0Tz};BEcYoNu-m=1Pn}YgsuGGs(#nzS~h1&1%JE4#J>H0e{8R$YfFWK@?n_<|Mxu` zm+sV`Ayqa!y50M57cC~|x&L-}=*n*lkaXMHE2kuNeY`$5XTH9DYonfLMtu9lUxi`d*;uT(O#)sS=d?ZIvVHY=W+eC#mMT8PU}hOuU^gF@BS6f&D?#&ef(%s z*(GY(MnxZ2Bdvqcx_Mm_wjF=946Ksjs^Shgd8Mk#^GE+qD{4Z}#wS1yiz7kv1}Yku zT|n!e0yjPegQLL{&&Cn46b5_w7h%zd^apC&sgJ}={iEky@=5xR?XBsX7qSv5VX|AB z7L6>+N^;j^B=)$3_m8e@)v>vNb;JG%v)VoTp7oWOc?T?9ChK%W56r#zvb5OUnC$mbe&cuc}93DPU=l^zV?#rl~Ez+~jXKT3)&Q4~hC(eA?Zfj`nH@9`HMPU+( zn}3U%MKARgpPa5~ezL{0=F}^%xhY!pN1X<>R}YIzA6s5mEh(WMR=!rp4L5)J!1tzq z4+d}eW@q7VzJIzKk5zl#y4JDEDYSJ&q4Zl?VxGP0d$}zvd%obmh5j=xXx|eUR00kf z*h~h20A~P-TM~xOrsK#MA{j>4(A9zAC6y`kt*f#&v`AO%AAfmSDw5^@* z#+tRVPA(%OA{7*(ZJYl{wQr+*>5^MQ$ECz#&E-a3qxX%l k`oqG#csO_!mjxc7| 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%0DQPfc(Nr6})hxron;V!7Wago9>e+9(FDK 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%0A=2~WpuGugl5aamXG&;H(Zcgqky0AVHp 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�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 vRbxF$}z(5r;IbW`2%<=F-02(({tz(z>~9; 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%0Dj$HI(9Y=d)JOOCPf6 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..cd5206ca0bcb972dc9008f0e48e5151812b61f17 GIT binary patch literal 65856 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 z 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 TaqXYT@b4mY)ukV;{^|b#ra#C& literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..c205117a4680801cf91db502c153a154d402be49 GIT binary patch literal 1377268 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

    fBeAX&1aMOt_oh&J^JGC7kd= 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

    jbF{DeUAjq&EZigkVhI#^Sq`BesSD&b?v2F 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\+8q=X6 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

    r~dFfK87I-WWRyMp1 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

    e!QI&E3ms*TLQ4xAz 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 z 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 Z|KH%seSpd3IQZKf=AkYZLQhNXe*iNIRvrKV literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..70382fbe53db97124a7cf3ea50f9ab51924d2f1e GIT binary patch literal 28135 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..67adc5b5e8d6875a6cdde7046339660a699ae210 GIT binary patch literal 31393 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 ZE*emWtPNeW8X7YAyB_$67$?O~{|B67zPJDY literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..e24d2b0a52fef50af7d4ca615ea281951f61aec1 GIT binary patch literal 55784 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 literal 0 HcmV?d00001 From 6b5840961407960a06ed20cb5dd1b782080653ff Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Tue, 24 Nov 2020 17:33:58 +0800 Subject: [PATCH 023/235] cmd/faucet: improve handling of facebook post url (#21838) Resolves #21532 Co-authored-by: roger --- cmd/faucet/faucet.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 346c412acb..eaf0dc30c1 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -732,7 +732,10 @@ func authTwitter(url string) (string, string, common.Address, error) { // returning the username, avatar URL and Ethereum address to fund on success. func authFacebook(url string) (string, string, common.Address, error) { // Ensure the user specified a meaningful URL, no fancy nonsense - parts := strings.Split(url, "/") + parts := strings.Split(strings.Split(url, "?")[0], "/") + if parts[len(parts)-1] == "" { + parts = parts[0 : len(parts)-1] + } if len(parts) < 4 || parts[len(parts)-2] != "posts" { //lint:ignore ST1005 This error is to be displayed in the browser return "", "", common.Address{}, errors.New("Invalid Facebook post URL") From bddd103a9f0af27ef533f04e06ea429cf76b6d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Tue, 24 Nov 2020 10:55:17 +0100 Subject: [PATCH 024/235] les: fix GetProofsV2 bug (#21896) --- les/server_handler.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/les/server_handler.go b/les/server_handler.go index d3e2c956b3..c0600b3686 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -610,6 +610,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { var ( lastBHash common.Hash root common.Hash + header *types.Header ) reqCnt := len(req.Reqs) if accept(req.ReqID, uint64(reqCnt), MaxProofsFetch) { @@ -624,10 +625,6 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { return } // Look up the root hash belonging to the request - var ( - header *types.Header - trie state.Trie - ) if request.BHash != lastBHash { root, lastBHash = common.Hash{}, request.BHash @@ -654,6 +651,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { // Open the account or storage trie for the request statedb := h.blockchain.StateCache() + var trie state.Trie switch len(request.AccKey) { case 0: // No account key specified, open an account trie From 7e7a3f0f71d60510afb5cd06282c1db729c1f60b Mon Sep 17 00:00:00 2001 From: ligi Date: Tue, 24 Nov 2020 16:02:53 +0100 Subject: [PATCH 025/235] github: Remove vulnerability.md (#21894) This type is automatically offered by github after changing to the new style and a security.md being present --- .github/ISSUE_TEMPLATE/vulnerability.md | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/vulnerability.md diff --git a/.github/ISSUE_TEMPLATE/vulnerability.md b/.github/ISSUE_TEMPLATE/vulnerability.md deleted file mode 100644 index f6bfbe59c4..0000000000 --- a/.github/ISSUE_TEMPLATE/vulnerability.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: Report a vulnerability -about: There is a bug in go-ethereum that can be exploited -title: '' -labels: 'type:security' -assignees: '' ---- - -Please do not submit these in this public issue tracker! - -To find out how to disclose a vulnerability in Ethereum visit https://bounty.ethereum.org or email bounty@ethereum.org. - -Please read [Reporting a vulnerability](https://github.com/ethereum/go-ethereum/security/policy#reporting-a-vulnerability) for more information. From 59b480ab4bf49e56d4564be9a1b5173d9612aa74 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 24 Nov 2020 16:09:17 +0100 Subject: [PATCH 026/235] cmd/devp2p/internal/ethtest: add 'large announcement' tests (#21792) * cmd/devp2p/internal/ethtest: added large announcement tests * cmd/devp2p/internal/ethtest: added large announcement tests * cmd/devp2p/internal/ethtest: refactored stuff a bit * cmd/devp2p/internal/ethtest: added TestMaliciousStatus/Handshake * cmd/devp2p/internal/ethtest: fixed rebasing issue * happy linter, happy life * cmd/devp2p/internal/ethtest: used readAndServe * stuff * cmd/devp2p/internal/ethtest: fixed test cases --- cmd/devp2p/internal/ethtest/large.go | 80 ++++++++++ cmd/devp2p/internal/ethtest/suite.go | 215 ++++++++++++++++++++++++--- cmd/devp2p/internal/ethtest/types.go | 23 +-- 3 files changed, 284 insertions(+), 34 deletions(-) create mode 100644 cmd/devp2p/internal/ethtest/large.go diff --git a/cmd/devp2p/internal/ethtest/large.go b/cmd/devp2p/internal/ethtest/large.go new file mode 100644 index 0000000000..deca00be53 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/large.go @@ -0,0 +1,80 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "crypto/rand" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +// largeNumber returns a very large big.Int. +func largeNumber(megabytes int) *big.Int { + buf := make([]byte, megabytes*1024*1024) + rand.Read(buf) + bigint := new(big.Int) + bigint.SetBytes(buf) + return bigint +} + +// largeBuffer returns a very large buffer. +func largeBuffer(megabytes int) []byte { + buf := make([]byte, megabytes*1024*1024) + rand.Read(buf) + return buf +} + +// largeString returns a very large string. +func largeString(megabytes int) string { + buf := make([]byte, megabytes*1024*1024) + rand.Read(buf) + return hexutil.Encode(buf) +} + +func largeBlock() *types.Block { + return types.NewBlockWithHeader(largeHeader()) +} + +// Returns a random hash +func randHash() common.Hash { + var h common.Hash + rand.Read(h[:]) + return h +} + +func largeHeader() *types.Header { + return &types.Header{ + MixDigest: randHash(), + ReceiptHash: randHash(), + TxHash: randHash(), + Nonce: types.BlockNonce{}, + Extra: []byte{}, + Bloom: types.Bloom{}, + GasUsed: 0, + Coinbase: common.Address{}, + GasLimit: 0, + UncleHash: randHash(), + Time: 1337, + ParentHash: randHash(), + Root: randHash(), + Number: largeNumber(2), + Difficulty: largeNumber(2), + } +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index d5928bede4..0348751f88 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -24,6 +24,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/rlpx" "github.com/stretchr/testify/assert" @@ -66,6 +67,9 @@ func (s *Suite) AllTests() []utesting.Test { {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, {Name: "Broadcast", Fn: s.TestBroadcast}, {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, + {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, + {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, + {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, } } @@ -80,7 +84,7 @@ func (s *Suite) TestStatus(t *utesting.T) { // get protoHandshake conn.handshake(t) // get status - switch msg := conn.statusExchange(t, s.chain).(type) { + switch msg := conn.statusExchange(t, s.chain, nil).(type) { case *Status: t.Logf("got status message: %s", pretty.Sdump(msg)) default: @@ -88,6 +92,40 @@ func (s *Suite) TestStatus(t *utesting.T) { } } +// TestMaliciousStatus sends a status package with a large total difficulty. +func (s *Suite) TestMaliciousStatus(t *utesting.T) { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + // get protoHandshake + conn.handshake(t) + status := &Status{ + ProtocolVersion: uint32(conn.ethProtocolVersion), + NetworkID: s.chain.chainConfig.ChainID.Uint64(), + TD: largeNumber(2), + Head: s.chain.blocks[s.chain.Len()-1].Hash(), + Genesis: s.chain.blocks[0].Hash(), + ForkID: s.chain.ForkID(), + } + // get status + switch msg := conn.statusExchange(t, s.chain, status).(type) { + case *Status: + t.Logf("%+v\n", msg) + default: + t.Fatalf("expected status, got: %#v ", msg) + } + timeout := 20 * time.Second + // wait for disconnect + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + return + default: + t.Fatalf("expected disconnect, got: %s", pretty.Sdump(msg)) + } +} + // TestGetBlockHeaders tests whether the given node can respond to // a `GetBlockHeaders` request and that the response is accurate. func (s *Suite) TestGetBlockHeaders(t *utesting.T) { @@ -97,7 +135,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { } conn.handshake(t) - conn.statusExchange(t, s.chain) + conn.statusExchange(t, s.chain, nil) // get block headers req := &GetBlockHeaders{ @@ -136,7 +174,7 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { } conn.handshake(t) - conn.statusExchange(t, s.chain) + conn.statusExchange(t, s.chain, nil) // create block bodies request req := &GetBlockBodies{s.chain.blocks[54].Hash(), s.chain.blocks[75].Hash()} if err := conn.Write(req); err != nil { @@ -155,34 +193,158 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { // TestBroadcast tests whether a block announcement is correctly // propagated to the given node's peer(s). func (s *Suite) TestBroadcast(t *utesting.T) { - // create conn to send block announcement - sendConn, err := s.dial() - if err != nil { - t.Fatalf("could not dial: %v", err) + sendConn, receiveConn := s.setupConnection(t), s.setupConnection(t) + nextBlock := len(s.chain.blocks) + blockAnnouncement := &NewBlock{ + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + } + s.testAnnounce(t, sendConn, receiveConn, blockAnnouncement) + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) + // wait for client to update its chain + if err := receiveConn.waitForBlock(s.chain.Head()); err != nil { + t.Fatal(err) } - // create conn to receive block announcement - receiveConn, err := s.dial() +} + +// TestMaliciousHandshake tries to send malicious data during the handshake. +func (s *Suite) TestMaliciousHandshake(t *utesting.T) { + conn, err := s.dial() if err != nil { t.Fatalf("could not dial: %v", err) } + // write hello to client + pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] + handshakes := []*Hello{ + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 64}, + }, + ID: pub0, + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: append(pub0, byte(0)), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: append(pub0, pub0...), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: largeBuffer(2), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 64}, + }, + ID: largeBuffer(2), + }, + } + for i, handshake := range handshakes { + fmt.Printf("Testing malicious handshake %v\n", i) + // Init the handshake + if err := conn.Write(handshake); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // check that the peer disconnected + timeout := 20 * time.Second + // Discard one hello + for i := 0; i < 2; i++ { + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + case *Hello: + // Hello's are send concurrently, so ignore them + continue + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } + } + // Dial for the next round + conn, err = s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + } +} - sendConn.handshake(t) - receiveConn.handshake(t) - - sendConn.statusExchange(t, s.chain) - receiveConn.statusExchange(t, s.chain) +// TestLargeAnnounce tests the announcement mechanism with a large block. +func (s *Suite) TestLargeAnnounce(t *utesting.T) { + nextBlock := len(s.chain.blocks) + blocks := []*NewBlock{ + { + Block: largeBlock(), + TD: s.fullChain.TD(nextBlock + 1), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: largeNumber(2), + }, + { + Block: largeBlock(), + TD: largeNumber(2), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + }, + } - // sendConn sends the block announcement - blockAnnouncement := &NewBlock{ - Block: s.fullChain.blocks[1000], - TD: s.fullChain.TD(1001), + for i, blockAnnouncement := range blocks[0:3] { + fmt.Printf("Testing malicious announcement: %v\n", i) + sendConn := s.setupConnection(t) + if err := sendConn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // Invalid announcement, check that peer disconnected + timeout := 20 * time.Second + switch msg := sendConn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + break + default: + t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) + } + } + // Test the last block as a valid block + sendConn := s.setupConnection(t) + receiveConn := s.setupConnection(t) + s.testAnnounce(t, sendConn, receiveConn, blocks[3]) + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) + // wait for client to update its chain + if err := receiveConn.waitForBlock(s.fullChain.blocks[nextBlock]); err != nil { + t.Fatal(err) } +} + +func (s *Suite) testAnnounce(t *utesting.T, sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) { + // Announce the block. if err := sendConn.Write(blockAnnouncement); err != nil { t.Fatalf("could not write to connection: %v", err) } + s.waitAnnounce(t, receiveConn, blockAnnouncement) +} +func (s *Suite) waitAnnounce(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) { timeout := 20 * time.Second - switch msg := receiveConn.ReadAndServe(s.chain, timeout).(type) { + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *NewBlock: t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) assert.Equal(t, @@ -203,12 +365,17 @@ func (s *Suite) TestBroadcast(t *utesting.T) { default: t.Fatalf("unexpected: %s", pretty.Sdump(msg)) } - // update test suite chain - s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[1000]) - // wait for client to update its chain - if err := receiveConn.waitForBlock(s.chain.Head()); err != nil { - t.Fatal(err) +} + +func (s *Suite) setupConnection(t *utesting.T) *Conn { + // create conn + sendConn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) } + sendConn.handshake(t) + sendConn.statusExchange(t, s.chain, nil) + return sendConn } // dial attempts to dial the given node and perform a handshake, diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index 69367cb6cd..a20e88c372 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -304,7 +304,7 @@ func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { // statusExchange performs a `Status` message exchange with the given // node. -func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message { +func (c *Conn) statusExchange(t *utesting.T, chain *Chain, status *Status) Message { defer c.SetDeadline(time.Time{}) c.SetDeadline(time.Now().Add(20 * time.Second)) @@ -338,16 +338,19 @@ loop: if c.ethProtocolVersion == 0 { t.Fatalf("eth protocol version must be set in Conn") } - // write status message to client - status := Status{ - ProtocolVersion: uint32(c.ethProtocolVersion), - NetworkID: chain.chainConfig.ChainID.Uint64(), - TD: chain.TD(chain.Len()), - Head: chain.blocks[chain.Len()-1].Hash(), - Genesis: chain.blocks[0].Hash(), - ForkID: chain.ForkID(), + if status == nil { + // write status message to client + status = &Status{ + ProtocolVersion: uint32(c.ethProtocolVersion), + NetworkID: chain.chainConfig.ChainID.Uint64(), + TD: chain.TD(chain.Len()), + Head: chain.blocks[chain.Len()-1].Hash(), + Genesis: chain.blocks[0].Hash(), + ForkID: chain.ForkID(), + } } - if err := c.Write(status); err != nil { + + if err := c.Write(*status); err != nil { t.Fatalf("could not write to connection: %v", err) } From 29efe1fc7eccf3eb99a69c52427efbdb30de1a9f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 25 Nov 2020 08:53:20 +0100 Subject: [PATCH 027/235] core/types: fixed typo (#21897) --- core/types/transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/types/transaction.go b/core/types/transaction.go index ec19744881..177bfbb70b 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -213,7 +213,7 @@ func (tx *Transaction) Hash() common.Hash { } // Size returns the true RLP encoded storage size of the transaction, either by -// encoding and returning it, or returning a previsouly cached value. +// encoding and returning it, or returning a previously cached value. func (tx *Transaction) Size() common.StorageSize { if size := tx.size.Load(); size != nil { return size.(common.StorageSize) From c92faee66e8823de50fbe1dab4b0c4549802e730 Mon Sep 17 00:00:00 2001 From: Alex Prut <1648497+alexprut@users.noreply.github.com> Date: Wed, 25 Nov 2020 09:24:50 +0100 Subject: [PATCH 028/235] all: simplify nested complexity and if blocks ending with a return statement (#21854) Changes: Simplify nested complexity If an if blocks ends with a return statement then remove the else nesting. Most of the changes has also been reported in golint https://goreportcard.com/report/github.com/ethereum/go-ethereum#golint --- cmd/clef/main.go | 6 ++--- cmd/utils/flags.go | 19 +++++++------- console/bridge.go | 17 ++++++------ core/headerchain.go | 3 +-- crypto/bls12381/g1.go | 3 +-- crypto/bls12381/g2.go | 3 +-- les/benchmark.go | 9 +++---- les/lespay/server/balance.go | 3 +-- les/lespay/server/balance_tracker.go | 3 +-- les/lespay/server/prioritypool.go | 3 +-- les/odr_requests.go | 3 +-- les/serverpool.go | 3 +-- les/serverpool_test.go | 39 +++++++++++++--------------- p2p/nat/nat.go | 3 +-- p2p/nodestate/nodestate_test.go | 6 ++--- p2p/simulations/network.go | 9 +++---- rlp/encode_test.go | 3 +-- rpc/client.go | 3 +-- trie/committer.go | 1 - 19 files changed, 56 insertions(+), 83 deletions(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index aef3cfba4f..3207042a6c 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -747,12 +747,10 @@ func DefaultConfigDir() string { appdata := os.Getenv("APPDATA") if appdata != "" { return filepath.Join(appdata, "Signer") - } else { - return filepath.Join(home, "AppData", "Roaming", "Signer") } - } else { - return filepath.Join(home, ".clef") + return filepath.Join(home, "AppData", "Roaming", "Signer") } + return filepath.Join(home, ".clef") } // As we cannot guess a stable location, return empty and handle later return "" diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1b86771db8..26dbb9faf3 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1695,19 +1695,18 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { Fatalf("Failed to register the Ethereum service: %v", err) } return backend.ApiBackend - } else { - backend, err := eth.New(stack, cfg) + } + backend, err := eth.New(stack, cfg) + if err != nil { + Fatalf("Failed to register the Ethereum service: %v", err) + } + if cfg.LightServ > 0 { + _, err := les.NewLesServer(stack, backend, cfg) if err != nil { - Fatalf("Failed to register the Ethereum service: %v", err) - } - if cfg.LightServ > 0 { - _, err := les.NewLesServer(stack, backend, cfg) - if err != nil { - Fatalf("Failed to create the LES server: %v", err) - } + Fatalf("Failed to create the LES server: %v", err) } - return backend.APIBackend } + return backend.APIBackend } // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to diff --git a/console/bridge.go b/console/bridge.go index 1a23269194..21ef0e8e7b 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -144,15 +144,14 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { if val, err = openWallet(goja.Null(), wallet, passwd); err != nil { if !strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()) { return nil, err - } else { - // PIN input requested, fetch from the user and call open again - input, err := b.prompter.PromptPassword("Please enter current PIN: ") - if err != nil { - return nil, err - } - if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { - return nil, err - } + } + // PIN input requested, fetch from the user and call open again + input, err := b.prompter.PromptPassword("Please enter current PIN: ") + if err != nil { + return nil, err + } + if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { + return nil, err } } diff --git a/core/headerchain.go b/core/headerchain.go index f5a8e21cfc..7c1aff99d8 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -369,9 +369,8 @@ func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, ma // in this case it is cheaper to just read the header if header := hc.GetHeader(hash, number); header != nil { return header.ParentHash, number - 1 - } else { - return common.Hash{}, 0 } + return common.Hash{}, 0 } for ancestor != 0 { if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash { diff --git a/crypto/bls12381/g1.go b/crypto/bls12381/g1.go index 63d38f90c2..d853823cd2 100644 --- a/crypto/bls12381/g1.go +++ b/crypto/bls12381/g1.go @@ -266,9 +266,8 @@ func (g *G1) Add(r, p1, p2 *PointG1) *PointG1 { if t[1].equal(t[3]) { if t[0].equal(t[2]) { return g.Double(r, p1) - } else { - return r.Zero() } + return r.Zero() } sub(t[1], t[1], t[3]) double(t[4], t[1]) diff --git a/crypto/bls12381/g2.go b/crypto/bls12381/g2.go index 1d1a3258f7..fa110e3edf 100644 --- a/crypto/bls12381/g2.go +++ b/crypto/bls12381/g2.go @@ -287,9 +287,8 @@ func (g *G2) Add(r, p1, p2 *PointG2) *PointG2 { if t[1].equal(t[3]) { if t[0].equal(t[2]) { return g.Double(r, p1) - } else { - return r.Zero() } + return r.Zero() } g.f.sub(t[1], t[1], t[3]) g.f.double(t[4], t[1]) diff --git a/les/benchmark.go b/les/benchmark.go index a146de2fed..312d4533df 100644 --- a/les/benchmark.go +++ b/les/benchmark.go @@ -75,9 +75,8 @@ func (b *benchmarkBlockHeaders) init(h *serverHandler, count int) error { func (b *benchmarkBlockHeaders) request(peer *serverPeer, index int) error { if b.byHash { return peer.requestHeadersByHash(0, b.hashes[index], b.amount, b.skip, b.reverse) - } else { - return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse) } + return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse) } // benchmarkBodiesOrReceipts implements requestBenchmark @@ -98,9 +97,8 @@ func (b *benchmarkBodiesOrReceipts) init(h *serverHandler, count int) error { func (b *benchmarkBodiesOrReceipts) request(peer *serverPeer, index int) error { if b.receipts { return peer.requestReceipts(0, []common.Hash{b.hashes[index]}) - } else { - return peer.requestBodies(0, []common.Hash{b.hashes[index]}) } + return peer.requestBodies(0, []common.Hash{b.hashes[index]}) } // benchmarkProofsOrCode implements requestBenchmark @@ -119,9 +117,8 @@ func (b *benchmarkProofsOrCode) request(peer *serverPeer, index int) error { rand.Read(key) if b.code { return peer.requestCode(0, []CodeReq{{BHash: b.headHash, AccKey: key}}) - } else { - return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}}) } + return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}}) } // benchmarkHelperTrie implements requestBenchmark diff --git a/les/lespay/server/balance.go b/les/lespay/server/balance.go index f820a4ad05..f5073d0db1 100644 --- a/les/lespay/server/balance.go +++ b/les/lespay/server/balance.go @@ -588,9 +588,8 @@ func (n *NodeBalance) timeUntil(priority int64) (time.Duration, bool) { } dt = float64(posBalance-newBalance) / timePrice return time.Duration(dt), true - } else { - dt = float64(posBalance) / timePrice } + dt = float64(posBalance) / timePrice } else { if priority > 0 { return 0, false diff --git a/les/lespay/server/balance_tracker.go b/les/lespay/server/balance_tracker.go index c1ea3c6496..edd4b288d9 100644 --- a/les/lespay/server/balance_tracker.go +++ b/les/lespay/server/balance_tracker.go @@ -261,9 +261,8 @@ func (bt *BalanceTracker) storeBalance(id []byte, neg bool, value utils.ExpiredV func (bt *BalanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool { if neg { return b.Value(bt.negExp.LogOffset(now)) <= negThreshold - } else { - return b.Value(bt.posExp.LogOffset(now)) <= posThreshold } + return b.Value(bt.posExp.LogOffset(now)) <= posThreshold } // updateTotalBalance adjusts the total balance after executing given callback. diff --git a/les/lespay/server/prioritypool.go b/les/lespay/server/prioritypool.go index c0c33840ca..e3327aba75 100644 --- a/les/lespay/server/prioritypool.go +++ b/les/lespay/server/prioritypool.go @@ -288,9 +288,8 @@ func activePriority(a interface{}, now mclock.AbsTime) int64 { } if c.bias == 0 { return invertPriority(c.nodePriority.Priority(now, c.capacity)) - } else { - return invertPriority(c.nodePriority.EstMinPriority(now+mclock.AbsTime(c.bias), c.capacity, true)) } + return invertPriority(c.nodePriority.EstMinPriority(now+mclock.AbsTime(c.bias), c.capacity, true)) } // activeMaxPriority callback returns estimated maximum priority of ppNodeInfo item in activeQueue diff --git a/les/odr_requests.go b/les/odr_requests.go index 3cc55c98d8..3704436a03 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -329,9 +329,8 @@ func (r *ChtRequest) CanSend(peer *serverPeer) bool { if r.Untrusted { return peer.headInfo.Number >= r.BlockNum && peer.id == r.PeerId - } else { - return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize } + return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) diff --git a/les/serverpool.go b/les/serverpool.go index 9bfa0bd725..6cf4affff0 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -112,9 +112,8 @@ var ( } enc, err := rlp.EncodeToBytes(&ne) return enc, err - } else { - return nil, errors.New("invalid field type") } + return nil, errors.New("invalid field type") }, func(enc []byte) (interface{}, error) { var ne nodeHistoryEnc diff --git a/les/serverpool_test.go b/les/serverpool_test.go index 3d0487d102..70a41b74c6 100644 --- a/les/serverpool_test.go +++ b/les/serverpool_test.go @@ -119,31 +119,28 @@ func (s *serverPoolTest) start() { s.clock.Sleep(time.Second * 5) s.endWait() return -1 - } else { - switch idx % 3 { - case 0: - // pre-neg returns true only if connection is possible - if canConnect { - return 1 - } else { - return 0 - } - case 1: - // pre-neg returns true but connection might still fail + } + switch idx % 3 { + case 0: + // pre-neg returns true only if connection is possible + if canConnect { + return 1 + } + return 0 + case 1: + // pre-neg returns true but connection might still fail + return 1 + case 2: + // pre-neg returns true if connection is possible, otherwise timeout (node unresponsive) + if canConnect { return 1 - case 2: - // pre-neg returns true if connection is possible, otherwise timeout (node unresponsive) - if canConnect { - return 1 - } else { - s.beginWait() - s.clock.Sleep(time.Second * 5) - s.endWait() - return -1 - } } + s.beginWait() + s.clock.Sleep(time.Second * 5) + s.endWait() return -1 } + return -1 } } diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go index f47534784c..9d5519b9c4 100644 --- a/p2p/nat/nat.go +++ b/p2p/nat/nat.go @@ -219,9 +219,8 @@ func (n *autodisc) String() string { defer n.mu.Unlock() if n.found == nil { return n.what - } else { - return n.found.String() } + return n.found.String() } // wait blocks until auto-discovery has been performed. diff --git a/p2p/nodestate/nodestate_test.go b/p2p/nodestate/nodestate_test.go index 5f99a3da74..d06ad755e2 100644 --- a/p2p/nodestate/nodestate_test.go +++ b/p2p/nodestate/nodestate_test.go @@ -240,9 +240,8 @@ func uint64FieldEnc(field interface{}) ([]byte, error) { if u, ok := field.(uint64); ok { enc, err := rlp.EncodeToBytes(&u) return enc, err - } else { - return nil, errors.New("invalid field type") } + return nil, errors.New("invalid field type") } func uint64FieldDec(enc []byte) (interface{}, error) { @@ -254,9 +253,8 @@ func uint64FieldDec(enc []byte) (interface{}, error) { func stringFieldEnc(field interface{}) ([]byte, error) { if s, ok := field.(string); ok { return []byte(s), nil - } else { - return nil, errors.New("invalid field type") } + return nil, errors.New("invalid field type") } func stringFieldDec(enc []byte) (interface{}, error) { diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index a54db4ea68..9b5e2c37f5 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -454,9 +454,8 @@ func (net *Network) getNodeIDs(excludeIDs []enode.ID) []enode.ID { if len(excludeIDs) > 0 { // Return the difference of nodeIDs and excludeIDs return filterIDs(nodeIDs, excludeIDs) - } else { - return nodeIDs } + return nodeIDs } // GetNodes returns the existing nodes. @@ -472,9 +471,8 @@ func (net *Network) getNodes(excludeIDs []enode.ID) []*Node { if len(excludeIDs) > 0 { nodeIDs := net.getNodeIDs(excludeIDs) return net.getNodesByID(nodeIDs) - } else { - return net.Nodes } + return net.Nodes } // GetNodesByID returns existing nodes with the given enode.IDs. @@ -1098,7 +1096,6 @@ func (net *Network) executeNodeEvent(e *Event) error { func (net *Network) executeConnEvent(e *Event) error { if e.Conn.Up { return net.Connect(e.Conn.One, e.Conn.Other) - } else { - return net.Disconnect(e.Conn.One, e.Conn.Other) } + return net.Disconnect(e.Conn.One, e.Conn.Other) } diff --git a/rlp/encode_test.go b/rlp/encode_test.go index 6803745197..418ee10a35 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -39,9 +39,8 @@ func (e *testEncoder) EncodeRLP(w io.Writer) error { } if e.err != nil { return e.err - } else { - w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1}) } + w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1}) return nil } diff --git a/rpc/client.go b/rpc/client.go index 91e68e73e6..9393adb779 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -404,9 +404,8 @@ func (c *Client) Notify(ctx context.Context, method string, args ...interface{}) if c.isHTTP { return c.sendHTTP(ctx, op, msg) - } else { - return c.send(ctx, op, msg) } + return c.send(ctx, op, msg) } // EthSubscribe registers a subscripion under the "eth" namespace. diff --git a/trie/committer.go b/trie/committer.go index 20c95bed08..33fd9e9823 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -267,6 +267,5 @@ func estimateSize(n node) int { return 1 + len(n) default: panic(fmt.Sprintf("node type %T", n)) - } } From f59ed3565d18c1fa676fd34f4fd41ecccad707e8 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Wed, 25 Nov 2020 01:19:36 -0800 Subject: [PATCH 029/235] graphql: always return 400 if errors are present in the response (#21882) * Make sure to return 400 when errors are present in the response * graphql: use less memory in chainconfig for tests Co-authored-by: Martin Holst Swende --- graphql/graphql_test.go | 52 +++++++++++++++++++++++++++++++++++++++-- graphql/service.go | 36 ++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 5ba9c95537..98c8622ee6 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -22,8 +22,13 @@ import ( "net/http" "strings" "testing" + "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/stretchr/testify/assert" ) @@ -61,6 +66,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) { t.Fatalf("could not read from response body: %v", err) } expected := "{\"data\":{\"block\":{\"number\":\"0x0\"}}}" + assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, expected, string(bodyBytes)) } @@ -90,6 +96,32 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { assert.Equal(t, "404 page not found\n", string(bodyBytes)) } +// Tests that 400 is returned when an invalid RPC request is made. +func TestGraphQL_BadRequest(t *testing.T) { + stack := createNode(t, true) + defer stack.Close() + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + // create http request + body := strings.NewReader("{\"query\": \"{bleh{number}}\",\"variables\": null}") + gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) + if err != nil { + t.Error("could not issue new http request ", err) + } + gqlReq.Header.Set("Content-Type", "application/json") + // read from response + resp := doHTTPRequest(t, gqlReq) + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("could not read from response body: %v", err) + } + expected := "{\"errors\":[{\"message\":\"Cannot query field \\\"bleh\\\" on type \\\"Query\\\".\",\"locations\":[{\"line\":1,\"column\":2}]}]}" + assert.Equal(t, expected, string(bodyBytes)) + assert.Equal(t, 400, resp.StatusCode) +} + func createNode(t *testing.T, gqlEnabled bool) *node.Node { stack, err := node.New(&node.Config{ HTTPHost: "127.0.0.1", @@ -110,8 +142,24 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { } func createGQLService(t *testing.T, stack *node.Node, endpoint string) { - // create backend - ethBackend, err := eth.New(stack, ð.DefaultConfig) + // create backend (use a config which is light on mem consumption) + ethConf := ð.Config{ + Genesis: core.DeveloperGenesisBlock(15, common.Address{}), + Miner: miner.Config{ + Etherbase: common.HexToAddress("0xaabb"), + }, + Ethash: ethash.Config{ + PowMode: ethash.ModeTest, + }, + NetworkId: 1337, + TrieCleanCache: 5, + TrieCleanCacheJournal: "triecache", + TrieCleanCacheRejournal: 60 * time.Minute, + TrieDirtyCache: 5, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 5, + } + ethBackend, err := eth.New(stack, ethConf) if err != nil { t.Fatalf("could not create eth backend: %v", err) } diff --git a/graphql/service.go b/graphql/service.go index ae962e5b36..bcb0a4990d 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -17,12 +17,44 @@ package graphql import ( + "encoding/json" + "net/http" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/node" "github.com/graph-gophers/graphql-go" - "github.com/graph-gophers/graphql-go/relay" ) +type handler struct { + Schema *graphql.Schema +} + +func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var params struct { + Query string `json:"query"` + OperationName string `json:"operationName"` + Variables map[string]interface{} `json:"variables"` + } + if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + response := h.Schema.Exec(r.Context(), params.Query, params.OperationName, params.Variables) + responseJSON, err := json.Marshal(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if len(response.Errors) > 0 { + w.WriteHeader(http.StatusBadRequest) + } + + w.Header().Set("Content-Type", "application/json") + w.Write(responseJSON) + +} + // New constructs a new GraphQL service instance. func New(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) error { if backend == nil { @@ -41,7 +73,7 @@ func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) if err != nil { return err } - h := &relay.Handler{Schema: s} + h := handler{Schema: s} handler := node.NewHTTPHandlerStack(h, cors, vhosts) stack.RegisterHandler("GraphQL UI", "/graphql/ui", GraphiQL{}) From 810f9e057daccda9eb5c04721b6dd4660f001cc5 Mon Sep 17 00:00:00 2001 From: Alex Prut <1648497+alexprut@users.noreply.github.com> Date: Wed, 25 Nov 2020 21:00:23 +0100 Subject: [PATCH 030/235] all: remove redundant conversions and import names (#21903) --- accounts/abi/reflect_test.go | 4 ++-- accounts/abi/type_test.go | 2 +- accounts/accounts.go | 2 +- accounts/keystore/passphrase.go | 2 +- accounts/keystore/wallet.go | 2 +- accounts/scwallet/wallet.go | 2 +- accounts/usbwallet/wallet.go | 2 +- cmd/clef/main.go | 2 +- cmd/devp2p/dnscmd.go | 2 +- cmd/evm/compiler.go | 2 +- cmd/evm/disasm.go | 2 +- cmd/evm/runner.go | 2 +- cmd/evm/staterunner.go | 2 +- cmd/geth/config.go | 2 +- cmd/geth/main.go | 2 +- cmd/geth/usage.go | 2 +- cmd/puppeth/genesis.go | 4 ++-- cmd/utils/flags.go | 2 +- common/hexutil/json_test.go | 2 +- consensus/ethash/ethash.go | 2 +- contracts/checkpointoracle/oracle.go | 2 +- core/state/statedb.go | 4 ++-- core/tx_pool_test.go | 2 +- core/vm/evm.go | 2 +- core/vm/gas_table.go | 8 ++++---- core/vm/instructions.go | 8 ++++---- core/vm/operations_acl.go | 2 +- eth/downloader/api.go | 2 +- eth/filters/api.go | 4 ++-- eth/filters/filter_system.go | 2 +- eth/filters/filter_system_test.go | 2 +- eth/tracers/tracer.go | 2 +- event/event_test.go | 2 +- event/feed_test.go | 10 +++++----- graphql/graphql.go | 16 ++++++++-------- internal/debug/flags.go | 2 +- internal/ethapi/api.go | 2 +- internal/flags/helpers.go | 2 +- miner/unconfirmed_test.go | 5 ++--- mobile/ethereum.go | 2 +- p2p/enode/nodedb.go | 2 +- signer/core/signed_data.go | 2 +- trie/database.go | 2 +- trie/iterator.go | 2 +- 44 files changed, 66 insertions(+), 67 deletions(-) diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go index bac4cd9530..cf13a79da8 100644 --- a/accounts/abi/reflect_test.go +++ b/accounts/abi/reflect_test.go @@ -202,12 +202,12 @@ func TestConvertType(t *testing.T) { fields = append(fields, reflect.StructField{ Name: "X", Type: reflect.TypeOf(new(big.Int)), - Tag: reflect.StructTag("json:\"" + "x" + "\""), + Tag: "json:\"" + "x" + "\"", }) fields = append(fields, reflect.StructField{ Name: "Y", Type: reflect.TypeOf(new(big.Int)), - Tag: reflect.StructTag("json:\"" + "y" + "\""), + Tag: "json:\"" + "y" + "\"", }) val := reflect.New(reflect.StructOf(fields)) val.Elem().Field(0).Set(reflect.ValueOf(big.NewInt(1))) diff --git a/accounts/abi/type_test.go b/accounts/abi/type_test.go index 48df3aa383..8c3aedca6a 100644 --- a/accounts/abi/type_test.go +++ b/accounts/abi/type_test.go @@ -255,7 +255,7 @@ func TestTypeCheck(t *testing.T) { {"bytes", nil, [2]byte{0, 1}, "abi: cannot use array as type slice as argument"}, {"bytes", nil, common.Hash{1}, "abi: cannot use array as type slice as argument"}, {"string", nil, "hello world", ""}, - {"string", nil, string(""), ""}, + {"string", nil, "", ""}, {"string", nil, []byte{}, "abi: cannot use slice as type string as argument"}, {"bytes32[]", nil, [][32]byte{{}}, ""}, {"function", nil, [24]byte{}, ""}, diff --git a/accounts/accounts.go b/accounts/accounts.go index dc85cba174..08a1f0f2b1 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -21,7 +21,7 @@ import ( "fmt" "math/big" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go index 89cdf0bfca..dd4d7764e4 100644 --- a/accounts/keystore/passphrase.go +++ b/accounts/keystore/passphrase.go @@ -230,7 +230,7 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) { key := crypto.ToECDSAUnsafe(keyBytes) return &Key{ - Id: uuid.UUID(keyId), + Id: keyId, Address: crypto.PubkeyToAddress(key.PublicKey), PrivateKey: key, }, nil diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go index 498067d497..fe9d2fe49f 100644 --- a/accounts/keystore/wallet.go +++ b/accounts/keystore/wallet.go @@ -19,7 +19,7 @@ package keystore import ( "math/big" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index 85fae8c114..6476646d7f 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -33,7 +33,7 @@ import ( "sync" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index e39c6bdf34..9f74e5554f 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -25,7 +25,7 @@ import ( "sync" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 3207042a6c..273919cb41 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -54,7 +54,7 @@ import ( "github.com/ethereum/go-ethereum/signer/rules" "github.com/ethereum/go-ethereum/signer/storage" - colorable "github.com/mattn/go-colorable" + "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "gopkg.in/urfave/cli.v1" ) diff --git a/cmd/devp2p/dnscmd.go b/cmd/devp2p/dnscmd.go index 13110f21c6..f56f0f34e4 100644 --- a/cmd/devp2p/dnscmd.go +++ b/cmd/devp2p/dnscmd.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/console/prompt" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var ( diff --git a/cmd/evm/compiler.go b/cmd/evm/compiler.go index c019a2fe70..40ad9313c5 100644 --- a/cmd/evm/compiler.go +++ b/cmd/evm/compiler.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/evm/internal/compiler" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var compileCommand = cli.Command{ diff --git a/cmd/evm/disasm.go b/cmd/evm/disasm.go index 5bc743aa88..68a09cbf50 100644 --- a/cmd/evm/disasm.go +++ b/cmd/evm/disasm.go @@ -23,7 +23,7 @@ import ( "strings" "github.com/ethereum/go-ethereum/core/asm" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var disasmCommand = cli.Command{ diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index d0be6ca1e1..4063767cb8 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -38,7 +38,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm/runtime" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var runCommand = cli.Command{ diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index f9a6b06b8f..c4df936c75 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/tests" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var stateTestCommand = cli.Command{ diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 6b51843aa4..bf1fc55b17 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -24,7 +24,7 @@ import ( "reflect" "unicode" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/eth" diff --git a/cmd/geth/main.go b/cmd/geth/main.go index aa6881bab0..0f50989208 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -42,7 +42,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" gopsutil "github.com/shirou/gopsutil/mem" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) const ( diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index a9b6f53e72..0e70451ed3 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -25,7 +25,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/flags" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) // AppHelpFlagGroups is the application flags, grouped by functionality. diff --git a/cmd/puppeth/genesis.go b/cmd/puppeth/genesis.go index b3e1709dbf..46268ec11b 100644 --- a/cmd/puppeth/genesis.go +++ b/cmd/puppeth/genesis.go @@ -152,7 +152,7 @@ func newAlethGenesisSpec(network string, genesis *core.Genesis) (*alethGenesisSp spec.Genesis.Author = genesis.Coinbase spec.Genesis.Timestamp = (hexutil.Uint64)(genesis.Timestamp) spec.Genesis.ParentHash = genesis.ParentHash - spec.Genesis.ExtraData = (hexutil.Bytes)(genesis.ExtraData) + spec.Genesis.ExtraData = genesis.ExtraData spec.Genesis.GasLimit = (hexutil.Uint64)(genesis.GasLimit) for address, account := range genesis.Alloc { @@ -430,7 +430,7 @@ func newParityChainSpec(network string, genesis *core.Genesis, bootnodes []strin spec.Genesis.Author = genesis.Coinbase spec.Genesis.Timestamp = (hexutil.Uint64)(genesis.Timestamp) spec.Genesis.ParentHash = genesis.ParentHash - spec.Genesis.ExtraData = (hexutil.Bytes)(genesis.ExtraData) + spec.Genesis.ExtraData = genesis.ExtraData spec.Genesis.GasLimit = (hexutil.Uint64)(genesis.GasLimit) spec.Accounts = make(map[common.UnprefixedAddress]*parityChainSpecAccount) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 26dbb9faf3..56880768f9 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -64,7 +64,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" pcsclite "github.com/gballet/go-libpcsclite" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) func init() { diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go index 8a6b8643a1..ed7d6fad1a 100644 --- a/common/hexutil/json_test.go +++ b/common/hexutil/json_test.go @@ -88,7 +88,7 @@ func TestUnmarshalBytes(t *testing.T) { if !checkError(t, test.input, err, test.wantErr) { continue } - if !bytes.Equal(test.want.([]byte), []byte(v)) { + if !bytes.Equal(test.want.([]byte), v) { t.Errorf("input %s: value mismatch: got %x, want %x", test.input, &v, test.want) continue } diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index aa3f002c0d..550d99893d 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -33,7 +33,7 @@ import ( "time" "unsafe" - mmap "github.com/edsrzf/mmap-go" + "github.com/edsrzf/mmap-go" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" diff --git a/contracts/checkpointoracle/oracle.go b/contracts/checkpointoracle/oracle.go index 1f273272ab..7f3127d0b8 100644 --- a/contracts/checkpointoracle/oracle.go +++ b/contracts/checkpointoracle/oracle.go @@ -65,7 +65,7 @@ func (oracle *CheckpointOracle) LookupCheckpointEvents(blockLogs [][]*types.Log, if err != nil { continue } - if event.Index == section && common.Hash(event.CheckpointHash) == hash { + if event.Index == section && event.CheckpointHash == hash { votes = append(votes, event) } } diff --git a/core/state/statedb.go b/core/state/statedb.go index fe30f595ed..ed9a82379f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -318,7 +318,7 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { func (s *StateDB) GetProof(a common.Address) ([][]byte, error) { var proof proofList err := s.trie.Prove(crypto.Keccak256(a.Bytes()), 0, &proof) - return [][]byte(proof), err + return proof, err } // GetStorageProof returns the StorageProof for given key @@ -329,7 +329,7 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, return proof, errors.New("storage trie for requested address does not exist") } err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) - return [][]byte(proof), err + return proof, err } // GetCommittedState retrieves a value from the given account's committed storage trie. diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 4fca734e65..dbc49d4f9c 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -1139,7 +1139,7 @@ func TestTransactionAllowedTxSize(t *testing.T) { t.Fatalf("expected rejection on slightly oversize transaction") } // Try adding a transaction of random not allowed size - if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(int(10*txMaxSize))))); err == nil { + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(10*txMaxSize)))); err == nil { t.Fatalf("expected rejection on oversize transaction") } // Run some sanity checks on the pool internals diff --git a/core/vm/evm.go b/core/vm/evm.go index 6c7b2370a5..9afef76643 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -525,7 +525,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { codeAndHash := &codeAndHash{code: code} - contractAddr = crypto.CreateAddress2(caller.Address(), common.Hash(salt.Bytes32()), codeAndHash.Hash().Bytes()) + contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) return evm.create(caller, codeAndHash, gas, endowment, contractAddr) } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 01249a5388..944b6cf0a5 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -96,7 +96,7 @@ var ( func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), common.Hash(x.Bytes32())) + current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) ) // The legacy gas metering only takes into consideration the current state // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283) @@ -135,7 +135,7 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi if current == value { // noop (1) return params.NetSstoreNoopGas, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.NetSstoreInitGas, nil @@ -183,14 +183,14 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), common.Hash(x.Bytes32())) + current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) ) value := common.Hash(y.Bytes32()) if current == value { // noop (1) return params.SloadGasEIP2200, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.SstoreSetGasEIP2200, nil diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 4ded0239d9..1137505292 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -341,7 +341,7 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *call func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { slot := callContext.stack.peek() - slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(common.Address(slot.Bytes20())))) + slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) return nil, nil } @@ -517,7 +517,7 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] loc := callContext.stack.pop() val := callContext.stack.pop() interpreter.evm.StateDB.SetState(callContext.contract.Address(), - common.Hash(loc.Bytes32()), common.Hash(val.Bytes32())) + loc.Bytes32(), val.Bytes32()) return nil, nil } @@ -817,7 +817,7 @@ func opStop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { beneficiary := callContext.stack.pop() balance := interpreter.evm.StateDB.GetBalance(callContext.contract.Address()) - interpreter.evm.StateDB.AddBalance(common.Address(beneficiary.Bytes20()), balance) + interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) interpreter.evm.StateDB.Suicide(callContext.contract.Address()) return nil, nil } @@ -832,7 +832,7 @@ func makeLog(size int) executionFunc { mStart, mSize := stack.pop(), stack.pop() for i := 0; i < size; i++ { addr := stack.pop() - topics[i] = common.Hash(addr.Bytes32()) + topics[i] = addr.Bytes32() } d := callContext.memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 41b0549c51..191953ce5e 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -73,7 +73,7 @@ func gasSStoreEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m // return params.SloadGasEIP2200, nil return cost + WarmStorageReadCostEIP2929, nil // SLOAD_GAS } - original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return cost + params.SstoreSetGasEIP2200, nil diff --git a/eth/downloader/api.go b/eth/downloader/api.go index 57ff3d71af..2024d23dea 100644 --- a/eth/downloader/api.go +++ b/eth/downloader/api.go @@ -20,7 +20,7 @@ import ( "context" "sync" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/rpc" ) diff --git a/eth/filters/api.go b/eth/filters/api.go index 30d7b71c31..664f229a04 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -25,7 +25,7 @@ import ( "sync" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" @@ -292,7 +292,7 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { logs := make(chan []*types.Log) logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs) if err != nil { - return rpc.ID(""), err + return "", err } api.filtersMu.Lock() diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index a105ec51c3..12f037d0f9 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -24,7 +24,7 @@ import ( "sync" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index c8d1d43abb..b534c1d47f 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -25,7 +25,7 @@ import ( "testing" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 16a8c7698f..5d806d9026 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - duktape "gopkg.in/olebedev/go-duktape.v3" + "gopkg.in/olebedev/go-duktape.v3" ) // bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js. diff --git a/event/event_test.go b/event/event_test.go index cc9fa5d7c8..bdad11f13d 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -29,7 +29,7 @@ func TestSubCloseUnsub(t *testing.T) { // the point of this test is **not** to panic var mux TypeMux mux.Stop() - sub := mux.Subscribe(int(0)) + sub := mux.Subscribe(0) sub.Unsubscribe() } diff --git a/event/feed_test.go b/event/feed_test.go index 93badaaee6..cdf29fdd73 100644 --- a/event/feed_test.go +++ b/event/feed_test.go @@ -27,8 +27,8 @@ import ( func TestFeedPanics(t *testing.T) { { var f Feed - f.Send(int(2)) - want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(int(0))} + f.Send(2) + want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)} if err := checkPanic(want, func() { f.Send(uint64(2)) }); err != nil { t.Error(err) } @@ -37,14 +37,14 @@ func TestFeedPanics(t *testing.T) { var f Feed ch := make(chan int) f.Subscribe(ch) - want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(int(0))} + want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)} if err := checkPanic(want, func() { f.Send(uint64(2)) }); err != nil { t.Error(err) } } { var f Feed - f.Send(int(2)) + f.Send(2) want := feedTypeError{op: "Subscribe", got: reflect.TypeOf(make(chan uint64)), want: reflect.TypeOf(make(chan<- int))} if err := checkPanic(want, func() { f.Subscribe(make(chan uint64)) }); err != nil { t.Error(err) @@ -58,7 +58,7 @@ func TestFeedPanics(t *testing.T) { } { var f Feed - if err := checkPanic(errBadChannel, func() { f.Subscribe(int(0)) }); err != nil { + if err := checkPanic(errBadChannel, func() { f.Subscribe(0) }); err != nil { t.Error(err) } } diff --git a/graphql/graphql.go b/graphql/graphql.go index 559da8aaaa..16a74b4037 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -77,7 +77,7 @@ func (a *Account) Code(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } - return hexutil.Bytes(state.GetCode(a.address)), nil + return state.GetCode(a.address), nil } func (a *Account) Storage(ctx context.Context, args struct{ Slot common.Hash }) (common.Hash, error) { @@ -116,7 +116,7 @@ func (l *Log) Topics(ctx context.Context) []common.Hash { } func (l *Log) Data(ctx context.Context) hexutil.Bytes { - return hexutil.Bytes(l.log.Data) + return l.log.Data } // Transaction represents an Ethereum transaction. @@ -157,7 +157,7 @@ func (t *Transaction) InputData(ctx context.Context) (hexutil.Bytes, error) { if err != nil || tx == nil { return hexutil.Bytes{}, err } - return hexutil.Bytes(tx.Data()), nil + return tx.Data(), nil } func (t *Transaction) Gas(ctx context.Context) (hexutil.Uint64, error) { @@ -410,7 +410,7 @@ func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) { if err != nil { return nil, err } - b.receipts = []*types.Receipt(receipts) + b.receipts = receipts } return b.receipts, nil } @@ -490,7 +490,7 @@ func (b *Block) Nonce(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } - return hexutil.Bytes(header.Nonce[:]), nil + return header.Nonce[:], nil } func (b *Block) MixHash(ctx context.Context) (common.Hash, error) { @@ -564,7 +564,7 @@ func (b *Block) ExtraData(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } - return hexutil.Bytes(header.Extra), nil + return header.Extra, nil } func (b *Block) LogsBloom(ctx context.Context) (hexutil.Bytes, error) { @@ -572,7 +572,7 @@ func (b *Block) LogsBloom(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } - return hexutil.Bytes(header.Bloom.Bytes()), nil + return header.Bloom.Bytes(), nil } func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) { @@ -907,7 +907,7 @@ func (r *Resolver) Block(ctx context.Context, args struct { }) (*Block, error) { var block *Block if args.Number != nil { - number := rpc.BlockNumber(uint64(*args.Number)) + number := rpc.BlockNumber(*args.Number) numberOrHash := rpc.BlockNumberOrHashWithNumber(number) block = &Block{ backend: r.backend, diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 3b077b6e08..e3a01378ca 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics/exp" "github.com/fjl/memsize/memsizeui" - colorable "github.com/mattn/go-colorable" + "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "gopkg.in/urfave/cli.v1" ) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 0d6ace9b5b..030bdb37a4 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -793,7 +793,7 @@ func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message { var data []byte if args.Data != nil { - data = []byte(*args.Data) + data = *args.Data } msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false) diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go index 900fec454c..f61ed4b689 100644 --- a/internal/flags/helpers.go +++ b/internal/flags/helpers.go @@ -21,7 +21,7 @@ import ( "path/filepath" "github.com/ethereum/go-ethereum/params" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var ( diff --git a/miner/unconfirmed_test.go b/miner/unconfirmed_test.go index 42e77f3e64..dc83cb9265 100644 --- a/miner/unconfirmed_test.go +++ b/miner/unconfirmed_test.go @@ -19,7 +19,6 @@ package miner import ( "testing" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -43,7 +42,7 @@ func TestUnconfirmedInsertBounds(t *testing.T) { for depth := uint64(0); depth < 2*uint64(limit); depth++ { // Insert multiple blocks for the same level just to stress it for i := 0; i < int(depth); i++ { - pool.Insert(depth, common.Hash([32]byte{byte(depth), byte(i)})) + pool.Insert(depth, [32]byte{byte(depth), byte(i)}) } // Validate that no blocks below the depth allowance are left in pool.blocks.Do(func(block interface{}) { @@ -63,7 +62,7 @@ func TestUnconfirmedShifts(t *testing.T) { pool := newUnconfirmedBlocks(new(noopChainRetriever), limit) for depth := start; depth < start+uint64(limit); depth++ { - pool.Insert(depth, common.Hash([32]byte{byte(depth)})) + pool.Insert(depth, [32]byte{byte(depth)}) } // Try to shift below the limit and ensure no blocks are dropped pool.Shift(start + uint64(limit) - 1) diff --git a/mobile/ethereum.go b/mobile/ethereum.go index 59da852397..97c46ddca7 100644 --- a/mobile/ethereum.go +++ b/mobile/ethereum.go @@ -21,7 +21,7 @@ package geth import ( "errors" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" ) diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index bd066ce857..643fbe5711 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -163,7 +163,7 @@ func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) { } key = key[len(dbDiscoverRoot)+1:] // Split out the IP. - ip = net.IP(key[:16]) + ip = key[:16] if ip4 := ip.To4(); ip4 != nil { ip = ip4 } diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 19377a521b..3bff1e1f20 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -506,7 +506,7 @@ func parseBytes(encType interface{}) ([]byte, bool) { case []byte: return v, true case hexutil.Bytes: - return []byte(v), true + return v, true case string: bytes, err := hexutil.Decode(v) if err != nil { diff --git a/trie/database.go b/trie/database.go index d8fe45f444..7c2f207097 100644 --- a/trie/database.go +++ b/trie/database.go @@ -100,7 +100,7 @@ func (n rawNode) cache() (hashNode, bool) { panic("this should never end up in func (n rawNode) fstring(ind string) string { panic("this should never end up in a live trie") } func (n rawNode) EncodeRLP(w io.Writer) error { - _, err := w.Write([]byte(n)) + _, err := w.Write(n) return err } diff --git a/trie/iterator.go b/trie/iterator.go index bb4025d8f3..76d437c403 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -173,7 +173,7 @@ func (it *nodeIterator) LeafKey() []byte { func (it *nodeIterator) LeafBlob() []byte { if len(it.stack) > 0 { if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { - return []byte(node) + return node } } panic("not at leaf") From 429e7141f2f41c1d66dd4dd711a47ca9e0f0c2cb Mon Sep 17 00:00:00 2001 From: Nishant Das Date: Thu, 26 Nov 2020 05:16:36 +0800 Subject: [PATCH 031/235] p2p/discover: fix deadlock in discv5 message dispatch (#21858) This fixes a deadlock that could occur when a response packet arrived after a call had already received enough responses and was about to signal completion to the dispatch loop. Co-authored-by: Felix Lange --- p2p/discover/v5_udp.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go index c95317a005..9dd2b31733 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -454,9 +454,20 @@ func (t *UDPv5) call(node *enode.Node, responseType byte, packet v5wire.Packet) // callDone tells dispatch that the active call is done. func (t *UDPv5) callDone(c *callV5) { - select { - case t.callDoneCh <- c: - case <-t.closeCtx.Done(): + // This needs a loop because further responses may be incoming until the + // send to callDoneCh has completed. Such responses need to be discarded + // in order to avoid blocking the dispatch loop. + for { + select { + case <-c.ch: + // late response, discard. + case <-c.err: + // late error, discard. + case t.callDoneCh <- c: + return + case <-t.closeCtx.Done(): + return + } } } From fa572cd2971fda0bf773d2817acb23385bac66c7 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Fri, 27 Nov 2020 11:13:54 +0000 Subject: [PATCH 032/235] crypto: signing builds with signify/minisign (#21798) * internal/build: implement signify's signing func * Add signify to the ci utility * fix output file format * Add unit test for signify * holiman's + travis' feedback * internal/build: verify signify's output * crypto: move signify to common dir * use go-minisign to verify binaries * more holiman feedback * crypto, ci: support minisign output * only accept one-line trusted comments * configurable untrusted comments * code cleanup in tests * revert to use ed25519 from the stdlib * bug: fix for empty untrusted comments * write timestamp as comment if trusted comment isn't present * rename line checker to commentHasManyLines * crypto: added signify fuzzer (#6) * crypto: added signify fuzzer * stuff * crypto: updated signify fuzzer to fuzz comments * crypto: repro signify crashes * rebased fuzzer on build-signify branch * hide fuzzer behind gofuzz build flag * extract key data inside a single function * don't treat \r as a newline * travis: fix signing command line * do not use an external binary in tests * crypto: move signify to crypto/signify * travis: fix formatting issue * ci: fix linter build after package move Co-authored-by: Marius van der Wijden --- .travis.yml | 26 +++--- build/ci.go | 54 ++++++++---- crypto/signify/signify.go | 118 +++++++++++++++++++++++++ crypto/signify/signify_fuzz.go | 148 +++++++++++++++++++++++++++++++ crypto/signify/signify_test.go | 154 +++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 3 + 7 files changed, 472 insertions(+), 32 deletions(-) create mode 100644 crypto/signify/signify.go create mode 100644 crypto/signify/signify_fuzz.go create mode 100644 crypto/signify/signify_test.go mode change 100755 => 100644 go.mod mode change 100755 => 100644 go.sum diff --git a/.travis.yml b/.travis.yml index fd31e3d506..d37458792a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,22 +67,22 @@ jobs: script: # Build for the primary platforms that Trusty can manage - go run build/ci.go install -dlgo - - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go install -dlgo -arch 386 - - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds # Switch over GCC to cross compilation (breaks 386, hence why do it here only) - sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross - sudo ln -s /usr/include/asm-generic /usr/include/asm - GOARM=5 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc - - GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - GOARM=6 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc - - GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - GOARM=7 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabihf-gcc - - GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc - - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds # This builder does the Linux Azure MIPS xgo uploads - stage: build @@ -100,19 +100,19 @@ jobs: script: - go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done - - go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mipsle --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mipsle; do mv -f "${bin}" "${bin/-linux-mipsle/}"; done - - go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mips64 --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips64; do mv -f "${bin}" "${bin/-linux-mips64/}"; done - - go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY signify LINUX_SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mips64le --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips64le; do mv -f "${bin}" "${bin/-linux-mips64le/}"; done - - go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds # This builder does the Android Maven and Azure uploads - stage: build @@ -151,7 +151,7 @@ jobs: - mkdir -p $GOPATH/src/github.com/ethereum - ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum - - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -deploy https://oss.sonatype.org -upload gethstore/builds + - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -signify ANDROID_SIGNIFY_KEY -deploy https://oss.sonatype.org -upload gethstore/builds # This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads - stage: build @@ -167,7 +167,7 @@ jobs: submodules: false # avoid cloning ethereum/tests script: - go run build/ci.go install -dlgo - - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify OSX_SIGNIFY_KEY -upload gethstore/builds # Build the iOS framework and upload it to CocoaPods and Azure - gem uninstall cocoapods -a -x @@ -182,7 +182,7 @@ jobs: # Workaround for https://github.com/golang/go/issues/23749 - export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc' - - go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds + - go run build/ci.go xcode -signer IOS_SIGNING_KEY -signify IOS_SIGNIFY_KEY -deploy trunk -upload gethstore/builds # These builders run the tests - stage: build diff --git a/build/ci.go b/build/ci.go index 0cffb903aa..951b21f910 100644 --- a/build/ci.go +++ b/build/ci.go @@ -26,7 +26,7 @@ Available commands are: install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables test [ -coverage ] [ packages... ] -- runs the tests lint -- runs certain pre-selected linters - archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artifacts + archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -signify key-envvar ] [ -upload dest ] -- archives build artifacts importkeys -- imports signing keys from env debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package nsis -- creates a Windows NSIS installer @@ -58,6 +58,7 @@ import ( "time" "github.com/cespare/cp" + signifyPkg "github.com/ethereum/go-ethereum/crypto/signify" "github.com/ethereum/go-ethereum/internal/build" "github.com/ethereum/go-ethereum/params" ) @@ -396,11 +397,12 @@ func downloadLinter(cachedir string) string { // Release Packaging func doArchive(cmdline []string) { var ( - arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging") - atype = flag.String("type", "zip", "Type of archive to write (zip|tar)") - signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`) - upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) - ext string + arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging") + atype = flag.String("type", "zip", "Type of archive to write (zip|tar)") + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`) + signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`) + upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) + ext string ) flag.CommandLine.Parse(cmdline) switch *atype { @@ -427,7 +429,7 @@ func doArchive(cmdline []string) { log.Fatal(err) } for _, archive := range []string{geth, alltools} { - if err := archiveUpload(archive, *upload, *signer); err != nil { + if err := archiveUpload(archive, *upload, *signer, *signify); err != nil { log.Fatal(err) } } @@ -447,7 +449,7 @@ func archiveBasename(arch string, archiveVersion string) string { return platform + "-" + archiveVersion } -func archiveUpload(archive string, blobstore string, signer string) error { +func archiveUpload(archive string, blobstore string, signer string, signify string) error { // If signing was requested, generate the signature files if signer != "" { key := getenvBase64(signer) @@ -455,6 +457,12 @@ func archiveUpload(archive string, blobstore string, signer string) error { return err } } + if signify != "" { + key := getenvBase64(string(signify)) + if err := signifyPkg.SignifySignFile(archive, archive+".sig", string(key), "verify with geth.pub", fmt.Sprintf("%d", time.Now().UTC().Unix())); err != nil { + return err + } + } // If uploading to Azure was requested, push the archive possibly with its signature if blobstore != "" { auth := build.AzureBlobstoreConfig{ @@ -470,6 +478,11 @@ func archiveUpload(archive string, blobstore string, signer string) error { return err } } + if signify != "" { + if err := build.AzureBlobstoreUpload(archive+".sig", filepath.Base(archive+".sig"), auth); err != nil { + return err + } + } } return nil } @@ -806,6 +819,7 @@ func doWindowsInstaller(cmdline []string) { var ( arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging") signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`) + signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`) upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) ) @@ -867,7 +881,7 @@ func doWindowsInstaller(cmdline []string) { filepath.Join(*workdir, "geth.nsi"), ) // Sign and publish installer. - if err := archiveUpload(installer, *upload, *signer); err != nil { + if err := archiveUpload(installer, *upload, *signer, *signify); err != nil { log.Fatal(err) } } @@ -876,10 +890,11 @@ func doWindowsInstaller(cmdline []string) { func doAndroidArchive(cmdline []string) { var ( - local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) - signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`) - deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`) - upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`) + local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`) + signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. ANDROID_SIGNIFY_KEY)`) + deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`) + upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`) ) flag.CommandLine.Parse(cmdline) env := build.Env() @@ -908,7 +923,7 @@ func doAndroidArchive(cmdline []string) { archive := "geth-" + archiveBasename("android", params.ArchiveVersion(env.Commit)) + ".aar" os.Rename("geth.aar", archive) - if err := archiveUpload(archive, *upload, *signer); err != nil { + if err := archiveUpload(archive, *upload, *signer, *signify); err != nil { log.Fatal(err) } // Sign and upload all the artifacts to Maven Central @@ -1001,10 +1016,11 @@ func newMavenMetadata(env build.Environment) mavenMetadata { func doXCodeFramework(cmdline []string) { var ( - local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) - signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`) - deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`) - upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) + local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`) + signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. IOS_SIGNIFY_KEY)`) + deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`) + upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) ) flag.CommandLine.Parse(cmdline) env := build.Env() @@ -1032,7 +1048,7 @@ func doXCodeFramework(cmdline []string) { maybeSkipArchive(env) // Sign and upload the framework to Azure - if err := archiveUpload(archive+".tar.gz", *upload, *signer); err != nil { + if err := archiveUpload(archive+".tar.gz", *upload, *signer, *signify); err != nil { log.Fatal(err) } // Prepare and upload a PodSpec to CocoaPods diff --git a/crypto/signify/signify.go b/crypto/signify/signify.go new file mode 100644 index 0000000000..e86c4f09b0 --- /dev/null +++ b/crypto/signify/signify.go @@ -0,0 +1,118 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// signFile reads the contents of an input file and signs it (in armored format) +// with the key provided, placing the signature into the output file. + +package signify + +import ( + "encoding/base64" + "errors" + "fmt" + "io/ioutil" + "os" + "strings" + "time" + + "crypto/ed25519" +) + +var ( + errInvalidKeyHeader = errors.New("Incorrect key header") + errInvalidKeyLength = errors.New("invalid, key length != 104") +) + +func parsePrivateKey(key string) (ed25519.PrivateKey, []byte, []byte, error) { + keydata, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return nil, nil, nil, err + } + + if len(keydata) != 104 { + return nil, nil, nil, errInvalidKeyLength + } + + if string(keydata[:2]) != "Ed" { + return nil, nil, nil, errInvalidKeyHeader + } + + return ed25519.PrivateKey(keydata[40:]), keydata[:2], keydata[32:40], nil +} + +func commentHasManyLines(comment string) bool { + firstLFIndex := strings.IndexByte(comment, 10) + return (firstLFIndex >= 0 && firstLFIndex < len(comment)-1) +} + +// SignifySignFile creates a signature of the input file. +func SignifySignFile(input string, output string, key string, unTrustedComment string, trustedComment string) error { + in, err := os.Open(input) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(output) + if err != nil { + return err + } + defer out.Close() + + skey, header, keyNum, err := parsePrivateKey(key) + if err != nil { + return err + } + + filedata, err := ioutil.ReadAll(in) + if err != nil { + return err + } + + rawSig := ed25519.Sign(skey, filedata) + + var sigdata []byte + sigdata = append(sigdata, header...) + sigdata = append(sigdata, keyNum...) + sigdata = append(sigdata, rawSig...) + + // Check that the trusted comment fits in one line + if commentHasManyLines(unTrustedComment) { + return errors.New("untrusted comment must fit on a single line") + } + + if unTrustedComment == "" { + unTrustedComment = "verify with " + input + ".pub" + } + out.WriteString(fmt.Sprintf("untrusted comment: %s\n%s\n", unTrustedComment, base64.StdEncoding.EncodeToString(sigdata))) + + // Add the trusted comment if unavailable + if trustedComment == "" { + trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix()) + } + + // Check that the trusted comment fits in one line + if commentHasManyLines(trustedComment) { + return errors.New("trusted comment must fit on a single line") + } + + var sigAndComment []byte + sigAndComment = append(sigAndComment, rawSig...) + sigAndComment = append(sigAndComment, []byte(trustedComment)...) + out.WriteString(fmt.Sprintf("trusted comment: %s\n%s\n", trustedComment, base64.StdEncoding.EncodeToString(ed25519.Sign(skey, sigAndComment)))) + + return nil +} diff --git a/crypto/signify/signify_fuzz.go b/crypto/signify/signify_fuzz.go new file mode 100644 index 0000000000..d1bcf356a4 --- /dev/null +++ b/crypto/signify/signify_fuzz.go @@ -0,0 +1,148 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build gofuzz + +package signify + +import ( + "bufio" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "runtime" + + fuzz "github.com/google/gofuzz" + "github.com/jedisct1/go-minisign" +) + +func Fuzz(data []byte) int { + if len(data) < 32 { + return -1 + } + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + panic(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + testSecKey, testPubKey := createKeyPair() + // Create message + tmpFile.Write(data) + if err = tmpFile.Close(); err != nil { + panic(err) + } + // Fuzz comments + var untrustedComment string + var trustedComment string + f := fuzz.NewFromGoFuzz(data) + f.Fuzz(&untrustedComment) + f.Fuzz(&trustedComment) + fmt.Printf("untrusted: %v\n", untrustedComment) + fmt.Printf("trusted: %v\n", trustedComment) + + err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, untrustedComment, trustedComment) + if err != nil { + panic(err) + } + defer os.Remove(tmpFile.Name() + ".sig") + + signify := "signify" + path := os.Getenv("SIGNIFY") + if path != "" { + signify = path + } + + _, err := exec.LookPath(signify) + if err != nil { + panic(err) + } + + // Write the public key into the file to pass it as + // an argument to signify-openbsd + pubKeyFile, err := ioutil.TempFile("", "") + if err != nil { + panic(err) + } + defer os.Remove(pubKeyFile.Name()) + defer pubKeyFile.Close() + pubKeyFile.WriteString("untrusted comment: signify public key\n") + pubKeyFile.WriteString(testPubKey) + pubKeyFile.WriteString("\n") + + cmd := exec.Command(signify, "-V", "-p", pubKeyFile.Name(), "-x", tmpFile.Name()+".sig", "-m", tmpFile.Name()) + if output, err := cmd.CombinedOutput(); err != nil { + panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output)) + } + + // Verify the signature using a golang library + sig, err := minisign.NewSignatureFromFile(tmpFile.Name() + ".sig") + if err != nil { + panic(err) + } + + pKey, err := minisign.NewPublicKey(testPubKey) + if err != nil { + panic(err) + } + + valid, err := pKey.VerifyFromFile(tmpFile.Name(), sig) + if err != nil { + panic(err) + } + if !valid { + panic("invalid signature") + } + return 1 +} + +func getKey(fileS string) (string, error) { + file, err := os.Open(fileS) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + // Discard the first line + scanner.Scan() + scanner.Scan() + return scanner.Text(), scanner.Err() +} + +func createKeyPair() (string, string) { + // Create key and put it in correct format + tmpKey, err := ioutil.TempFile("", "") + defer os.Remove(tmpKey.Name()) + defer os.Remove(tmpKey.Name() + ".pub") + defer os.Remove(tmpKey.Name() + ".sec") + cmd := exec.Command("signify", "-G", "-n", "-p", tmpKey.Name()+".pub", "-s", tmpKey.Name()+".sec") + if output, err := cmd.CombinedOutput(); err != nil { + panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output)) + } + secKey, err := getKey(tmpKey.Name() + ".sec") + if err != nil { + panic(err) + } + pubKey, err := getKey(tmpKey.Name() + ".pub") + if err != nil { + panic(err) + } + return secKey, pubKey +} diff --git a/crypto/signify/signify_test.go b/crypto/signify/signify_test.go new file mode 100644 index 0000000000..af77eaf227 --- /dev/null +++ b/crypto/signify/signify_test.go @@ -0,0 +1,154 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// signFile reads the contents of an input file and signs it (in armored format) +// with the key provided, placing the signature into the output file. + +package signify + +import ( + "io/ioutil" + "math/rand" + "os" + "testing" + "time" + + "github.com/jedisct1/go-minisign" +) + +var ( + testSecKey = "RWRCSwAAAABVN5lr2JViGBN8DhX3/Qb/0g0wBdsNAR/APRW2qy9Fjsfr12sK2cd3URUFis1jgzQzaoayK8x4syT4G3Gvlt9RwGIwUYIQW/0mTeI+ECHu1lv5U4Wa2YHEPIesVPyRm5M=" + testPubKey = "RWTAPRW2qy9FjsBiMFGCEFv9Jk3iPhAh7tZb+VOFmtmBxDyHrFT8kZuT" +) + +func TestSignify(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + rand.Seed(time.Now().UnixNano()) + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "clé", "croissants") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name() + ".sig") + + // Verify the signature using a golang library + sig, err := minisign.NewSignatureFromFile(tmpFile.Name() + ".sig") + if err != nil { + t.Fatal(err) + } + + pKey, err := minisign.NewPublicKey(testPubKey) + if err != nil { + t.Fatal(err) + } + + valid, err := pKey.VerifyFromFile(tmpFile.Name(), sig) + if err != nil { + t.Fatal(err) + } + if !valid { + t.Fatal("invalid signature") + } +} + +func TestSignifyTrustedCommentTooManyLines(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + rand.Seed(time.Now().UnixNano()) + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "crois\nsants") + if err == nil || err.Error() == "" { + t.Fatalf("should have errored on a multi-line trusted comment, got %v", err) + } + defer os.Remove(tmpFile.Name() + ".sig") +} + +func TestSignifyTrustedCommentTooManyLinesLF(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + rand.Seed(time.Now().UnixNano()) + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "crois\rsants", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name() + ".sig") +} + +func TestSignifyTrustedCommentEmpty(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + rand.Seed(time.Now().UnixNano()) + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name() + ".sig") +} diff --git a/go.mod b/go.mod old mode 100755 new mode 100644 index ae1cf64aaf..9a35f6447f --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/huin/goupnp v1.0.0 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 + github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 github.com/kr/pretty v0.1.0 // indirect diff --git a/go.sum b/go.sum old mode 100755 new mode 100644 index 10bec96411..dedae7bc7a --- a/go.sum +++ b/go.sum @@ -123,6 +123,8 @@ github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 h1:FSeK4fZCo github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U= +github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 h1:F/iKcka0K2LgnKy/fgSBf235AETtm1n1TvBzqu40LE0= @@ -214,6 +216,7 @@ github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8 github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= From b71334ac3de38338e618aaf8ea6b4a884d2d80f5 Mon Sep 17 00:00:00 2001 From: Kristofer Peterson Date: Sun, 29 Nov 2020 12:43:15 +0000 Subject: [PATCH 033/235] accounts, signer: fix Ledger Live account derivation path (clef) (#21757) * signer/core/api: fix derivation of ledger live accounts For ledger hardware wallets, change account iteration as follows: - ledger legacy: m/44'/60'/0'/X; for 0<=X<5 - ledger live: m/44'/60'/0'/0/X; for 0<=X<5 - ledger legacy: m/44'/60'/0'/X; for 0<=X<10 - ledger live: m/44'/60'/X'/0/0; for 0<=X<10 Non-ledger derivation is unchanged and remains as: - non-ledger: m/44'/60'/0'/0/X; for 0<=X<10 * signer/core/api: derive ten default paths for all hardware wallets, plus ten legacy and ten live paths for ledger wallets * signer/core/api: as .../0'/0/0 already included by default paths, do not include it again with ledger live paths * accounts, signer: implement path iterators for hd wallets Co-authored-by: Martin Holst Swende --- accounts/hd.go | 28 +++++++++++++ accounts/hd_test.go | 39 ++++++++++++++++++ signer/core/api.go | 97 +++++++++++++++++++++++---------------------- 3 files changed, 117 insertions(+), 47 deletions(-) diff --git a/accounts/hd.go b/accounts/hd.go index 75c4761106..54acea3b26 100644 --- a/accounts/hd.go +++ b/accounts/hd.go @@ -150,3 +150,31 @@ func (path *DerivationPath) UnmarshalJSON(b []byte) error { *path, err = ParseDerivationPath(dp) return err } + +// DefaultIterator creates a BIP-32 path iterator, which progresses by increasing the last component: +// i.e. m/44'/60'/0'/0/0, m/44'/60'/0'/0/1, m/44'/60'/0'/0/2, ... m/44'/60'/0'/0/N. +func DefaultIterator(base DerivationPath) func() DerivationPath { + path := make(DerivationPath, len(base)) + copy(path[:], base[:]) + // Set it back by one, so the first call gives the first result + path[len(path)-1]-- + return func() DerivationPath { + path[len(path)-1]++ + return path + } +} + +// LedgerLiveIterator creates a bip44 path iterator for Ledger Live. +// Ledger Live increments the third component rather than the fifth component +// i.e. m/44'/60'/0'/0/0, m/44'/60'/1'/0/0, m/44'/60'/2'/0/0, ... m/44'/60'/N'/0/0. +func LedgerLiveIterator(base DerivationPath) func() DerivationPath { + path := make(DerivationPath, len(base)) + copy(path[:], base[:]) + // Set it back by one, so the first call gives the first result + path[2]-- + return func() DerivationPath { + // ledgerLivePathIterator iterates on the third component + path[2]++ + return path + } +} diff --git a/accounts/hd_test.go b/accounts/hd_test.go index 3156a487ee..0743bbe666 100644 --- a/accounts/hd_test.go +++ b/accounts/hd_test.go @@ -17,6 +17,7 @@ package accounts import ( + "fmt" "reflect" "testing" ) @@ -77,3 +78,41 @@ func TestHDPathParsing(t *testing.T) { } } } + +func testDerive(t *testing.T, next func() DerivationPath, expected []string) { + t.Helper() + for i, want := range expected { + if have := next(); fmt.Sprintf("%v", have) != want { + t.Errorf("step %d, have %v, want %v", i, have, want) + } + } +} + +func TestHdPathIteration(t *testing.T) { + testDerive(t, DefaultIterator(DefaultBaseDerivationPath), + []string{ + "m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1", + "m/44'/60'/0'/0/2", "m/44'/60'/0'/0/3", + "m/44'/60'/0'/0/4", "m/44'/60'/0'/0/5", + "m/44'/60'/0'/0/6", "m/44'/60'/0'/0/7", + "m/44'/60'/0'/0/8", "m/44'/60'/0'/0/9", + }) + + testDerive(t, DefaultIterator(LegacyLedgerBaseDerivationPath), + []string{ + "m/44'/60'/0'/0", "m/44'/60'/0'/1", + "m/44'/60'/0'/2", "m/44'/60'/0'/3", + "m/44'/60'/0'/4", "m/44'/60'/0'/5", + "m/44'/60'/0'/6", "m/44'/60'/0'/7", + "m/44'/60'/0'/8", "m/44'/60'/0'/9", + }) + + testDerive(t, LedgerLiveIterator(DefaultBaseDerivationPath), + []string{ + "m/44'/60'/0'/0/0", "m/44'/60'/1'/0/0", + "m/44'/60'/2'/0/0", "m/44'/60'/3'/0/0", + "m/44'/60'/4'/0/0", "m/44'/60'/5'/0/0", + "m/44'/60'/6'/0/0", "m/44'/60'/7'/0/0", + "m/44'/60'/8'/0/0", "m/44'/60'/9'/0/0", + }) +} diff --git a/signer/core/api.go b/signer/core/api.go index 43926a75ff..7595d2d484 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -322,62 +322,65 @@ func (api *SignerAPI) openTrezor(url accounts.URL) { // startUSBListener starts a listener for USB events, for hardware wallet interaction func (api *SignerAPI) startUSBListener() { - events := make(chan accounts.WalletEvent, 16) + eventCh := make(chan accounts.WalletEvent, 16) am := api.am - am.Subscribe(events) - go func() { + am.Subscribe(eventCh) + // Open any wallets already attached + for _, wallet := range am.Wallets() { + if err := wallet.Open(""); err != nil { + log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) + if err == usbwallet.ErrTrezorPINNeeded { + go api.openTrezor(wallet.URL()) + } + } + } + go api.derivationLoop(eventCh) +} - // Open any wallets already attached - for _, wallet := range am.Wallets() { - if err := wallet.Open(""); err != nil { - log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) +// derivationLoop listens for wallet events +func (api *SignerAPI) derivationLoop(events chan accounts.WalletEvent) { + // Listen for wallet event till termination + for event := range events { + switch event.Kind { + case accounts.WalletArrived: + if err := event.Wallet.Open(""); err != nil { + log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) if err == usbwallet.ErrTrezorPINNeeded { - go api.openTrezor(wallet.URL()) + go api.openTrezor(event.Wallet.URL()) } } - } - // Listen for wallet event till termination - for event := range events { - switch event.Kind { - case accounts.WalletArrived: - if err := event.Wallet.Open(""); err != nil { - log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) - if err == usbwallet.ErrTrezorPINNeeded { - go api.openTrezor(event.Wallet.URL()) + case accounts.WalletOpened: + status, _ := event.Wallet.Status() + log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) + var derive = func(limit int, next func() accounts.DerivationPath) { + // Derive first N accounts, hardcoded for now + for i := 0; i < limit; i++ { + path := next() + if acc, err := event.Wallet.Derive(path, true); err != nil { + log.Warn("Account derivation failed", "error", err) + } else { + log.Info("Derived account", "address", acc.Address, "path", path) } } - case accounts.WalletOpened: - status, _ := event.Wallet.Status() - log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) - var derive = func(numToDerive int, base accounts.DerivationPath) { - // Derive first N accounts, hardcoded for now - var nextPath = make(accounts.DerivationPath, len(base)) - copy(nextPath[:], base[:]) - - for i := 0; i < numToDerive; i++ { - acc, err := event.Wallet.Derive(nextPath, true) - if err != nil { - log.Warn("Account derivation failed", "error", err) - } else { - log.Info("Derived account", "address", acc.Address, "path", nextPath) - } - nextPath[len(nextPath)-1]++ - } - } - if event.Wallet.URL().Scheme == "ledger" { - log.Info("Deriving ledger default paths") - derive(numberOfAccountsToDerive/2, accounts.DefaultBaseDerivationPath) - log.Info("Deriving ledger legacy paths") - derive(numberOfAccountsToDerive/2, accounts.LegacyLedgerBaseDerivationPath) - } else { - derive(numberOfAccountsToDerive, accounts.DefaultBaseDerivationPath) - } - case accounts.WalletDropped: - log.Info("Old wallet dropped", "url", event.Wallet.URL()) - event.Wallet.Close() } + log.Info("Deriving default paths") + derive(numberOfAccountsToDerive, accounts.DefaultIterator(accounts.DefaultBaseDerivationPath)) + if event.Wallet.URL().Scheme == "ledger" { + log.Info("Deriving ledger legacy paths") + derive(numberOfAccountsToDerive, accounts.DefaultIterator(accounts.LegacyLedgerBaseDerivationPath)) + log.Info("Deriving ledger live paths") + // For ledger live, since it's based off the same (DefaultBaseDerivationPath) + // as one we've already used, we need to step it forward one step to avoid + // hitting the same path again + nextFn := accounts.LedgerLiveIterator(accounts.DefaultBaseDerivationPath) + nextFn() + derive(numberOfAccountsToDerive, nextFn) + } + case accounts.WalletDropped: + log.Info("Old wallet dropped", "url", event.Wallet.URL()) + event.Wallet.Close() } - }() + } } // List returns the set of wallet this signer manages. Each wallet can contain From 566cb4c5f098a132d485a375cb7a02883c88a48f Mon Sep 17 00:00:00 2001 From: Pascal Dierich Date: Mon, 30 Nov 2020 09:03:24 +0100 Subject: [PATCH 034/235] accounts/keystore: add missing function doc for SignText (#21914) Co-authored-by: Pascal Dierich --- accounts/keystore/wallet.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go index fe9d2fe49f..1066095f6d 100644 --- a/accounts/keystore/wallet.go +++ b/accounts/keystore/wallet.go @@ -58,7 +58,7 @@ func (w *keystoreWallet) Open(passphrase string) error { return nil } func (w *keystoreWallet) Close() error { return nil } // Accounts implements accounts.Wallet, returning an account list consisting of -// a single account that the plain kestore wallet contains. +// a single account that the plain keystore wallet contains. func (w *keystoreWallet) Accounts() []accounts.Account { return []accounts.Account{w.account} } @@ -93,12 +93,12 @@ func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte return w.keystore.SignHash(account, hash) } -// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed. func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { return w.signHash(account, crypto.Keccak256(data)) } -// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed +// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed. func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { // Make sure the requested account is contained within if !w.Contains(account) { @@ -108,12 +108,14 @@ func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passph return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data)) } +// SignText implements accounts.Wallet, attempting to sign the hash of +// the given text with the given account. func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) { return w.signHash(account, accounts.TextHash(text)) } // SignTextWithPassphrase implements accounts.Wallet, attempting to sign the -// given hash with the given account using passphrase as extra authentication. +// hash of the given text with the given account using passphrase as extra authentication. func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { // Make sure the requested account is contained within if !w.Contains(account) { From aba0c234c29c72860c369ec97553716a3fad11cd Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 30 Nov 2020 14:43:20 +0100 Subject: [PATCH 035/235] cmd/geth: make tests run quicker + use less memory and disk (#21919) --- cmd/geth/accountcmd_test.go | 59 +++++++++++------------ cmd/geth/consolecmd_test.go | 93 ++++++++++++++++--------------------- cmd/geth/dao_test.go | 4 +- cmd/geth/genesis_test.go | 2 +- cmd/geth/les_test.go | 2 +- 5 files changed, 71 insertions(+), 89 deletions(-) diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index 2f15915b08..e27adb6916 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -43,13 +43,13 @@ func tmpDatadirWithKeystore(t *testing.T) string { } func TestAccountListEmpty(t *testing.T) { - geth := runGeth(t, "account", "list") + geth := runGeth(t, "--nousb", "account", "list") geth.ExpectExit() } func TestAccountList(t *testing.T) { datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "account", "list", "--datadir", datadir) + geth := runGeth(t, "--nousb", "account", "list", "--datadir", datadir) defer geth.ExpectExit() if runtime.GOOS == "windows" { geth.Expect(` @@ -138,7 +138,7 @@ Fatal: Passwords do not match func TestAccountUpdate(t *testing.T) { datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "account", "update", + geth := runGeth(t, "--nousb", "account", "update", "--datadir", datadir, "--lightkdf", "f466859ead1932d743d622cb74fc058882e8648a") defer geth.ExpectExit() @@ -153,7 +153,7 @@ Repeat password: {{.InputLine "foobar2"}} } func TestWalletImport(t *testing.T) { - geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") + geth := runGeth(t, "--nousb", "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.ExpectExit() geth.Expect(` !! Unsupported terminal, password will be echoed. @@ -168,7 +168,7 @@ Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f} } func TestWalletImportBadPassword(t *testing.T) { - geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") + geth := runGeth(t, "--nousb", "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.ExpectExit() geth.Expect(` !! Unsupported terminal, password will be echoed. @@ -178,11 +178,8 @@ Fatal: could not decrypt key with given password } func TestUnlockFlag(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "256", "--ipcdisable", - "--datadir", datadir, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", - "js", "testdata/empty.js") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") geth.Expect(` Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 !! Unsupported terminal, password will be echoed. @@ -202,10 +199,9 @@ Password: {{.InputLine "foobar"}} } func TestUnlockFlagWrongPassword(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--datadir", datadir, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") + defer geth.ExpectExit() geth.Expect(` Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 @@ -221,10 +217,9 @@ Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could // https://github.com/ethereum/go-ethereum/issues/1785 func TestUnlockFlagMultiIndex(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--datadir", datadir, "--unlock", "0,2", "js", "testdata/empty.js") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--unlock", "0,2", "js", "testdata/empty.js") + geth.Expect(` Unlocking account 0 | Attempt 1/3 !! Unsupported terminal, password will be echoed. @@ -247,11 +242,9 @@ Password: {{.InputLine "foobar"}} } func TestUnlockFlagPasswordFile(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--datadir", datadir, "--password", "testdata/passwords.txt", "--unlock", "0,2", - "js", "testdata/empty.js") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--password", "testdata/passwords.txt", "--unlock", "0,2", "js", "testdata/empty.js") + geth.ExpectExit() wantMessages := []string{ @@ -267,10 +260,9 @@ func TestUnlockFlagPasswordFile(t *testing.T) { } func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--datadir", datadir, "--password", "testdata/wrong-passwords.txt", "--unlock", "0,2") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--password", + "testdata/wrong-passwords.txt", "--unlock", "0,2") defer geth.ExpectExit() geth.Expect(` Fatal: Failed to unlock account 0 (could not decrypt key with given password) @@ -279,9 +271,9 @@ Fatal: Failed to unlock account 0 (could not decrypt key with given password) func TestUnlockFlagAmbiguous(t *testing.T) { store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--keystore", store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--keystore", + store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") defer geth.ExpectExit() @@ -317,9 +309,10 @@ In order to avoid this warning, you need to remove the following duplicate key f func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) { store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--keystore", store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--keystore", + store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + defer geth.ExpectExit() // Helper for the expect template, returns absolute keystore path. diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 913b060361..b0555c45d7 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -35,16 +35,25 @@ const ( httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0" ) +// spawns geth with the given command line args, using a set of flags to minimise +// memory and disk IO. If the args don't set --datadir, the +// child g gets a temporary data directory. +func runMinimalGeth(t *testing.T, args ...string) *testgeth { + // --ropsten to make the 'writing genesis to disk' faster (no accounts) + // --networkid=1337 to avoid cache bump + // --syncmode=full to avoid allocating fast sync bloom + allArgs := []string{"--ropsten", "--nousb", "--networkid", "1337", "--syncmode=full", "--port", "0", + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--cache", "64"} + return runGeth(t, append(allArgs, args...)...) +} + // Tests that a node embedded within a console can be started up properly and // then terminated by closing the input stream. func TestConsoleWelcome(t *testing.T) { coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" // Start a geth console, make sure it's cleaned up and terminate the console - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, - "console") + geth := runMinimalGeth(t, "--etherbase", coinbase, "console") // Gather all the infos the welcome message needs to contain geth.SetTemplateFunc("goos", func() string { return runtime.GOOS }) @@ -73,10 +82,13 @@ To exit, press ctrl-d } // Tests that a console can be attached to a running node via various means. -func TestIPCAttachWelcome(t *testing.T) { +func TestAttachWelcome(t *testing.T) { + var ( + ipc string + httpPort string + wsPort string + ) // Configure the instance for IPC attachment - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" - var ipc string if runtime.GOOS == "windows" { ipc = `\\.\pipe\geth` + strconv.Itoa(trulyRandInt(100000, 999999)) } else { @@ -84,51 +96,28 @@ func TestIPCAttachWelcome(t *testing.T) { defer os.RemoveAll(ws) ipc = filepath.Join(ws, "geth.ipc") } - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--ipcpath", ipc) - - defer func() { - geth.Interrupt() - geth.ExpectExit() - }() - - waitForEndpoint(t, ipc, 3*time.Second) - testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs) - -} - -func TestHTTPAttachWelcome(t *testing.T) { - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" - port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--http", "--http.port", port) - defer func() { - geth.Interrupt() - geth.ExpectExit() - }() - - endpoint := "http://127.0.0.1:" + port - waitForEndpoint(t, endpoint, 3*time.Second) - testAttachWelcome(t, geth, endpoint, httpAPIs) -} - -func TestWSAttachWelcome(t *testing.T) { - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" - port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P - - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--ws", "--ws.port", port) - defer func() { - geth.Interrupt() - geth.ExpectExit() - }() - - endpoint := "ws://127.0.0.1:" + port - waitForEndpoint(t, endpoint, 3*time.Second) - testAttachWelcome(t, geth, endpoint, httpAPIs) + // And HTTP + WS attachment + p := trulyRandInt(1024, 65533) // Yeah, sometimes this will fail, sorry :P + httpPort = strconv.Itoa(p) + wsPort = strconv.Itoa(p + 1) + geth := runMinimalGeth(t, "--etherbase", "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182", + "--ipcpath", ipc, + "--http", "--http.port", httpPort, + "--ws", "--ws.port", wsPort) + t.Run("ipc", func(t *testing.T) { + waitForEndpoint(t, ipc, 3*time.Second) + testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs) + }) + t.Run("http", func(t *testing.T) { + endpoint := "http://127.0.0.1:" + httpPort + waitForEndpoint(t, endpoint, 3*time.Second) + testAttachWelcome(t, geth, endpoint, httpAPIs) + }) + t.Run("ws", func(t *testing.T) { + endpoint := "ws://127.0.0.1:" + wsPort + waitForEndpoint(t, endpoint, 3*time.Second) + testAttachWelcome(t, geth, endpoint, httpAPIs) + }) } func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) { diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index 6c36771e97..df7f14fdb8 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -115,10 +115,10 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil { t.Fatalf("test %d: failed to write genesis file: %v", test, err) } - runGeth(t, "--datadir", datadir, "init", json).WaitExit() + runGeth(t, "--datadir", datadir, "--nousb", "--networkid", "1337", "init", json).WaitExit() } else { // Force chain initialization - args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} + args := []string{"--port", "0", "--nousb", "--networkid", "1337", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...).WaitExit() } // Retrieve the DAO config flag from the database diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go index ee3991acd1..0651c32cad 100644 --- a/cmd/geth/genesis_test.go +++ b/cmd/geth/genesis_test.go @@ -84,7 +84,7 @@ func TestCustomGenesis(t *testing.T) { runGeth(t, "--nousb", "--datadir", datadir, "init", json).WaitExit() // Query the custom genesis block - geth := runGeth(t, "--nousb", + geth := runGeth(t, "--nousb", "--networkid", "1337", "--syncmode=full", "--datadir", datadir, "--maxpeers", "0", "--port", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--exec", tt.query, "console") diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index e4fc2d4d01..d2f63ac7bd 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -159,7 +159,7 @@ func initGeth(t *testing.T) string { func startLightServer(t *testing.T) *gethrpc { datadir := initGeth(t) t.Logf("Importing keys to geth") - runGeth(t, "--nousb", "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv").WaitExit() + runGeth(t, "--nousb", "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv", "--lightkdf").WaitExit() account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105" server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1", "--verbosity=4") return server From a1ddd9e1d313553b0f584d5d3aeb2a4026a1b0c4 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 30 Nov 2020 15:23:48 +0100 Subject: [PATCH 036/235] cmd/devp2p/internal/ethtest: add transaction tests (#21857) --- cmd/devp2p/internal/ethtest/chain_test.go | 2 +- cmd/devp2p/internal/ethtest/suite.go | 38 +++- .../internal/ethtest/testdata/chain.rlp | Bin 0 -> 1585630 bytes .../ethtest/testdata/fullchain.rlp.gz | Bin 253004 -> 0 bytes .../internal/ethtest/testdata/genesis.json | 2 +- .../internal/ethtest/testdata/halfchain.rlp | Bin 0 -> 527009 bytes .../ethtest/testdata/halfchain.rlp.gz | Bin 126219 -> 0 bytes cmd/devp2p/internal/ethtest/transaction.go | 180 ++++++++++++++++++ cmd/devp2p/internal/ethtest/types.go | 45 +++-- 9 files changed, 243 insertions(+), 24 deletions(-) create mode 100644 cmd/devp2p/internal/ethtest/testdata/chain.rlp delete mode 100644 cmd/devp2p/internal/ethtest/testdata/fullchain.rlp.gz create mode 100644 cmd/devp2p/internal/ethtest/testdata/halfchain.rlp delete mode 100644 cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz create mode 100644 cmd/devp2p/internal/ethtest/transaction.go diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go index 604b908687..ac3907ce8d 100644 --- a/cmd/devp2p/internal/ethtest/chain_test.go +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -73,7 +73,7 @@ func TestEthProtocolNegotiation(t *testing.T) { // TestChain_GetHeaders tests whether the test suite can correctly // respond to a GetBlockHeaders request from a node. func TestChain_GetHeaders(t *testing.T) { - chainFile, err := filepath.Abs("./testdata/fullchain.rlp.gz") + chainFile, err := filepath.Abs("./testdata/chain.rlp") if err != nil { t.Fatal(err) } diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 0348751f88..5d0cdda720 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -22,6 +22,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" @@ -37,6 +38,8 @@ var pretty = spew.ConfigState{ SortKeys: true, } +var timeout = 20 * time.Second + // Suite represents a structure used to test the eth // protocol of a node(s). type Suite struct { @@ -70,6 +73,8 @@ func (s *Suite) AllTests() []utesting.Test { {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, + {Name: "TestTransactions", Fn: s.TestTransaction}, + {Name: "TestMaliciousTransactions", Fn: s.TestMaliciousTx}, } } @@ -115,7 +120,6 @@ func (s *Suite) TestMaliciousStatus(t *utesting.T) { default: t.Fatalf("expected status, got: %#v ", msg) } - timeout := 20 * time.Second // wait for disconnect switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *Disconnect: @@ -151,7 +155,6 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - timeout := 20 * time.Second switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *BlockHeaders: headers := msg @@ -181,7 +184,6 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - timeout := 20 * time.Second switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *BlockBodies: t.Logf("received %d block bodies", len(*msg)) @@ -257,7 +259,7 @@ func (s *Suite) TestMaliciousHandshake(t *utesting.T) { }, } for i, handshake := range handshakes { - fmt.Printf("Testing malicious handshake %v\n", i) + t.Logf("Testing malicious handshake %v\n", i) // Init the handshake if err := conn.Write(handshake); err != nil { t.Fatalf("could not write to connection: %v", err) @@ -307,13 +309,12 @@ func (s *Suite) TestLargeAnnounce(t *utesting.T) { } for i, blockAnnouncement := range blocks[0:3] { - fmt.Printf("Testing malicious announcement: %v\n", i) + t.Logf("Testing malicious announcement: %v\n", i) sendConn := s.setupConnection(t) if err := sendConn.Write(blockAnnouncement); err != nil { t.Fatalf("could not write to connection: %v", err) } // Invalid announcement, check that peer disconnected - timeout := 20 * time.Second switch msg := sendConn.ReadAndServe(s.chain, timeout).(type) { case *Disconnect: case *Error: @@ -398,3 +399,28 @@ func (s *Suite) dial() (*Conn, error) { return &conn, nil } + +func (s *Suite) TestTransaction(t *utesting.T) { + tests := []*types.Transaction{ + getNextTxFromChain(t, s), + unknownTx(t, s), + } + for i, tx := range tests { + t.Logf("Testing tx propagation: %v\n", i) + sendSuccessfulTx(t, s, tx) + } +} + +func (s *Suite) TestMaliciousTx(t *utesting.T) { + tests := []*types.Transaction{ + getOldTxFromChain(t, s), + invalidNonceTx(t, s), + hugeAmount(t, s), + hugeGasPrice(t, s), + hugeData(t, s), + } + for i, tx := range tests { + t.Logf("Testing malicious tx propagation: %v\n", i) + sendFailingTx(t, s, tx) + } +} diff --git a/cmd/devp2p/internal/ethtest/testdata/chain.rlp b/cmd/devp2p/internal/ethtest/testdata/chain.rlp new file mode 100644 index 0000000000000000000000000000000000000000..5ebc2f3bb788825e2c5fc48ecfb85a697f016506 GIT binary patch literal 1585630 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%0DTvBvUquo^vS7 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

    NeOABySp1{ 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%0Ai}13c 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

    nbUZX{hpjpZPUAB$I%G; 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 z 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 ?*P;xLSmft+f-sD>q#W*}5Tm^{-48pv7c-AM^n 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�znTxNLIBD8&TY8B41|8NenD#Qxb9AN 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�JFOi#k#23}q6?4pV)2UaJ1Zs{BY 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 diff --git a/cmd/devp2p/internal/ethtest/testdata/genesis.json b/cmd/devp2p/internal/ethtest/testdata/genesis.json index ed78488b67..d8b5d22502 100644 --- a/cmd/devp2p/internal/ethtest/testdata/genesis.json +++ b/cmd/devp2p/internal/ethtest/testdata/genesis.json @@ -17,7 +17,7 @@ "coinbase": "0x0000000000000000000000000000000000000000", "alloc": { "71562b71999873db5b286df957af199ec94617f7": { - "balance": "0xffffffff" + "balance": "0xffffffffffffffffffffffffff" } }, "number": "0x0", diff --git a/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp b/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp new file mode 100644 index 0000000000000000000000000000000000000000..1a820734e10c39ab39cad27671c4fcb6b9305943 GIT binary patch literal 527009 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%0DTvBvUquo^vS7 zGh@~HRoVN}X^{A=u_`%(ybnZU{76Asf3}rNmtkwo1uGN5GC-BCiEV(BY*qao{I1FM zl%t=A(k#kZD~fT~(I*9eLzDZax)bvDy53dTfboKO!XhZ8Z518S+q^wDtl>$Gvl4$$ z@tvu>zego9eU^@l&~@b&Y{eVMjvz<;l@{_x8X(lz@9Jn%bl^^v$J-htVvl5v*9erq zRHu;rXfTqJ%8*&*qOD|ofQ~|+IlJwSd2Ys7Wn0=Q)iJQ{gOJk;IpQyX z&}#tQSMN7_%!`g56o+_(+}d?#-U_2&eDFy?giz8NIH^n-lay5p^mmCTX8B0LdjnuI zc=4P--q|GnZgMN;DLOrae|bACTs^BvGwBcdxG89-97$K;yO>(wL=ZBA&9N-!WT(s@ zI?!&)zp0?F_ZBfsybtkU5@G=CXcK(283qPmLU1+Xq&zyXE<+K8wO`F^N$8>dhRc|U zzmCb3a#`@ldSx(&HPAGP7{_n8pza31d#ynu)Gu{Lw|_~KcOuC5Z_eFTL`Mo}1dhK1 zYW?CEf^rDx-76;gLN-OL^C2%6p9sB>-=}Fk-Lak@mny&}xisN?%)r_G&a1 z;P@pq>E)aC9oU>R+`q8>9Rk9y0Tf?->g01OSXxCTNdEd;h2{t0+&39dDdV$;jJNk$ zie`M2^4NfQO1?i;<*?t~Q~(dRz^t<=9gXP`{|HNL8f&Z;>CGB*-+Ar};~4^GF=$u3 z(TZX%8lQGPl!@;YkWY(JutgcLZpj}D&JTCTy%fc|3-JXGF#uKpz!f0k8oFjaF!#vG#&%dH3}XhPgTa=0La3Z`6d>W*+eBT zFX!S*sbMjU*3xr#pv}*F^n~J9kr|W~;QQA;IklGKF5!Xd`Hu;>5xzl;{(!nOwsfv; zBn?SI3yArJ7yttR1^@&Dcr1B!qRo!$QBKQdC?e0&`$znFOd%DDDZ1mlpzp4SC?;(Z zDz-q47)ks!Ch7+_04&bwJQw}@m_Tcbp)B9)SCJkqREgg{o14ND1Wt99-*!i&|%L(DJ402lx;fXHhAu~%o?Ws_9L=4+02 zRd1n`{HQ~S)(^N{L&!{+t!5dG8-uvT1+;qD+QO9QvwH&|ouaaeD_Kf)N3N9-r(I^U zi06?Tl~+^LHmBv?s(x>1I}mG^b`y?c^qzfBD_S@&ACO@qUz9dS>6YWC2=JdRzV{kp zejx_H0Du8RT?3fBI^R|V0X5_`569?Hw%y_UxaC-p+p)*wTri~^E>%gE_JskEQIh+q zJiE)%6@b6(CtVOngsAl}bCBx@ORHk0V+hY(A=F{NS-8WU5|f485Mgxr2I5@au&)bH%55;9L!rw}IvOm0TvOF5}Vqa`c( z_`d0D>kdd-gAAU<(wt8HHM(+nz>{BT8tu;kF2%*O^?NhTPopcZXC0gK28q*F+DT7- zJZO|)YK)|Mk?M6)sq)#*I3iz#8pjh#Y?obgjvX#I#Cz6{_E{YCk~1&0u$RKkm8vh3 z29TtYP#_i)hyk!_1FkkP;A&&)dUbt|eR^He>3GIWdVq$A%DPR1uKY_~tB0tCi=EtT zDO#RzKr&z?2y2$&`OR=cc=m3{S=!)29qv->|?hq~eocNFY?7F9}@$?=cd=Y0eks-2f28cD}Hh+mOHPNTqaq zKTmIMB2*SI$Gi~h^Uy_7X59wL3eY(kQ<7`&_*wNkR)NHLglNvim@;jIi>eIw$!z># z({_mYg%|(>00s~T24IGHb&c<*_Z+s71z(QW1RvtQO`&}6&u>3Zjo?r^1CNV+OMWyS zh&&-fn3jh2&zu7$rsiQBz0zs*ypMJ=YFUfgPZC{ITjbb&fkzlv?+dh{1hC(^-xkz| zj;b~6Upd>V5gDlcptPUgxosMQq0uyJVFY4+AqKzzfC0o`1JJ#?%HQd@+EFOP>2b9l zEuy|z6*qo4d*lAPEbf8+M57!k+b$44`Teu=Bietu4+a+=9OgMaaIif8$>JfoFyX-I z)>yHdqQ+!T|5Nac9$Ele6BqNx_X`ajj+-RWgU{-v;x*zzycbO>4}}ue&G=zwxxoDk z+usAuglhn=uKIlN(@VW+sxSjqi)juZq8(UXdK-T^RJwRtI~v{hlvN!KNSYM>OzW}p z?~@$hFOK1lK?%ziGphnYj;+rW2bkWECq>a6iPr#juR8rmKCdAG*$K8IIovBV zt9*=F_p#W_2}xM6%dvNlmK!b%7zZ}*wUp9mtLs6UYo*tNlF?{GlRlNc^5O!%DNw7g@+gb z8v?)plE47W#jhHV5u@x6Csg*}34i4zI~B6sSA3@I@R9fz`f}A}j*A7KBhZA7zqvVH zo8$(-CC3@FBs%|e}DA+xBcX- zoPyw-Zqx&fxFME&r{>&= z+t?tokx~rCxS35!wQo;bgZ^I)PhgvXSH6hNTqOfy1dPh=`Z<;dv_!%W) z`vO7t)YOa^HsiZn=NCdi)-4>X!jGaMzuQkr{X6X^qd>*0eseCSpY2Jl=x3R%HQrs! z9%^U_^QrtAJv>JECQuA=iahsP;k&esoktygaxU$&gal{Be2hu?x?f1ehKAQ$Q7&P0yO zl4%wYT2H<)wTdK24?7v=%FzGy_7`Mo7~txe0HGEhS3S|!u@p`8buLzvXX}Ut9g9?t zd6N!{M_&Uu>9QuPr-5id;^Vw0i$izxZ;nZr3^w{_3b5E|1sme)QVM87GOYGlF^uJ( zGQ5vyzL>TO87`WAoYEGTBGCIpEm%^ z_8!pQga0sAmfZc+=({+GAUjt(=TUKF&@tgo4XLCflodeCSmK_hD=+$tCY^q#Q<@;+ zmY?VO%Hy+6-x}sD*17Kx^9wNm1^^5o9Spz{?`mBX-&)U%((NaT$9JT~#aO?*$QB1oE7Wx`{qhez8V!OXWc))fat3e|8Co`Gpt&0{{k)aSg!u zYGtJJAr-1@ijJb&MA!i#h32-wB0I9E;vTcAh_)kSUM5CBgUlR@=RA8KZUFd2is;Wq z=U#X$SMk~()u5f(!;cqEV*D&8@7dUYnM?&`1#tHYDl3>2^h^G_JeI5?0i-THT+VqC%m#JkhAMa_jKH4W>g!lr77yzpP;0lm+ z4dCQzb;PXV3~4Ot&fF#*kGa$5w)s>SF2cUAL+0-BtqtKxsa^nYe@?0_uKtj70{}CQ z(O6(;t%d#ZP|#_L6m$AnRP}Si_V;31TGqSrk3^sZpn}4h-J_us`c$wJp0CReICfzb z+xt-XVG?Q5cWfs|Lx}l>7yttR29ON~V5NAq5bOJ`e3qA#q)z)^aYh|ML)<#-$b}I- z*~z{i?u%ZemlFf=kPnq2$x;n&0ALv7bzas?*l^VBHrqONPex zDg;UZr9>VnZKoszakdR8}VIm}H&UIzp;+)c*Zb>e9 zK;?67a`Iw-D5uEA#+kUAb-=W^pgn~$lmN~+*Gf>raR5Qpn+H#qD#R9}MnBj}%IrH^ z5Noa|F2d#ku7>UZ>+SC`z>6@z)iVJ?troA=W8W+(xwBUA2Km0muEd)d;#Z<`uSpRD zdLjw?_4fTb4OxLm>b%cm?@;|YA9DK`{tVk#8y@*@^O-9 z<%iLaUE}>w0>}u&-#?b-B+K3@T|`@GEoC+vj!B}+aI?OK8I{7fLvJks|)1H)eE+fi@X z5-u@TzK4bZwn=1KJ|UTrAf5E4Dr-JuL$;REhjHJ91bBnE18Q-XZr_h`on1ByL;*2o zeTdBoS^e~rkaAc|yq}sT-+%iSJCXG>xsR9}!F7GtbDy^eb<9)_w=veOBWQ*%K6~p+ z`4an8tNN#VQ|qm=X+e7t0DreBzdfMAyQTQO^!{+>rsHG|r6r$i)A=!xt(%}=B*ZZd zVgPK=0}pzjKjZ`l!orn`sGjkt~}lit;PWDjM{o8KT*b3L|K zkeMjDt?ASrlPMjd3;%qiVEd?UJAH&1HuVL$qCt-MTk~FC0~i$pfZAgtT$sf9`pweU zSF|OGhL~LoFSYbPI1eOBCj7`=R|iT8RVgv^b@bi<*yF<($LhQHmSRFo10{fzBOXV#?sBZUs};9OdpvLDXHhaON+btIBflw$*H=2sy-+2Iu7BPao2P^`Q=7sj{@w$q$I zV`$`N&{=6MJ(*{uP9AJ&VXI$&m|utiFaTfxdDj4(wE&>hIKa1fLu>e8oL4rK;%^w* zFu6IlU&^)W`t;2^ONR#lZ;dRSP({sd-T)9B@GLS?{#=T?=`wxj!lIAgAbR$p(#+U5 z&XEDDMffF@09r1xL&U}xdR_}I-(^kw?q{jr6M;ty#9bM+LwPVKoCX@h-B<2%NO!f;Zaa*pg2L3>ZBvop; zYUsA?T}oQ=Fn@CEHf_33nP^ z^g_b&nDAHb8qoDa{N`&wX!Z>NWzOscb^n!+@4owt^_;z|!kTO>DQBIk_&s+nMd>u7 zpaifTW=buq)WO6o+!5GNw(+?>=1YaFw^W`r8?`OG{ii61`Gpt&0{{k4a1Fp%8vsH+ z=(XO*GMIft!54svBZ^4jqk59WEF{kpD%buMy<7~4-@H*C>@pK{1HeX0G1Lj+26LmB}??Y2VNupXjj-Xu=n)m?~C6P=dL2%)DbG%I4B85+jwyV5t#avxq zH-nl#2x5LA2EYJ-0Tf;Xc)bDuxzss+#X_i}MOZ@s~o#|q(vP>p0x+Bs)^&;psWCCA8)I2 zA6LA=Zz>qEzca1>yxTBlCcTM6(3kMYQ2MeSVtye8zyN>&6kP*2PX~Y~rUf(^! zuYG>UQf}kHC0>=;!CFf?5%%NT@5Gy9fEt)h%XVQ4V^;tWtbVtjoE6y$o6?kS-}<<+ zJ#g?MgN0-GA{If&jL2$w^_R_e1SkQV9tZYvwQwv9XXG$E=fZpecv$+@DR}L3#lF$?(a8nmAnE z4VR0d!v{I3A%j@m03P{G0^iLu+u?5EQ+Z`S-)}!e;=}P$8uYGb9J3rbIROa9)js&r zX@SpE@1=e-X+j_{6q5}vbm1}2g7)oZXGuV)A)TkrVd@vk>PJUIfMRl_Fsc6jRV|S3 z_;NTe#6kiw09I+hm8Jw-X>6ZA1c0dMIUS$Y?(Fi`lSLnVyo|EZNf4O8cFJad6^g2M zvg`&7_(^6uae!rYGt`i1j;Z&YXMB3ia44Rat4{Dj4WEF#M*lfV4~?e!VLG&(r{wwK zC0?H-y~}K-j=8t3E}%K$g^&}z{(6yD{z>4$_h^Xug%|(>00vNc4Pe9Y>MGFXS8+ez z`xMM)mqyFxp~y8>nxvK6XY{5eK5O2yW7Psm4ALS#G>Tro0r22KXhYwwpMi1zog$ai zWpk@nHp{vG`{wn2Y~S&_1)yE==ve@P2ezT#o`SA>6c@>Zb6ys|$~$TycC9qQv)bNL zWDxTUF#rYt44@1Qz)my_0Alu#Ds?6BhPT_3!A}0+LbOC!Wo9E_d}h}8hTQ;zlM(2Z z%FM|da?X7NfFVbm!s`x;kvSLPPUfuj$Cq$2ZZ6Fc-G;a??h(tYMMHV4LD?;UT~{XZ z-b2&O#~i3-ZhE5V=-I`0*NgN0*HAx+Q;7M67yttR2JreCKy2UDQg>%7(&15u&)go` zDldDCnY_G|2%vaE)MvkDQsNd$8w~WNbT-GABFnu2Ag(0j_@wly9BD(a^WCp+*#)ih zUb}My2FryIAPsXHvOo#oRxNU+bYbw_N;!kfjl5wS1rMh^suhRwiC050PU0$GA?6oi z01N;aK>0O*84dub&4o23$M8~cq%e!loeR7h(Vm02sgHcnC<4|=se<`(>vB*d6#4o;p{9Vj58rTp3_b+UJ-)>f1 z0|-(FfJk&|OPucAr^$C7VPX9yr1c$v`A5yd7k2u$--u30{Cj}1>^SBH(QnvqD!|Em zz(6E#k3Z2zxN1B_kD@93>9V5W%~;?qz92q|}<9Ku`h{-T*+g;TH*VYD>6 z<(%|u&+QQdHKvE5GsG7-!~j?Y09Sy@YXHMI08kg#X$3937q91zc7C_~Oo+6l!z@?j z5(yG{S@{nueJP;W9SZZ(_m5a_0HomQEp~Cowgh~uA#iWmcPV{H8RB@KH&zaNyK`4R zm<&n)@5P2y8&U2sqv+t}o^UhATB5!sBcIowN;9~vBbR2W3^BhD17HBa0II+M9B5Vn zpdp~*bN9T!Xgt%39oINVE3uT8RRUADY4tDYI%gjd+<@fxaE6=;r@ybd{M&tU`ZQLj zg6Fj!F4}NX*Qx`xexdf;`ZiLVFd7B!KARzEPd!UFa0uml(LHK9_&HN8gl!EkorYrP z{#QrDqLqe@^G`XTSB0Vy&Y?yex%R|7YZ3J#;N@(Q_wEA1JF$TN9#sOO{8V3+1y&l22%(J|}gm@oYn5%j&fz?1LjG5Q_=K09dsF zSDUxj)u#RI>bzdUaMI2B(_-xDqU@&D+OS&M7~B(qdp7iKdzi0)rVc=CEzSDFL(=w} z;RffTN?7SU5LL}Lejr^czUEY!GhyK)-%e&c7TVM=jSeUQNPhGmdH(}vG>sW|o8a;E zyyT+$!EQ!j>Oe{?v+D6Fh7yttR22cYA;7G&`0KKy2+h+GSd?IO$pE^jn3>ZAT z^`fp;CRRf{G0=vO2peeJLGsc!S1I)dKv&s!Ur%n;n6;^IPgZ+wne=mhTNQLVA!8>? zpDn0C`3NNdvbU7s(XAi0KbnZcC1&TyoEOla3N@4sbC8*8h}#q0hL~T70Wbhy0JYZu zEW-hy3z_!!ADnTVGOfc0=xuwCrXoh17(Z~Qj$q?u?rJ!s0ntDq3bM~X{&SjJSjNZt zI~yVlxQaZl4Hwu5I+d*!Ih%uwKB?;@A$Vy++wEFz=brsfkNNn1yYVM6E;8&}#deDx zIOeu>Iu5oIeUsx5^9wNm1^^7;-8F#PBLE1&$!Aj15WsSmlrFF}FuAaaSCyXvFCxU* zvoz2XNk;=nB+w%d_q6Mu{T+49*MNVH>IVM^s|Jr z0?ehJ4>M`cmgE{Xn#b)#eh(z$kF@r&My13)%5EPIy?~fshygGFU;uSs08VH?0O;)y z&GO$5h7W#WkBdBIfGcrKeu(l~j)SQi*A)q`mh0)*)eu%O2 zfvbY3ZDvlgtFmIHLqiBqYciuJCL2lsPv2XVV;f;}Qd|^2k6gy~LP4@?A`=^F5O+*# zu9zeSLd-A302lx;fcMt`^!ct%bq{9oBI^6oJMOQGm)N@WMdJc*%MS|<`ObJT+gzM8oau%>hY~>VOPuWv zG>mVpjd|~h8e>!1bl86G6GdTv%pme3*qWt-m|utiFaTfx_16H(5CEVBb*6FF{pdR{ zUQ7>E-)CQPdD5Flq_eFfoVXzw60B$jubslhNFH!cm_f*Bg=TP}$C zg%|(>00z)-4d8qo0DAYVEMO8Ljql!4_1V+-=|xewheKs4(&tWG6v^=T-vB_DcXx(# z&c6QBfqFe$qbCGM{B0!J%_bRwCSV2FMO0lOrG?BA|C;4dAHddDeg z`<{>I z*DrsXJ^t4R0M|eH|Nbkk+CpA!G=ILr{rQ6cSGTXbRKmfLg7<(Ql=yv@=Jyxy2Dti8 z3kP@gG{0~CpSHiVj!j{JtAIbPb)Uw*~7%^naOX1VjXCHw@mN* zF_&#=CJp2@c9(xo3`BL;?(q1ZA`f=@Ou ztg&aWiq9I#ip1xq*tlA0qmz*}{>G=Q?CpcwuBRf(@$+aY2ERJiqeUR*7h(Vm0C-4h z0RwQ6YrR^B4~bVxG7UF}!CFD;(mqczzmw$l%K$GpZ#)yzhqunZ10NO>1q^0R|86_- zcTt4k*p%a{{DL!EcmCpHyy+p$4>^ z_=6P%r^}0I$*0-3^Spqh=7U=Io;BXP0nni|WQ1@OP;B2VeSc_KZJFAp9^unz$^662 z-nDwdA$TYOXs}3!B=6`_s}cL`;vmH?TevQS^uZ;pq9~*VjgxzCL(DJ402lx;fVOJ@ zYcl|lMabh)f>5cS7Nbw*OM9oZ1Gr+(JW~#J$^)P0?#C}<0^NPWGUxkK|ET~#p7ygt z+DBW@qTpp`#H3%GPgz*l5w_%p3jo>5E!JnD1VHkXyuUEDv9?kp>yitJ8?+p}cW+a3 zA}j*afjJa?!T@4^AqKzzfC03F0l11~UcD{Fl5>wgonEEvOCZk4o7>XJ&fF9Jw5E8U zE-bX5CdQUzyLm614wiSfbxx% zjYY(MSlS|4;Cy6bRjeYYI?xV_dHwn0{%%{dPbKhvr_ReZlX;^X08IS{^-Uvj?cqVb z%Ro%j#LQ=`?Y|Vr;NCOU2QL2T+lLZB25Vli0$Lqo)HAUp9<0+Xo0Iku{I`nXj!KhE zRk!DVK+G@102lx;fR1Yb3-(uM!Wb^;EH8&g#Xqw5F(V)!jP%3%+?ezW{?QhE_kL&Y z9iW)b?0&FqPR0!Yv7aGqmyU1m4f&sYkhX6nh8W`A3;`u5j zF+lsUhvCwnlrG$pBG(8g%9P~ecewF^h#_+c12}uiHvn=?MG4;&^@gUatHqq9ENwm( z#G$n-q)2L|om!pp+^B-G0z7@&6%1UUNDjm#q>_ypX;`?26U(l$=g$3bjEkQL_URY6H}018h_9@=Mq<8$x2 zTb9PSXx5)$KS`iHb@68JI|=73;CN99Wfh&4?rlq2g{1s37SYv3{#LXc0v$*;1k5J} zKKP=^Lqup}>WwD#HG9Q_pT_{n4 zj*XAMzq3mb=j?8@V?+`jVVq&+ZWj6&*h+`VW02p~N`5ti1bHC>e06lo+Oy`A7^y)*!ooF3|D0C-hiF^zn3hSYc zQI{v>#!^5FVwBhRS-a>rgI;lWik&^Kb=cVQqjd>9lFm;bW9MErcBoskHnaCNC{02M zp!TrI)Tr#0&FhKmju_+enx2lm#VnS`<3DURf@!yz)*VgL*P7(nkefTt(`5Mjvc z^r9CZPE~|T?nZ7DA@xP-6_Lr`rCLSN?A&39Vgw3A%6Hw*FXy-ca0|C>nk*BK@>YV` z3uTG78mZ=?OinsU-U~wERr(h2`cMK$3Vr3(A)8V0e!#}B!t)|R;1PY{PpR-Fc>E#K z++7FQ!Ygq9!uI#5r|%lTmnr}V?$sgQ3oYeHnVE=!MU&;@XEy#>Zzc&Rg)h<91*=JT zfILl9&dN@w*f#)%jaPM3Cm6n#$m(6*XEr}N-Q{q_Gy7R$>qT?bKBNot3i=E& zzYqgp0Kfn~UIPGF06=&Z_lttNmYpW@NHeD2VJ7XOf6K)yOxfrMArvI*0uF%;{8|{D z?IizHfGU&X5TAK;;WTrV>}-q42tx;IO}o*enW(bvTPWcyVo(AgFL{3I^0Q^-CDX@q z={m2)Z}vhWss1(;FZb3{Mcyx+L(DJ402lx;fPrfOW3~WL5KzWsT8@#nbV^qHuuD5j*!5TVC5LIY< zQ1yARW2)^glmOHuKhb2`bVpvCkE(ntLHX{v#FwQ>d-TP%{OOms8e(QPylYQ3bO;he zrN18&vj~*xoq3%R!9MXAe;jgvh9X28A<61_q9;%li1>g*dzRSopAZN;$Vf+ z07?M&H#6GMc^xth%Sna|ru%z2^ZNSfQ0GF4sH4)%;t{$bzQ7>{z$yT^0t{aR7~ukd z_~@5hGh58q@7L?e_~_QW>*!^fAKCSPsEVe(J5eF=0jL@I4mG-owde`}g7xq2lMzp_ zboUm*h6m`^esK(j@mje(sx+uPgCmK?$7(e0dH^K=1yxlvF4g=~tx{4%LrTL11@mXX zr*$T8lig9Ow+r{_K?+J9ktA%QMCtFRt zCG}I0aCBk6_%$!w^=se2H?xww)W!dPjp@AP($6I*r7>84i$;GlGnsyR(J(clVlg_P z$gw)?HNA#djt%gd@q$=MAO^rn4Y<^df=i8;uhP}~O$)Phj!m!ort?hE81_wbRTrzA zFLPdsm__)K+4~LI0v~xrs_c){h~A7fNA}q<2u82$Jk^B9UPVSYj8qGKU8I(M8a8(p z5lFlL6iNU^pC0dFf2$`A$7~MB61cAt`XXO)OCYmpIcZCWS&e7XiO zgaiQT6ol?0TJ5PT1#>8y;XJB9XC8Gc?|lwbcRP4!vbik)RJxd~tA)q^aRWeQAWGfp z(KdsetIj2$86k0QSvYXv)8Lz7hLv;W*>iO$0Vwz0p{h{Ng}-Fe?v64vq<`^|_(H;0 zbJe00uAy2H;IU1ppo7p=5k8$cWNzeucIt?;lQ<1Hk@OdtEU~d@h~u=V<^s+T|<~os(t~NgaV;VW=qS-2o^8 zptz60)!OUv>1(wg4H+_3@Di=8q&ukTV3jwJin&|C`j5i>3)|m=jqz&$-lG6e$FpXB zYq8G&_bOo=pvtb_l zj!i&(fkO;{RRC}Wn79Toumk|LoFwucr*q3WnV%5F3l1w!Ue?S=y0z%qsMHQf8gzLA zgLp7|&SPGW+yJ;sG>I=%^Pb@Xv3~v6>$OVpz=-ad1_IB4bS0}iCpc#)0Sr|v?p+K6 zYDl-`jFGjBw~O>t89xGJIk2^SZRLr+z)rXP7YYCvz-KT3A9_*%h^w|X5f_7#nJ$t# zPJZF`dBZ()kmf#5s584Af{ED!}LR}J>y(`A%|cD)nBV+I!9KBd~!!aPcfeOD^RR9%A;u}De;l9Yz!9VXZr_x~P% zFV_IPZ2+Jt?jnnTJL`m1gReK)of;LMQTSRrl4jpwFd76oJX_%eS_>;1epKJVx&csG z^fM_Vk&hGY=TJyM^QVxoP^8;d=+l%Xae5LQeKF9kcOn{qOR&jpz2V@L!$vvnZpBHG z8i+`$AQQyksW#RA6?W0pzXxFQ8o*s{7U;+u1+w0t7#jB5z5$rmf}T*Y5f2HlLUqoJ^Dy^kh8zRExqFiwNZq z@N|q~4H%qet@O-6mi1BjIpAvpe1|w~$mmHKsupWdFT^1pVgT$I06YYI1q1MY%c>^V$*T(RxBcWSE>{V^ zxhd0XhFc45MVQs9=Kf;COa6JCU`K{>9ApeA0U+ygTeH8S!0RS`uO^gEm4#^iz!%tM z-<(RWxA==o<2C5Ep(tHK`#pK>6>+6UK&(;5)*N#SP zM0E@ggK}78yK0*i8`a5Wq&r17#hyLCo^?!P-haqRcVCmg!ElZ^hZcySMty)r!#>|J zP^0ek+#lLmN8kH-mWgo<{twzJGvB!xIT0SGxfR9a8&WAG4P~mN-iKIBAO^sy4Y=A& zU00jIF#xF6@7;HYCl;7vXiet?ewr~FiYmc&qTvN;B~-3-XB1w*2QQ4#B}B>oxx!CU z@Lh^$WU1i=-CBNW;+>;Am?Ct-XH`TUF~3?Hi`>|ttTvT7iD8A@E^Y8AOmclJfr*wh z1i@cbC`A~yB-2(8TRkD>7h(Vm02shD7=Rx`Jpe>DlOST*!9w-T2~oYOZD5UHIavZE zK~}^^0AkHYa4rN=h|Mdmb3Np{Ip9R0qE=p2{iZGOzI#F4k@wcYCFni^?TcM_93a=H zy-6D=0c5>W?YNyszy2z)C*XzSPp;a}p_7ikP=5Xh3P`8I+GvEBUx)!P0AK(!*8qH* z0iepXbJO#(2QMyGWVNhI&6@X~!TD}J^*q*_Wwb1zWeWz9s5DIU{nA^!0U#R4`RFXR zh5p%sL>rzq^)s7F`j3=9F>oyuQ3ewGnmftW9a4=P2j%pX2G9tW8yyl zkW;hk{SqkRnO{~iCjQR{PakQ2_BG&ee@T|&Y8 z6}1}MMpcn{Di7xs%l6kQG|tzzo&F#8?m8^Wt!)@SCEYFEEuGSxf=YJ^h?InYf(%ka z3DO}rq?Du}Er^mzcSwf_N{L8*qYmu*`QG<9uKSp0|M$*vJ4~F;M$g}zYglt#=h7B- zOb4TEeFanU5Zqkhee>h80>~9`JXP;`ZneF#8;tQJDpBb?P`ks5fE1VQ*o@#*J`qj; zm3%0t5}=y(&LO6i5doF)?yvS}F?gpi*6-&+xKSz{w|n& zE@j`fRh;`0K#?7b`I`yGRi7%iTUpnN&OWlSkiVw?q_LKDWqT^p5Z*(bOK;#1hvv889m=38$=C3ew6 zZ$+%_PSLr;WArP|jBr+fHZ%9w{iHIzH&j{fgiq@tISP~AoC|jQ{2Z`ez9(`d0DOT1 z1|TW`tO6`w00`0oL7X0sYf}`fG+q;qUW-C%N?Apc5maF%+bXS9Nb>g4V+1olF}tyH z`#c|70sgk1TyD79H=swzsDx=Q-5{vfpV5UHnR?BD2J>pz-H#h-&5PjhHIh}Jg zAZNfV=1W5dh)iLdsxjB4s*Ae8C0!x~r22d2Ys+u@Fs16HFjTqg1qt=BE!BP2t|S zyuf0UGJYU8RR-{Yc1Dmz~;2Ln=^8DR?QsdugKN$%rGi2$%-F|Nw>3osH ztk*3~+E-1Q_Z^bdW>D`iR9GX=^sl3J z^Ioh>cCLnnF`K{BHfeWH9(#Nm_l>H%p}^e#Y1gn?jQK8e9GoCaX2@!5SM@5=JAKvO z78sdDp@b0Fr}aD(;ZI?_{&o)uu;2g&{3l3ga{gZ&e?hK>fS`YQQ1D|uau7uJL6)~M zMQh^>`QUr4!Rok@XSKnVXeh;vDP>RNm1-Tp^qKGceSc+tx_nP!U|+rRqE7S*vHrNu zo21Pa>&cl#*G=g=ShovPb03;l!wFy{!1?Yo{cq@|sb$1Cjgsq(guWYVRC~;6%Q4*_ z(~1%2;Qt$7{{mRM01(Cjf-Eo%Oi8a+$}bY_et5Gt;@8$Bw(pFDi3#$>3-gQl(h8>O zI-tHvvoUZ9;LD44GhNYM{GofnpXb|D{F7Q3`<(V9zj6(w$c8(8g?D2dG_|?#a|U~5 z4pml1)~<)sM^x<+#w3DrLS{E5yk%(z0bk&N0f-6!o08Tq0L&VJAf}keHu=jx)==fX zvtCUAWiCHBMaIOD3XCU@o+Db9egk&xT$DG>mY=!=5P?LJp@qIF5L)7-kSdzG;t_6| zXC-l6L{@8EElOww-jBlR+T4%x-<^SR|$6*6sL~zt~t{F_@X$pr0cB z!;?z@98qQU>z3K16&T67Mjm4AQ_4rGTOw7(l^QJ28-j*-6l>;KH-S5Ev)^T6IlQYll!~AiU!OtU;qLD48Ye5 z087uI{YM$pR2)o^UwF}{D7WzT)5ht%a~IQ#6)c0Wm*sEjO@N(LHE8-W3%M= z+ltqt*p)L99UIv!kBO*6=|jG7Vi0#nvu^fj!3lta1Pd*+3iF7MXNb_w*w)?h13HON z{%R_62+ESt0~1TY`~n6b0Kfoj!T^9Im7p8rAZ5R5e)RoxK0+WURf0=h@ZD9=cAoqw z&WW&I`1>EcV04CsC%W9Nj+X%P#-z<;KJ5O;F=kRO#ar==6ALqQ)|8R2Ul@J#hLRuN z?YKWlPe$r(ZzL1aWUcZRWv$$sl~Mep`qSV#vYW%=#(pATegOjz0AK*NE&wE8gCIL1 zHUX;=E1j))5j}GGlL9oIP8^v1uTaJ_Y@8$sR_MV3>9%k>?b};mYn27mV0;X?&n&C^TFN(tPcAp z{0Sy*JAnBG3_t*Y0r++SVB;GILVkX@(@NoFviCaXp3$RsBRZCLcgz?Yh2tg5NEo&S z)4+0LYla60AAc`R|7|~6gYS1^L9C|s{@4M#YBm4#uWM$EXNn2K5%bo(5fx=#a01Y3 zTBI*ch#aj;?EQH414moOT42>f=liaHoBC&}v94$kPNhhuqR~jvk=aCro+uU7rv4%M zhJG0rx0k%wbU}_K5MCVhI}Fv09o2zE#OF_iM1IM;KVGeFn5)zoFT9JDREq!r1MvM1 zfM8@3?%*JWZRmM1#9m>(GF8&a)IFqySmWWm20Jf0Bd)d*S)fa=Oq_~=uboi4@#ZcG z{BA#~`QNpl49#j%Hj1~k92QMU{yGyA?;dMzcT1n?M)`!8kcHLVgg#VOw6xj#Zmae8 z#L`K-UE0RCPR|U~Gk=kgUP-l5TJ858d=&LgF)+WJDHE%2G69 zottDb#X^ih%scGUvshaBffR^RRr}fd;YguUx#7B2r@18e);Ndo?>IH zmD2q`#m?Q_=W@G=mxl)qz!07X8EMj7PI@t)e;G|F$ni?L4KqoNPI#OFP>B=T1CpLEfupK8%rar(}cA{!d>?2!7af20tO%ezyR!A z0NB$8LAOwA(;29YYd0JnUh&zfpWNwm`oxx-oTpZ@0qd0Qo^ zkl->NYPsvEUrKJ|-V|vztMOYb^q<*n-hEIZAqD58_hz03>?n8m>drQCl5GHXd{icj zURdkh1S!*6w9;ThUcmeU1|R^y0PMm5Jkcx$K}ss??I;+TohUng-5IzM;9#J0cQqa7 zSld-9?`cnYF$?%%HYg!kM!)H@0=z{V{VC7KU;y?m0OYZN zAc+kVc&xW-r{&0T#7K!HM+B>h^k zUCEKK04NoM2b*!}e&d#nx!CuD(I+oip27*BeZ7*2_TH~{I$6tf-ldwfQW}z#S8DzQ zxo;I8Yx)U)1~BW?bstp3rwb zgXi`WdnLxFI4kIH^-d%krtFMp#>U@Gon% z3921pupm8=l}oqc#)?!KD|PH3_)d^0*>kRXr%M1xA3bfSdG9ky-u150q&61N;R_6B z4>HBA!Kk0kp*uE*6F{pvxCU3iZocjsLr!-ss`j;=ijCK zU;qv;09dJkAd_W38C5U|*gnT%sm4ulrI=Ddh}B-qTX_6rZ10lldJJaFu`TaTmokO| z_}hN6JjYoLyGC}6^>hWpNO>(Y|M7_~ZM7Z~|~76@JKZAhVer!BmW= zu1hzrDW{8{%c;8k%z-o}OE(cBDMat(`L%ZC#%p!DXFtcYUU7>uKdPs!V`CjKGsq`@ zj%YuL1UUX41NG|ph<2Qk%)7f zt_F)e5Fjcw(>ajc)9kF-#Sc`$+Jm^~Z57)pS94P1Jp3V8I`HY8^m(r0clGxBrFlGx zG0D^yOOB=9pAAQ`E7lSN(fA%C^^T{R=r4QJeMXju;l5VvF;oJlv_J-aS%!Jh8Tnoa zb@T8-+zPcxy6K3g#MLVp3SGe!UjQo!U;v`jz)H;#tki@OzXL&RnMf(Vuc%d=S~TV- z)N>ugoKp02EM3P@Co|Cx-=_Eh7O3WDqWog=?Vq`ZZc(JCbjEvFYjGsCeYb|xqI-s( zP7qnDD8xxKek}=302nsZ+n5=2&Lvu5YtQ^vH}Nbdw!NJ)9m|wsE1OOO-lM4VJUV|WW-u~jPiIE^h|RFJJ%y01Ut>3_uvY z2?+AKHY>FnL&M1|LS#zp>WgWQliC+m3T2Jg0|^Fy^?&668y$R_CI1=!4*;Kx&)J3c znntq0`Bzdsa$a=CsGxl`uHfsK+Z|+a$A2%A3K5*&74R=BKs1%1X1AR{LB>=r20X*duthMH3XVV9x zf$i68O3*~)TwK_x z9h=x4vBTdB4#;P}kbglm`ath7ei0KlgC(Qi7&U7R7T!axvA`PGclMAZ-t66j^F4qq z-JO#dJ_s^6IM2~5Q(t*qGV(*x3Ui9TM8ow*0Du8Nz5oyoodReAS*1!{ zcH?=O`Ac>yjpVN3uf+zs7hZLeAT@ou)Cb-FLNpmg=1r^OC4eB=wbO%Pom&eQ&F z5T{%q{R`vIiW4fz1%L(Uo4~3nrlEmEPsZyzPK-k$W80o$?)sXJ2PO3GjaZvHy_(=_ z{%B3J&qcbR0RFb0q!i@OY-4^O%lEYUNAzx(2ZkQIqW-q|^=I>uzK2uZW^h)3N-ShN z-RygnbfO{t`Z&7->-43sI>jAPXG~?0{LY(pAg=aK8ZJmyVvlpWqK>94cKA7w^P``+ zGRpLKBzqhwHz3+i0t@*I0QC=mKkX;MlF*+7Ey;Q7+mM~W`-XJ9)5DlaO?>j+#S%SLvdxfm~OOEXeQoSvYgy< zW&SRuOz7{&VMMv<@kZSAaU;o-NK64ZvArfX7W}3`LnGU;!E2kCY1%HHZlMfbWp)l- z1asnP-3Oda00R)y4Q#qWgH;=FGIY_YP4oFnA=ydS0aQX4B{NCgq5SDint93OFRxbI z%JCMv!6t$b&d(@*KQE^nr(G=eEa8apQLFVusR#Kx1GS3`_rC4g7jUZS>e!1&zzN{- zi|^&G%`#=LNwh(13f37y{opbbG5w0bUnxgoQ=j|*^9vY&000AUFR(+xTdK!hgr^DU{$dk;o?k*{}XVv#hP_^#Ldz;V<#-CUjB4AiO!3}OKb zG9!(5a7w(p1R&_JnQys}*!-@dRrI@5l9$*k`FfsKA8Vu$_VuR@%*1d4fV@wAaEz{A z@X$h>`KglUPF0X{9d;ZVJ*SIqHoNdWcEA@nU;v^5z$yU71%LwRXWTU7=ld&bU2^*G z2WAMfS7e7;yoCwk$*o(Zux`wS`rZKJFsz`|a=xX#1aM90#iNi0=LvfnyhSVmg{_Z_ zhDS>Q3xSVP!mf^{&tHKP!1^6?+hr-D&2!3-`S7O;vY&}+ta6NPU0&%wPot11CI!qd zU;qLD3;-q!KqNBsD3{Ip*f`{w~(YW*2i}W-ZMP4tdoNiK=73x<5W!I=HjGm zlJO~PE{>Cu6r0L1v$x~Ws*}dQ2LR?5FaQAn1_0{9dCH7=CC;T9y&iN>;Vo&q!Q|1LruM~xn4fS9Fu#BS2mmku z*cSlaK#z1KD0^7A-!LQgAxf|Qp3wVBr^anTche%hD7~4*XG1B6U(odKgS+q z#^DV~@wGpg!8ClN(F)p#*`9OYWL#?0nim}yK@BH|6979Q|DbU$PCPy-^<-pTSGbDp zO+2de5z^Yt#Nx<(Mn=H=0tO%ezyRRD07MZ(k99}exsF5hRUIF@PPAd6dwd#$F+KLg ztY=pGbeQ4onTrzmh8&%sxJ;nRWd+DqVKEMvGa0KmwP(nswMDztT3I;BlNmy>EB^lV zBZq7_0ld@{vL+T~3?HPr+eaCLiN$E!nyA=Sq_(qieKU;uD00JuWW zb_u*%>sA>CP8#w5k#wD^kRYBtj{hmWk%S}W1Jo8Ap%SolR2fWJ=%yW{uw0fPR9JuTY*@#L_$ z|M7qQ)1vav19F!S|6i_LexHAT48#ioL4$&Sx1WsahMw}~AeVg9BBF4%|GZgU`(~rM z=KV)1QdmS@+txG9!~TSi!JMYyvay&yx_-Bx)c$wcPewpSI>$Tu@*jPiaCPvKMiD9{ zB^EW;jJV4@!uSz4HmXq*f?0OeWkY_^T888*60+FxfjzG15?y}v^Q~<2nMppzjf<5@ zfk^Rom*@F(JfKqTryht7>9)%%Jx))C_gILwPj{Is#OqD477B_NpDz#$ z^0Rm*W(mG3G~--}#i;ZIEI5Dxh>8TONcew1f`X%2p=aQB1?ga#Yj$L>2eg^6KKeSH z_iPfg*7doVamV>lhi9ZMSY#fT`^)#%oy#eS*VYhi2xXazc23Ba%aFPHk=aAt(sM}s z;yoKPG{Z=E8_%%ml$<=c-RiY+H(Dxm)o#R}YLHAM&H3kU&v@*k0{Km7x!K7TmsPA zdFQ^>o-a+)sf(j?Zjmy6()pDr+3zer>OC7lc7_R@Q_?FlyNL&yD#Y!%tslg5jP8>u zFJq;=$h5jERD0jJh~p7pegOjz0AK(JF96Iyn~o%L3(Jo0wY09R6glZ#$0zxw%Eg?h zBKB~Kz*3AtJ@OcAOZDufrXEJyC4i1OmLhh0{?00}=`oYr{psBn_O1`D^}Oj}SWXar zx*Ko;7_dHLo*+6&`zc<_a2%z~PT(?`e85XusD%IZvp`)r9$z!a zT0>G>!E?-`Xrz;B_nEn`E-!GgKB5K2Si%Y59N&S`H2Ku)rTS}Q8)*T~t%e(X!DEYr zq#fixkkq%B0P_nNfB*mkKzsop3EF#XllnSWFIGY-W^)1YaF!6|O|j|5TS2`ZY%EqQ zH)S|(f@L_Pwo7Xwg)afbGzULi#-h%q{jRxC$BvGxrSkf5lxuNc7~xJHwx3}=oB%Qk z_PTs&ZJpDI#wR3FOb)}#@dlNC(2kZ9zZhY9dczAazkmS<05AX~7XWsk-3RZS;XYX} z4fy(u@fanDAbLY{eFVpX&Sh>eW9Cw9BD@8;8AI9n9)9sJoMg}K-d zSXlt`3mAX^00Te@1Mu_)wEyVL_ZL3GZ8q77`kTvRF47FkechxM$j=(CB3t(^aniJa zeU5(~y|l?Wz69_!Q>smp|~S44Im5>+B~cO$;8hYb*lHFJJ%y01N=x1%P5`2jVki-Y7+x>4?Jf z;Z)(SfFT8Rhqc<08wYU<7O28b@!!F`CpM~Wm6g4h02Fkh-7y?aw?S$SZ9VD?P2CNh z4rIeuBdq--j07V{4&VeJ<1p03p>cT7i^Nx!WF3Xi=jiLF@w)yVL{O3Pi}s^-!2ALR zAOOGski!7Pq8ET58!reGs5t!WYn)$t49h3{I7e-$LKV&-alm^ihGDHl;lS_N^>-gi7q31cbD-Zx+04V+d2u3z8368ae zZbabyF8$;5Ri19iu@^RjwFv#H6W<`pD*<#y%UH2&7`Q3g}=kLm- zB@zf!mu`!Mgml#k8Ce_^baB-RNx}+g&lFpM@Y|XID^g_iE!Akd=l|f^Pa6QI&}=DRhLy;p`v+(7l@6Wxlw$8+`q+I!!yi7R6*fiS74iG z>12uiC|s>rB3<;jnuqAF^~Kt!5yoBHYvE1kY!<`}Y)x~0Lik@pLYH{byKWJYdV6y; zz)3zPBB{upNqhf_dsckB5n}buhT-7lC*gA)LC>9s+#8QhpJ7*34W-fl}Vmfq+_K?)CH zFP=Nud+V7Cm|wsE1OOO-t1tj@w9vhm?k2jr@0$~sy#)zrmtm>DBzGs>sW5I?FS5GA z!TQ783(TlT6h89)(CreyyKZu!Jc6G{j>i~jxl)!VNz?;^7W2drt@w|*^*!zJ-~>Pr zG<-OosK0cV$HVodc?SoNg7vr88crv>{X3~2KY(@t^9vY&0009(bphZZ69@thlP->V z(8kHY!K~X%Q?|t#5&H83e_E*xQ>*dlp)E65#ZPEp>#o~BdoOw4+C8)%wI(DTwO zEe|L5Z(cM?$?3WBMMj9C-+Bj50F}QM34N`O*!^FpSJB5_N$uUC_1bq4j6Y_Z3V$SX zh&V7G`Uc~FJ^p?kOC17&J`*T7ZXCKFWE-iS5+nY4d)~t^|0Bk4i}!7w2a*xx>env! z<`t+ufSw3=JHa|gs!H^-0vtv2@qeqA#>(}Y$?9sgZYRm?bf$YzV97P`HA$IC{2818 zifnmGW^-Q<8rnW-zs+D0H;4c805D@e_DEOH$ zbVu0RkBRp-P?H;6)b7_!b##o?PvL#vR~L}NiGKz$WtdtAOTBA|1|JTWTmqoG!%?fG zBDvVqiZu>CsgiB&(>4FE(J4%)}&?&YHZ*0Up%dku|hi7G&`+m)j?F|5`esArG3Q< z-PX`S!t*`Ml(`9G=C5sNbamgbtjR_nYPP}&z*&fqG{O6p$VU2SHzdp^D^4W_Y?_POvrv9&Lj zyeT84@;p>CJ;FKzjBTaCAav(G-X(yxK<`Q0XO17mKl|3so3vL|I~Be;)I4j5)<@SJ zonB^w6TnMk)wgN0oy=OPMOfMr#m!rCAH5pxb=$3I@!e$%44VXefdd90DgdkkFkApA zo`-gXx|w3d@jwOQT3dyI+U;@^v(14kt-%aTMwx2Z6 z5)_rkE_^FXPCpV9L2p4H+1r#mp~hyCD+PLIx;`%YVbcAR^~b`t(>KN$frsdl2Z`q67! zKbb2t!!{Q>2~Hc4CNt*mO-uC0@ib8`$f4LQs1yp_p56&!_L}K=nI*N_a7sI${?a`$LsDi^ z)BcXl>@dXJZEqmcVkw6@aTlp0v6VfrHt6r5n+0D?q~iDi{zaopbM z=``35dOBi${lg;Ta#~qU1u1!GWJtH*FCFzJUsdlFzam|>`!B!zb*mnvr&m>J5|(HMl6;vHJ4iAZ+p~oW{BvU z`QA^z7UKc$Zr`zc?A?q(FY!XT1qA9+n@qb030T1R0#@p3x~V1As55~11q?s{fB|5= z01#vYg4hS($Qyp@j6mSLmmsCcj0X8?Yp%{DxNQE_on+EkHT323X2CK3GfG>>=6ZM&hl!`AuHgf z8JRl=W%rbQ6C`I<1k5jB00ICE0NVwCxiJvrbfH!$l_f`l1@&2sl{Kj(Y400_CbLsK z5wqx23%gnn7_asVngHr4%_RWG^gzAb9&9cpT*}~1!5nL!;=R*HenGo;u&ZWTtu5iL z00(lyK3<0w?}>HkUJ{JIUo0_vbs9)@gUTg$An-EP4A0tO%ezyPqr03^vcLbrP- zPpZ4ef}^C1h0biFI5c)-~cQ%F-Mx7VHryX zGZ@Qv^cTmKq{T}B#yZ&pxox@QeFQ~8w77oWZ+Q|w6oz07T|WB&zQ6$k5ETGc0XQ!J9It{P4_L`_^1z?2 zb46x0F^C-3-oM{X>anPdp6(U*Gb&ck0nA)H^})c9PyZ6Yg5HOLH*6#}wf@$=ygt$0 z1!POi--&~JDj^y_N2c~2-~{m5(7=--oh;62^QM5EPy&g76On^!soX2}CVt7ODLKT( zr~g6$fC1ow0Z29(0zs&@dY9NEygSRph^?@1OU_T!&~%@DIlv7ww;O9}6mtiYf?VB4 zSR8Pn0RFb0Oc8n8a8F8Y)nu@(ZN$g?ZnyW2kQ+4@>QC~$WfOXkKAZr)bwAFdTRhrY zFsUu+7h?4y@`)YtToYj|Rf(O9mvdx>4DuQ^y7g!B8A}fHl(Gv1kA`5|K1)RH!z=zF z#t>uo2GM>Jn8@ELfcp=CKkX-z>t~?59$j}Vce&Z~^7`DKwdv3b+B+5%{jpS<yX<}_%Ncj7JOB6$-->Z%% zI8w`fG2l3nJIgT|%@kl2@kbz}^3m5Nd`jyQyT@7(%T#$;HJ=%~M?Qho>epSyh?-k+ z0+3yCknEK(MYlYVFIF98VqS}`4wFi0^}G?H*!y}{i`VPtD1S-ngBagXPrE1ZZn8?| zHz^T0sMJyACyPuIP(SOLEbkBEUwe!GE!kZCW}YeFYyud7m~LRzh6h${Qm&$bAP*&^ z;!vvERr1t-aj(2GYOvW?R$SwMA}{NdPA^k0CIP-S^VK82QYY|oy5X}gJlFb?86QU0 zytI&h(9v* z60Ff9k)o`Y9J1qYEwcOSiTF# zQ*y;Vk89BOkAG6d@52e8|MqLXw}S0R&BC*V#_ATGH_5V$vGW?V7n6Dfk4{1o0P_nN zfB*mkzy|}6D#j0jd}k=;!n6~u+}F$z;ih$9-)Yq&?mQ4PxPwmY%mG<2&Kv4Yjg1<}g%iM=XHSLg_;NCF3Wt4K z792gWRM#w~?tjOk+Rh)_c=Z9%dJ5@Z7=Pbz@?QW*9)NCn;i&pZBZJhzTUa{1>-LGV zW=xt+YRTK=oUJW9TM9)CutT$j`NM4))=L02C_PEL*|)@2+Dki|Ptjexqom(4y(rpY zk+D^2xp_hgCji@T3gq|$;sJ}HwQ4`^O}TYp=$k#K0e6_jwidT_<$iYSK%w#f#)3%e^9Rw2V&h}`Q>Wp}lc!>E0+5Qj8k$%?*^;v^UgQ$LjI;D` zl_8lS;d$p6E`0#L&@^Cv0Rs>KU;qSR0MZPcKoEwY@+r)1ts~a;vhIhUY=^P4#{$Hb z@v`oIA(+xO@JR=IbcxLN-68#a34r4$diJvf@{_|MZj;##-Bp6B=>Vn%ny7zU3F&=jmkpcExskFI00V z-Uc^09|S=f@|TO~zCO~Otnv#<&3D@7(=DYcN~)J!eHD~K z<5SdNn~`BRZWr<2KL-A7KZz6ADWEmv<5EheW&t7eN#Mas3Z9#f&M)RbQtMiz9fPw1 zD20w~x0r}F^60Obqv7g^GSPaWJm<=o;f)=-A{LT52gyycD`@S%B@s^zNqkHsC>uB0 z7s{KI@x&)Wn<3M*&IN+~;gQj3y70nxk;xHhkKvYrbn1lHpWjkDWJ$7iBEOEl03e41 zJU`+p8hB$oeq$N%Nl?-r34Z zNdEZ>y|}pjPyC;_KQ}KMN&fTT@6MJW=*2%lmygL^Tw{P;`THhNBm@M#{ipq8`hzME z#J^nI;bbnX)z#{TC%)nw(R>TmfySA2y@kN`mc+MfR$y26RaGZ~T9)7KCvX2f$(y4K zdXl%eq7~9@DMN$I1%X@751Z_s(=LtFyI0t^k41&)$tdk zXb1@U4*&{IA4dm4avm8xT&E@ByUoQOEuY;8F02j^nVqB(a`_rkAS|RS0am;^?yl+2 z@XzU2nYsZoB4s7RKVF%S$#JkCeQTw|RjQ-fOgPG-lJ|>`gBQRzvYe;5FYX%EyWUEV zJtgd~wm7txZj;`RQ%LSDow*K}U%&ta0N9iy1_O{G<_m(nJgaUpZcXA;j+c4CFs572 z-coH*)>dK{l?bMk3<;y@*$Jf&GBt#=5@gQ0tO%ezyL^G0644#LD;J&wJm8VB?XKgVdqy=68#wZS+z)Y z^mVVc=SU<|su*m zfwu*9m2))t_2m(tmrIYQOtxLLKMqFZ(`MZ5_e#zs2w}4N447ZQ00aOSfa@>-&yAlz zx8j_;mR*_YbYr^ZK}XQo3tZHDNv#_a{n0l-X zJpQXMvWGDaZ|h4+j~0Jp=119AT6<+wjKbio0LOI{GttiXYE;`t(`R)veD#mw7n9xOR=s> z{FN1jtp_I4W^Ba}pk}!Q@KXs-ao~0F^x@{P-~HVBDkG=&TXr*3vK!!`aN^8k95?}- zD;Npn%Z-^+`%_Q1CEgi9v)OZd^7u|Z4an8eXqxmIV15Au5CC8Rq+kFtslA|WSf*nQ zu>_lTmR<7nMWRNwr!kVxJ+oJoPWdb(E&f0(xpwU zmHZ7RV15Au5CC8Rq%Q#Yr$Zaiy6dA0H$ExE+)J7YC;3#TzLcY|$sS@Qa_}&OJ5#a? z%$aVl+K@|yeOUph+G9NwaV6*Y?3(OI1i8FD1a&;gb=dmqPl(rgO^l}C1n@TSWTq-) z^9_N*VeI1qcS*($6_fcR15a(Rqv3tejwrzV0tO%ezyQcx0GRy@Z9@Ok!@ivf*;MarUEdU##^qHAiF&%fWPf0A-$%fdK{z$q#^I_jhu$c zn&dUp%6UFhq<0o4VsE*R4JQEYjEBaF##}G%OPrBuo^^fE)JIiaOg^uVeeZ?olZv?q z$uJ^6uWh-;+LGI5z?(R!QFV`9)eg7HQ!zG5l^C6E2+@8L32^*f2$KB+;7|L>ELk~d z3sMw8R!x*=nQV!IyCPf0A}L#nQ*secMXGNx2l0Jrm;+;RS_j0 z5>c`(^OWD{)>HcPWpj;|*zuscx}d}aByXLTcB^QVoMi=Z-bc7vYgg^V(2zX3x#dHu zOrK_?RRq}LdY8~7ve0}{G@rKe}cww>sSs^#-Iw?c5 zY-O{6t7DDKpyZdwEfRAxrE|Lylg~=Aey@ENC~)`kffukSHNTmwN>T=s3Z*8pKv?G6 zd88HZ`HhP}?p!|_(90Uhuv4`lIkA~gP{HSHR&qsQ`}Sf=;xJ{s?_%w9!<~zBZbn6E zLq+m%Rwui^%FDh_*y2^OaBB!<;U||+ILY7hIH%wrh?KOm4!GG@Srnq+G-&E`-?<}_7`MCo^53QLbDv+Bc8ELV`r0P_nNfB*mkAP)oZ0>uIZX~!bZ z#UKnd9er~JC&j!TX~4qTBL5&(acmU2DfrGv6_`2d8uLuAo82XVP$O^G@eQOdG=`+I zH`=;S8rnoeao&j@4opxAsS}Ho!dU@|PULaO$TJM}%LO-C)HHI`&hU^_-+%ML6TE5| zTb@A(m|wsE1OONSg$n?7RL~J^eyPncihnKFH&sBZcrbkl z>=%(PO|Gk0e+fYS%k#t4_BSkVM=VM4R_)*Rx`RGZtmR+tu)Oj-Y3S7>H~}y-Cq9p% zwVvqZqoWE+Uu5gOg5g+}=^PpDd6zv}LR<a1n+^rDRJQiu%eHl00%#|y4MXW#Lj6)7VM$ixB~Lyu zKR!dLQFi)?DE@%Ie*sPa^Ez?{zqH18;&>-Y?!Dp-l>AAp+5h$0i#*)SpEExV-2w9p z7=Qo(1E2&00Ab$*K_XAIr&yT=s=Tn4>+>r&-gqtZ=RV%rvUXLqyhVq7tq9CPax3lK z?B?nvfVwAw76J0X3kqm9NY3Kd!^s&WZm1XJtS>i)ISA``5yA=J>3bE;qYPW+5So{5 zPc+OGG(KRa=om@m zT?o34_E~@=oB%Mu`1%^(yFR77mlsu_>ZskIK04prKeqc;46a%rh9Hi~Li!iR-yeKc zE&yzbf*`dE6G%g)eTPD}6``7wSmWG0RVhw8Xt{|DGX}h`YNo+z-v}AYx^6LDR)B0$ zN15=?u8)(o(W?`7nH|^^Y|ssnnUhc?>ywhkIp94m`^}uY-I}^*-8(@G_nG>A$=gxS zneq)OA+!)<-GgE*bifxlU;v^5z$$<$3_!Lv7z8`HYbq7YeoXl7xiTitC4lPO#A4fzM@GIw$=HkGyN=u0O=Z=^`PZJvI6dQ| zoz;c20Mqa+>ewI!^)f3mAX^00W?Q0ifa# z1ks{-d6#6z@xZpA(lR^gjtm_G1JllfvYBpKcPT;fRuemV85O@QB{t#`u4MZ#*-WRqCr-%7!vORS$3 zSDMa#x1ZFzXg|r5zD?i}5jq!gMCneGz3-*#6e9Sy{bWV`@Yj8U+3#x4z7CkpM83Hl z6!|UwewU`QGXB+UzRFREL66;eQNp?8+8%cb1Jle6?07v{SHJTRy zCQd+*Pu%rLIPU!T%S)8%^;3q9l~$nfU23=eo4t&sDXb%Q;792`;(3KF&n^MjF^hZY z-?BBkoh;${m|hd=BWddh-hxkB)P zsu-z??VU?hFE5zYXiF7 z0Ru%B1@p5Tm2*;Z0qnSLlgeh*s;@Q0Qyr0OaRj(_!A7F_OT&=ag-ZZCq~?MOR6mNv z#pWC&xP+Wbu(qEg(b8jcu^-vQI+*jp2_XAHZS}87PYdv27M|N-`ieeyhVYR7-G+xG z7OOLVjRIhP0Rs>KU;wmX0P;}qL6Eo1U7063Q*UNW3!9GV$+;f{C#{8iqfF8CtZu`8 z!Hy25$+pS)%o^Z-3BWMX_bEkUu+)~6fdR?W#KUK7xA|ln2TQs0^(R`eq*&ktfU`01 zVFi4i&O5EhDP1X$(YCkjjN_VxzlJIQfjy8$8Zf_r0SEvv06G@{?%V`H)`UF*E!4sz zKyqAJx4GYF5tB7R!m`CS!d#PSfc#j|l+t z3mAX^00VII0zg*<2=cx#Q$R7fq6g2w1Dm47uexPqAk|CY%6*G2K1?$Gnn^H!C?-Cm z0`fo2U}UU@1BiARX_jJrIn2L&LBb5>UGm6KexT7MaCpNS+z%&!mj|Yac#^iSeVuoG z>Eun)y{@5!hEE@;GAdmoxw^@w513!T00aOSfLkyC`8>QJNGf{Jo4RK*+@T^(b2>|` z(mHB%R9Sn2HMc^pe280`qz0SZeF^UR)c@tO0vvtG_#zPBRuOa>FH?W_6|+ml(067$ zqs$N3%%>V`*UR7p(4jW)gltGC~xd=JqFA#U;qLD z48ZLR0I@yLZ4W{7TTU4u{X`Smts0P#Gt|*5F4|&67DlwsN2V z{n(OJo23kF1wD%yAxl`wr6SiZ~{OZTs=BmNVw}KJFslC z7X4OETPBD&_(e6*=y@=?G?5JCW0geX9Y=b{Vnu;NSw_Mg%MhWJA%8Acal7ZTSQ(tN zi1w36faC8vfbJgvf7(yx?}b1+LNTdVwiBTxZ|j!cjqDt@z$>yRfeaP;Dw9UgcQV`C zfn~=0_1z6I+kdy8)cSZ*)`>+j2d#cd#ZV{>S$^}A}>>OczW};m$vvAfu>Hi z_jTbFFy2|7p!R^%aUz`9>N^cl=|&Z=FhsJyev_Zt^G$0iwT#Ah@zeNvrgv&}Jzy~b z3_w&HShdlERht4sGwA+WQ#apdw@o!-Bv33plC4@oel3S1XimQwOyAP!S3^Jr#;C>* zi3^_vUrsk-@0U59RUX~8a(XGhC%Qi=SEO~#cli}CQF@>zK3mCFEC zoyW^MmUd-#kK~bDRiqcyrg>l^Y=zukp<>~e0P=(MP`euxM;3WOYc?KIPN?Ju)d}CK z!;lgthqr68PT{No$Uo$lc}N=l2l!ovwOe8Xr|&(U-Fww;w~2P;?$7FR8o>Ml1|R^y z02sgkyrj1WL9!+ozlerU9G5%oIxDSdn0k_~HWhtr+DocOk?I=KYaC>8Z2_g@tx>ctg>CQ zIqWsmLN9Xe4S(Ho&k4l_Fu#BS2mmkuh8F;WQ=y-6hdL7lB>8V5F@F^5w;z-T!U=O`Tni&1o?3}5<0z32NWJ$`%8|HO82JYg%@L=3Sc>8=1 zsFv8xC**ofcXZzrqpToiie;R=NnE_^&^JcDHCfmS_yPwEKvV!&1u(e)P_YPteDh=7 za*F(76M5QZ!0&~u);FiPm`r)~>dwYzjS@UJTClIH_m@=g>xN4JJRkO?Vh+UhGt};i zzLGg?sT(R@J$LPh$XF}6W9jn+-eqUh8cpTQ<`?{tlX9-?USoAPzm0eQRCXhkc3$+$ zmC!(Z?(i=Z02lyM7=R+O|Bt=9j*4mx8-`DZbV)Y|NQ!_6(v3(-BPHD+AW9BBbV=6` z0s;ckNJt7uw}f;{g8~MAqXV4h{hs$*bJiUH`PQ1fmds|)HJjtPeq8r>&)(M^0lvto z3FXO$bhhhqjht#rNaH{EnnWzRPafiFb9|)W9eOJQe4u30U=XQ4eFb1ivqc|6?z(v_ z$%%-o(q!x>Wvuw@-nqoe`tLtWn;T)h&dqg(0O{ASI31qGWcvev%lKrl|7!+1sT!(dF21~15qIF0Sp3vpXE>c$yf7201y>_ zRq9dli%jKkCSF1 z@8F&QE+EPEINx8FGn3PI4ZQlc9mjMgK>J2Sepq`BB|How8M0JqvIX_;vFl+3c|cvp z;N5fmXoO--Cc0~HR{7riNse90?cfMh+B(sC_~#&($o|VC1f*dg0DKZaK#{a40A!{_ zxK>5QC0kMXXEWE;ko2WBOjGv3GWJGo z#&ny+@yrL}z98J-XX6MT#H4*~IL=`N;GiQYB5M&;ByM-B3Vk>7NM@2Oj*wj-lmq3N z<8;hx3aBFv)ByO9bTK3uT>^Mz3;wsW+qiDpVXgVWuq|o ztyUFCw$C!*{q8&N6@XDJOwams{`Y>uKhwi;bZ)kS9K)4Jzvvuu?R(Vi$ri#0;Om;I zieY`-d$pf}uL_%buAOVwi@B|&ElM$5KD@;^(6mPI&#DOaBHE>*<_zER z#Pke=sp#&jIep@<9Dd^Ugw>Crapj&hi~w4Fr*Dn&Gn~c8TMQ+wx;^BncuQy%t7X4o zElF_0Y|9$zFF4cycm=qq04A3JT+RU?XDtOxk+fy;$4`Ts0x@D**qFof6l|Vm#9gl= z2xl@f1lmw%a4ulx+gw$ESRX9XN_qb~jk8W+pJKi7gnuhqWkYryBYJQ;xzyVwUczg+9$^+bfQh>Mx z;~mRvW!6Y+Hb%FdZeq?mcO?wuyL5;m8n4@Cfe#Xw)^4_pzq|qFamglq1cFK1Kh1qMA>J>aG?GW-s~-!mT9ayXpvaL zYQ_sSzfc3<04@NSUH~W&BL;v_&1-~*5^$P=MG5pYs8=(miGqf$ea*h5AiZm&Ys2dV zTKeHCN4xd=TmdL$w|Mv?p^rFY<>#6r>$GWTLdCoF690WvlK}!*P{|~W0M=dJSf1sD z0+NH=?&#&$4`T*l^J=Y_e@R1?V4S0)KZcrLr~z;Q7XZvI0p#@oK*Iqg+%JzzaycED z{66En$;O!D_h)>-UGLlRR)y@2pCS+=`Q~8v!9&U`0DVEyw&V!td>TK3ou7r*J2m$` z+?AQWYeB(#eY%4)bs0tgWrkF`cb7$aCqBE>sZNC7xF*3oz8E=&L$+OMLtczzK>4(h!u6-C{W>}S{r(d{Xx4pk>B0w&_Zn+8y=u;T>)vd zj=|~`0P%fd9yUsXBZL^=O~yB4B3frGmS0}I-?SSjP;tJ);{+psLu>B&TJGzKK0TYs z4rXXI%zg89pZ1C@4&&m?kMe==WhfB-!Up+QfW;*Mr$q2gF)hxI98)&|>!x(w%ecs-aw5Llf=TI(T zy#95w`(80>5+SSr{Lj7%_Lc>IC-2|$WW9!VJ2jX374vQu3Vl)_dT~A-JIHb=KqbUx zgkCi0DDH)Q(Oh_`g~X<(oRIBW#1tYv+A~mTz+v;-^$=F&xhLWlY~NH~p1K@^ihH*Y zr)<*RGhhwCE5Jnsu>1o6+B@!3KMpDPoPN>x$m%2g(7cR~ zn`jBp`NQiu5%JhDgbB+ZH@kr1b%8_~z8H1LWw>;LgWW#SAzE@X#TB()Is58P{;6Rg zxm?HdN+IcAA7q2RnHOh7IUq{=5jWJP1S}_lUfIZ)f*w2_&juYOTeX`{R2gMWSCLTO z^fh|6I()RYEy<0q~l40l@kaK<*9jpvM~g14k>qHdF7>+J<~aIw!|u#*TDb%#KC!xD3Jj zA|TJtd(6D1Ajc~JFE?+x$2Ox*PBco!ib|}aS2!U%`TF7nD~}_E9lx@JwHpLupM{z) zfy^r(MUJiXar6kDyuiXz1>$i26AqRL3ye;v`Gpz)2XFzv<^n(&W*m6t!{|E=kv8wrvJ<12fH?0os>6oY`mX?tu}?Jg^DQ|Iw^nrUjTVs<>Lo(ZBQb(-kJA!{pMU~0WE8s=}VObIoD+MoR z@1aM+6#%MRD>@~K?OB-l=Roe|uW=>1is2JKlRpkH8D-dS%X7jAptCKZsB4UDWRLh2 zhPtsx;nJvJYd(jPS(L>ff$l2hCe-{w4S)l<0AP0sV4?#6dewI`?ae_j%4}cpI!I&K zW=H?Uoe(h=sXG=Ygz_SO6hHxHp@ltNp4V3ZTEt|uJv9*#1W}mN%hU|q7vFRbd{eh2 zoxeZ&9`Q&3)-|ECwCpMb0_ob>~c)$14T^y)}2;UV4%V-z!hnqQ~^Z~zwo z>@NV6i-Z9{Z_rkk9)FKqKXI%oqt+&;)jZK-8nw(9?thfvHCV_~2IPO7!N^Q-bmIyD z&rRd`$VL$#4%KSDJu!CFj>lbDVMUgD6{j&Y-tRr6VXOcwM0vj494>=Cjvk05TE+H{ zUT;5Dag;M z^VNR3vsj`EbLU$?wz#?!$@cdoR{%D{9O~27cn`Pt>{H0@_# zr2z;=0J9`f*-VAQiLJfcexB%dI0pJam z6ZnFx&hq`4Pt9nDUjWv4a(ei`7qRhN8+436g_+g+Cg{eeT;ewvK zlm>!K4A2{2pJUROC=Lt&(ta|TLpd$#-F)5+mY*Dz>ncN$x|^Qyk`b#)X{euj7_5k3 z1VFnX_x&K}fS1V);2VmZ`Roz(_|QuZVG=txEEi>|hb^EDKVWQAO!^}N+-YVjb&nrD zPGL`k$Ryw4nWrHIip2K7+fPCl0s-Ls2f*+4lW(R503ajY)%*MMVHjxFNwrD<^w6h8AK=LGA5gS^C*9} zp9}*@w;yYxGj*AV&L9dT#~}-nt;z8>Z!G~{_Wm^e?qcQzGGRClr8K+A+W&}dCzV2f zr)yM4^&Vfsj|#5mrCy1GUYApjBb*p`!O;xqgF4vz3MO>)A4ItWMXssky|~NJk{XqC z2BWk9Y288k9Q&^;q8o`)8Et|ChdG(1N_?%)I}Y?RdP_*54kb_n;A73jSo7qf)Ko~^ z1Yc_tEj%7^!ZFoM6n$79M3oZeR+w$ESz39}=tFh*M9C=Ad z5VM}I__rH$v!X67)+aGl&eXa26< z)iXiK)^nmCJP0+vPy^rqE&#Y(0w~M_fCB9h99Q?#rrPE(X2^xzL|zz{G(AO|m_Uj2`u*j;#1@-^cMKojubzW&pXCL@mP%p2nCPjWnBLnSBnOK&UXm_ zU#-;K+_~^D&lTV^ZP4=(m+9lF?!mT?u9D=y_0KtF0W~vmj9(61|tBQ#DW#h6O58vLDy%iCe)@`r=b002CCXQXJGDa zZQ>!Q`Gpz)2XFzv?Gk|dApk@_HtpZu?90nfWEI;ZzJa0rw`?|>nBq_VF7?n>((lxni$Ae02~U!i`@lf>iW$MjgxF8%ACax zDjotr?3?l%|dhYTd73nghl;+c2 zKKg@$vAyCSnyvLxZ9;aH{Atxf8l*Yp~u3&fm4$kbPfTe@~}hZ(fqh6y4Nl zM#6hfq#=kW^a*!UBh>st4S)l<0N`;603ZnfVS5?2_dAbc|ET_GCFuThJ%N~jZp$oW z{)il{kT%KwCJ^DiD9gIkk7h6cNc%~7QKX!JgpVe)A9&sQ*Jq0#y@J2nPwM`g_LIj+`4d`MKMSIBWwH3A z4pB>T!T`15Z@q?a=rp=qL=iwJ)HI=G_BF1Zvd?hQ?He)Gm@72gwRq5nJ{o3mVqK59 zoOS#%tkFOhLQS!x1d`YfztflCXcz?YJ4l+Zvmup|7KgRhDHYKi%YfT^`-48NJcQSr z89&q_Ya~Csi~2RK%%iuN^aW}$ff@j>HW$^#>!RA!sPO?nQbM#FL=PYDl!@&XiA4@- ze45=EQ8Ykq&>Z3r(GJ`U0&;vatm=62YvyXWS+yy+-FT`spUd``$0_*p0}jlY^y<=4 zC0Q~YqII`duy#A}S1+4Z(L)V@1GoU-eF>m`8UPXqq4Hn@x%&z)0OGFi zunhX)V!v5Kf;@Xfu*vBb5&cvVc-CMm@!4?@v@%3P_K2dv3u9AZqs3;lRLR-D7eI>Pi;Q15PGc{xF zXEz5vX}2)ap042-sxSgjW0sg3Yx8IJ6@cDa^}SpJk8Qf^Oj(T|i-yDtj1=~5MrS;F zL>3-$G2p`pz!>BE3!<+~^l0U0QjcC0_G{=oYQ=tb$dz^9b~h#>^)uA`LJfcexBvjS z1aJ%naE;`knuqxyif(^JbN<>iXb|^{)6Bd`Ax;IhL<_Or8c_W6bR6RZsq__q8vIh$ z*u{_e9OH?|rdqpH`vpRjO9XV~(cGP!he%$q-sRtxC@jyl>3ra=mS)`gwwsdK{!?~E z1lP}&bZ_ZZVQ@DQ_QX%uOhDlS!wH~WLI5oUIBPMnXk1= zzuWuM?GBgD?HYt|n-5XBPjl3KVYjTOe&JTQ~qH$^AD^LjVL`0!RVRIGHP2 zeBkkJs;wOO%vM75^jHC#j<`3U#ZN+}D8=NM44{YB)i# z)ti~!&*GGzkcZ5-&O?=U>y#9jO-Rf~k`}(?rEl2pPNgwz7FC(t0F8Q%t@LkmiR=rn zs3ZrI&HFWh+)b;3zFec;MzQ~P>*VT9;P0l40Pr7hM@S^PKac<8i}=Mp;PM!xh2!O6 zNSDb!&w)EiUTgsHL}7b zf2oUObXU&-uJ#}Rf&zaZ`O|*#Ei!oKS;}yqFe)2kGB|rdEkfQ4yIRGK==wse;&u>s zC2`v|CLr^7I+xhf=Ahs0C-weT`$@v-Gpomx#CI%(b06!Sl&#$7ml>+KyFDCe5o^yn zRPhspz2A@CTDWwwrEQBRfAXM!KG%|ZYO;J+h_(??6YcKN<;>(wHB7_wniBKk=Nr#* z1w-=74E+R~Qm>7czmySg!bN6-aY)Ly^J|*{tl5M++-FbWljZ40i>G%DsKJxUBc}b4 zZwOzG`%2Y+*nxly4g`SD0SI_&2mT+g)=f_iA zO1j!mpxllzQW%0t>OVu$?WtJ1eY12I5H|!eOv-_3d%+!YF5m=Vyp#AWLIb z>fJ>Ru>{ttUZ87C{DGbAZCl{*tl(Oz{_!>Dk1zsQZ$%2*Ca0!hUXQEvQ+7xq`RL+7 zkzdHAdl;Iho#X?*`{pW(|6vCL02l}Wp92ukAP&A5rm4?C$CdigEeSVXWG)@ie$dSQ zehT&&P9b9h8j5_JYM^%H<0o5)4gYi>H2vP1?x;r7o3DB^kK|R~N@?Q*gWl4& zQL7b0D8I(k8?3|y2{r;t!{7iev-lq#AppVx0pN200vd_I7sP%85kD{eLZ4B}is*zo zOqG29P-L-#h+YvNRNrec%Ju+g^QswRCoApi6#$jn5~}?(6W8ReUZXb9HoD+M2#26d zbS+tB{J_1tNR|X6fK-(6=~Wa8CmvnooL*z6Us>ish~g9E2h`v@@x61H;2ntnL*B1m z3IPxv2ml`e1Mmf35}!Lyt5sa;UhF}9uY>1?>t^ozmoK`kdW5%#+mX{=mAeAj7*;=< z#Mqcz0Z724+V&%};A(2rz=%PAy;aSirW&FhZUguU~xVNYh_! z&$hu4MTyo;Iwmnc4#oMi&45+Z*FrjjA!%4zg+r#a51U3VLn^)R$O8Ujku z=HC{(RfTm0;MW3$TT=c)-QroL#=^{n zI}|fGf!TEw-`L2*GVqzhHLUZI_AS~HAjjFk7#<_cm-Y?#mtFlSvDaYXL^?ClTMV4XhLHlvq zpbUtL#l6kylR8h$tF&q|i?fA?M@|&{Bb4LOPi0Mmh&b}#?I)qbfB=a81K>~l$!0e2 zZTLj=KmjTnK%E<9X;vYdZc*YJ*Qr^;<@&|A;V664`UD{A(xBAsk6~lK+fVBMyY`cP z>pksp?6_)N$fvPnnpJ0~KTH`jw;w-lSKShDL4238HYbuJxhlvu`OB@ndE}dmBkqm& zUUKa8v6eTIlS(o8{U1y;F3Oq_0c`^h5s))HYl{`bA$Y?61v@mO1amaNBx05|Y>wr(zc50T)- zbARYrrsMd@5A>J{)VYP%VJ_5UXw)%4A^#O8fV#fq)7XvY+iK?s6n&R7A7vU(Dan1# z1-Z1|3GBA^@7y_E8kC(O*h=5#bIpwD1i(oC^g%7c&j(&Z)I2O*d9obCGhaz2-O9X= zKckaGY!C;p!)qRN(NH5G~0QVi)SM|ys?U6N0eckUEO?sl0A!IKSAJkUO zd>5VRd0j*`kZamw#}ts;cs1xrhk7Iv(BRMKxhzHtwXXmSNm=AX2%k@O);~Jb`Lgc= zBY*?hYQ?O3z>YM|J$Hsyo>#&Xu2DJJq)MpcRNo)26sf}jKus_700@BCO8~#Xorn5X z`~7CA-%s40ZFVJYZ=q=g2IFwb)L)CidzBRQ9T^44rEx8^-lIm^D~1OWp9orB z86lXUumHjgwA0IvYh^9wZs0wDepz&N=3ppVz+VfoZU zTn^ijuRv8C*`97DL#muDZ-DgkVh2XFE}&;r8;^9Q71#qe>BB;hDo?{N) zH=#CTV7@D7`Fix}fpKqA0gM22QbZ2J9+o)02?Cb8qmKrYYp_5|i4rZScxIgU2K z0YFVJ^Z*Ee7Z(6pB*6Vg?;3vzJ}R6SCTj}zAKXWZQyEpuLZ_IiY((L{LCI0F47`KO z`u$MLAomJ@;T!FtuB0J;zMtIeQ=55_l`Y~sJaq?NWfH}%U+6#DzzD$L9?60diKh6d zZ)I60gWBe9qi%GHv1JT@>n3BcCQcz70QCGqjer11xCBrF?m*o5kvfc>{CSev(c!VA zOyr{i<5d4=o$rgZXUmXp-B?!y;*BV9;5noH(+t3LVafhY_-8H!y$52QbpBwsW4Qh>Q~8#F z) zEe0QWgpp_-tR#lzy_5i23!}bR03`p@5{yl@R!=i}*xxmXW%E;rNTpI?%_tuM<3`Nu z96ceFlyDd;K)QR`_0xf4%}$bDZ)pT0BiHhubLDSPvB}W*0vMxrGvENArWbku1VHj7 zfIaZCE14oa_|9j^=fL5bKxW(Y%^F@WFTGO#&-aHe*m}wK-y0t z0JU!SwVPfCj94hU--{FrNoZHq}n6KZ))7lEj0d>HM= zd9}aWPd>VAKbemHP3HTJtbLUa6CP~!-)Nm5T#NtRelir~{ygnF@A}Cd+vkO|LPyo^ z8*41yl{p`91&i&_JiF!rAer-{HnYarURDKi(y-PTNm=RqmL-kQG83k$&D`a)?#n4h z>Zy#y`>TC65^4F0j`AEsbw+jp4{QYw1k_^C-@44#!YB=wsr+fgoEFnTy8#ZN*2k?d z5E)^29jLb=!0CMeJ%1Fu)If&>H3Cv`~hiUu^AJu=lmUPoomfT$VZ)til7nT6-iNV<{FQ2pOsZ$T>8u#fM}9uW#> zJ>x_*0s0`r2*3^hJt@-;(n1bJaT{X^@fe-Hw{(=7k>=aue6zR433PA(P}2)N00JQG z0zmt1@IsIDfze!RR@ywId*cMWS(NTILu^ipsYqIT!%%~#N;zXXB4a%sEV^oC^03nP0&AMmmy%P%tzyQL=kcT@* zr_^KY#c9?Fvc6*0YFVJ^Z*Eej0*rA*dE{u zu6HKLri(UGabDe#vtWNBQ6kC&Ky7|H*|XXpo>`C=S~wj1q> z*3Zv@GO7n;^gk%QuK?KH*ncI&)Z93I03@MMod%iA^z-hl=19hKmE1NTd%FiCfDsb+ z3L)F&ZgiA4s}ZS4peLI4b|~XpdZWa6yu1U~?BEpudVZlsKmcT20$2rsTToNg)JE4+ znL3Et*u*4qI$~^Z2Qx>zqxibyvd`GP#0Tn!7R@}?-H5*epmdVIrLW7&-hv!Dqfs*c zpjb#*RA@wsTCWPL_gvX%14aNL)KdG0#JKUjY@W=I{f8c=tyQbn-7J;LwZ~Wnv^hw_ z0YFVJ^Z*C|&;@`_bz=Ys@CngX@e$$kP@+?kp=!qhmeZu{Kd}yh zdWiHcKjTv_n*o~r!iq$@)Bph$kc~B=#!AB5cVYxT`%#{uVu;UJ-{p2mAZ&dY7>i@~ zloZ~6^4|i;{R7}n`^ony8UWDDSj^69#KEnfX>0GNe(>pj$JoqK@r|B!&FqS2oyKzj z;sXkQ9{|uaMnu#zF)7411^;{`ZG_(!IvQehsMzTt zg|cIO=1QGB|GJ2O`kE~&{d2ovUu!hW(HKZ%nDbNrU`J!la<9dfoatb@%A=1GYS(!A zXZKrXT3yL6XB`Je<5)jDaaJf#s9-e8BB{kE)i&qhBL5V(qHajX&dmX1wPDKDBQ$6h zL;j3sdmA8PJoqBZOh6$$?dxl84WAPmc?x*7fo=(E1f<&J1p>g4fPgNv2mt6HM4yq- z^tK$TMtp!ERb1Nc?K<1DTZh!S$5Y?$xKdaHZ?mKt5_A^4xO%^lfBl@4Li4rio~Umf zg|WTmS>-Y}XO_;L4$Oplq9~V47y+EU7V$(IxtX`x%-Y74+>>b*6w-yq6SJ)QBr8!- ztw;q90Gzb{x@86*v(MPf}`$3HE==E916b4oU*^Q` zk(WS1+39?Xhefmab<>DgRGatdy+P%cdhIi}cl$v0bi~Jqu{Hl(>Wtp|;@f)j*)c!W`mRgeWPUhK8M&@=X_1{ zIQOESUvL26r2W?o0-zue06q~Qpj&wZTmketETg+{a;Cm|u!p-pn-cX}z_s$P;vx3R z7@tOAy$ST;n@^0FE#kYX050F&QYwVaE^Mv@`JIu|vdmn60f-(lqZFGV0ESl(^}+~1 zhHDY!eHnUGiZF+?dCtS0$IMn)0(OD^mCfIqx{rov-~j$>_Q9`*04NLufcL=w8j}H_ zk^_6aW%uakiA8}Ly1Z=%UpacK$6qP2soD-oGHbb+12sAT%ID$$$}0dusCXq#CR;pD zBKM3xl+;rv4rZ4)CuhsEV1mBg?UwL{5x{$&q!Akb4;+V5XO>|DeLr>>{9h7Sv{rhPU;xSL z;457{^9VH30#L8*q+m@gtR(hTOLNIc5RhT%RrqV}f!+X>zK*5{$&;;I0k}23o|A7+ z%5V#LGh557Mf@3h`PZ-YD=SOYTPa7}!mwWG7-A(p+VKWmb|CK8{IXX6(gS=f3TA3E z(FVU3Q@WpvzVHeFPTGInAOMO30pJq>0zRNU0)UPWZK7;H0iSa!;@=0=+_N`RIS{sd zTHu{HH{XYiox=b$q#Hj8)I2!90>FV~Dje9eE`5iS^8U_&N$?tp!8auT;V9PlMJ4*u zU(7I8fGE${XOVo(f|?B5bB!Moys>O3zZ|B8i^Y3{kof?P@ZkXdYxcpfhX5!E1c3L! z09*qBAj9_hzO9)e!PR%wm@gY~0QQ~>?=3R2zO6pHgOa_KYXjt7sTnKlKvMz(fV7`H zs*#Qm7F8*-Y?25F7~mZM78D3fv$C=k1>T7A zKg^@#4<*4nx5CCd1)@Jt+b_L`Ui!QJq#@)cZ`N)ApiWTA7(v|_!I(~6|M!$5$?q>L zTKdLx#&p?wCM@`WCL%Gl5x_^(z;lz}IAd{R1iBcl-`7J5zDBZY{#=23I;lzj3KUqG zclZX8M(MjLXGRYNN-^Hd6I8A=8Im1;g&Z`&h4Mkm?DUHFq!hdKlnnE+HpBE zxtzO@JmeSVIG-;e6smKEnI|f$@g9A#!;~vlN`{|GYs!mIeaAX95KDs&N58`i-4a7&(} zuFt$QSE*#=ff0c7cYHdG6JWV~@5`pr&LR(-!j)>_FA_^#lS4-%3o)tiA?ZJ-|8htw zy9DsA8~_?DdaAP-mT_lZDZ!+=wf?$mXo_|Dpld_NSBo@B8(%WuEm!(q>xt|_R{$jG z{7gJmU0tzLhuPNh{0H1}nU>a97-;(vOS`KYsJmeV;E()5YkQxCzI5BR0g3in?a7Ot zpAH=jPqr0uf7msh#livnH2~!o0Q$I80iahQ8boN1akicZ-2eQD;w6!rJ#T=oUs?3^ zll=-Oi%u6H_R(19IWx;Y*FwnR?RwY7ak`ZECdF8Fs73FQZt1=%Zp_ecy1wATY9I?E z0JTHsQ^QUY+$_TWXLx)}lK0|rX}J}vlhV7=RAl|?;kRJ_h5TQna*rk{o37_%gcuu4+m?#t8Onan%hj|+l@Oi@q zNOALQcM*B)pC$~}b|3Aqp7Ds&MyoQM?j2%u;(qZ7)|2BLIJd(cjIY7$y@olqrPi9j&>i#ZAs!B#TXP+Z1 zt|!H4B6fZL%OI@nCtLLo38L8UAL6ca$lD?H$z-_E*7S{#;{ohg(0X3P!sjLaLjEs? zfT~LXn?C>`2@zf+X&Hmzi0cy>w7-&uXh`a2mR};(C8PeZIpA%c0pfdlZ5zJ7^G^k^ z6Dx>Z_|Z%M$uZ&QJ{#5<1jJ=A(SGn0h!<1#uDj1^$uJfTCIYKP~fTTgoEt++cm zF*^D%w;Jay_YdJNA%YR`3h);MK!$+o3jhPUyZ{i!<57z?*SAJkmEa{Leolz{D|5=W z>C;S{OKkpB_x#`o#2{%S%Cs7szp4N`{Mt6FIsI=cg{LJSeoOT}H(}qUcIIVJJ8{)Q zO16geayYE(pQ~yb8|j^QEnjsJY-V3`(n9)52+TRL4ob4nGm3))_-g=aE&+5013+I= z&%Y#;Y33*B(NH^VY>K>S$HKhV0jR@uiQ^T}E4&V*l$XRURGYa21^{V4`EWa{q4Mc7 z!;_e8%QuXgZq*`DZzvuLwFL^;A<=n}e}S|7g>a=v9$|Bw2+C+)4xV!qL4R;{wNZ3Je`jIFk1WSe?`w7&W!@t*U%o9)YAQ6UO*M|rRM(){^PCDZ{P$IvH+1gX zlIA3C8Xn^|-chy16KJb(h--2#&O7<0KhE4a0wU7#oxqoSG^s@b#&;rr{B8&96&2$(pO3S_#ZjFaT7^WS>L)M)Xe6y)ru1#`Dep@2 z(IPp0OV4v=B)_Fj@$vM|#p1n2H@=_fDj}As8o8?SmiZdc*5Y)rq@=IspTj2 z>T3T)GRhSuGkU(#4j@LueqScweEbT40&x%1={w8SDlX8oTn?)pJGS!!M0Olp_UVoE zX}RfC7yTP^Evi!&P|0bc1Dd0Z}YYn{6$4eZt7O z%rYo49S-2H0cgAcFl=!Q0FhN>`^jnzYPZcjO}Rsi(=g_dap2TJ`L+7ImNxcLI@vd-ev9X16107bQ07m2t z0U*+Vqn5>FHw`7d;M_>2FAKeDx`Dz>tBADvl=|UE#F#*{L_SNICwQ?}0Mb&mbvYuf ztB{o9P;7H5K2|bnDC6k9s{a<*s=?K_;sj#_c$0!k!Gplu%|z48ZbKAMZ0{pMGFi3R zH_UuXvF!9A8XUl11Muz=Ky3>EB#!p{h+%yM0p(D!Tw+d?y11<~DNIK-=Iiqjhg;R} znSr8NCy|sVL&sMDOy3G)x5Ur9qg1-nuAKOl(v3;a>Yc5rm8~9iG`-3jSldq)2{dJX z|9p;dumUY26);6C@V|ipK-y0xu4acUh-b!C#-N~LRJhj4 z8#`qf5{Xvg(}tp0_c`8!u>!2;-nvJDo<~3~uVSk==+e4wlf^NVElCgB7fqopHID?{ z$iiHE^>BlZaK3YnOhD_t^)+$qfIEr6{-rP;@&sH(c>77{LLd`Ct$zUgZa+DiJO=>H zw>zIM&|_cDFE!91L5+Y&Yx^S&5HN{YW`*0hrL4`2Yt1HNDUSAOPAg0eH9qKt;37ovM3Q zn1bojeRVvPyjTwgbFVLJ(fk;8B2{`nwFy*?$hdp=W6RtX0LNmU*6^E2pqPgrIEEJs zzveO2kQ3-<&J8N6kZmsH;ll_Zi@UU6!6BY{;^%W2b8XIUg$BhIf5Eqvwh9<*cm?CH z-~gcK7it6qK*uG3&vpQi#hZ5aU_Fup?Ov(Pf^!!4N zfB<-Z3823Q0IEfP^2$u&d*gcCiZ;gmpliL}qyazFgjTBiMq~JXwPpa#JbAEAiaS!S zD!_>1dR;VzB~_I54}`|X&*TQ?rUMq7&Mg*kHky^Q8I3RkVDJ1k)7e3M_ZvaAd#R{y z@2?tsD#fmP@@d=!R!12M_;T-1v->XsAON~908EHs06+srR2*KNGgVe{S@FoW!^P>Z zEO$446~txZc(8drR{aPhm%g@1hY-?bdo#0ctgg zLukk87>SwanhaW1+PYq|-N81(aTHar%LJcDTjUY|7w1e~Z=!I(A@?X|$mRS#g zz1;X7Y#(uCMv*od;X?q_^g<7S0O+{@Fv)-h06CiFCh!eVW+Kn7r^}?ZtV@(ObyZo) z{k(BcN}wfXx1ozHLw?4`ep zFu@4Gu6{PjQMq0rW)k-!&&`qAEDiA&IBl|@b?kQN?ta~P00#g)zfdC}06ts-h_3*E zp1djJD<+ZnY#q`&s!Y9-Xf77LSHG#)J(y)=hW&_l52){!cVmZQKnn~2(tc9s8d{#} zdLxIp4PvtWPiNaES*7D-ih5&DGX*=;^lVr!xOS{L)X6|BjHG@e=l{83>V)`JH%sYJ zo?Npy$%$2>R}M(Za!;_lfyG}tS6VsY8yX*{@te-H3eaO)xxRLE3MRiLz5%w@z9N&>tBIPMI#1A&@GF99m@#^6ecVB})%iw5+RRC( z^PcdFbE*={#sr9L01=oK8?%4O^P-qt&N^1TRA%cHm6@HRermUO{+dJ}^l9&2;DloH zM&I2O+blO2tIfND)}6aNxMb=vGA}!EPDBwS(G+F_NQ!jn-IjHq3B$V&LZ<{h0#a@I z{$K+Hd~`PhFM7jgd$3t6()l5>D-`jr-{RM`AHEgid%C$BR3{y#W~o5UG9mR@-iK~i z!woLu2PsLt^OAelk)?N5ENyXw1zrmuKmZI} z0GQH`0)SjtxKL|$A6b~w-W)`2m*9{|5Kws~V7`4=a5D!_Ag>5$;#`sEYSH!2b#47B8FqFL-0!UH5w>aNVvTI6iCVo_pmkE;lGS3}<$12(AhgI<~ z-5VGy0GC7tMsL6fwbAAb@5-mRLHc#>?jHe9#)=kebdem;>EHmMrWbku1i;V*fN60a z0BBsmfcAs(1CmZ)q5mP@cy+z(%jz0nbmxNvFZNMN?M0x#mxQ-1$EgPzU-JZddZ9-^01RIOs3-+bI&HHwkvaX~J@2&*9UfFkh!hmv;|i&|C;Ba! zk{D|x2#77f(jX#cx0wQ74!7^Y#$1khc5oz?~|DP^B#{;vR; zwYZj32YvYk$`rfTEr^e~tCvly=%EI{Z}GgCa~Qn@kc7u(W8(LnW``M|qxbW5%Pc-kNP;8$B-i^1kcs7_={`-UnJDm3zR z$T2|uzCo2V6^sA^1~%Ws(Gv#W?R>FXYw=w1%?r_^`z&)`Pj5*YykA3ef|_5b0dN2p z0LCr=%wC%XfGi0tkQHuNKb5-u>g)H!@*1M}GbJ6qECV_A_RkDRlYu}L&CUfZ_vkh- z07&~uW6Lr6=3NQiN5?G#zU>C;Q!E|$xBUh-&pYrq4~_N#FajtI8)C9|Y?1V;mF?%J zic_$9|10SKY46PAq3ZrPZVh7{OIgx0_GLlR5Aq=`? zYmu=MkKj!zE=lOHa>oxbBd+zt1*L}Ud@B2Mx?z!i4 z;ReE)(aP9Q)lT7;z$+G0N^R4QUC3`kl9KZ(sW0DGZ{PJq%b$Y>;FXPKY5;=~wsv1l z-O3g)59 zR{ghB|F;^F)+6vgm47*VT~LyIc)k5iVaoM7dKfAD%Ngqjfc5RxhKF>`G_c?z;SBZ|EBUjGAO2>S0_=yLIUNkN79?KCfllCuU7Vw&<6= z^5ZY%#WLhCtLAfQ_pZrLI{iEHlhI(HGg0n=95X8cjx0~pev1#METIF zkF{6Ny5G$fx{uAgC?3MAU(zd1C6MYfi-b&hK~|lkx@y&k9g)lG4yq-x9vr(PMSq0s zkfA-9Br)`l)5e&jGaMkb1Ymk+M@h@C8q-R{FBU16)E>z7YLZBiL%Vu{mC3WZic8Hl zUs_wSN3`@xO6HNATU}B>w1&rpZ`BZZwXmIH)T0MJjibr&GYzs%3EI9l&(XoD1wb*8 z&)ZPI2nbTz_WZ4 zs>qRD!CV+ghf-_TJcH`b2P2fv7Og-UaeF}hNvz=?k?3y2gCSc05OutY?WI|EyC(%# ztaRp92_fa8gtf%e_m=P~P7AjNQ3(JPAsrQh9!Id1@7bSks>&a8#G4IK2tI}^^96=_ z%3r29 zcSBUkq=7)$767@=h}$p79b=Z0VM%N{9^ToYs5d(kFhs8wT#UL~jtQp{fF?RAVMb9_ zRnJ)6+;>UkrGPJ3ISP?}Q{DF;Wvs#>H;VqHXg~vi3_v#-fPTYH0OVR}zwKL4l>hwd z4L<{uQk@5w;5HYB)}C!KVixWN$Gos~uGxJ-&AWa9P-&aQSjS%TA+Fc_PP#TUkXgVK z|4uryl3n6p3Zj1-wckc77$TIb4Ys*opi8M@W?|IZp<{d3?+6NC|6zZssHB@FMgLMX zpaDPzpl1_+Iwt^h?a34NuMi;wEAm-sKY24|QaNjVyau56(b8K^zVO`1OFm>0!355*I{Rt%TQxL&wr&;Is7Wwo`>IB*1oaH`TZMFJM((TSL#zru z4l_Q- zw$TNc0$I%y?8tzNxH+thqs3YD0}k0O0IXYAf8^KhYmKo1?};tznYA_CKL6&YA))fB z5i~GR`4yF2fWsdZUd4>VJj`6l^j5Pqv!$8UtKDg7^#V})dB~|qCW`*0Xg~vi3_$ND z0D137Yw!V)Z@2CxE4}43Zpp0dI)JI>G^M#npvSoUoT0Hcp# z@zA&uw>+Dr=G(ENin@{pEvYI7=y&G{{cQAZXet4?*I>r!@i4ZjXUZw{=-_TGTh@HRF8}jzKE~Kbt$m5#oag>;b zBK`0mt9x(Nh++4(?`6q3-OarPKtce@GZM&I!0 z@)New+wGoOg|qmB@$_6xWpkc4?8kmw8KG8$KJt-ua>~y^oDB0#gR!Vr$)7s5$;GK5 zoz$)K*uO%5YJzE1*h-nmMU6vkHMd`Uf+(`E9%BtIPXMALmi(e8Lp8zsn7O(~W0A6^ z{l14!%Y8S?TlAX;@4lRz>pqKX6lZUvMF4UH7+41YLw|e%GekcKfRY;GoNxqNl6#Y? z5u3`1mx9^#&1#(@o)WKx!o4lqpJN^KT0L5GDt4~PPdfj9@{{|RwIyx*M{Sv22X{rf zUcY>LFYL1JlxTOn$SRlnDYy>Ww#b>aN+3?CMPi~XV~zzMkodYkTG-h2B18O0sQQsU zyM-m%13k4Svlo!?Nibl{v0N1TO@1;e^&d`(!wG)Ax&py?)y(ES1@6i~6ISNA|>7UiVUk-@7 zfBSLs!XTH*0})p$W1jT>AkJq5!oiLgIH{Xegor_r`;|k7qzFNn3<<4~&OC~J8bt#d z@?_)($uVyz00w}}KBFj6?#wG$E|*MCES|>c&D)o@k1wR6uA&A!g8$=<)&J3AV|O!k ze5=#rI`+}u3zuRs{A7lq^w32Ow(F&Jz(R3t3Y|Evp18C~B>+ytRZpgX7vCfmbVT>~ z_=c)ew*+LJOV6#YxlfCc~=fMGHK z!w%m8a1Mj!jn{L#<1Q3U&~U*oIpMR?6X;qC8BVZ=c?v1t`l>lTR&kzG;T&{45of$c64^7{d&LLVX z&?VnJ-di8r#6tss?q5h7!`=^@0KEO3q(AEQ>~Ns?U}+YRq7l@l_S6iC;9qIh&rpcT z7i?K*`haEEaoVYQiT~YJ7XZz%)|zHgV8YFXYdUrSM&2b4buj@~{6z(?=9+8OP&@xQ z`O!ZX0TZ^3JAv`r)hCUAeGB*9LG5uw?$Cnev~D})H*RPVfHEN)0DL3^@Il`g0FA#A>xXVT%)`d7IaqO}^HynxQ-h7E)_aQuqeiT}f9>>_fSn3?Pf-;L z8y7t#tC;(KK-k+4F%~acXs7bKzCM)^VCq$y@anH)~5|BJfx>NG#4(Sf*uA}n9A}P|EfF(v6pD^~D(q3$uY1>7;Dsh^ zve+#5^mPogPp+bwtnZ;rV?u>XIrF(JKlV8o`at#Oh9mMAwU*|7JZkGYBi|Yw3(w9v z%TQV9^BH-}vB|Fid^!db@L361shCb-F@|rLefuNZ@=CqQ5^j-BZHd{lpkLO94Zr)I zX9Miu4*`b$?S2{nM8LhQ%w;VfFgVAC`n}3{_}4lKV$qwq&=LPVw zRKA+H_x;&(4Qh?G!Q}^+3Z)}9X7=5T2`Q6`Ed-{jUu&vCwgonaMkH2(-~VwK^%vq& z<=yE`U1~rPl~j6@G)q@_Swb5ie;AlyULb<$IARf61Jku5`A6+aRGj-v1?(+4P9G*A zDAkmpEvn#1gWC37+z`ONB{=&IccT7ctBu%peom2R1T*IrcV^w2__VM#TDWwz71Ubr-hyp7^Z`sIe$PDn4yH}C4E_1s$R>F z5V&X(@bdnzq6`3U{>vYkh!tcTOY6%4IN%9_k|*H3`m&K=PdCXTd;qL=q4xfj+xq&l zIM>r2OE2-SXU8?=CL#VBRG{9$DY}Qq33IRLHk%l`TrX#llJJuX`Prf?<_WK+6*%6k z)9uXa#^GADvC>j4mA0rwc8x(n+TaU5F-*r<)oTk#FhtP&(P8QECl!HcmjzZ{!W*%3E-OuVpL)t8vp zO{1SONoF3Q$S>yyu_!Td6n<226hW#|zRljYl5qsKdg1Xj!tEhHE5!%SNC-LjenRut z-S-0+&TP=8kCb0f{s#YvcW*3j?xxbynp4QeP&|dr2cauJd|&Wxbaw1#Z57>Jy&WuW z>oe4ndpD2WW|0I{E+gpG_?6qNh^h7>FhY?XYToazdzH=LitmucFD;S(FTh8*&A`Z&J*$1`%VU1IfuW5Y)+dZmd=rP801luRhCcfhmc&;KAJ`(sQ=H*>L5 zq5CTqThf}eQ=1(=Rp(ReZdv`A+9N~EV^KQwCz*LB$l5P$zpU_oE&lsmdp*URZzQR_ zE^*7?sK1sAgW+O?g7=bg>7~6g>7MiHY>*Q@8q0KIG6mU119LD+BklB#h)lW`2>OFY{ieU$04@=dOKIQxau~CyL8y_TX5=qc z3Z!HntT2c1x6liPDV}vs0w1})Kbia!sR}aAHxbe-P%73>y0MYHYiXq3S=gVqIvke2 zqb9$ctW79AyQQs7r|C!JSEGXNZ+`DH&jnYdT}54kRN31+en$;ot^j~S`y)`!37{KJ z_$C>BDJ|7#?&G@Vxb>CHoSy6;Ju#f4>OO{WiCm3(`Coio26D83@Tx_m|5UVnYH@Mf zPv)jl?7qy&X6Gk_t#augx5t>o0s+Z=s-*#LVwRg@$vG@N`kcJ0je^gFx!*@M&c}?S z1~N;)o&Ohye<}Wyhy$2;_BYAn72(S@YXANSw`nOE<7C(MPt~V{nq68vAU+F|wrZbT zS?=l)Q&^C+|Ht-@<_zf9c|ot5!WYL!cr&*mu*yy-Icy%!aCsD2k;0`XbW^Yt^TaS4 znrLk@U4hQ2W7{Nl3bPhlW1K4avo467$Pxq;%ImCBJS)9xWg3M)pKVv z`suiRvq_hdh7wcgisjwq^R)uv%BwU&MnXY%rEM3LWa($&dOsF%%Z2jU&gsLKN(^}X zSRuKWN@%(uXlN1#Us8Sm4nEK$j{E_DYEsvJFd1}Duf~Y|S&DP^zT4??J;p^{Cu=|c zv6X7oEduS1OD89V6wbs}T%QetxN-=i&ml}5#~4*L4>`0X_W%Jvepx3AU%tAY_^B0J zt5*Kw7ml;mQz4@rJL*U@}L+B=R=L2Q>$`ag%=9xSH>Zwxc=6#z9$ zwfxTThSY<%hy7Ii%d|ss@?&oeZZ#X;)UC@R#s^0BBIK*i zBYDz|4mAqT>hL#OcS7@TzZ~#`s=S)?%@rryi-u$9n8^{`rjz+)wJhLF_(w{&Ssj~~ zoE^56fA~r(!9?t1wn2c<#NqXCWM(W>CAohsc6!6SDIWKiu{%shaN)ty>@l%Z71O{v zk*cbIq3oGc6=#0N=WRIDLIr{uKg@Dbo;C5dmc^57?GJ+EA# zP{{G>41HWWG2DKa%6~6=g%>v9yX1E(Z;qAfB%~cXrLdQ0OH$CPeNb%_+6rn0=RQxz z@lvwZeGh+_TV|{$KzxIn>MPtPTLFd`TyZ+H1CnE6+InyxH>Qd*0~?jajY*_@#$8lT z!-Q4Dy7Jw)?MYQVk=+8_5`sU-=0&TXj?$4PsrBcB3u;vcQeVt>A zU}EntccAXEwv+IzIM~R65}&Ta4XLzFRvi%xn*Wy^`E|+3U;ruxPToUOiHS*Z4n5vn zA>4|OO3R!%3gM|UW%#pZ7eIlKYU>@v)9HSo#IB8_8aXsVc}eJkaX*p9AF3YJFFlg@ zz%S=4P1vPn_sQ5&zUrfWoD4+e`YGQv7ukr=L!MgVD$P~p^PL@eq;YlP!=Iv2u{$oU zUa@WgglT{JvY$7K;c^OI>LA@X#q_S4IMwAhO<)FI-F?0oP{+F44Bw$bsphm)Kd!R`0v)wsd?03xMrU%Czija5>-e3v zuC2;sl3-KDnxka#*McB^iF zZEO1`*@apL5pR4=23Ak3&_^e%Ht&EwD=QLf?PW<3D$Mv@KTI44OjD9-1aZ6lfT?b) z^C}81ZZZjqIbR~94;$TCj-A4-@)*GOzV~F*%igImKgg*xxFJ+W;BzAvnC&CfF09%A z`zEKTuQTUF2INL(IWclc5vwJ}JSej#Wq?D)a{aFEf9#2T@B9q<7Y=hVc*LZ1UG7m! zauFP_8NDJH+3h9YA?P`zo7|lqD%yBdWB%tXaELXVF9XSviH5HTb$|3=)LAfK zwP%^=xO&Q5r((|L10Lnrea>6k-@Ue700rQt!mMztpaoK}O@zwJVaorOaT;(#~+|AHcS#$ z2JMg?zk`9z#kD^huekTCpsL*~TM&HA?K@UCNUZtccZL79YLTz2hPV%4j!Xc-4mM08 zk>O}?mTc;{8UnvuAIZQ-q^$;mUH0n1B3rPXQv5R+&S-Ztv||&DrQro>I8JBPJ{Z>? zXL#)8?B;w{-*&xZvX!=elmZtP5qU9p4D2WP@qp^Z%l42JifCct5V%8Rf#Ru!Db6O zo=I<)ktbgFbpD(Ex9iT^w^Z_5lnKdsmgH`%W_zWdq=+~@)jV!f)0!i>uQI!p`kR)K-)Udrjt7HPJleL>)FH^fVFreZeZ-zY_|se0 zOJBP_RQZmXCg(bF1#dv0h4ZQMm!>v2k2_Eb?6KO-Hf3-m?EU@*I?)7Q|By6^?}gtM zD*u6*}1v5qn{mS3TX@2583pz#jwlI#rkShS2WZHjm)qHOPG1zZYLg z`JyQwm)R1kyus*>ru%PIXm?0!F*CZYY`m2Hn145&6bo0dk)bue;^{(U>Bnk3S1G>K z*|GYaB4cFk2*%Wzct83)fGEI4-rd{q8j+eGsSswg`Dz_nD-ko30pBC z4QGE^8+1r!Yx*Glq@SW<^%g}IU1mRDy|3zg4?A{mw^}#&wR7d7LWzNRtGFk0xjb+B zfB*LWbpWYer;JfX~#hpL&DN|hf<=%i1>ic*uMOYDw z#PHNv)NTf{(LjX1Io{X6et%-xJ_x*WdS9k&#so)O^F`Q~c#xUXuxPP4S3IYLCHU1sJ(j%U?Ew89s@T+W!R`NTI(*T)qQGL1zcbAC-+pCaEo zVL>#RCzHmiQ?HetO<7C_KHSO^Jk#?tQzWZ=y2xY9!hQJ_1y5Oi-3iL$|+Q^L*>9J;DCdQ<1jS!W>K(7=!+`3vU<3Taa$kYrco(57k-$JfejD0wT7nOZS2CeUMIre8T%j4NlWl2P-KyA z49wcC$(q)2kV-y+3Vw=1zQ$GuvR*)Q_Bc7Ki`??Itm2nF-PDH%*DNCWliK8oH`Z%N=J)u+Bh`;psE6DS+KmW3c-pP@4@a=pPyXc9@E_cX2{X7Xy!t5W=l4M^E`?&)K_ORY zmq|yZ*9}jt&k=Tqh<%t{;Pir#SP8rJvH$2Ks#of)!XS34!Q(PLdQ_)xE+xxQ2nRA) zb*Fbc*KOLEtnFz_WwOcv{eRz~UVSSyLOc8sUQl`4HSY=g4IU+!Ly8{LC3nMnz~SEm zN9a{{Sj=Zt5h?36CEZQl?`^hfAu(F}eMw6rc+u2LDCrTC%sWA%j3lIv;olo~IZ)#7 z?!WASK1K8{*Er}0!!;4$^L@-7^7wej%YC-geq`l({_c%p#9lQeh5*XY#Cg*rx6D|) zgXdENmOUXsxhr+y4Ky zBfYjgPZVI@ZTdL?j3G}KF)salJoH&g?3&!@k~s1oTtOs+z$mRp(N4ztv0;{f5dAJF zWP~YmrS{RaN}s^fcS^=ikwRl_KBGkV*up?!T7^x_@f7vRmm ziTf?3Q9PaxFyRGj8KB(;{zCY2CX|=90~g){@*N`*F74L;jjyMjR5+p5+vZ|D3%Gxu z+s91czmZPiB|r@Rv^h}7Z`$#7mubchrDXE3RLF*oe{?nMTv4KW^UvDi!Vs#@$xi{t zzoKrged=GgvC*4R^}^MS%|dMMy^3+Na>D4kTT4bqB9Z~E35YKjV#O=mLAI#vO|B&q z>OYU`KF%OVb3a^`@yO{7fh_}U4Oze8{4v?t60#~DO)=jdl|j1;WH=DEC$u0od+tc;<;7*1?^{C4?+5)*^GnbdF0DgSxOZvS|_)|JrJ)|Sf;l`R|SzQ9I@ zOz~89AS1v!sI)t=*8f>K=4X=|S9i~8!=}ir((M{{J%bPnDh~N2==7uZZP%yuFJgIu z33P`k`*g9!qT$6i;goY;k!rs6LT?O5{>pGY(|~WJBmh>`dmHY$HSceES0=hTk{RKE z&8HusKR{DXl{`3Dyq8{v%&ocb!k}`?z2=q%#^&N09{t814m-ZU-w)kuIp;rb5<3ieOA zW!i=gTUL*$0vQZCl&(-f2p2yPmZIZn_Amma)hU;$-3pB*R zIy4*bD_k&$NTh8T{#v6DCy6NwT6~XXXnZoBFHE^jpmeLr)X@1Cg0?8UtcMe8@3*Lf z`)1f@Ul*+c8QM@sPlr>D1DAHObZE3MPXLTu=Bd7LZz|5*E1SDlHcsVB?WO{&sXA74 zdqSX}Z&aH8=c@U{w>Qu=vtn%ol_A6mj-TyT@`|Otqk8y*lrKd#CGF~JiDy}!-aI(n zkqD=9ZSzYXh3)ch&quDE2!1bN-WNfYc@?$QNVs|Z}FtSX_lm(y#y zJ4&Ov?26%yXf7|tGULo5^!4)P+IfcK(aVEigim(7Qdf@fobEVVKYQ;_-B)<-gdU!P zqO3&+qhRLy3}V6uC{vS>m+|&pW2gE~nda@%W6p7EEDO(4(FeHy=?8iUo{>1^T;`QW zVtK~AWq)|Dw~C+`RHa4~coBuYUiZ9?$VSnX@ff(y?_1$jAiR^fjmAWgWKTRLmu~7F zYkW2f#+)zDR9(bAL8o51{kS>DkfB^z-ru}0upaK*uaiWT`u(s}KI4lPO-1{Pr~Es+ zGheDq6_%uwbK?+AqLwD#umVxl^Zx<&YNDPa2mm&vVgG^*%fw5*NELq{!eiTA zaF%=Noor_!O0xN5STXI!hu}|;UBFZx3~mIA3MxF2gU3nG22GbWw{fLUqeHGl)St>z z=Sbu468@e2{439qAB5cmR)q0VgT4a2&%JA z99~dTFaLYrp91OfC`kN~@X;;#j{}8L^69XBVVadz!VuQApzlgv(DnVO38Tmeh|EGd zymVogU>?IodKcBgwU5_bsvvHvz}YYBwtlDm1_ze#o!u_oOw0uq6hSsITLspietx_T zp^81RmkW{mwd)qufH@PP+q^%$1cL)JsDhB8Qi^VSG5aqsRT=pB*w?0NH_16ozf*m zgR%7u=gZxAhCO*G+u!N1U2-P+Hmm^>YkxV!TpZMyOChCF@rJHTl+drq>keDG3UYaDGeP z_Z$)A#l94Fu?V*|Mq3jzZ4t(`&7iO8Dl6s4i*>!^Lb*-avA^o`wB=N+z#&1Wj%0i` zM0`bIMs!Je5{wjDs6iiAB6<1Qn{f<8_p1t7<_1=#R`}`h*Wo_7SZPg9sWwLsQP!eU zNVVYGreVK6eAOP#km*C{(DAl#!TM3Fw`xS~8B(8llc)Vo60H1)CF%pAY%tFfLcvx~ z_q52a1TnFZrUl}uPh; z@K$(V_YiAM!-mxR08^!)maf6pH?V6!zk+=V!T-{xHsnuco$Z%*Todu)#nHAF``3@} zN`knGP*qEnj)fIDBjMbSJ^8_WiF=11H_(zepAR>;zbQWAZ+NeZUh!E3EXGyTb@e#) z*7X|H%2+Fr7J(rmqMyS11k-k%@Oem-nyDKx6wZoy-42KF`g|0O>Hjw~iCkg~0R<4iNDI>Zy5X0+Q+ zU_OhH(8vR{9Jc*(BLmG|z8;@regahDGSJ_IXY~+nqq#2kgoiO1ftr?R2a79`QSA_n z);HO|&f~IjEFNa&ligr?_}X|ubd?EZ6TOYS(!s80GvDCcj&z)1ai*l$Z}CY7#xi^p ztQ8M-_w<(o7i~eiF(?7d#uLpU z*<^<%6+Zr_PbPgpa>V<2+{dfFHC$f{>d&}rM9#DC@B%^Fr(ON2``pd53!&2tEHtqH-1o<73zUd6_Njlq?-`y{Oyg!b- zd{}xYCIK{VT69AjLS3IZwnRIVQA}(iZ5SVd+%|tDM7MNUC42TS39fcXqGDTlK$aud zk9Y!CAGXI-GY30;tcJq1T171y@F|XKS{L;{OVkDRo^=*bNS@u{~Z2U;G1Y$w&XuN;dPa#-<1;}-}BXyiT)Z2A>_XNV)dP?2I zA;|nQH$&PO>v`LrABjnw+j?i6yK(IU4gQ(z5G8#ff^-Sph(5;2xeXE*yDLA8lWsXo z(>iz#3OmI0{dQc98mQSFUeG6A(Xn4*jL1J9A$|O(#Y;nL6mzXN8hH2;g#le(XO8&w zohE1!!gr5xy^};tK4*cC1v$R-cd9PKS5a=Z+C=8X0cCC9j^o*v$8wS0@b?L*mcluz zEWlMM4KrZj_KGPkPeuMDHaoIgrfO6Uf-2q5yk)aWD-XOh5Fg^z8h1R~i5NbJ0H`nu@tAC&LekAfZ?GVk#OPabH@AF# z&=;^vq9{mxe2i?}gVrCb^$#v~$8DVuz-geu8@7F>6bOfq>=NC#p}we915S%nv7_=N zy}gLi{nDX&H-~6fh!#+Hu54yS5$Go*?$1%qE^Z!~bxIc&WbtRfzwSx8mN_Y2c>Hf3 zkQq1r!CW+8!?`7KWq71pl;Fz-B1DdxAZ1wV1iH%(7}*mc4jTI&zV^WLx^0|VP=*;e zAxXJugsv6*Cv6`P(;hsgd18btX_uVxV@2GObIDPynwfkkLOHT|t&F3$LqZ}B(A*_Qx?!GFP zZ|1S61Kw7>>9u&n3cxTW^2S>*5Yv9yn@HDHUzQrfZB-guGVaegW!dzq0&oro|0fg1 zAd@P6@ERCo6Z}qeOC&+OPR|LoB9e6`s&UNjm=Gon9PutHjU{%I^MaLhiFe#407ebf`U93_n|Y#G0Vd^rNHV-y#cZMKME$0B*FeI|(DyZCGo&nVxzv zd$Vffx3}n`S)Whb7km2fudr!jR)}|a{KXFJd2^U!473{6{?<-Rw zTLiy3boBdB(=)kHGH!~o`U>4FlB@fX4Adqpc<+$SoSGrSaIuPFr`Kzztv;ke>9_pH zJ1Y54u>cr7j2zu0U&Px}w7YFlCmZtHdtEb_AJzc}tGK=gyG8hjk&@rAK-x(Z>9Yy4 z+eC&BqOZlDZC_a?R28ann#uWE#aqj26*AWCBJg5LSLnh!9`(WvrP9ColuLjRd)Fm~z7zT|}oN)8EK(506OXM%O_0Ad9fOw2l2{6j=j2bp7$-|+3$ zrOuHpT(9VXD04&OF4usiy4bnK=UuWUMfCJlz$?p)cLEMe;U`;3jan=ugjZu}cPZ;3 z(|7mj@4oRo25_`4ldH`uGDXhyW^Mr;q5X#c4xSPQmMO3@J&H<_KMTL#`(R#9?c+{( z*v6sA{e&_v_~eHTEFMo@?daJ|(bS7%jmcL#e>lkHKRaeo@P1eR5Ko`_^24NCZ?X@Tc< zoVTv*&Gxso?_nQ&ZWI}5ft?hsc`X?a#032)hqDBx_S(_ue5NM1w;Mmh5`9mO=3{kz z{qrsXGSRU&Y!`8dh47>H&`_~5Pe^F~Bk3l^-3tfQ1!)rTj*$^^SUs>PNIpCmH=|Jh z7H=8Mkb7tykheps63A{A(=Q$cKg?s5A?DUtgXG`B)Yf!xG&rvO(0s^bbW%jTID5Ozt%|w^jkDgmhba( zc%s;t#lLnhyVdc^B(lS1jDNsV^lS+HRNla+l+}mpMF2sa78U%7+m*jDlrShpw2hMe zYA04-60zh5d!%ZmX*rnn= z{yKAegjc+^u>-Ccd%QK&>a1tGJPU)`x9d$I3WDRFX>w$d()_lv_}1T$rjTU7V7K1QF;COaqu(s`QKw_p zCZSYt(ct+#&UpCAU(sic3mF4d=p8WJYjGt^zzirB(@crlTSRWsa7dDiGEh8M^3SG` z^Ntd?d`@5x4ckvyng~m6>C08;=Q>lqwo-)iUv+}5a7blIR_}~1xtNZX&OL;5_x2Qc z?MVihM##MBELg80EExk)gMk_6S*YZlTVsZ{g4~?~+R^1bId6D-i2{=!Y7ha#E-XKG zTAI|pWNVrvLFpV#+l<|Ka~26oxOhRWmkIZ|Dq0I*z(T2upmiuL+U)#Y|4Z4LPJ3eY zU|y6zv{t*tk!gJB{l28~#xBaHKib8S(xvd2ipcD7!I~KoOx0}$9;6q%|KVMrix5+h z`1ODLy8Q~bRY`!M_BYtoQ7CLdJRYfd4mXRNu{5Nv8A#6M@z3(z?%fYDUzblDN-uQd zqJ&PnRxT_jXvbk)2tO7A#|JBK5p7eYxB41;udlfKh}==D8-POlo*E%~uKEQt{ABG9 znkWAAE&54%kSjeC!Eaobe0k_^SjTxS{_G3qzf4RC>99eUiTFv>4YnChU z6ZBagxQ!v)WKp#=nn_r&ojxS?s~d{xGebJNObSkF7;GT;EUVs_q@W=YIKWcfmiJ|g zbim#W)f%+%m-{0#vyP=6_n?<~K*s3PuleVvF9r!zD02aC2aC+hm43_enH6w&5Fk-t zxgjSZ#utK(+ZP4L$=ct56&pfeyZ3}{@A5$g8qQoe<&JxHaD7(CePv& z_WVtUcwiO{ulF82f^+Vc4FwmwIm<^Keo+6+i+cya?4}(kRncJd)lpJQNQ`{MPy6GI z-8%3Rf!>yVePMP54@7_T9T7PA=s*Vu+s`T} z?bt4Gf4)iHmJt2{f>F?s$xBMo1{UUi!EzW`WKksQL^mjORnrrW_rrp08)36VG`m)4 znXhHI0Y!|7j7UF>z5F~v=$clbc`f%1ZuqZo`@#W`y)xe{bOme82P>(s;Iwjd)f(>= zgj6!X>nygwZgp84DwZr(Fj${FJYLTzbgf|uDNNz&XZPluS?Nv6g+OiqJR;aqj70HU z$=T%}B+q#nS#R%`iMtW>{Y;Z=@0$X4P<(zAKL%{{Ws5$e%fo+J)iPPtiTx>71FS3&k)l$h)mQA0WGVl>Me5cch;o z6Lu@QqXseaIxY$_AU1cf5k*A`()bdo=vDxA)e-s5xdquhCm82hk66 zm%e`Niutb&pVEtoB6o$XwnR&a-(Y;|<>}$rNnrf>lT4DL zg8aNxZEt^JW>sd))6JdP=Hov0w%?Eq(#fhr@?ESP(^Z(gz<@+xOliCcbHjm0i;Y5H zW>o$?B9K9uOClabC)hOyXga^+*)v%q-BYC>+y_*Xr~X<@=oZh1`~Lj(4RBpnW*%A_JZ26QRw2FlQkj6@6o^B z2sXampGBWD?`?|~Ke=Te#`s7jwnonqdV5#3$!fYBvNwoe6nBd*fJnTpL2?2jhiX+N zIbmrd*VQyOd?5KCS$m(yD5rMNPt|v7Z|90T?2D6l&UFNEcq!~(!fESb^9J%HAV~~e z3*=PD!B~GGV!Ldc^P_0y1^kxoWdDH&xoPNeKEl*@Y~{1@Pgo}j(|^uOU~ zCLwHsKO2nWA-~<&NX|$uhysQdgE)S*a2;8u=sYPcfqXw!+y^FEAE_X}&0Ozw=Ad@u zcJr{}0)}GzbYZ#L;2E`Mjo{;R$8ZW-0~hexXGr^)N>K3!bb^6FuO;!*N!$9L$7j3- z%~Bl-U#zeAYu67JSJ}tMth~1S?Pb1w`}N`_jOgU+h$05MtycwTIDEZH`*$+uK4~sa zCj}AzoWaEZ{>pGqaIc~^=7G?DQ_+5^er}&ZGayIJsf}sy@vo7OE`|YZ>`xHN4Op;OJgw9`WnR`}ic58$}9a?+Z)b%&O~4 zJYi<&f!z0)@F7nBuufK{O1m&zI8jwTe%BlNStvxksmKwcieHdp8SZ^n4|eM^HlJlV zYhZ)G)@5VABfn+0uD;sR-L**tvNBnY6L0stMQ#Mu9`^Y`vK%zK|Ow^)lZIJEadi^ni=reV1fC4=K%j=W#XyCQU4rV zG+J*aewvkpp`KQ=e{ht2cbz=JFI+)4{r}V7(kKu=K+__lvQ9)-SVI7OkVK6`nS9O+ zw76D5LOHs+##%uTc&8XO7OqRexo80Gr+WH*qXfTnX?w@9mGRSeg48V=!QVf6im|2r zz=#;@6&$jzAxfgutvIh?|)vc!15 zt2CrcBMtu>)9DyP>>9abc{b5xnT!lf<7(RIanoVTCG_{cZh%E9Emijh5$Q2Se2`>Y30g%F z&mv6h!Es#+TCj*r$JhXHyex$r;;*7KayjQnOJMacb*2Ti>*&w&UmEh;WYbiIc7>`D z6?0EQ(d zCXtm}V{AW}bdk%Sij+CM{3b^Voa=A5FvccOy>W)wIH~KqfMW#ls1)!|I`&RpQ9eg< zvr&uv%}2Vw*e8Yz>tV{-*kxCg+2P77;x;u<1Wf!g{h#k1(43c^>aA13T8IG(txPTRG;BHm2t) zK1}(uWbp)k>=;us(U*(UXYob<%>6kTG7`s1@FjLLde2z$K;X&5Eap&hrzP9u_w~u~ zzrU6ecG_M_Ueosf0?%k@-Uhq7kgYR?$-lzO=4I=AwCG-Z4v3B4o}o;FQv5(!W^^ zM`#yxCos!A@eiES^gplyU=))Jc3UjR!})~N+}SVo`des@vzLzNyA0lh<@zo0J|P0+ zHtZ&wAgDif5BT=mxgUy)!Q{4#=Va266WcY1sc@K~`nEeJwI6al*%$4ghU|>-<7&+% z;ppnrW;D;+>U$8I%n4NS?raPm3+!P^xK-hrP;p@G^HYoFkeIM#_(64<L0@%8s7lPHOor~mn`z~+n)lS zhE$}b3D+CUPw>9Rjw>fE8`|0#_af2?2O)1@2fTK@w@7E{ZIkRAfej-U23Bfrj0 zCT*P-;6mjnrF7=`HR#xG5n2;vlQ%u05>8iz?WK|fV-89qI+=)cx$~8O%RH$yGN~u6 zSI3)9`u2na;iaCwZ`R#L!p>0s77PVNmLq0P+6{u00Pbdc(0y1R1Knia5*cFnmjfBt zr2zOQ(pDkB@6z{m>ru}t%=mKMJ`8RFLcW5qtF!t z2)G3A{}D3JtQe!GG_z}22R`&ssVZy*`)V)uQrIwdLvdUYXHzEbO?fT5BNRmi*RJxT(_+ z-^u=xN~2;fqua8d{74t!ajx}tpiAxRp==imKU&HZv77(l98@Lm2wYHP_zT(-bf}i@P%Xqg`oW9w)%FV*o08G%?D^D;B-Y5=JX~*w^)R(} zQ36EEI*D7NZcz4HBm-B$wbzE!%D~AneA=Qp7uP$Rl*{kqtCOjrd^hLiE?l~;Tga^t zkmImD95oY9wk#rmqzjoxQ)#s3TeK|{scO#W6IlUw*vZXU-^)woon3yRr%$<-o4B2T zx$7E>b}ai9?_M<}p!TAuZ8YDyw4~=^spPO)U2@pdGNze|1BcA*X=X#DmcC*}zO9#8MhLou_EcEWp3U(B$bnoxtqq9nS!Sz32+=5`Qk9#EnjePtAlKT8)R3yPqkJk!9h!@IjOH1ezkIcZPz@R$$s9##_MVe?vKO?0A<%Q&FskGLo2rTeAF%wv2zI;7`Rfjfhn#&8WM(fM1u zO*U7-K|(C~3H?RWFNp6?#-1l?iX0h|Mx`~(HN{eLl_13$*{k+uIk@X|_r__?>3&(( zPJ-r9Tttba4+f&#c529JJF)b?1x|g%q*EF(w0P0kEnwmiI~OwM>SHg)lsgrZ7}2Q1 zzUN)WHa{#MIZ6dj__n&S+K~6r%DLn6ufSmI-ATM4e!}k-+zci#R`_kYuu__8&8=pr zoM(nD+1psqQtVNTFJMX3G1Uj7CM$4ku>7s|>kTB2nH5?6hcLO16jHhyZAJC$%)T-) zhqy5)b`>kq5m{p5`EzEO2lAFXnJp+NfrBC@CC&SmQKX1=@erc1%S%8yHU31sJ0Xbh z+IR4?_YIjsd9_>j_LIo7*=dsYvxIND|HqMp_w=gTfRS`Rg)j9a?;7AYmk>5Sc%2l^sDSDJNytfe zK#ZObmdAO#DULI)J+A-0mM4Ds3BwXG!96kn@%Bdo4ivI(Z-IiAk8PB@+o?{}H5^Tk zj4WaSV*K~C7R7%SzvnnLs1wkhSh6u(mZrD_zd#QCHL7n7a2wt2x^BTyK$x0ns`s+L5_p4k6cfn!nEN8FdkVjy+jp-H zv?}FrP0m#Jo743Opr6|jsF5#A^d+0hKBf#x9)9g1yMNMlB`^@VVX91-uuu7IT0tMx z%BAUU6VldqMpK4DO{k~KU}X*$$3JG{b&R&7;Ll}u1`iWdqzEUdrPE*FaB|j#%F&Fv z_rA0A?d5{7uZ>Ul1N_oxtCn1Oh!Q8h&#|oj0`kA*_!qQ?SCMr1^Ji1du_y!eJu&r06(9Y`4zPDuCe`V!z8bhRRPVJO`1+|mJKi$+PE_s?sP_dhx z^FYZEM(l`5m77*<_+bBGxJhWPjQ_v*%YW9ApJoN#F{hca>2<7CHF=$xL9zg&(t!jA z7*!BH8d{;f;cLVOh3tk=p3bYAMNzaTU%?e}{@)iUNzl@>1Mh+uCD1e-Q9g%_x|wx{RQWB`Z>=z@6QJjEG(w#w2%43 z_~(NhXaI-gGOY$tEXjKt-iG$J?#*x;f?X?e(qH;>0bYE*`81~Q+g@S&DiF4r{YiF*m{e1jkH#%7=yXsaJKnNp3Kl|E zFZhB>G5*AMsg>U-H3}oc$nJA1MP`Hj=6ge1`>Mv7OUbnpg%0ZTJNpwNN5OBkWWeEK zQ{z`7W{^keE6$r=y;n*1Zi+II2N|&0?$qTE_tNy=fp|{)u;(og)Ws7ZzXhCcIR!Go zV3=$D!z)8NkoB?CQ)NUVFI+R_}UL9QBC_oY9lLy?n%B`ZW+)*2;6r*-7j=WBR%l4Oi5@ z%qJSSum50mQem}Zn?tr7p&j^;H?O{II48lc%Sd$_Ju`4@wLnDP!o5G{8>@Kp)2Pj}z!yJ%Z|! z_BQ2`|7gctMc*8B{_fUAFQG0zQrkbfq}_X{ODCRDBXAxFXFu+L8+arAffD9w2Y7wo zT?6QOEoE(UwOg5wX{!BKjp{2scg?!6%TZN3wQVXjm&{Sihk%ToxeGra68mFnLK%n7 zdZw#jeW&9J-oYG6vSFH2K{A}n4*Vv;c^jh)SnTU;h89(Unx>NJqi6jL*J1HJP2XCC z$@jU8I$y5>9Pd2}d_PF{+LPkZoA6i^x)xjoqKh1umhGfC3%R&f-&@80t)+eJKDqf# zs(;=jHt-ek^kyrmruAw!{ASi|;h5dCq*y!9y8M2^s&qImZn=u3xPE7>_LcU!3^;RK zQwAH=HeRHAv-4IKJJQgTp5>k2x=xyUh}FuU8>F^*lxN}3Rv$Y-!g4+uld7{#bwWqK zA}waSW_BgG?p!sJva80C#wuDkiVAQ`cjD~`slCbv#xc=LCl7z%TZ|0D@}E&;ggc>W zN;x5*GAm53VyFl+Fli8MXGEui`=<%Plv&3v{CUQ1(@!EexzTizDrrkisA2k4FOat= z;E)VrfX=kOzgH7MpSqlaWNcJ1QBB6bDhqcP=abq(r!|y6TcjGyp?vo8U?$KR)N$Dd z%~a16*Ax!32)OqiH4Q)|+ft%CQ+lHIJ`Fcht=u73dp^RK>vUr&TK@YYL+hFa5%Gih zMrA=>V{BT~0UdKh9=Oo|Q6Tz3XrA?cI#$FoD%btjmPh{F~i`HwOJXGGEr-!6*`0d>m7QD{t zu~+n8^zpqse<*3mIpH)NmR`#Vs9pUWNm(9P!rkxTkNt@Al(DZaqWuv#27q1I;#X}c zo?Cm3JQulYYIcwLqew*srQ19XkHJhn@dQ#Hmi1 zCw3rtLlK@lp8Kr1J}M&Cr))X4xSZ*O;`*;2w`q=3(<>0X(U){q``zZH*ds{!eCK+8 z>Zl6{QRskyvyoAPxaMcZ*ayf2u_&e3=mk?LOPp9k{iS(G}n*F~F2 zh#5CYXK6myDSX6t_dsDqGG%}b^|W!Qq+scviQ9({Y`xzTI68ScG^o!P2gz&$!S7ktO?4tIyZNUTaT$} z=G$3(KD{_}ajlj@nAB;qVq;8I`WzKqXdBwpu69?Bclg*pClXbmNC+%_v7 z78=Ncz<%$&Zo*Ex{Y!bR zS1J{}pY>MOLc;U(XAwNX%1U#;vl=ppynSl+c42>>G7a-7)5OF`eebXeaN1viEB>NP znjAoyuVZVYnu!Y485BNnr(iQjtiGY$v-9r6OO&s6Yily za&%v0_>jC1b)&xWeS~`dbZ76&{ab99f~y|I!v?#RqLO}wTRFFod27 z{+_-Aj%W+$2`}DA^d~6+drA{r$x_sN?{K=r*;(8Cfrw&{(d^^53d(+fR=;`!=g4)j z9vHLi*J6qCkCxegr-}W7%F28t%Xg(kqLzv5S-|O}557nDtH068Bpq{a&k+^d|+*gqr+$*sk?X0;rCOC-a!=HsZ3!b+L$)x2YFaS;G^6}Wvl_1$XnkJ&~N7ee*xdz zA4v@NbVW<${59K*FJIaae^wnd)J~33oAi}SOBOH)(EpkJ564D(j_4L83Bc{+y z$1f-zBdE`2?|d>lj0;y>V2m-bL8QzKe~7d56jW7j6kz;oa!%3IE$^Q)X+d4%YeCSW zM-n|5mGeIPm$7PfJx-2nhbh`-Z_%B2V+=!j?fvUQ!4dS*PWk5B*em@`TH6u#(gVxe zx&w~@IJupcn3z9PjxD=UIRb)nk~QO~qRQ9k%XYfak~!2;f&LSF<}SH-l~KKnq`#hQA= zygpOH=bW!CMFaAk77_NTW6qX+x1xfFLYb8N`iln^bgYC|PS`LNNq+5krC#~UP5HzE zQ+;s7`m3b=^#3C6=TpRi22jO`$br?kCzP>f*~4#IkUO{vD_cIp%>2i4Z?+;vY#dk# zt}F^#h;xL~9QHxqo6T7hQ0kkB6)?z%+HF&r*XL+iiA`zt#RSHRCJDrkg9s+<{XoeZoBPQ+-+}@cFrwMDvLulIg3zEM3|NT#vZym zRC_*PI8_Yd#H8Os^P*(Aw(Z%^>#-Yr-NxJiHR+zQO$*o5P`eSq2h8gKG!K5#97qI5 zd1AyVBtO)}B@Z%q+-GNISk~zL^SH-ew|Tor{`Vp{_to8>@%o38g3^(E;I>0}w&vn( zrLh9!pQou3kk=qmILdk`l~}J#frz*p?3V}o#7d{$efcJMwzbe9C~zt6mQ#*86ESXD z_jGoy*SV8j{2`YzMhyNb;9!|M0C?_i^GL5TyzOFL4)0XLYAUs%X>)M3Jhc}7pANf0 zv3>;nwCBH2=~wl@E%odZ+9l=Q&KYik7Z-$%(}u%S?VlrD8Dq0$xiAdu=;NS?@uvch zUd1$V1wXh)4BBDEmcu}Di95tc*_GvzktHmX&L>abEjp{pUeDG2~EasDw zvA2vgRlPg<*BNni3(_9jcH#h6=>K_*oB!>dThr06S~9ZuP+cqVXbFVehAiMkxV!-1 zAybSk8_zfEyN@eNGYff@DpaW3^kF_#u}|n6(^JYqi;B-yO&6E@`&59t^)V{%Ea*~y zYiyHka;71WGm1@Kz;=7~)^-NzN(p135zEC}UX=0E3$hI@6UpIu+F|1AZW} zIy09^+TYh4`n>-15~p#r(gz8tWcL0hK8X(~vAQhz>~tW(vvB`Yz$?f1UJQ3yLQM+* zq8y3Su7tEw-dx#cTMp%v3_I_~fA}%UJe31o6caA{V|3q^*o};xv|>zk999XrAHz4a zb`X>s=o5F6EoA#?RAs=cAZl5Nn%k$9@0mky_tH#S-5MX(zaOdIu|_K=j1xMmzn}^8 z?x!kKafKj0J;S2;wcu*-^;C?g<5-(uB+(XbBY)Z$#%X<}wTB~+xYe}$uiB~0tj|7s z;cV;IlT=jj$FURE&o# zJVZ^%{pIw0QhZc6#%>>U+?vM}Y8QJ@&5`)759?$IFk@GmtZ-l)HRZ~?_`!R`IsW(Cml?6? zc}#ntgAaMkb7RAXd38FC1T3sRR*xy+C>$4i^p{SHsPs6ogEc;E^OX-@u7eZgfFq5r zQyc6)U$()ABGi=0&{zw3-fa7mq@A5rPca|_v6>lVSLKgVxp4+O@wXL}*e*GW&^>-C7ElJdTfmxC=Dt z>9)1Wpk5xU`));xZ)CAfDhuMc4`*heeAgify(WH%w%zAwgPp_qy6KSo5xe} z)UV-4KYgsKoXQ%HoTgTqF{F9fKC*ODj-e3r)lgn-JZ(%a*k1;W4#`>K+c-6N99}QZ__bNRKHEfSJA$7885xEpS36WXUw$+H7wqYUEWaX-Nm zzo1v<7XnZS5z9=s#eR!4|_L0PcuH_lln*MyP?3LnSL3t(C=WNU<73X8yxrE@r$9`O?=?8v%6FYUrq>gh=er=ZT(+g}$0j~h z`%1I+Fw|?g)OvkqlX~=z$-{~TuQfER5Pv2+=gEIi!PvX6c$&}k(Vv+x4=i7SvPSZa z2$?M>?CZoW@XryeZ1Blx=QPLVBBAMP)1KJvtYJUOG_@U{!+aoYsn*}Xv#V$3)%B#` z$|ajcier;|+=La^3Yk!WhC8YZxIaittHs-7uDB+hLc8O#qcw*&xw08v=Nl4TN&zYH z(trINfHI0N=GOdQJloanc{0He9RvcTU1RLa@Wv}0z|ff7H+l{*^z z(ocSC#Qs1ByQ+VvmM5%OR&~A02k{5BD5}hPfAx#9OB3B`1@m`aoYRyQs-I=@YOY+n zg&tjs6;RGOY4>89S+lf#>cN;;#?KPUpZ&bbeQ5WSVZ3>h{3YJ3p5rZ(5CN;69ar8e zqBhxz9EhJ&F2)+??%z=g*>7@ZnOrCw@=-_ZNhovJ#B3+MJADAU%Blma7)1vc_R5f% z>BD`5seM*LCbyv#pWKYAfKOdbLERVj-9z-u^V6+rGmI5NgEOY7ji^F}K2oQH;Ah+= z|I*OsUdD4IT=6fEg5dXMiEl_TnbUi5?}tS6{~lp|l2p;jSLvBqSvx`0SF+mzVj`Q} zyFzPbb`%RW;U=bTFQwk?t_yg{o?eeI;OW5clV&U{xo&@&g!MD;#3YkNkzbOSYuHTi z>Ygt;B%5WeM@mi^=yF~El>AS4_cIfAj?54D%I6+yL4y~=BF>vlWtE=yS!5KFb=5Te-z%aG;gMS%T}jo!EypXOqjjZhi%^eQK(<&u}91p5OP2l7~$o zal;329qZzv>iw_HkwatbAMPwJTr|;N3NJy)S=Q8JXZlg3{OpgHq#M9L>R7tJ*`3|i zNr%pm-5Hvl$RLAZdgYasYYmb&5X_{@v3(AHqMHxpios~wU-j&49JMar=UClMNZ4;( z`18Ivo%Gr1Nm&S37ksV}WQ0p%%|_>p{G zr!a3=rf~=_aCszo<{KHy=+}F$Kt9uHnthBh+sW#F*AtzOH_$@X8U?$}kwR&90gr{S zh@gi6hTpWctBj)f=~Z|C=EQ&P} zEpY>N9SX0k!u^uHpWf;cw=T{zC4x{Km>}=sdi|<(?P_)YrLEZpJ(+#=fZJDt|F}lL z3XI_S;S;3Q=K5ds>mXNUsgx)d3_T-_&_3|btfnvkrvMi%aG!#Z+9_{ouIi9|E%8=C zM|DH}-Ro|Q<$#<4C$`w(JRUmQ$D(q-b7*Y_h(|O8uMREhSU^A014dHg;cB?&tTt#1 zc)X2%%Ja-;v){I(Q=Tc-KUUNwCN*tq0;kZ>&0BUnDh2uV8W_y8&mwj(sBugLymSv) z&YjOEflw)5>h5}lh3W8uWfSrySN$fhx0eJyPLgPHV!we0-+$%-_tc68;tDgH&^%N~ zqir&#xW10Qc7DAvZl!C8PCnyf8}!oE2%fuIZaYFqTpYtPx{j_9$e*35VQJCQhoPwH z)0lIo$lD>L{H2^@@wBaOUhOo6K{?=VUWF`OV=1lytDlxJ3l6P%?1m8eK=Cj%rmyF7Dx>c+?}lN#ZG|jfFyg6eR4E>oK7)XmC;*Gh++`d^x!SA>K$Kf zGBx>X4(6dA(091Y=zPMK`oQew&v=$5*Hsosw}YAGZCN4_>fAZeK9j~iQ6S2}Wrl5t4Qs+VVw zH)YLM%$EB#T&U7$iSdht5Sy+}G~L!DMZ@(jUb&xk+>f8&$b>N@N2F`)g-`ClB9=)C zux-35Webcya*iwcuJE>c#gEasFO%XgT+Oea+~_oDx04e6-FaE~!P{^Tp6i*xMU!ehAKFJpxv{Zk+DufpnS#cx*YSwhp$ThmS`DtH z?^NUik!EvuVY$>XK%}?6k7E=REhOSGRY9t$Jh+FM1}yBDVUX#W_nV!pCHtEsN^kYc zc<4A@jZ{`i0hJ7%P@G`n_;2TQq3xZo{&}_H_0}Q7q@7IuErFa7*sYg;`SfGFVJd45 zT@&B`5Om#q%9se+&+Z6}7uAuI%0CGAOv5QT3&(${@2*`>)8d#f5@l3_#~*wwo8H?L znqR{KO@V@fjq*uth7phHqc&dtn91V8LWuW=#C~GX5#X^JCFiPfjeJfBkUhVDGRhQ3Z=dGC<+EHurPzSHMw!vi9oIi&VutoKMmPsuY~QVTqF24XFX?E#2ya{SHrP z)5ZSHEEemhylv7h%zCDyK%~#)1iB7j{Wj+(bLjGBOk7v5QQ~3^9h6B)WwMo~lxYCM z*);_t`))QAzm;Kn3OR+iesW#z>ou2hMiP$-ELE2bb~NC50}AE)WyQCl>Y9ut3!5Tp z-z5rD0$sdQB)=v>D)x4D=E5`nH`R2xJ16t6^Nj4p>bK9lv#lluB@^1UkJUnlL};YF z=VHgHq*v(cS1Qz7!PR6L@~qfgg;|}a*+_UM<**ZAa73|rhOquX?}m4$X5pXeLaR!Z z>N11mVxT(wD=toir=ew2HOBmqKyA340K9K!Pg-AD7DqgV?do5`OL*wy5>D`v=lU=8 z)dQU}f@Jo{+6z1+TF!hgghVeg(-TdBM&IXU_ItjR<}zRmt=6=ao^y<>#$%sdu%pNR zG{20XuYONQnfTj;-DnWQTYap)rnU>T!@Om9S=e1Ym_y~aT2vyIVFdr_I&^Uk?8P^< z+w3Q#%A`kn8w7e9&wd87V-7Eg3^ zNrS~Ne_G<0cDyME6LKY#OOE8{3H-xy*nTC}IeKZ}Z+qOunIQU*%78u2PqR-!oHgF8 z`W;D(#Am19g7f`uPa5^bSC>lL8a^Grh(0)^7sb7w3*7D^9P+4h;`}&L;%oKG3+e8+ zgJmqboAwQB(@$?+tzMU8pZ8~xYvX9+GxmZWF=u_6_RZA^6x=V~hq23O{!P07jay#f zXhA@jZt9(RnfGxLmgfj|zm8QMzf}zXBKYWf#yd3k!AEw?2g=&%t{qn;7WL9se4gu@ zb~l`4QPzakUEad2gjZb?QtSMxP5(+yT#fnk@u9He)o11r0HScaVazCUsxFwuO-x__ zB#%s!GT1b~gol37B75hh@E*cBg{qz6Qz2{es7dl3-KKj-_1oW*LN?f)d!12-U7+&qM zj_~}o#GuLo&V~^tv_B?!L^2yzOmuSGgdd+%97g#p`vD%%)d6vR4AxYp#umNJzy5+7 z$;!JOrDPV7#`qCq_}Fx&XN_AR{S_=eR4ECn{Klx??O%DOR7Q z*n^q%Yw(hwe;%;*=Xj4HlvEF6(E}QADbu>CPIskpV+a`gtO0*|f6V+6Z4~|pm9TqU zECPg(ezm4g(H5k_OH$2iy-b*O67R098FZ)KnZEkr_*eU4>4T6l`a!;K0`L;3qG-I_ z2P&#RI1kqIGE?Ks!hffO-S(Y8YZymiH!e-u1t#4W(3(5&D3p!f=XbqXGy{_De}%_w z=`16MIF{KOvXmw9qB_U3>ZJ#O9NC;`Cigb+AxbBeJ={xPs@yQPoFV7cH)K>lmvP7q zu(#d=UWf8-F2Rmrc-OHk2b}$e>H_DzX}aX9>o-5D`Hw9R56f?k%M{>Z(qTcs?6~b- zSGtD}S!wf(WyZ&}l7-xrpPNnw@0t_F`L7_ZPq>-ld3ml+Kq`*q@V=HSn5>m?j%p^Z zsb9YAdmlti$?e~bal~r0zifiqICn{Z>wNa+>b-t)LnWz^O}$m(G3K|7pbT5Yu;~drD;BCI|ll|0-g}u z>ho@CE*%c5;l#rsAJ3vx8=DIWQodQTFoSKQ&kwysr#UvWVdK&dF36^!%?qr|>d!DGsSAB!C^U+Zo&@?IT6%^dNB@B?? zlPJ5}G-=c@*M1~U*F}6fC`8^8d-K%HL7N=D--o+OkbhBTUZ!HuwJ-DTzkiJ4zbm}X zUW_uSVU}B?po!tx2rJ8RMqJDnTW>sD3(xyhQl_2`dCzrf%^*tAyo>Grqv>$8x4mss z$~O;KQC&&mV|xG92dDg2d0;%Kb;=g0trjsMYvq$9(7B%TWn}1o)*MssH5Q$gwZ5^g zM*q1NdA|>*Mx4W1d4+B5WUtf4)oYTR?<)a2`OD0k+9hsD(341B-g-g(R@+$>3*w!k zf~1F&v^Ro$u#ilyP%7254>u2>MG_zX%~oXg1r!6`9(+mjg);G|U?_81Id9nANAwrJ?jTH~eN@F;|6woce4r9c+Yh)cHnQKoG&H5>hNf?hKzTA;&sO(EY|-|!97t2E27sv7)t!>%8;WFlT zl`|OwQYhx_d;C%b;>hDQ4H|RqUcFPzy77N?DZe9C9N*Wkz5SBsna`_xpb46ItL6RY z_u5Ovx;f4gmi|J6a1EL@Bf=CIrAx)eghYK_$i*5HW(#Flf%@Q6M%bfh>DRl3U;mnw zv@A`&r4!F3Ud6u-DWdUm)p@s$5(kYCwW-%J2-5r9pxLYY)^r~Oy2)w0ou(YSF_0vR zK4JBr>=)YiFOIlZe6?^)YN4XG?2qjQ&3WPQc7?Pep>t%Ry#(qgKg0$>hH0nu5fQJ3 zC#pY}j6-MTUHhh&eu!E`LXI42;U0abh~pgxg>xpa`TCnUDA`0UjT&KTb0QoHP*(Tq z^{ud3{&|Al+20Vl5vcJWZkw?JCr82V1`tfv)i7grh z3?}qB&pU0BfUH&??4WaWaaB2PZ+D%< zA>laZbOo!2ZxyJvN-_`bZMh7Jfet4iil%KQW zS9*!Kv1j1Qu7*TOw8w%QkyNEqj%Ez@%sY8(id+2pz)52olo%$y)%HV{^9~?(`1_6P zc5gm#((~)VEtqRQ432M~rZ#$7<4?J)JPJQ?Xmn^}&s4?J#Bi(}AGN16Gq{i?LA)^c zEn81+g*CiZL0Vw&;b5?6{Xyi#>#&!JA(sx|R~OXHba&X4T>Et@VV0QK&_`X|8=2H` z%V^IJWw64QD~tYicXoA9OEL*+^s(<28MP{p>}~W}Gn!t*R~4>T>1M9zC*-$NghwU| zPdH|!Kqh9plkTsge3lX^jrP~?Gk*r}C|ShcT{yPTmXm>@>`&(!W6ylhsQ@M=mxHwe zN8!M&(!9PWhm=^lS$2LF-fCnuSIzDp0nlF$G(kP@2rQ^9`031gT@i+tR1+3a>o(B( zrnABF($U;0ijxc_SzZDT_OjYKetEuF2`99#uHG=oNyk?V`lA58YE2nUY4v;*Q6n+ZV$vxt8Arx8FIm zRG)P!zZGup(J`)n_-(BcaJEC`{)x5vR_xZ+MtL1EdAZp_b?6=67QeB|s^e-)2(i=D zr#)A5{FnP6;PWOG7ODZr)@&B-VcZEjL=oKIH5dfVt|p+tQdA~luyUI@j)6~4a`PO< zI2C2@ZXbXq+!&w<^8gyJkN)R_yBic#e<^m89{mqiU4q9)u)7>%mtnPmk90G1=A4IY zq)`_~k{>E=zyGjJ0ur>%P2Laiz59>ofAbyy zrfMkpbdEN7Mmh>9oUZF~$|mJR6q>JRj|^mW9X1jH_debiY7)J4a~Lj02Xt3S(g9bT z$Hu#qWs?#3W%{PWz2n`b6gixgEGDeNjMJ;6Zwl1xL0Lm-yLk}$1)2|}6tb4eiLX*8 znZKcOmK!dn69pIKhqUV%=J2+ccB?qkJ{q?g2$Oa0QZZg2-2hJx#vEpjW9A4*SHn&@ zr?X|EA0uB-yXmv)?1{Mj-FD8bv{QZdLz zmoz`|Kcn+L8@c<~;A4J}K=N!VGI;23Cqw-JOT6z{tYvA_RMQg-{BQzH_{{OgFIoi*It$PZ4?)RXkPBpL8 z0qqg{V2`AuW!B1}lJ;A7>_fj~@tRQgCzWFlYhm+xn%nD5gW$WEG$ zKS$*O;b9L2HzhdC#hl2C%KVu1H_N_u9~w;0iBjGsHxx%^zOa9wr;QrylD=={HC7(s zt4wEwMsp%H(@oC63}|JC04aakdE;b$9aV~V9W82Y20lTdT9MsTc|9!z%v6#xW_neG z7Y*?Zi1g{-BByqw*_A3_`0w?D7#vT z17vadU+nJsABsX1@6m2=P#bMpNF%AC0a=S0J$)&EAcA{NH=|5H+&!CmF@tjZ;Iy>_ zy&X3Rx+*k%L*G1~3YgeCjRdaDY^+enw^^&4&(}D=u|2Eb^rU@^v{%RsX@W&wHH*~^ zRJll=aLjDNkHrupPi2Z-uZk`HN4v5==bGstKvE@k_iv3(f4IQJyQc9Ht9gCtxB1}t zRmIDxmAd>6!Mj)0DdzXza3J>$uuxeH`p4db7PUd2uShuiYbm#Ap>CPpArucQ;DeQ9 z*T5qvyWFU7&u~iLArr}A5W>H23z>%@`7X}8ztt-x>4SzV7hjTX^_dNVRnhq}fW@Jm zBPDVVw@T>VZG&aWNVg@Q`SZE-@q+8){I}m5QqHhfxHp-H!%kr6J=ZcgFpoTlO!Y}T z&PLGG&^Mzxj_a}4*jmio2Xna;u~+OqZ#Nij_>OnGHf}%Cr{Z_PgG&XEqRz{E--NT$h;G@99$ z_!Dwi5Tf>b4Kx`h)DL#!xa&^3$1g$8IEv!Ap@07dn8;{N~q;({KQ`^(U^h+oYP2S6dXme-*h0^CBQFe_*BaQ9makTAhibEDskO4p#!@%buHoX?sqbdk$3?#(=-kIfW?B^i1-Vt)Voi{=S^ z)unl=#Iv6|i98(OvObuZ#D)5_xYzEw)Hh;tMYrbozJXJ}e_Zqp_s>82uT4`n1f|l@ zkDaK{Q}K#GS3p98o)#0{xY?$= zU>Cz0Nn|s-+Y|J$V4xJK`GNk-M&8q5*23S^`kT~y>&cs%aK5Lf>UFgQKgo89o}S1@ zMb8>7xV_|>*z=qUD~ykP7jRN3nO%wUGv-1P_9p+S6e`0=Rdt4P)T$JVGP&gW zkIIg!9=M+=Tcxosaq(HjMn zJ21dAgVlNT`+ND~2H+s#pODCMthf6yQ>KwiaJYSX`gR}cWCpGaF=Y-;Y(EvhXznj+erSx^FzHXx7dDFinf4`ZfDf@6 zi}2fCkg$*EippZtUO+co*4K$f?mh40JEZTjuP_N9_xvly;lymm_IOX!ekPf^Y@hAA z>}la)&!+E0IJ;4~XxxD-Y~WMcQvGym?6Z#w$`-?PAH$DOW&LPjX*Pd7XhKVSp~aLc3^tXK%)#3ODgk*g-r#7f6eMR9i4b+q z$>p^#XO9TrMrOQQAB*o!?7j7d{~g@oJ=d7P&wvS<50~zjKdpAgx{)S!ET~lG(JB75 zF*74iK zfa$0ieN^X;pmXm>>~i?Z&Qok?A2*La$F(ImFy*Rbv=`_#^i$qUWu`}?8}SgReVyYjBxq`C-o=WQkhVRNl^UJ848LV9D2!cZb$xU zLbL@T!e{;0p{BF1-zswvbtb*Q3E=ZnH5eP3IG#DKFQUZQLk28+&y(|z=wJ6aUw1)< z)s4E5!I;O`2;R8UeW|cM1IINGy4z3Gk9%fBm+@63qnifb#9{Y5&rc-%&@VrDkrN%VU@aOLSz1~Rrvf$;hX z(=Y=V_hE!<^GqnvI8bnv%RR$uTS}M1gs>a=*5Q(X<}h~5CdTE;4--A-EyF_hhe?Q) zk4?Zt9d+hJ9)Yu_pDMMx+T)A)#lgjP^y!C|dY+5}7=R6`n2t&n=jh3lpdniZP2p-1p_tmnrsAs#Tl&2Q>-kzp}mjH#)RZpka_}UescNoZ3 zQp$TZV%VPu0f_e%0F9>*#P7I)0TGNQ9c|6L^)_ss{*hA}xVh_+Tm3PeIzfPPL)7(_ zee93d!aInF9P^1EP2qkWmHN|5D~c_26kiSkzlpFc9qsy@c^^ABpp9V4N^iWOMI9Fj zD)i`>hgeVR*x9h2NNM!NLsyaez9&zRro=lCNJoz&ihqDI1Nffu4-w!F2>dQ_M!3X$ zz~2mHu+m?OX5(12Y|6lc6>ZoCL2K?F*8gQ8n64hE*7Ya+h3A=gG#WTG&dYji|9YE9 zw&f1aFFZ`0jm9hYS^SnTpN&k58lz$DD;M)Qul)Y<^w3=Nda|7@d-5JvS}6&iwmMn; zej*s491DBni-2VC1qmGK{y-W&4oUk-P1V3B>o@=$i6?pI5Mu&BbGgV z`%M_CskAIKW8WTyygs{}-~Ri?S0?6i>HR0yB=EK=+~|80!gmzXe>bMl+Hj1EzZx#k zVDc=ZPhQQ*r>0h;d~iW~8>?gI8B`@F{l}8ABK5n3&;X~bjo~jmwVOK$N$Xnz4>UZc zC+X!+Ktt8{9WP*FohQ-s^78{RGybYff56m7BdOk4g9IiAmD6^OZ&;!SB})cCKv8Ps1lJ7a(BAa0NiKZu*N#XAdYwGO;RWVeSbOAo|c*@J4pFfC1H zn%CxKGa1IOxT1#+bv-ok-NMGk7JsxHCQZ|Ul~;#VYB}r=vv(^eA1^7JHb~0-r7efo z)sM#QA;hRNeSegl;J(Db-iQO9BM(mp@T!SMuVa%vY8S`+eSif`rGM1zT!UbSm|Bn0 zN%sA;at_ku$g70Xtqk(V2b|ybw|*HX1f0_KDxtjz7=^#;ssmpc=0Lkh+Em^F@7UrH zk-ZAG50#jnz8izU;z@J2eKVeR(u#QL6Rxg)B`y_^AbY&Dh@J-s)DByrz|tk4Eh{>fsmw7?Sy|H(Uzvkk1a0cOmE1VOYTDZrV$);_-D=J3S7P|2( zVO(nqnfv-~!mYSJy&?aX&iO1|W(*0d&d)MDz7=`KO2aVo-@?N69*rC?%@9#Qlwu%W z((j)B&i*)>h)dVMH0-DysZCt46O4H<7mYH+>qa*D1HlOTWY@$jIZA(CL0gC!EvkXE zs%h@Bz#zBFHGhRxx}tUWbrc-7j*=eh0=HxDGF0>bo`-+-dIh~F*CmW3? z6{=E3ktsT;-!@WDb4ntcFtbBjpbE#O`P$n$C6T|GrK12F1E2aMF8etr4Wauj`e4Jm z6pjmoZ9;Fs#XjK?vTzv!N+co9X_k$AB)PRM^i+OQf4Lx`M2Z<#H_1&sk(L`82vReE zVE~9o`83e}0XUif;EkuQ?+z8dmdhTZXKz&uHk6`5LgK`#rIQLdZ#K>*y-4Ei8bt<|8Y?&(eN z-AHvj$M5#Sctw`>iymvZsEtXh|C*jJ+H=!OgTZ6zY}$Ic1Ui6z1X`nAkW`Z6j};BA zxoe;FLh~33<{yWV1lfz?`Xx_g^a;dSLfderAg>jxb9%v_%R$?Zgcn=6!o>QTrG@+z zSmlj?*v}ipS1ak$8_(h9H^`!rAIZyZKj}?F|`9(k% zeO%DUXjez+*no)U|5&;PwoJP&`qX4&vTbW}O?H!Q+qOAblU z`#SgDd#$x`pJPY#8zrzJ+LawM_H>P@?Ug*q0^V!z+WL=6p2Aiw08ch>$6hx-4PUZe zd0-{*P}Fi=$=8)y(nPIZjnYXfju*M|p$Ny8+?LkE-yMWdhvRjaDh)%vHuO(%Wnt~A4wwSFebdbe#5 zP^e$Y+Uz(QnFbgc+0>M)nLTYGm;3y}b@_>NRSBDkC-t6wy|>Gdvgv-4DX;Vay7C`W zAtk`ZS3oY#PWt-Yh%`IT7dca4`F-e9u6g;hR=PGlI47=o2x6Y`Adj)dnyIk?Fy8^q5FjAm0GOOm zQkHnUW#o>;9eXA^gITaOTvD%j#>Qh0MWViT2Eu70up=z7P%b*57uo>E8pN?~IaUR- zmZIFBnr%W}#Ti#+=cQA%CM|T2-_EM4{>TQK>!un-lhqM;a~xmkp$$C-7YyA@lp*%) z;3|_#=k0iz0VttB{9r&q?Et62geNjT9dD~&c&~NSN>SI~d-4fzKW-C&TS4m^>UIm_akl zpth^G9Kasxth8@Ec9&P^F-ARRB{~FkHo`AeT*yRd0aP<)9H%)Oq)7(fhMKCO{q{Pb zmG!;$6jE2LSn6!&#DItg*d?OoHHv~|9*pRV{$}s;*}aeUOw9zG+Muc4Kviu<=7Ho8 zg8AVfAw3`hILRrX+Vnk;5;(@?Flxap<<<6e`4b@6hc4{rj?z^gGLsF_a>|vQ?(RDL zg)so@ZFE)j8zDA#v5Dic{ex}V(713Fr%D0X7b(B(dGCh>P`bMcRuBTq3~ZOQJHaAg z&^Qs($cN@C|@&vJLaKa;_+0cYzGb0spy?)9`LUvcL7>>9FFluOeC)SKi)nUokDvcTa1;6#8a0RaGlfIoQ9 z68t?H+G@L_srmV}knC#+HSu-*->ch8PK%ckt|)GY5P6?gF^M64lU@vf+l~F5YIEu# zF@i<6i)VU&P&lG!B^#=*%^ZOj)peDY2nryu+swjzpPyJ6e`nB|Uc;-GY%)H^|LlBY zh9SFMmHjyo!Y=>`UaI&dJSg!y|^@Yk&H7@#O4a&9CGm-VuQD9>*Z?_j~{LA*1XZO4RLf zKHb*MYPg7_1l=n5C9C{S1V*g8qR1*W>Ph6(I&V~R_-kHXT*r>ez9Y)yIE73X@V%l| z(F5#GL$}_M7bByto^H;xsaHzp0M1a=6{;6>_a?D9wBCSt0RE4cJGX4)C1z5e-@N}i zCj}LK3;+I`U8^>UvGjC+IQRI^{1>44&LF?)&_M?Zx!-dq<+_PIS-XbGnO(XRdLMgb zn>=i1udmXa&?Rwa^#A5#D1X8NGSk(DzwYl+u!deUgauXvjaNE>+t*g>Ogo>=C-0td ziK6(GN*9)N{F(K-{Jj-xoCg{C9XC}j60{xsPoTFR_S-OCF+J)j-?8@{8N+(SwVYHuIq z)C|YnRa?^Ada=_y_OX7j7PS#Wh_n)GySQ)LEC6-%NJ-hYuzKs!v%-&_e!7dK8_5{y5^0Hj0uyD22l;4E;BL6l^AjZJY}u z0%kmHePhc2_!lZei?kMZSI*i%+k|s}( z>*>KJ<}HmKE;O{lTty6uKr$%zGRoV;^rZVN%u@Ykx}ly|B}L8Tot}F z!S)Kf_Xm;`102BB9LgLg z3D(!~JQ>v0a%0~>BW_;{9^@2k=}jDaAb{f6q&oy17-I_~*sip^78c8h*pA+Ic`p_m z;oG=R6)!v`)+>(;3ZMbTT)U`%+S$ob5{;H=_F8qez^=Nz_}cdg9-fh(#qEqs**_?u zJOtoPtT)z;i!qzqvHpqnGWJ*)yr~_V*O_hgJR#aMKw4>wE!`Yd5mkhcwt*ezYdQls&0x5Z^62?t~I+`?Ew1}YEWwYE>gWC?V&qrWXTm$cl|@+>RCPfTb(HO;1<9{{(zlm%*3j|_>!Nre_Y zWf~j(O65oTNo-=>+gsZ@bF^c?4{aW3Nj|MMG!}i2eFwg778X+kF^VNOtj;Faa3RpH zorfTZl%3#ngK*7Jy(U6Wrl2$|U$Ra`U)jvNy|?^SY0il5Sw7dk3^?XaotnSdWk3JZ zG^n7U?;trw?I68cL?ST#J629Eod5Cz$>3bZsiN;R3r(#nRBgF;L~TO_@z!wrsZL!T z82}~OK06n2>|S^3e>>Qu!gSmiPx@KS$n&P&Wg?7EV>c{~szRl*mGl}DVlH4F9QT=H zev%d-3NxWqw?7oTKkT#Z)4}}*HQ+!!*J6y6EShxLG^RkU2TfvMfIi5f#1()X)mI!E zs{Qn}xWB6%a*C$m+5v>^PT=S%AzkdLmBtfF+KC7H zR|eCR5Rfsw1-(pxS)qs$0L19f*N~NaT>~@&nHrbi<*zj7QMRl{*ljp378Y9ebRwTX z!vh2wH+J}5R;4h5F=C2|^racu21CUYkNg)FQZl#q>JsapP3rf2{wN6a`aI5UnNA>^ ze~a&}VwG=n)sk(F)S}!6F*}ddx8xZ&`=w=qNc~8ZqnWSpUHw^39xnulNdnRd z2Hs-_Y;nPBg=`y#3OGL%m7gZiIAtG%{+wk3CsQtXXg=>Lv@Cr_PhkyoA%P@s-8lZ? z=#DpaO2~R!9uw72jiN6|ck>~M_rN6Bc+!>taiCoF@^t1mxXYSG+_9jfFxQ5PKlG7Q z*+}!nJ8o!Kci=<<$nJfuji=x{Wn%ud&cu0L?p^D1-#?Gu4zzxt`b8fy>Np=$x;%yl zBjx->E7ooZjvzeK`;p40Ow1y?`^PmBH35@1_YYC@`zss5-Csm0DVkG;q5d2}D_lrL;;8lYn+}^#;0$uHto~KTSSW5`t8N}uNv}K9&7DGuuL%o z0zsAl2sCf);QA{6VsyZTD6M3^EyE0$Bl0??zGf0mBopni0GY{KQANZG_6c)PpJLVuDG0CQ z4^^$o=?>TK;TFRVuO3gXF%oQX*4$Z5yBTXD^1Hd7 z1BaH`%Sr{LQ*VY^grEY?&>LCUl!|G6$u-2&|FG1wp})H za=Cf9CmTZQ?*OEr51jLJ=Yi^*Gk8;XSQM06gm-fqZ1VB68*?X~4>+V&>hv37@fSsg zNu&$j#JSRyh2b6EGQxs}fppV>#U50nUj5qkGCUG+oi+)W-cO_UiC zma$pIk7u8olz(bTKkmiK1?C^CZXVX6xY_t`FxV<_n>yu1j zEU|2Z*s+65=CX_Jz>7j%^}JBS zk}A3JyA`IoQAe|Cg0iCMkhJ-_izkqP6_|w-5P<{;0*bk69r)fC#7xaEDwnp%XOy!g zk(1T-ayGOU1GukPmZzw5xDPU4*r=buxd(T4y8sssg?@S1IUVZGzh54F!adRBhR5Rt zzRPS`Ua|SqtwJywHHj95LU}D@6ILVmmT8Z(;Y||q#<@#9u`F@m9g@tV4at3yn*i7+ zxe@n)>o3U2o$#A5e!N?6gv$OPBwG4Cho$-^TpO}D_1N1X=M+V$oR^h`#qWI$9I(OQ zuZFdYs$gQB_Wkf~CXGusqRz?oRU$UiWsXqc&MoL<0I3GcCb|Jsk#n+W)AAOtDUjEQ zBS2CW+zEC4lFnjM@xQ|L2hi_O&wO3PJhNi%(Qf`fw1`3;Y% z7YHl@uXJf*DY0lII@|HuIWU~3lIhh(acPyUi6l<9@YH5MKsY!OdDB!$p`(+^)SH?Nz)BS5G0u4x(fF<m(>X3o@R&QBDd|yw)@E(kYeGh+>u&U20#XW4YhV$ONxe>$0+Z@bkrgC zJl0so!Wlh=k$n2>&XfqRlOTT^I1-9#{xhh1-pO}`YshgH)$okuOSgLkOFwTG0 zfr5&NgA*RE$VeG1HuX2=PAeLI>0i^gJs!Y+@;I{^2rxqeSrA`}}KNiaRr=Z=T zm?&6D8Zg0&H~!^V3)q2|SLnDPvJ8tC9JP{pIZT=2pk`7{ltt)U1eHi+1K$wx)?O`5<6dv4@_5z&9I%zg_;Ef^asK`ToqCA<)$Hwzzn3hhCITO4p=y`vu%K>?L4+RiHf9MLovrxhloY|2t zQ3j#lqtDKKLn6KGGU4C0vEc;`2eUho8AiUJ=DK>?f2ps=*FA$hFmBdYBX@lTFTTBj zTwHRnSPiK0RhkVJqnMS6xf4NSi36+VNYC*3Bk>pq^NdSA%Hb@uU8Sa^_0&SUujxrp zBdB@P0ZDCc`~xezX0|D$K?_ z1KhR}??^@sE(xVLBf`5fW`$0aJ|!yxmfyb(fr>w$U6lf@z%)8ooNK zaKtVE;v;t{PG#UZjEJx5hF!Kpa8Bw zup#97(i(i_@2~l1y8XFLsNQ;Ks=Wq^-f5* zyrynDAkN$5-d7M>ZW`FI@Ph*i3tA(=RBM)+#fkZjeShECm2B;mL6TKTx2KC#OF2RX zaNV!$HJ}55Dk=O%Wj+oddc#5K6OT&T{`7W5odXYn?XQd-Y(9iB&vkW2V}-qW-;6>< z#Du(p_!#>$(WW6%)|(946>2iieMjD7X~(Bsj(*zHeezpu@CUgHsBuG;&Hfm%i)J7? z|G31IZc>|>byc|;>qI`wzLUqso`I_ZdL*yrc5&f&aJSiw==Sjh@t{hTW@FMRVS{dHRGsaXlLkWr1Z(cP|G zj|{RKOuGa8bmQVfUVbHb*kz^T$t1QpDp_{3jlH!!&qvi9os^G5Kyw=k{$oTJq?5!# zGlW@|B`w)VA7NVdK}du6Kaj@t>-n=8~jMr>h-kC%@RU()Wy$~`+acxk!Z)-`mzLq3}oXF!zIb43h##QO2Jga1y- zLK5dA9Kg@{#3!ulJViwabzf@yQpb~QR>)4|^Xxfq)Sv(*F;9S~9fsdxCi|R7FXoTg1_>|f3qh4|#(2&7eb#Myu z`-hhG0|f(n`(hX9@#+h(lN{F}Md;l2O-T-$@4-Z!c+K$BIlGp^;v}MjL|pci*ZE_) zZ=H1V(WMGy+rL&w8r{9JGKSYT+%@O$h39v>U5QMBjK6IrEk;!b+!tKh;X!RnNVqWP zxDV9)Gu_>A=D~B@@L7$1qCWUoYx;y)%I_a%7eGf1GRJ8f=)zl(@_HYDU+iIFmUZj= z*Yl?*dwuY%J_?@x#JdF2r}7#t*Z2>(Um$1<3uLbqug<|rWzHE*M{Z(+(%X6BLj|_i zdQW50VH_ABQ!YX6Msnhd=iIzfIEKMM#~?>WRq2{fZ4LE(qy*F}wBLuYNF)SyMkzsi zi!eFfRX_q1DvK5$y5MEA2U41joUq;*|DA7hBqI{{2Qv5CD6e&8+MR+5h^UAE}RXM)a<%-P@iAW zFRLXFv4-LM%r^NDV7AeI4Z`|urXE_ib(Fw{Kbv0RMCS9)>Xzx<6Th^c`Z`YPTRxWL zWZr?x1xT|N&OIaFGxT{V`@Na#1o?u($c`~e(bp;r1uxu=$b`LDsq9~wg-!dq6v;gQ8Guw7f0g3pnJNwYAlPFubc;2 zJwdK1`!lOR8bY0&>impMnemw4eK?IYBkOTLZV!`&YaX zEIB+`W89zT0}Xp6II}*98A9|wEzzz@C}A!$!x?mcXvL!`7)Qb znvE`xg0IR{+sB@bbTz)QV4DR@YHj77E1?UinFdRG6*z;9jCq%W;*hO2)Di?y_jsS_ zX7*XUIU)z0>hrp+(&ioXyFfT{RONdF4V#G8G3c8!Z67l$?>VYQTyYKx_k5F*%+T|Kk7p&Qu+kdLO~0dVoVIulrIxwPre zAqH1uRKtMw|c+MQ!@c)KNy>>T!`97U#D zUH)h|>22}eBf=gD#ysK(oXQ4#Oko5df&5mMM0gTgz>SyU@bS;+-N7@)_75YM0p7z* z)i!b0+=08t?;-5+?pD(qV$xzK9|I_=^F(y!pNYbPqBuqw1JN8VXt>`qqpcq;D@k4V zHK0hK2?;~V8aFiQxk}zHC!?+vBCgjP-lEF??EA<2zp<>~DEvpR7@rjs(f${GqFhC? z-!Q^VOayx@zXKC+=CEv)opguUvL+i*S078QpxCxz)`s^gTX;|pV(EY1B0KJ|r>kp@ zp0)DYs1y`$Ar z-?DOmDhNC~BJk|nyo6RFCaV9A9bZhd+Efo9g-s*BI7sGUJ=@&8dML~s;*u@^yhj*s z?$a}~r@;Yh4R-wWFMui-N}M;zh5a!mOWr%W!gf9LgoGz~Z~vMZ!p z&z2xP(~}Do4o%pQ!UMfX-(3#VZHx(!pz#Qyb0{N)VQeG3AdXto9xlI@33l&=xELy} zl1${hS+(dQBz^k58wvQ~l|2Z{lLX4ipeSL?;iw-;hWc#UeOSNB4k=l=uQQ(0#`4BO zCz*Ln=gk|cI&)GwD3ZpVI$EzR3hB4+ZH46!jNqn*OZDX?d2Yrj?GUAo(*xiQydb6DH z+I-l9Eb&b*Ic;yHZx0qwI(pg96}Q3FHt!G|snVBLESsI;HBd|8tt5tYaLC{3PYPhy z&$bCTFaG|BzY?cxtZUU(O#yQx)9fW1cbTZV>EX|Z2ozP)zyd1A6s-4qd4&*)GTOel zx-;sxux+OZw1*`ucv+9cFuZ3z11!EE2$V$qQSckBEJ!^`OU?p0kMQXyavbSzBa5{w z6py`6H47rXxiU|k3qy(@<7RAH4G{}Z2(pm2)2}DiD6T7Fk2HUhc{`R#_s_2wQvRE? zrvlwhXony48aV=ARHNDPc(8%!{(^<#^cvjH z$20pY2HIjsmy9h1h1Ytaru{D=spta?=DozAhZh|!F_i9VSYFp}WD_TI`0j`Oa^>J= zs}fy4r`HWO*?{)6lREoPQDyh>uACjfy@v3lu}xJ&*xOQ<=qLVZ24ZQ5RJ(#A&Df73 z)o=~Rq#HJhIrV9=dau*Bx5LW0;Ku{X(8jTfj(%@ z8Gd@PBD#w?QRDXEoX*#$W?9~vHs+3IchV0fa)~rF>x51h;FkAn#;(s9aXRs3C@cP+ z7eXEJ+R4UT0_-h9iA;Y5B|!{DK;DeJA<)gjK%dNbif(03uu8;V%j0M)G#9qsV4ix+aUC z{#s8jPoNBjzZ|`W&>b!&%IKr`ZxE9Fxm$wQxAx8pLC>pZR6Eeb%E6JgP@}?*+B~84 zOqP0sI*Atf1_B!ZMrn-TZ=og?HV+PnZkO|+FAHs;Z-cL>zu5_haxyx1M%ybkqp>Tj zGT$Kj7`1z`qUY8zIxC0O9nLwcSVV?JWAjMwzP-p^71k+p+o61_Dm7r#2`pg;aekKu zB~X?@2WxG&@$7xfy^EjiDX;smj1d(JASt8KwSMdT77(h$mqx73H)-jO4 zhN4ZL;n!5WUsk5<0&Lw-XKz#G-1{wvzBa*_~L!Qj*pWt8+d6urGqKcrqXd zovjN#0PSujFWu6}*7xN@M9nwLDcrO zicO_e98{bLX_~N|6OtzOYli(i>Cb%r^h;mscWl)Y)U18HQEGzEjMIY!mN7B@gku+j z0%8TNjO_2-lF+bL+ggoOM<%qmVD091)YmIcv!OO*Ld zL^GdA7Xe{7ILKH^YF-!qY*Ck9oiK_Yhsg<2AUEf$)W`Lq4VGNk+0T&TfofnO`Lbi* zrTIC||3i6hV9Z;|2UPD2t00ol?SteQ_p0^=)(Q9H?j@WZ!X^GiTI5f5L|c;IsNoK> z5)Dn!?Bg09Nv@}LE9%(Lv5kL8;$%Z%;S!Kv6~6cW(JwA$@=P5MaGK+;us^lA5v4t* zB>x**5Rx`n!%Jm0(HwD@3jGVW^uO&6&rgcUI--H9gX`dGsM}qWwcpn}AAhXkNTPR; z^Sa&Z)DQ>f=;C{8gKv=Ke`lwRetrY>>#VsfQB0DSO`oZMG*h%kc-IlLQBixjo{$aa z*va<6BZ1EOc)07}34Jja&C_C$H9F2M@aZ0PAi2#_anT3AG2Dz#@N)o%03?~%ad7=Z zrISMP*4`63wQ@SmN9v6^<~kW7L_UvaIk!6=@p6KY?JoNxgH`RW48*A_RL`isy&Sy- z?#F*xZajQfA9cLihM`m@iB+S>-%PZYviS63on395I+$xrbMN)Nk z(4P$V7yE1%c}`jkCM^dlc2r?r(Pg2(eg!gI2C!{#k%t+y#i#AjKPqj}*8N65Z(PaH zu-n%%RdM@M+I7GF(Q^@ZIXA0iw<@vC&YJ=7!mbjQI@RyX!RjPkpDC8!cNA(g zg6`twc8#6}Rhw_n0a~`yl5zOH%Q~+UDG_GvVuO0|sJ1uIN%GJpvAs|D3G{ZKeS_m~ zP;JPr=WWYMqxo%AY#zfEU#7?Jm!8Fx7ax215V5F_IvV|d*Gogc8hnFm0SIhNTLttad86}ucfA@bh0`osdRP1P`hm-O z*40P)tx2(;GyFeM2M*-8t|h_LYKe2{VEnrn-MYo(+I)n|Jhq;{S%psTXIa==O`Tvo z@`_(VS%=tukBz#53_hvtgK@`ePw`*|3xCm8Fk&|j@D5TgUY7m4ZJIAc3c@q-jo)0i zt`?i%PX_^8^)Zi%o>|hju``4^isOQ$l7at*zLlRa|3w4^D*g0zE_C0${>iZEhsrWB zpEMick#NxU3Arm(a=Lrxs@8?s@&E=k#|D*8aPkoQLqG^WiBXCIjWYe4;YvDP${?epxLAV91; zoAGBH!!PU_n84k?ORX&d+|l*0(2f6I<%_ z^n{m>J>!+zviIb--4GIkkzIahM$F)6j>rwf zn^k(^Pmh&Lk0z~e<0VA6@bL%m(XpjZ@S#k%0&)x$d=SNBC6_9;bC1z!#^_XdUCfRE z#zC527apx4!DGRYq%IQcHnk>06Af72W!k}}lG^TJ`v&IesONLWrZ_W^J_(K|0N5!z zCP7k(&8-l}Nq=srN_l1FS8x%y3Oa)3-@x$d)hn8t?Wz}+2l){g!0+)bCEtKf8sClO zt6TY!$M)%S*#z`p#KBF{y4>SN*CDvspBbLo(C;!?EG9+cBg094zm)~@dhZ9@RmT1X zCD8^5Na@=k?$CWw%q9YmVm9yz%Wn%>mh(0bD0We^x54?ARX!zDSaWO<2$AEM^C9&7v9lX>*ePbU9DYN_AcPj!91(8I zmcb)8C7tlsWNM)?rg-$f4SS^ew`G!R$%4%Jnd=vRe%-zROseca#+^aYy&H6T_$sz@ zun-WTKH-u0o;oj<{a#iM?#+kczYg(= z>^Iq)PWc}Ue_2+0;h7|a$TiEO{c{pve6Add?_dNeJSOXVx2q?~%0t(s62VZ%ylGPV z(Bl{2wM9em#J1s&lC7%4$0HjH&WWSUaU7w<(A{NE z=tV+CWqlIryIrRUSwT36G69I*p%Z<}wlKnLo}pQ&z-(&=Rxz=^5PKii%-;HWZJ?Ax zF}q9{Zr~eE6JXlMa zBgj_npTje9Q5{DRPC?@VZafb=207=Z-`QliT`S6-VDEe6{&Y>?V;Tse&y3`%w6>?54K9S?bHFp zF64ZBRd1)gICy=9jO+wZwj8eKLC|(E8uMAo^8fI8^Ftz4#hjG09HPlTBssmidU(w{|$X zMcJ>&CG8L$l1!365+?(EK{mA;2Oy;Chhn5JqkGU1!BkB^xLH|UT1uVeKZyQ6-s*-a?&|G1Pkhyt!O6`o(I}yk}G<~9+ z-w^QV@>T%-a!DDL**FmCkqQ(IFNcR_35V0Z)pNv93yE(q?1QGXx$IsBehb8r`yYFo zW0-wZhuPF^x`XjfD}+^z#&C!j#TR#aIQGKiPJ12VpqTaI+J8Tt8RSPRh2Uq_I4rL|zOv>YDvD;ux1xpf45BloP%mZz8FRxk0Rc#MvY zj%BNa3>A(M#kehmu_eW*(8!`a2n#{Y4lU#i`ajM>ix^!ia{8nX2 z5c{CYTZ)VMFm_8LPU5PmM&ccN%8Bwlt1Q+_u5h4u8>-^a9`G6$4_-V@v=h)iPYx9X z#P=8;s5*PE-|g%Qm`}zORO>^bp(M%jYcg+dmTfgq725ly73c@>ETYWCr*A#505aRU zQ=unXv+SBNMeOhRzmfQSI)8h!drk3^w{GueB!-asUCd1fg1Q{9!Ohs(hTi4hvEva# z;7=s-S0W+K*S8)!M|@7-0)T3+`W+(3`~}Nx*@~X=*fxTM!e+I zC0L%ZYyXIy(z=PJoxDTDz2fOj>?CaXAd0*ruvn?wUUzZ!OUz_@)hwfZ zcBKUvfr_S{LNN6ov-DKAx`rHmGRH{F&spZRFZW5!1(6L>Q~HRfnaFe{sms5nNVcpB zM^}1;sd;oq=d;`7rsW*_-AM;p)>2d(vTa`;a18&s+|2Wkf}C_X4ZeV1uT3w)T%exm zl67r8T-j>DMz__i_u{FP7jS+eJksY7ieiZnj)p)kz|yI$AgvG0Dp*@CWEE?obN+rS zFbp>LuE4b!Vs4t_Qc)xQ>H*wuU&P}}vU7t;!cASjt+OtzIrokU*=^*TzK+{sp;n7& z*vDc`H{0sMrl%^SMAr$aIqa=HcLI|La^S}AGQ)=CIsTi}ZyzfT7hi!PPeur3Sg%s; zE<$us;?BmN>k61YBv4ThO70nSu&(4jGM-82(>RS89 zBnF2u!Y~T^FVNu5X5ssE#rb@=hd*y9%_O9fgH=9zV~QS#w#@O-+~Ly6a)Nt9FVS5* z`RfG-KWaXj(g0w{ci+E*{CQeT`}i}oy!22~z1o@OmS()R=&M2n1b$EI29Kh8)TKQ4 zDP9P(bLRyt;EO*09QJ~-8j*$CZ>c^7scx3_!zWYQM!r*@OV|RR#Q;)2Z0eF088LS^ zUFPuGtnVU%t@C~6*x85ilK~elMiE*rOt$&J)^I5l$0ei$2C_yFlV0*}~ zxpF8WvyIbNL5~NtvWRcT^zo%|J5qXsj36))^H?`>-;A@GSBbuuao1}67xVf+ltp^j zNa>DCPCY2mjdpHwar}Pn4KRjH+NQ&af?6bYXQ_S0bO_J=xGD;9c;V=V-W5U_2G+CT zaB-CfmFInB(7H0G{wQ@6EgSW#vmZ$#U-U`&Cf?Youya*1mu%KOo(^exA16fN&8sM# zz+&q}c^pOu{eNKd3A4NJ_+QTFk!y@63ln_SE}Zp~(a{0pk_wQ36@)$XfiB(tg`J$r zzDfI+qhdFD0Bg*uaZ^jeAi;$u$}&CuLhAKWfQL|h@aqvn!a0iOqv@Y4=z>IkpJ&A9 zoy4N;)13a5;sv}tn#F;P!+0p6)f#Cgsn3K1d~B210hnwB{;v1WoS!#XCQ`?c-e-BY zi>{A5J4=Ro%FgmHEIKH}EaM$HA;yUAH>ci$3PifpUOj9lB_S?toa#gr{A;`$0Z--L zUF1M~Hgxi8;|o3+!JJHKiK~35k6mpcZLyO9fRtx9>NVB5V zGaa$v^PEi)95)HY?hC}!XQRS3a@R5XOGgCi%<%x9I>};`<3mdP`@PqbE>sw0E3PhR zJq0NWmpN0rdNGIpmI%dxH3aQ>U^S%1yjV0lt{gsNl>nPZSX=0Ii@W!b_&;F`&JQ5!|p`oE`iQw4ty+D~`MT8*X$JlMYibf-4BU?xue#Ex# zhJmBZ{Q7ACcg+!@3bQn2iMGNHqIYHM+*2FKPq({40RKm+R&WVq?480I;AIi0$WlnQ zAb&E_lbpfH*udj;AnMIbq_+AM{Hyvw5+}j3MGFv{Mi8fJN8U$kxB886$(!Gh7wzAw zO4g2~^5_{m#$5Qx-9i*C7H3CNcv>$@3*IuyD2`1dKAi8#EsI>b!Sv4d#}Ekc zi8WX7gtTQVE?aso{;C0@uh)E|$Sg4~N>i72N2mX23ek0>r4Vxk{cqJ{2u2U21C3C??!&6 zS!wzCS}=<7-eMM&4TTQi+w+VPf7_V!j`u!iacr&d1g`ni3Tja{9M%vHwEtvieJmaZ z|KXpmZv*R(r(~7N3(@8d)>#WKlXr5NUxo6u<@}@GpU)#9E__1)NWi^WzY{zpcHCL7cfwlqE_DeL zMVjKkUced-RE2CTUG;_JW$izDr3KQf?3G}ugz~!BVc&4~xI@yxeZjfJpDRobr%uz% zV+c(p{(&UdkagJm+uc>x<`*hVQL{LVy5vh7&CT1rtA5zlj;J)SKdwaEVholi!oCLl z!Y)cDkwtB!Nmsf)6gxuNL(?x;>S2_K(V%)%pqeO&Lv;9^^*qKW}2QKjW|# zey#tu9GVcZR8(kYm`e?iQHp+Kd-pHYp55iRmRB%f%uXy)m44_fTeoBj205IfCCk0d26JiT)Cqb7)ydO_%5YZse z8sWc|4`lh=ml{aPpXlQU9KFl_>c&!<>jG4hvj~Kd^w7OC*QowGyG3M^z*@BYo2_*n zLHkz<3A#H`2&KEo$K^g6v2m%BF;0V+WYvQI#P!QIZeo4*WzJt0Q>}&n8bzEyzaQ}m z{2gh{lwXX*&~EMcZA*4l90s}&y+&Tz>g(QRI$EN+>kH*=fDRR6S)tVl*Q%))+M`&4 z*EQ|Re%)_XZEA)eqqmm7eQQ-9{i8KH>{~owgcB{|Z1cv(G-R$o@krbqMzSnkcUZ@$ z6OZMbbZzS5zi9;N_YaWQJ4A*$wrS05yZ=ERl~us{l<65!M!eBs7%r4!gL{6db(lNs zTGH+J?oBa&u?Y*vueqi>^&sK*Ig(;nQTEDq(5o7IFX-|ql2tv*`Vka)5}t#cQrlj+ zwCLT{2_2Ur@z7G~l&0rkUFR}WEEFFZTD<-r$ddwtZj;=Y6^a`z{}yyzmEB{XLId&N zmv|@M%vmR?*whOXhT~G>pCFWi5S-Uso+XRm04X%w4VWYu%*s@a)IYno&oz zd8Zz58OfunCKdN4h~oV=zhN%7;Dw%~cv~(3Edk)1PAAV>pSO6U-WH1gp@9rZQOmz= zje}5US_e^8E0U1|9D?~_0HNlh=!^bHckwK;pxqAMp_B!j=xd<+Xp;f|cWv3KOv**s zZ$Hk3jKA9tt&4QF+k@!&kBVzf%I0mnMb!8S?%CutXXg5USsX0tz3$aa^(g=21Nt)E zi$9-&IVh^*|fHj`Wg{y{XpiLulS(%Ro$ zMY-ZtjXfpUp>~Dga8iKDFr0(j_njl7R$JutKpV*KaypPRu|9k;k2TDJUuZ@CbGs6d z+u`a0tGV*WLpFTB5inw5L`}w99Q-R-O(1hT;d!L!TcPF1w*;H_vydt7WNN`hiVJKh za5B}GAtR1cKecxfV@XUx_H??j!?R7F61Q!+!d)8A^?X{L#DxhCeHzUiDd#{45p#CW z58vT;D}x*@=hx2q58}-~n+Dv2-=I8u=w|OyU1`kiX?76n7Aj12gZETAQE2#F@n-ch zeWaZsIs#`gHt22LISIN~G~TMME$0{o3j@M+Eg5Yag4V6xd*bt$%kR2Xl=yKa04~Y& z5O|u;$Y&f0=WbiP*#y_97@pJvVp54T)oQ7%G|`_yrvpqlfojYky6LXOI$T`tQ;Xpx z>J^*=`+grqE)45~BCJ4`SS*XLut{7y_WMiLzV;wFSKsu0e?PQ$8Bi8oAen7t`1!Ze z=p1Te<*hw<8taqvfB#`Q z0l)m;f`DrG81MNfe|}ciH*$LOMu#qCkLSIQsxoj*gHoUExr$lx08J{+s|I!WZgfcC zk#6-VyLF`Qse`;d-o$`c2_Z4N=zjD&t$6)yHFsj%D*{sRbnU9_8cm&7uZ+xFJd%@o z2k+~l`@ZHrZZI7D8}WZQ3j)O1ek~+)L%aT+5@N&C&>9vquew4#i}`YtnOtUewyDf5 zr(18D#Q(8$4eWI=P4vc2ZX3UG(EFv-Q&NY_+mBqTYJDDs8M#&TV2#R^{*WsLM0Dm*aEUU z=0q=BWjS17^FwEbMXsocnEtH>;_QGtOGzIE_dnwwGx8`}e2KInZ%rg23)?s$c)Lz^ z;W!wns`hPdYE;PwRstGwrP2Z=eFJSKz*j5VV3H&`uLWT=Cqq+?Xy2UsEk$hU%FkU}LdGywO^9XJ7G zO{o}0o=WKyY>ljaI_&L@Q5n1$ojNTcF(`2 zxwj`s&KK}4V*a_j%7@E?-ivvAg%V|HPXFVGylcvZ8q_in6{%cgSg$J7)10GBM&>I? z4I78hin-#hwRh=o?s_V)xx@&?gdHKei^+lRgl%|TyS9-V==674xxN)M%m*~(vDk&8 z`c=eP-Z#U#=uFspWl$mjZt&Q=v-;$Q7*hMe*6ihlO)JBzL8uDV*bQ) z8A4YCNYw2c>}5YehUBbk5?*uC;(gs7;gsv7CLhh(aR-B0=6Z@ z)xhfSUD*I{qWtgvKX1^pZ7gxy!qF_BJJ2hD0?@Eh-hJJ#=zyAXzLqk!&ATm)#V?nz zd_-rUx`oDe_u0Pmwa-um05gHj=S+o-B7BgX>t;(}EMwjnHnBdYZEsB@j>}rerBv>n z*^0<7{0o4s4S<9wcSG?~yeyvPp~!P{B|j@vr)ZMC6g1mZq+A$DZIAfWaXUbOMCce3 z=m|s=(pCqnzsS5F2$~`hfc7)JRO7f#1wNn2O7n4lsR{shD`Ls;P`sr zFLb#29=RnJqz(r$Z2S7rps;c4GiE@CWP7N}ez9;pQs1#aQkJ^HplW`P1sXsfWE={b zmli-S4cOQ0=2mK+tz$WmHAzUG;fvCq4M~G5@Q9i_{)xpnvRVK$@0|C`&F~C@H)u>X-e7` zM^I5QqWf*-QePW?gb$3D{}*84edE{Bk$L^TF|+eme?`nbnifgN#^Jm|{LX{+IfB%h z+n(q*dDIiXui)ou&rzxZ4Y2jUmZId$&i8*M5=+rg(wG$lDYyN;rs#@p`wsbXF z&YYt@;9kD#0GoG@*H*oI^91vwTR8cY81F1l!j3Dumd1Gzcd5P{gzCA5kY8F|+6)H9 zC2Bc(*H>1*ibZdx7Ja2XOm^+wH@3s8I2Nf_3oRFrF)B%((-kYA84 zE4bBU7acNjneJa{L!Q#}O7)3PV`9ie@t@DZeE8fKWr$2I$FK9z(*!G|IL$YkFT#tR zMJ~7Z)&Va zlQ3NqRAY!9zdK=;btdhHOHvGM#ijW=r%1vHkI?jr*Ik6*CVghJ%atMZj{Jnn%Z&x- z04$3`W+otTNac&~yM*RJsftwUMF@#9X6%PCm<(H%pf_W- zlYjz8K1!S9MSh{NxiWxp3ROJI-;LYRcqz}W*!*4kMMp*L9i>^^^5dPNc$7U}AVA?M zsbh-Otn2(|aL|fINo~ZACdyBUMDf@=3VuDL(}F($LLyc#>35%xEI{IwOyD9(4g}r9 zjj{b~e$zLA;r8vofHv-J|G#c8isSwwIJzG;YVFVEOfFEo#7CD90(46S>&TB5aA^>e zL$J;|g6L3kW6IX+j>l#n??n>60wD6^&3o z@+_J+7!a}j?=az39h_56@BpPOy z$rV^xL)m*@V8^;Q`+9rXRky zGuCIHJ>^99`=s;$)G2!F%XfM7s*g%9eO!T~u#dz%d29*FGRG|BG!;M&74h&q42$e0 zfW!go+bd?(Kd%#e_qz5d6fb@iEs|!r-AAllbhwI&N2ZN>=dVF~{#}$R;MC0)8GO(a zP3G#4%$o@=Q0?Rzz54RobvYzsIIDoU=44cJ>Hoaz=`LxmUp$=f-u?qZp7%da3|4Th z$PoS*rQ>l(e~?Ritf@Yxe%ngh^oy^SoIcV9rFj|n_cUK%0Ii zwJ=(|Ufi}UU23s@A21u20Yn0EdD*Z4FECy>KCoVc=odY@12g2Yg@rwM8ueL05q728 zd?rsSB%~DN)@y6N^URM5@E*X%SEzecUBk=Fp{c01oA8_=yqHl-mLwQcct{+)QFp0f zx_MB<)wA_oJ}^vwW*{_W`#UtGtTER4W@+XhXkJhEW^G(3PyT^2_lWO1|D_G=?-<9R z6x=MW2YO#MEw?ZYQAoV3G(3~tZo(8ags1KroB*R_RsP~35ldMc(siAZy1X@gQf`Q- znCjDit$|&F$%UIGJDa)U`QIMECRRGWWpMQ+_w!{*g-6&wGBmql7`Mb@ant!s-w>;1 z-{F$UeyknpJUb0tROE!He;vyibGx)aO;vaQ}>MIGfGl^@k5Xzm(K6w9RLv)fr5@OZMaH=zU8BNl1toT zT@LfyA#(*pDqJgtdqgkH_fAaUHEd|u9boeB$%c234WJR_netOt-j!6^fIejUK|H|2 zrZqC8u}!@@KWB|>pzDrE73~i)Az1zv1wf8mD!V!EtKkq%UW*InK;`-nWgXSdpM1Dj zRgvM5KTd(A;cHr^`D$7t(m_6`m-GK$(|7M-T3eKOxLaf z5VMbeaKW9__V8JoiTzGXVi%m^aSe4wN{5hpD}mx^qY29%GS2weQ#^(4HI8NYEoqFt zVZh`olLt!6(29VR5SRWBBh4z#M7>T+X+dDSks58iZiQjWtIn_PJ1tX-6)K&k&MAd9 zs}ay6VH;@K;ePKPZ%PYzL;;|<00B(BP8dg=l;5;3(IqzPgcpI*L zH&tVX^)RAxpxM1TqVeHRTBuKHIinvAyK^jUjV*e*@=-T*-SST|Wy7=-RVV{^1H#1@ zzNjuZDxVJ#FOYW;i`}oVrb{`jdtTRzw3D-KuKIo;oauY6;oZJ}tNV>;GLW#6Btjai z!-C9z-V?iYqaQDk%!n_d%fcY@SAkc(qqt)&_-R!g>|PcanS{+Saz{fO&iXvYs8s?` z)E;-fUke?Ll?Z;yGU35TA+!0|Ud|{ErLJALu$FaC+iRQ20hV z@xcEK@w(ARy3^YtW-j5q2uR$AubOrYAL+(J0gmb(i(!}GyBY@W9&V$gknm0vu;AJ< z*26qYz+GB95l=cUw{yPkD0}KZM^*)h{ss;;Ei`2RjKjPeC$a;Gb(zO$2V zW$RyMFM(fipachO_dYPNLH^zf0~Y#ard%H#R?n6B@Ev?vUP@g>LymB@r1YeQgFQd| zg%)8QzD!==Do;2aPUw}dCU;d0xBtY5Tsr=Ve*x>!WCPE%*iPz_rkPLV^9A^KfG8|- zCK#d@sF4}c0LOd8axz2Ta9f?%EaCR15$sawH?L#Y{3gfE&l$$Ff5yOJHqAH1)R$YC1KwMifVMHj=CL^LL}N3l{CZ#UI6W;ksz`V&uc5lrnC{%6EIh)7p2 zLHp=8fF^NC|9pikrrTlGrq6?(#fBAJjtfqmqz7;%%@Jd}UJ9 zr|NdCnsE@c)A_+8_e5mmt}In`w8F0XD~(GqpOqm=tnTrV`TTywq9Z`SI}GKzM#BWf zoq((*w~)*OS1(^j)$od{N*rNA3&4Q>z_mI+j&HJv3H{%+G-MH3`e2$`fr#odbh?HXmQA zFOK5qhRn=id~W?$<$lKbQH37}D;7-pJvNC$jnl^^V_{keIzlI1R-GU4a2Zg1A9G9( z=O*?f)z=x!!CJaN@sZc&WJeDEFs}L@?ApcXUbj$eeuKC8KA9}c=z7@GotBW!+?uGp z%HNAPZip0~t{}Xpa^%RUE@B_y0ZVjc+G*GgQM(yujVqtF1`ZU&i_N$+RYoB>_n|yR zK5+qm+7%XFN=ud({K4S3wY8n>r!{O)u=l=-I&bwXqjf+!>;i&_#W;vqfQr|y7>LZf zpyt)Ya6C_`A?v%qlb3O6*Qur%WvP)n^rp~d@1;SvN_O3t@WVx0V?g%dO3d@J~fs1y2s#C0LT>98x6S@mQ7@w4yqN~ebxC&0ZIwMZ z)M2LRN$X+o0e(iHAzR#g@h8p2wbjS-eXbn9GCun?V0;PVYF341p#IM|$Gh=uSHO7b zv^zIWUw~x{!^V8dw0xV#6^?FE{PE7i)w+wr+h=os+OO~^vo7j^8ep*LM3-*^epWJU z?XG6j(Ya2oP)*|$H_%IK+H~f>J!^deM`7x{=Fe_ldy75=hI&%PYkrP>u-wQPAK4;> z!up1kCXe#ZI?TKEVOB7iH34#OMTW`y3DCC+zCL?5Cc&<@wNXF6?kpeD57C^v6G8g& z($9)}UBmOPD_+#%E8Vtf=lk%ggjMk>c>ky8(iJE=QSYI)F0R7q4bT@&tn4({Vsgl; znAcOoZDWl+7d9AtVzG-+L){3?%X|fYuO3N17+nS>(Cah&(mfzdg6mRxc~DQZ9*e+A zF~_OK16bC%m&ru^AEAH$GptSu?y7k`xJ&_gx~sNE?QnEo0W%(_aoJ4yt`cHPbA)* z%Nj-9NprW+r>yUL*KW!2`U>v>1Juim%e^?b_RvE?M*fJ>3_Z<6QTXVNjU<5W{u_A6WET-o=`auYTgXpCV;C2NYhxEyv#!YSP7+d-zluRAr zA4;TmAH44*p5M6o--cPZ7Sa6#3o{ZAXL~a^Z~p+=rCd2xRmy6NOU;4nfrklqVBhLiocMzp7MxW+{kh@`nr&;b^xs6e*Mk9Ki>{Yb zsj=_w`wqDab<$M-z!l~lTnTyau)X4&T6g&6RO***SF_Ibq|C7nATBQJ39fL%E)|@i zMV8orP)`GO&%CoYLz(qc0a=2FCMafJzZiENjgf`exfjJ#=Og}39)K>6$J2a)Z5P#J zqh)W(A!g607&ES6?RwaME=fp64@`K@l{-i?|4`}1KNuCe2Y~S&`<2v;EkjzIsoR*V z*s8|$VcQMKey4HBGr%9)O-#l2*E9Z4pmXTMD?IP4lu4~-P5eaDW_0O7X*(&o8D^Q4 zTG?)$dr{#!%Ib(95a6=DpXe@KC>#0XbgJisTovA#A`Z^+$JS_Sr&7^7Z97} zey`tPE5I{hnp13XfEuY^pigf8rG`ihcESe^uBka)HYD}OpXKi@vz#Q2>Lr^Z@!+`xj1 z(wV72XX*2woYGU9hs#i;Bg{q%g<6hZBBErR@1>e#f8dM9`_frM0hwOYDe>)U{DMvV zHdcdT=LaMrJcenZ_Xoo?30k0Euz&aigd*B?@@ZHN_c>v*ote?hJw@Bttm{jI8b=DBXB@C}L}xk7nv z%jV5{NM3;hb4J#6K5cj(ViDp;uJHo^3}mrNFOTaJAf0XbVn(tN2B@B(bFoOb51kHf z2qmR^;Iq(kg)a*ap|{Yzr2P>;$GYqyRC10)=<;FZZdvlW7i^vGsGTGw5*{)T2-1Ml zC_P!$#E}dNskv>y@;KZcAJoF#+Z%Jzkw8!97=lzXkjoLh@4F}^SZ0d6Fo|F@-$q$C z;?Fd}X?;54E0W+b8mt}&!>ZPhBL{wbM#U3Q|9QFSC-)z9KAdl*?(5O)%-i-O{PPA^^OI)fZe9jC>WTiocXudb~oMf1Cxu>ThR z?sDy3{{eE5CBIfhHmJf3a zhyOJ?(f>!a{_XW)H1NaUh1&u9JOIZEIGp&^;r~y{aHN+JdJ~X2EpUF^d7|*T4pM>oAIu#s;K0}Nx>tQD0 zYYXiT3stp<2a>6(v9V6r-LtXU{r+!hRXXe8`n{5iw`~99N!j;}k9>vW%}>g|dU&Lg zBRJcseAuMd(f&%E3lB9Kq2t6+g{fGI%ExFQO3ZkIYXxEzpqGs&FvaqWpLCtJw7t1` z2tyAuW)3a9eUJSD@%D)9Ai)rs`^#R!ktM4i?!jDZ&NJN8T)C|N5WdYCZFO!iND@!` z5Q0)XE%bX0aoPpAj^lm#z4})j?{RCH6;A@Xm|AC4eami+!~JP1nG4!p9cb?feNfNP zbx8r3TvpjQrkK~eW4o5D2lNWr?x-#D3r%%jmA$F8liGqD+BwNj zARa~wucJWlU~r2x7@6<P2C2TpceEo>iI;jrYz5>CaQzUwKSZj$UoN zA21{=);IX}emELjEPgKh%ibRfUHjK}E@>PFG5(tG3iCL4VSY$swVDJqSH>i9`jBE( zfuLjNuhvth+9aUAGIj}X!<1SE81IIDK+WSsnr``{u76|HF6B+;)ASTVr@6X8)zHA{ zDaeD2w?0#)nvP|nPy0MMcU`DcQg?X=L5U93VCPb+GNC!6|ESp$!QWNkKqvqeYq!F& zBHa`L9{t)8+Ea0KG6d6q3d=7y3og^@$%^a*orRA@F9v;|E^ahUoql1~>>ft%sX>Cx|C(5%)q-nD^eiAa zz2wfQ)L!~+GTMI2&P_}A1UWX+BUw&w`6teh2SEzN}a3o1}U*1?u-HL0KUKqQ^L zFU|!Pe1GNf30kY0X1qm`k)vK=Fn)PI16L#;qfS|-c+24J8TfD8!5}i<0I*kc5Hh%= zS`TXQ%Y`eF8(odEBhs!AD#)ijOobJWJ1=Cbvx7eO+R`=^Zo7D`;E|M_RG+>KE~4u? z<=wH6Z)#cb?YDn_DVWp<`U`IpYMq>a+FQL>&D% zFki((Z1fLWG2Y3fc?y`ZcMqQ#j4_{4n}+W#sd1H>Ub)F^pXK!XboQ`gv zjqY~Avs}QwMdmu^$xp;r(o!lsayL`-hl(9J=hv!FZe?nQxTooYF#lGL|Gsh$T5y^4 zF5oj&!OQ^35fq|ydTC;Y=~0^jc|YzYxaMkz9gipM#vQ5gU3jQ%6L_Qvl|~Y*9Zu^@ zVAQ}gHOm(M!7V#}SiRuj;lMZNh-U7rJ+SX)TrtbmTUcJ2ZJLSL=aKklNxiXYvon zJ$*f!j}z&VD5)ZElZ^?`-|?)ML>*e%@KpZsIjHw6E5ikq$w07ZXa+ZR?Yf2|SLcX~ ztYNp@@p7{Vjrl6jxjIWTZ4MktAlYovuTqV!gCHH!7z(JbCJ^W4|B2JQ-QeV;!v zwil5IiU&xlWAXv`;qdM114?)leZ{$pB(baaT*O8VqVLF&)PrXmxW(jokR@)V6yOCcIF4OE+A65Rz62{w&pqlplecrYV@C)-7$?Ha~ zXH)-B578*c=qsu?&5B{gRnoGNMCakg%{NG}`V^phTaM17L){eZQX?zzhB2#pqph~? zp`y3duu>zz!oP!udq4O?0&p3dFa!Rj4#x1OmSd!r(ipRlsTYoncwGJZ@ACG=y944x zmpz!Qa{C%#N&pW-l}{_~Whc~X;d51{oN`;xan+Mm^5$miWo^!u2L=9wfR^Q!{~VHH z?jl}kzukuyD1io z9-{{>qnzteAEav9Mr?ExqA!*U`7h}db1?j$)ftU4;@#Uf<;F0G=6P<+gX%(iJ06)` z6R%DKme~oiQUt($7C&36J$Xme9;&KFA=VPV)}t66KAOM8>xO)<*pK;8E85}T6$9Sb z0B|q%5f_9#wDfCw*ra?M`J2$0ZMwywBTL1goz{vz=d^gZ3)2Z_{pcJwE|SqkuQ0K( z9)xRW(lo7K%M19GV3{X&bmql;{2OtzBD~35&lrX-tIu8Rk!pTWz^ja#`QRdj z-sXLKOuUl-Fnol2`A73@HWhFkv@XQuZ|_@VsJ4>Q*HZ&#MH}y-`**g5Fa$1oSTn^u zV7__BFvYXG(dNZOd0X8oPnxIQo`YhUTR>hQjd5oG07FUb^SwkY?Q96*L>Q5<9@6dO zOR@gctRIH9J8c~PtUh;8<%x#dM0v#Y*v!AB|Mw2AIR_BqQV1|SZ`I|Za^;Phkx}j( zb$|3bUe10NQeFE%xY;f10(YdP`G*AiLQD4`Usvdq;X4ka8F^|Pu^X}SIjUTYhFehm z{7=oLhUY{B3B43Xcx+pP>%BvdH`WV>Vd&CBZn)hXLQ7-YoG+7@3*-MGJNvtFiEVHh z6s;guTtw4ds0HZCVGahaAQg$A;W}>dUD-KIIp`2PzGsXgA5qWAD@`y$SE5>--jf%Z zEIP}1)Hg*Ms17Q4u)HSR-2na;i@VSEAW2%>S@-*QCiy<4vCUt%909_!tI2uKRn^+# zr~@$1IE~654!_Rwaq6M5h+dCvOvab?9lu~LwHg96zY_w_=4#i-GS(7(O6q!fDeh5q zkBvTIorUt)yOaDn9nj++H7fPf)q|p68>XC()7%uUl!Pj?_~Fh3#1az$wg<#zbo=RZ zj5u9|t=%*n5qH9Yewit-%-Q(S7@4@v%%=hIUc(@L0||ViW|qz` z;AV0p7`!qQ7NG7MhmnM`N`@lSHCtM&8MC7zRxlFx%A2-UiW{{BuAx25M-5~CUc3R6 z;k@7}J-f)8Id0IkwD?c9?e}v(PrJ67je9Kel;Um?Qm}mP+!xsiqC%|J0OP%TshPD* zI?Gpa#{b!MceC1)#?c6Od%rhGnNm5rKpY%M9j5DM(6Vb`%x?3-fr4UPPw=C(C9$GLG09+-O9hfi-bMn@``@%I6DLZO_OTt#f%*l-y& zv%ZjWZwf?>+#W*gM-tz4j-gJUJYr`e??YRFvXvp^b=jYiafaSyNnV;r)K_=8| zjA)0cSh5&URq!8;Z~Ewh2!itx^fkCGz-#(62Kp{?ZAjpTNx*3!zA%2;?;zQm?j-Jq z=s0_+oZ)a3wdW$M=C7Lw+g#1F+6)|k=yXLTCOGf1U2O4V6Zf}4QAPcf_Bnpp0q%P4 zf(ylCu`@xqul&@~JWqC(i0av`8Q*z8o$H?x=`Cqwt^NIRs=~MZ1*TBT+28GNoo#9l648%U8U1F zl5V>h4dIZ8o#5~)6i(WU-V^m8v0v|qd?=ql|KCpDhZmqnx~EA)?IqSfa6@?qw_P5v zaTbqlD{RDrae0EzK&3PQgS!4$Ncew4lSz*u_jxs`#6uiez%A zPEyYJ9Q%Qdad}5p$unzrCxggal@8V#!oYUj`m}OQvs?8fE-LmAQ<|~5xEhqjxF#HI zl(aLspVcG(YB%@ycCQyC5(VJKlH!m0n+iJ=tJUS@0%D399E+xe zl^1miqTd(9%K-i9QoB(@s!-n0Z9EkHuwRu-ce%3`EG`;5Vpj+p)>Hflz0Qv7L@EEM zxVCOg>RYi>!Ag5nk3>qgK2Cue|LyJXX2Jx%{Fq>*Z!KK5RE z-(K-qdzA_l6g~hUUX9Wg+714*yBkrAl{5T>qu_#um61r3M_yH#>8O9Y@!|~gbzYKz0fV*G>4n4Tk$Afz^N@7HGhj{{TpE*KZMcWt z`?0x5geUimCKY|~c$sL445#L4&S?G;cY^e{g;o8FowI>PUhp>r_<)*U`0*v^ude2w z1SvqBB^olDe~IQ9>b>E*`UHq|uuDyjlBImpi;bPgBF*-Vi|G1`D$pL^HdD{bZ(%{? z`{$*nDM*OFm;#U+E|xJ?-y%_7S#f+8MY$&9H@eoE*l#Jdj-E3F2lI?b5}{&0qy~UWN8I<) z(W{vpFr#36d`EIr{#?sGB($E#t&N(gw}{vLa&Ds8vu%I^LDU_z1vv~E58IrMI{9IH*Wx}-7HKPUej%yhi8&{bT(l|nsj&n z{88|?8Eaf*>r38}a3N5ZBmGD;cko_${^+7^%Y&A0q4pxSuBkR%K(s0?{mE@s13tN8 zG*nyoKN+GV#UfiV&EUU${=}Wlig&QA9mZpuU94~$q4hB`aC33+rew)8EDh9%0D132 z64s;_8l=85qB4k5IQAMgTZmwy7cV4~bcJ$D`kH2ah_Z6;!1~t&B2#3yKKQtcA+tM7 z_$>hj9B;(74YR8{0CUiFF{$t8_cj4vEqjBsG730Nu=&}VKodF!awRM`0oSZw0?ei+ zCFnqkr%F0hgK8&5<{(1At=@$N5n~G%m1$^UCNK`oFzcEo7gAGFS#t5M@hm6H0}RxB zYR7(um*XqII8Cc2+(=1C4&LkoQMa9Op;H!PNC+PW6}Egbg%w0k6Oq3CELCpUTA4^U zmI6GFM{(yA3-*o>^9siIk|AU9y(ZPPD#yP0=gxDsEV<5N1!}?b0Y(=dxW{8z>#axq zkhM&NYVcfkQ`6>F*XW(!AL8I>$GbTA(=86bm_^z{ZIr-}MD?EqW?6Z&AH zu;4NrsK|M83JA%41;ZcE%%=OMgm6VvYAf~V}BKAt1EJ1jsddFxk&Q;+0nszuF3g{UXd!y4= z$+s_3A2j;chg{Kc4PcyXx1x;X%aFN}m(@rY!$@ryD|?wRppci6uBm>G;{C6Cjf#Q# zk!ssTV%c4`{P&xv^?#@B$Cuh(zNv@@>W0~l;EwC3#(1May#$b8p|8QRMPCqb5V+HK z>iW|>Nw6I{8%oeBspS9~=BN)H-X9zP`Y}!f4(mk^g|>;*DC?Nm#7X_@#H&@0yZ`nH zTa4*;Ha^Od=1P_TvS{c*-TrlF0^KXzm>``&6%5S3Oxj%$yH8a&Cr;_1IZ z-pc|Ktazy9n~Tn9Rq?5j855TK7ZE!(r0!Abd&W2h9ezCxTze8Ab_XES=R=;Gynchx z?nHYz{oZ_;O$8=p!i^;St8ww~6}PAZ#h~v=U&CH7g5ZxU#{+uQsHdCB?~@{+Rdtc2 zeeM1fKRlE=QaEti11*gVj^^qi8~B+O)b^u_Sgeea57Op#u#hx^PD}8NZcB}aP`XzL zcFW7+I`}u$!uvi_1Mqo!gTf7K$c^}|ri*!*+VMJy)=aa4m@(NvvQ~hgY$~py8tyZn zy5AhJa2?hRett1?5q?2JdR~&9#dF^&{kctkTBc}s$1HjyGnqf|%YWVgT7Au32{$Z` zh4#-bo@+$4L(i*$GEienKLQ@MpSsXuWuDT>S^W1WE?tS zh4dg7dFB^-kY_7iQD3q0_|CrAN7%D(KT2eh-dsn}WtxqyIvv&@;K+F^z74#zXtg>U z{&w+-8e=hl1Y`V!WS&>?#~S`#b`zci^oRi z!v;7_rNod)aA0VFX}gnPeVv-!OMP%#pUr(nh;AJq4hS(q7gi}DLWbWrKyq6*y)WT4EH5onpE8m`DgdT)RTJF}e zrSz(UjvNUV)vW-q{mMd^|C@_rH1a`fu~rqT5rCPONg(yfkZFcz*=&=>)B+bYtlR!E ztlLik2!3JbHCR9kyB&t@3dT<06uF8C>|cxBf&0>2MjGm}?*Qba1Bdai9zP=1QDnT* z@xh+oa%3BUOJH&6HLDR&{fUq39GT&{`(ssyi{DO6d5fIYaN=j&tyQa>86QDkQ-12? zqxE_vnz?a5kKtz#PfVvcOEc5xr!@fYONV;r70UvcFqxjNUm+WM7{2h- zkvj`U_a-ILlp^iJ2FjM~AYD|yj-Pr3>bW>9C_WFif)jENV{wiAM5!P{ID5udpyu#5 z^w_ix@TP5RdHyT@43T97t^z@Yig9W&MxE4{k%d1HUx6Da|0%t>iOHoMKUNCE=LkqK03>7GfEI zbDh4mOmPfc)^bdh_}1m5-z{kZ`%965Ir$r-XJx!4<^-8ehzIU6Gy#oq?=-wM%bF?f zFK$Kbi}^dJb7FmK*Qtv?X* z*Ym`3|Ec&(M}DOi1aoK>hG0+3nS-ZXkl-*hvuh5z%18GL;P$~R%pfz5qy)EI$~bKk zq-R9Uh_`s-P&d`uB-k`#!U|?H_)|K7xVeoy+{4cUN&xDJ6+XSOzsy78D0G55qs<;n z3Zf)3`nPPU-&&k;O0Ym;$#Db;jyKWW*^dm(JgTf6I(Vp5sB+y+g)W2YkyIEi(}KP+ zQnth-@QNSq%jLm+F{cZ5yHpw(G%4BcCp2zc%>YT0#Ff8kJX70F7_gA7>L{G>;xLw2 z3D^7`rFk&6da(@ah(Cgz5H!>B2Izgm5iq*nqbd(Wst~$%|3xrCXPs4QVVNLEk^VZ>wvxhm##9Gfq7gD3zIec_44AvHVf_$= z0lM!S|D6~t)2dZQ_fYRssT!tMGt$H1Q<`x~N!>P*0~&Mw=^tp5cv!(J$pQn)fi%$Q zVp9qOvuC3ftKdje6;JypqkQU|?6Y#+g|icN+^SZf189z`>{vmsS~@A*x!~7v(*<5) zGyXLsCl)#ZIuOSFB>Cw9{lnuUy+9t{K1&V2Lz^=$RA4`Peu5yHjYkf{DPg@krJ&JrO;Hgi{Cg?%rQo1kjvrLaiU z7ZZ+#JDhQj*R{UWkvxYc)U7rM)b>2FfhWlybm`!qN`1wOadU<-ZAjt}EJ%Aa9b3!h z!-RUTVIn_*9ReS4BZX?^ZPOr}cO_t;gwthfm^H}4Qx+|XxgR~Wd#q#2zwKk~sMZf# zss+Q-cROP?dW0y7{XE!6EGYh><~>TxPV=RGt}l54;H2)q|2uM2RVLZqhfQ*!8()0S4#Ebiy8 z_M?%(PfMjXgn>Ut5F`;Xz<2~ry}O1>E>7(lB;WN_pBV3;_6y%xv{XG`WI$^~S2 zpiV>xXxJDum6($M@C^;SE=f;%M#CY|G8ZQ;r8_=o|9?4a2 z1a%bkulYs@^AYS-QD8DBF$BrV#{tm?;ElsedO*i8W;KOnNA>t3d|zgVw4>cfn4B;$ z1$NW+o5lK#reQSnAZY?QnT<#;#LLzQNm- zLdT;<0=>8aQJbkRSEF#o(C$EO-@kZ+e~-8FVW`Y})*o4ISD0JWx^cn;;p@Mx6L>zY zc5V){Z^6;g<>I~}RFv%0MWKDyYFWj9eM&DJvTfjBg{t6ApjOzmOmlro4VD`lq?D2? zQg`VELINGc4}INj%R}3ydD?VLHqONbXQ~I6W7!b0f1Njwa+vDDf*d{*@_pwe+yRX< z=h3CM8&jWZxaUxKT9y5%%BPn!>%@XDBxQdyMQG6jx6~H<^1CCej(#^C$GA0Q- zt5zluQIjyO_#ak|0=fUX{guWh?u?!$0bGbE=7NJWoHN@)5fbE z^pH=vr0CHL@Ry%7APo=n34l^0~*tgcAn{e7|!B}97tj(NC}Sr-EgM} zu-oC1qg>VF`Il`n-zzvk_8egPhLSb8CIXs=%XBWCW}wBfLV?if8LuZ?U4?vtL*s=M zSaJ|_Ac5>3btK(|*u%WTuSJ5%Y&Xx6x-}@{ z2#;JYjU?RgQ|wlYG^3Ly{lLabXt|03Mb;Axq*-`@Zbg7*R_VdV>%hZ|qixfUtrSU* zd&h?VvIoP_3wUC0yg_jFq`XdEe!irw%U$*vqq~RqY6Nd$*Y9*{YKtaiL!t_AweN^* zkGC|~^Tx3i?*wZC(V9CA{yH(w$Tj$@xO$H6Z2@kY6*=a^qXrdWfk3^a)ZBl14c153 z(*@rvxH;TDq%6atHty{fvDuhkjC`nu$X2PyoF1FvXMbodt}e&T=(kx**#~&9aj24= z#f;NTDKed^!0XxeVz1-hk%@P7)Mb1VF4XYOu;72u<*ZDRo0ZL{b}rBjXZeqGEP>n4 z8?zP0;aAp%qH!o6#DexW*tjPk);JmLd+8Q~z@1eei7BJ~Rj+Xefzc7i^da%+*JRg8 zMRem=IDT4e{#hC$J+)F&ztxF4=gU;VZi^=xy4ZqZVr84-xGuZDXpKB%ddISFWLZr< zdaY8!t_+N=wbhByw$Cr=raLv9eBy5qcX!jrTvzH-9{}rx{l4>vM__-O=U@yJZX{I= zg$~lS$upv`M#J0O@be7Y>SM#n`?9_gN!d};Xp#G^Yl5w7FFM#oi~h9i_;oAKq=r{C zQ+aKS_h_PZUEY(FI%@^R2RNhGCt(vXO+wOA$4_`NGzz2r#=(&a>}Jz$>G)}$DKrB6 zey5-EJ=sLk!}Gq>i}bwuD+KP3t{J|xr|NhUt#h25gSt(I-9W#g@7 zn_ITcWw(}V*?h8XW7)Rt`kw0h3(oJHbK|=vSz$k#uPzazK<$nW2 zk4D$IH<@oY!Sji%&ZGRrYx-Z&7nPw_Ox<9`7WSDL#bos8pFP)GxpFW8JdbDgC}$&$ zE7*D#?xul+Ydt~(XD>De4HhRiUx_7m`d4B_BFv+_CBISo0-9A7$=QV-+K^*-m3Eoj zWG}F%#PkSU%UXVxsZ5@T!hTd(;Jtsl8MFX@B3fo$100THOV@wJ&)9kE-|3VFr8$g$ z{`TUhkxQ2R(wR)i>NS7|ba*zD>*MU&RxKPZ9n%ea{_m7NDb2}SBQlu52I*c))c4fv z7ZIRiEGtHlo&B6|!I~U0++3qb{^z&h=a^Z7Z%lY@mx0nB=?I&*q-*<;C4dX1f@@Wf zv!0L#pDy`Iu%FfVYatW8tNwk|r&06?ODEP~glhrbO^2)NY`t6Z=;6t!IJMi7<~|%m z=VfUzNP~eA{hC;enGrQ1=Oq+gl&^M1W)g0*@+WpM+3jt-140>xw=$m`o5dRi{J?&U zL%+vR?eC>E^|Ec?0$;}H2u>C$8zs`2!h#*xe~+{Mqic4{HR!0z3{w{-t%qk;-179& zXAs5HXJbhU%E`p)#bFx2GpsIVkQf#dCnmeO!pf z;lTtgdFfi-ZLVgE#12TAs=1~zJX%|z?&{iTlIuZlY++-4J@mBj5)UxDoo{Hy1{$ zlXTF|kGm`(mK|0}<@pp0(`L_l05YJXD#P`GI8mx7b-qBA%gtFoPP=nXWF^(Pw*&PZkG|5Z^p_!3?}w$FBA*dpqd!}H2_Bj zV2cpI3H2YKww_mgRg-c-UuC7SEHKA6f%r6ACs1YyWBs^NH-k~s^`G7ZVpcJ+ z{Odi$iw^04z$(oW9(S$6R>wVVbj_2*RxI`(-T&l>eTaN!#h*1SmS*gjvzwTJG1zQl zA-mUY1^+GSt5JIIl zftouK7ykXH8k3OY%e>2EfQd-5VI)xWoT+zpr1capgG}78PgvHZsZm6M%gM{ zZVyM6y=RB-8m%ifhy%;&_`OAx|DZk5cak8ivyWogNoJTZr%@q(-$?$WwCISkZ@F@J zr}FO|vr!TK#X(8_s+v7Udl75k`e8FZK@x#jpQ>K4@-N^FmUAz2kFod4+2P?4{DxAE zRtFq?;~ckmZQjXk=rEP89Jr4MXP)&C<3x25B;ROPSI1H#eld@G$I6$Ix+>npeZ6lP z4^5A})ggt+FuM8CS6Od8|0QZa9PAzmesdSu?x4w@AC`Z%icf~Y;X~h=-}J4$gTO~R z>jwf;a71bg*S|)FO?Rbuy=`um^^D`m#t7MJ4dW|pjN|X!JI&6U2TJAFNZbVKMLU$T z5$;Dcu(GwZ(#$Yjg9rp00T}&A?oi1@Fal|1n9eoDw0oAT36Kg&`UO{eg3$8?)5JYS zn=dqtoZ}Opu&E%cAgN;IfiG=8rg9 zX~^Est;Q-|FMC!Ql!?>^yUn_zKD18g2z|gKLO1~iz!L&MZ+jf#v&~nEaX3ZeEx=7H z-6I4GGEZFI83ki~gH?w(98`>RckpSHjxJ;dw|sAe3QiHLw$k6xBBL zhW;j$!AT&JGW>pb0l?ecU4DX(QQ5Wp=lX7qYgDY@=M24b)WYJf%Shd zLjEt=<*#?#mEiBj1_oqo_yM6=)?jMiy%rcEa7~g#N2W8TGtvlVIyXA+0twj70tLm> z^oQ|sW-NlwX&9dEVKc(ji!<*nQF=?BBLWi>E9R$~;>t_sZmBo_L^CHDnACyj^~OR}T0-hPE*se)^a-CXNnwv}>-j;fU4v|SN)3nTH}Qzv z<+V6(Ke?5~^EAxr{sDSB0wrb04DuF2QjsD?w73KeU!}oneAhM4lhUoz3d+PTVCR>g zL#*qt%dVd@8fZz}_I#MP*Y|v0$`}v)8T;lW&5vPI-S6&I3m5R|Xp&#!eDfkLZf+lr zdtm~-g-G&G;X&j8$y6o*13cZg1w5#IHucC}PZ>F7Ay58F-~9v6J#cGIsbZ>UVRFiET^0~8 zsX*m>(F2+@^9)6GhyW)5+Y(mDq#Uc0AdM^ z5|#j`KH1i`s5&gQPGJPVz=$(&YtK*u2DbK|<-PvZ2Lr?B=~IT(6jZxIAsj+HzZwA& zw9(;=K`us)r#n_XdbCX^ZxH%aztJ$lgBhMe9Oj>SEpV9KP#Mi~7|+~=yd8t+2yRMD z&)X?xu|7VAAg`@y&!awiSxoSnx3Z}#8gjm9HEOc&TfEljuvbRcn#Yd z$;{*PcK0Z0z?_Uh86=x>>A#ZdU#4LZk<^=g$Ab{if6bP&b&Es~Z&&?(R{q(^TN%7e z0GL(acA?v-aZTrDsciuT$K5p)>8Fyc)0P+NkX8Q-+JoAu418Blt|Ng>m|Zt_kkQXk zhu!1UwyO3U65A19JD(B(@6h55<;})7X94t_`4OE;Ia3ifb3;3VBcKRZ{1oUAc};LI zx<rqH(noD3b%L$y`33>vT7Omc@=bvZ!l-(T! z>zW{gr}M`Xv-^7wLi9W`qaaGVD8g~r7ZgLI-*pVazJi;l`2U6l{~G-M8@zru=0Ryl z^&xa1L+1@p+G7KoOGX53#fU@ZYqwJf!ENzcTyM+hn%6wL{N4pqzFI!?9qK%}Av^fN z6i5Ks#5nj55&dpfl+y9UQjVHjc?ssO%6cz%E0VTk7ou3$!SN_r9(rf8x8c6w8g11_eU#Wcs=~$h`9YT$gb#t;Tg(c-l%1ZDS$#yUp{9ug z;7?q3x3Lb6#;!&~Lcmqq`PKlTCocXcex=HEW)iLa?}_h^LTQq?k@AWi(U`>IL!}0Z z;JpQom0@%)U0v|e&*zVVl6%`wCS)xlI@898TN@>Ky65tmU7-3XAG$aHMui2?Bh2ew z{(`gVCY!3-R^*-6z}wZ6)Ry*~H{sMFGz?KGii9iV%bWO(=8_R?X;Ae%!qR!}`5~}A zJD(vV&=vb3#TH}PX=a`y7_qD8lOTXv>Yf-4y!1)h0}r>)btlz)5PviLRPW5STXV9PcLjygP> z%pR$oO9KAmlL+93^*ZV(kGUagq;o#CF_kIHHY(Is`5d?;-^iIY+trtM8fRE+J-n0G zazwX@AuA!R-~Fp~VKMld?5$*1^@qe=$N?^peQ3r|0g6fy>+EA^x5*bZJzu(k?q}hl z#SY)T!{rlVI9QoeGlNrSP2!F$l4K+RgoF=H+X(x;TX#4J?n@&_CdqnWQnr`nCE9)w ztE!@z5@o4&P@(Ukk|<*Z_;2;DjepXzQFjNA7|=OvmCRbL6=@DQtn z(qI~We$6no+#rc?WqGN`Ju{0D-*zNa7Pjz4m)UY63z9dlpG`hI)5EUlZN0`Nn)*k%MCjYIXM|w;pd!DvRw*8$^kwj7X**9zx%5Q>r+&9gc5rN8sT;Pl!J2%_ zTm(zOSgyy#O4VJbcGQdviRJk#8EIbN4Ta!XU7)mbhB*bD750}4)Y5~_WLTWRS{cus zpeDeaF@jd=YIMXs-XlL}Jm1oXUr8B%GQ7@ppAMvxoO64Tna-bhn3@NE09hP3ffq2jn0usl z5gie^6?P|8=la_F@Vv$?=fd5!?{@v(|4#GCPpBw4fdU!$0)e;Kv7ZniH9h#0s-R;t_Kcxhs>x;;!^r~HK-RrWVOR#8ygide zngOdW_XzxDy+#g52@<0By%Bh7Df>+GT@t^QAKi-jbWK(8n?gtfDbo2@2(Zt1E|8u& zFM8X?nxGMD2W*Ypu)*`fgO=rClKTmlKfK`Iv1EbJ+u^7l0E)4M8=-&on*rN*C$t(h z0jj%BAu*H}{9k~cjgg~7!kjPZ2sTXTe_>7A?$8^U0tpSD{rCBvYu%=~$)+ zoY67(aIPP1?v4NH33)5VSUWpoEf2TtJDoPjuEuzRE+5oWgDa%;WOXqHdm85O&EP_` zo3;>(@}Kd)G3?C!xdmDGfuk^{<0;;0(Mb8+2R;Z1(n2^fE7 zUbaBg;jm3vVx+*$y|o8r5l#H(TF070l(&8=8+)SB)~zrW?Y5Bj8%pzD;4|%>e9S*d zen0#X8W2wy9Iq`kbNqAk*;3&VX?~;&$x3d06T0dYH6wdWdjxfS=g}J#wMD&KwJU3} zi#v>6<-RxBT&tG#Za?`y%RdQSW0~b0Opc$E$}xNae5Om5rQyigpUiZSE>Ijz#k|P+ zsoJwV=Kwz%?qi8@YuG`hkYFZ)ZvhOhg&bgq<`tqiDyQn;aNZ!_NcSnMjrcHX;K4h@ z_m%#@-h5RP^4Q(;RA$j!dl|mK%bDX}6Ar%h^jYBGU(r`|*&l!K=#mqej%74L2|ce^ zMDhcW=k24^TaMUqVy)I-GmvMet4))g=QE*vQWux7G(-{?r7kEiy|BEw-j(O~{rB_)ePxC$}pV1j>}YMYiEG ziZXz00h9QT^`%a}uu^Mx=GnQTGOjU%r=6fu*+yFDUw0>ia#@8CusdXwV558M&#_wY z{<>R5;403O5xbG(y5O|2l?97$!(RlT-}#CPFue&`H7sPkJmQyo5%%iT%sPjgwnUGr za+6g&&)J6m058zE3AbqS@teC^_;9?Z?q6?LYKA)u`s3579QkErq_jO*rJM&tbSB$= z1|d!W0r!5RcIkpf%qky%@z!cS36HK zZ~w+#(r$DD3r&-~*Pd`$Vc+}+6u;GmkEsu_D?CdPH(4G1{c{`9Q2#lmmA7Ot_guWT z58OtIvq+SDZnrFzl$yrw(h{cBtp1`PgA$e0hX+<#mp|gd{Kk+LbMou*`7Tg`1UA z-6|+&G^`1+k=j7^G{}*)ze>)mAF-q7n`>vv`qXZq@0IPWO5wR*b0z5nA?#fP1xKjr zZK(hEqQ#!HT1y+XTb?&(jp*fRA5B2V&6og!U*$FxXNT0W1Ljpbtrml|G$92J4=J2Q zGPec>eEju49&^5d1j7n=HLSuWmRU*5v4x>;@r;2gqc-s`|9w>YL%l!z%<+GK_}};H zVFoRU!2#v{K6775fNA9aY^$YLoKo%xdYr*Rg?^`M%8~3F4o~->47EAJZDHPaan7@Y zL09x?@k6F$hvFMG(OFZ6={UFj+FNe#7ujay*LB{7wGVwB|NAEr^_TS7NYJWEN1FYe zNRE{GNAQU7ZfE5Y09BF(KYys)|9q-4u8HuzRms2DI*tsOI;sNif3&G3Qgj zN?m+HYv&vy#1#v&<_XoXOkTLiqqJ11iTl~JRM9E7GP|U`1ZomDc`=FQzI!?KBaxt? zh2Ed~R!V^eK4|mk#$H?!Ge75!^zPl;VctZ3%j^@=uSObj6`o^a>xZaaxKBfTx4AB( zHmRW|8<_Y(tD~HkzG<9(Bx7(wI=bK)5k_44uhah&1T#w)BumZz2}zDW&5j&Rc>i>< zee|?X3`Pnz85{WGW)M0gHL&pP!?kEE_T(m=UY-8u$PAG!fA10eJ%G|ffF!~Ocm)HP z*8f1N!?!51ASGGylCr_qKHQcLbe74x%p510`@*em2qMG2F0x9#x@v)EaAZ}NpU_;t z{GPYbHrBkJKp|7Y?fcu)q*)Y6dxO40=L?{D_I`e{Vqe=1a3c8elx{2C!~xcETIM&Q zC2b5T{NQZ$ad(I}WHY`K4bY43<&?X3aO{_rtkvfV^(5pjV=fGNmAUT!PwTnuxKP69 zqN0vhw`sV!0fNaof%ed~d}*KFp}>HTiZ}^XDwFgLs%fItV+K~eU`_}|djTzF>(0Gj zq@!HMu6s&J9^HRlHqC=4j0=o-9P5)Beq0{z?eZQY0JVcYe*Q5S`jlP?Q4jB3HN3M_ z_k~!6QLwZ)cKpMpHmqL|*IleKSm|YX0gP2%9ri!%rb!kL(cyBh9hq^YDRf&dL)v8g z3&_y?X22$Lqf>aG2egU z>~W~%JFCh8%P0Jw6Yt?_>uaSUhDqCczB>X#`(L`IFQd(PEJQ93D~eo5z{+)wuE zw>CZpH~-tL4=I2m;Qhw{%rQUvF0=Q>2>o0Y?z6Ol&1Uvhh>R0;5$Qvn<%iIry(t{o zD!87kJ3$m0=;xiSMEyDy8>l%AqYX zvTd&*123SidGhCX@2&;{7mz*;f&exVFF7>$M-Dx934P7S;lpeOG4NVp+qIXl)jX3s zwF~78figd~AHP|L$N?18W(g^bcgkh>Vd9Ane$MV;{OpAagYpb5=jKIUH;e^Q1bQi; zKd04HZfL2GOm?BEPgr697)IdcGhm2hT^xQ+4S%P}NR@;bX2auX*&!ooCoAQyE(~X)q5pGv*8-}7RkZNxjeH^j;thXItrDg+Tbv2 z^wbic{$npbboDY%E~j<0g`~pA(qrp*s05WY340%;d4- z6iebfz+uN%OEA^cadp5szN$;e)x-aV@RAMSx~^bxm5xTWGC?eqXDPv4h2{P(b|tp< z&vt7PW&R;3-a10tC5+7oGiH19b@G~V{-5)=yOJ=CFF_zIfAk(fu8IQ!fylfeBbhHd zciroU-$5H=z8?s-a1UfgHX}cBa?)1O_jl9RjN^Utx`vf|41PWN<71b;Pt-4|9l^-T zEAXH+l))frWC@9WsLh*o3D#Pdc5hDj^HMok)HdG0`*L`wc5!m*z+otpVMc=WRW$Pb z+tWZH96zA3au{&b6(c3rOU>6OFb5~gaUYyyz-1j+BoSk)Sk#5<4G%pP$9q>g8E zh1EM;99v&ko8Lj7yDv3A@3bz-{WD(E6RuMliJ{lqcfa^$wXBZkS^P!b z-AwWLLBr2Wd-fp6-_evtz#j)>2s-(#$GWk?KiibRcY-Bou!iK${rl0_V@}j0_hqY} zv<^iOV1$ourKw;#-r<>NmvxY@{akySQ+E64O}DWl>s;g6_hD=B-X9(U6R=31xrN~T zBu8AQT-FI$^EKyLg4CXf9&&gj@^fu`k+d%aTf~cwn6Nh^SamUW>G<;fB_MFjLXbp~ zPcp95N$iL2ww|XlRhC?lKe`}*r)-oFy>;#zL0cIM)#%x+{_upLGo8wCZ-uLw?pY)z zEA`uSY7S8N$`5d>)&>k;BI{=Nk)9Ap?9^e}uLpS$8)FT(9OE%m zzrSFc?}|I~!DtcO@ynX{ZulY-gfmt6Hj#Y|;?2PwWdRcLt-JL95hw?-pAa#yXWABJ zt6)YVxrDPsi(0jPx~cbl$P)C0?+#VP7G9W1)eRa3M@$`ttZe}riLw$-Dk$cSc|4N; zxYt!a!`2sJ4BYBNs&${n&6_Yl_!12X9lEyE(HCwXf7t1}wthR3u>u7e=m!AJz-a); z9V4{s9C%PFy4&Qr9~Qe-UR0uta%B#N{>BiNZGJh!L4DXjK8qF(mnK{ATapBHbO!31 z$-qW_spicPUpArB_$F3TDd3ggHwNmO%IMOw4 z9*SfqqR6i@4{L1Z-y%~AceL^w=T_1Un%1hG-!XaQ?rpbG?Rvff<}HRwk(s} z$YOnim~qRad!^YTHsZVcMsHBtGE? z0_Myj==hA0;Il3lmPrOonmiaH8%4n^6ldLPupQQ(H9fP&&NgS3X3Ey3Pg-DZ!0r6m&$?r0 zAIDR^IT;rcsP<9Ix|_^i!j~B&wTATl+*9#g7@y;|Yh`6MV|&#cO&$T5YGCPk)b8}P z&7u0uaMK-Q*=$(UWNS^KG26{4>;>J4RF7}&CuH5fio_2L8NY6pjW$N2+-qP9S)yJ^ z&te@rP(0lw91_+K=gb@CMc)DK8swY4n>m50X)2}-{S&D~(DgJv!Mo{}LPd3)=4_`I45r%7a z5o5?r@-&sDB&)wsMOk{^Fo6}(2pji^FZsP=1V&l^$w3}{gpk&63V)Ul?PCdtVzO-& z;HfpN(jJOTwAXd>TRl+w(SzX$&4*r zh1Y_UMPbMgcY{?2)DIe;=D(tg=8AO(9rU|tEuCkDhpUoVKd}j)_vE!&_&nj2Ja91{ zX)=P1Ll^NJj@{5(4q#WRIxVR)$qe>Z5zAD>^u(1 z2Xj=Fd_F9%xpcS3r_x}2E<^Cqy6f>S^Ot;p9Xk9B36eVi<@(<s>l~o2?K&;s?RM*6Ua2RbXCfWkQ_Jzrpux(o#7D5IiUQxT2I0 z!T2CGNv?URY}U8C;w^_-<#l=$xVZ@rPSS@Z+LV4uNr89=v}@FFGMC5)(392R{1n`H zYIq~g)~13z*Cfxri1zRl+~5wCKA|Q0xJ*B;#;z0BEN6I$Ca}Spl*>V)nV_jJ`J7wt z;2R`zM2@<1+i#`H%o-BPV+XRdt8=`YU&D3#*kewfWD5IcFKHvw(7hrv*m?L zwJ!KxwNqxkI&Qdma+9~*6WfOVx<~oXp8b5N&mxg5E`z^7`tH_^)R{j6^hs^tK8j5b zxcK5V@^n#(34~mEa?K6NbN?71diTzjC42?srABnP-VRxck$@e~kBSg9d?)Yt)gktZ zdJ<&!-Hu`42{01-W6HSiLs z3tb4JO$f86)nr4Ju@<F)-m2wB+{T~dDcSKMb`bC6xTqI^g0gWKSv~3r z^gYhqSAdp=r2H`w zMBpvq-~QPi*C&^^{aIn1sYX)yF+ROmkm|NqbZgUenz>7~ZDrvzVKj~0_7Qi}VZX;b zIUoVN|Cl81FME%hcbnqZql?**J^jbuJnkuU9wqougfjj_KB$?<2I1+iMJNBY3h{HS z27hyN2k6^PY;fW#r(pR$BeyD61dtrF$_-97%>DVZ7j zObi1M#?q%cG0}YRiWiHXm+vxk2I9$~crh*Mn~#Z2QC{=1b)BU}W21lh@b!%ut!jKEwQ% z4c*DD_NNqG5f*ya0akieZ#OY1hY!m8ulql~>Vn~iuwwWI^5vQ}Y}B}st3;tA&c=H2 z|D{iOBB$zPls^J9TSe6%VzZBIfB?uiX1li;ZIR!p9AQuDT{cjuYj5#H^zlX}Ft45c z8U|%0wm$U|z%?0Nk$tx8;4>us0-QCHg8a|R+5cjc-7=0F;Un=Y_h#nTUSA1+ZF*ja z#Y|K9u{rXkU@+|GGc>5J)A@yu`q}GipyR0#6{t<_-f8*YnAwu~ZFF-QL$Q71U;I7i z+iLLYDI$yC1S8t`g1SoE>q9{R0ru39!JG+2)Ik0cR(le|M(yT48`93Ph^}?Ci*2=t@WP6XWp~O|$S(U4xGv7tqx~`W9P{;Im)Vw{P&8=91=YgjopEgHI$1LX)VY~8*I(DP<`#%@*JH(kO=u$r z)1L7=wz!>8vh!zxlhi*M++6m>$XI+i#4znLvZ;07vJy|i=#m)F`$UFk?UJCyIsIs8 z>VD?O+ML#X{5#A$p7#K>$Da1!bL|EhA1gG<71`qEyvVtxA>bG&wN+KlX~4ltLlL&< z)loJ|{{f5UKlOC}*~3EuMq+c+ zx6pc_aYh2;Gh5YAx$Z?S(&WNbAfxyDHmEX4A3tRoxK}>Ct^455^?o=2RNmta7$%td z#DuF?`J*QLb?e@D3>_)dRILSwKjw_p(TFxtH5rtCs`;zeg0T&UU`lHlsjPE0-gvbn zDvu3z8+-b@#;C~#q6yq7<9^BvZgi+@DQJtItF><3oZzSHq&N;+k0y;@bk8rz%-dCa zFZ(d98FYeEly9B-!SBUI6}*6>W45gf_VdzxK-uvv1bSnp>3`5&d2%82@S?U=+0_^T zhf!mR)p)t6Hi&Us^e)`4;7mOIS@QH4bKp+t`awh>YSeTI9G^MOu>y~XE)+A5uV|x_ z`{P0r3*0YC``2BY8wF!EQU<*kt36Hl1Ksztecy_E6q^7?c_KPjO}fg=h)bH8%nAX0_JJou%0ChLfW9n>tT2P_I-Ui|(9yimZv_TaQ>x{wayu#vNs*Gan zp5z4-YYtHd&nJlI1^R!+viQSk!%>s#ZhDO#Sqv?`3)YIB(uyfp*2BUd0+0AsK$^%B z239U^e0x6W7xPC&Dj>f@*7Q>J+QdmKG7PlNRoUGG`EF70eCe70>^HDr9mGTP>t&N6<^_`1l41{&#bGDS$iJJ1 zXpt7##*2dv)CWc=Zx8<-LY}w+j+nG2DW8_-wd2ygsxS^zSg*k?T0hT1A@Xt1H&Owe zZG7Aw9@4ePFv|i(mXr1&{LO`1ZKjut3N>LUCHK@|e#)~PIdk5cuL4S}NgH_@QxL;r z+3wifL3zrmCDV)QmoA)^OQkjB%bt)nSlR;8w=p z3Rh5u50XZzyw&b$poEN!vJ;S?n`G>kNVS&i^f~h;sA;#Q5lQ`-;aC{|rf&3I;)#&n zi23q*PK5T(9bpKn0w-S@zCX?7uU`5O6A3tkeYx{cZJ}9cpw)mDzcbzPsKIGp#>fFB z$FN6=2su@;LZ!XT|&DLbx;`tONB{q%FAmcRb)~?1a@yQYQ8@uR;$zEA4 zPEXXv7}F6{a{+Ukw+oydNvI?{mJ>8iRyr4NSB3)FRrg4>;^|Lz=oJDI1f?EfAKg(r z@h!Nr4K`j4$ig$84gX_=@>@~3x{H8jcQT9^A&`ytL;e^jY5#Wm?^C$;Z)vS*9%8bq z*)Or;8sy;&lWqpo0otdmHC0AMZC;uD{QYThEQWLVk*=_72~a0itCiBe`y;`NV1^@U zweUZ8H(Fyv!FRGV&o`5TBZJS&9aYP!knZvI);KPE|J@G{i%4EPqBJ5m!9i|s)g|n@K*uf^QF-i1Nz45Y&jQv>~kw38!2 zyRo^z4Gt%m%0sgR>oY0LqK)y&uvyN>Sc$(Ahy z)?lym`#izBFJFIIBTyUvBAy1rky#Cw-ARK+L?@?L6?Y!X_PRWOn%!M?X8i z;9I@X*=$p!8aWr{Ch0XI1eltIdh?O}XNiPwa*7FTu?lv2P;3LtKAJru^;x>Mr%NA* zQv4m%CByk(Y7Vz?bv&zGVm;2yxdlz>MA(!BV^~Ov-oiz+9=iQ>gF9slj=HMJ1uhP(nzvf&f5K6LS@i=#* zgt;mQt0WFgaLZrhO&Y(y{23@K0n)gi;sCuTYxRdBge&?&z4NVOk5fMJLH!;BOctCyo3-ww?k@kWCfRwa6^=Vq0R4{&E0V5Z6hCwI>;HorMczaH#r>0-icSTmk+9B;c>)*J2rQIJ_GWi;h28l zD(bh>GxX7XSg*h$s~tJz)4|6soc44C(7*fQlyrhh&&ySQ(47j02->%FlVhStxby`E zT%fqCl+DHJpW)Sz97B-#a3U8Y@IPIBJ6?-6(zR!+e84+T>GDq5Nd0+jLGn5crsSs( zQ7tHHc|F@lZ)q@hFMb}blGr%nWiu!B7Q81u2BKcg0@;F!CQzID;w&pBB4Y1Ol~;t^ z1w;*2HflFKsbaAGjrPv~f`E|3uUxB#mgYcVT}`(o7n$!DX=OKYC9>&%f8%o+_6mPY z4gL-F1}O*aKp9Z&Z6p1d{7H9jq0`V>B{y1826I)D=*=Y9i=F1W2@7a)k27JuJD`F# zc>njdIGt=fJH`1cW8)g5rfaq+)uXTpcCo!!g!|)3*5DI_(Ba+JtAYHlLJhhn_>lox zJ(W%w6AV#{8bkduGZuG zrSUaf={F^)X~vxzfpMF#15L$Z9a3&TG)!ex(l&CIiGIvIeyL$UVj}*mT)e+7O{-b3 z7ewSkf^Iz`*ZwU!Z}aa-ZA^7cj#C~m!^M4iS2+24$aWo*RCB~w zULY;`C?&Im8Cw!e#!!*Npn22vz{4c3wd+?oIoIlS(dU=_X}^q(Puh(SU;nG^3cgM< zG4CjKI4A?MfaTIY?}b+Hrv!UJy43Sgj-3&Vv;tr4zU7)>1#Zsb&9 zp}zln|2wLq_5#qE7ih%LReAH2wY(Btz-!)OOp((Z8U*?uopGkL0foNp`ADn&omYr) zfn775qSCnW$_flC8*r@|nd!it74~y!stsvlqI{$dUWN4qAYm$f;a`%FoSraXwZeW9 zpF|V>&h>KXj!qukDe|wioZ;i}cUU)M3@CAqdZjqAX!duccc$J?!`8rm9V-l-8duuU zKMo1?`YJ9*wkbTN^@+*@mZ)<#?xN5@UbWeIC{cYxU&>qA+PudaC;9Se!ivB)B^w63 zywJ{Mcr78)q*dB!7ro(&q3gyP^CO0>jG|S{kbmztGkYo!ta5orb#oV8fX_$y&lBzw z1Jhy^USoqu;bC@&hG?Q*36YdWDABh+-Ku=&ri}&-6d=bX0sIfgXr5C$PcHf}+&mgJ zI{U7+4$KS7*V!*yOt zB>?LAKhJx;;KYu0R`yTEfAvHWYrStEk1~Y1IUKTyzrU3ep$t$_v%^oN1r3ez4(H{L z(t@oEJI%XWil|YYw|=xNWg~d!4Gi9ktH#g(CCWELnH%fr=AIproUyHR##UQDRMN}8 zG>e{H7ByD1e)hcs9(?_z)@rT|-fp?tNp{ReYI|p5(y~EOes)qJD(RU?6RV=Xh4aaC zj2;5a0avU|f@5a3gWxMCMP{l34s1>oAsJ=HBrtgGO4-o5`9%J$U+u=YR6=6$b;AO^>NX=~T*4s-TlK9lq=kZ(8&eCG zrT7p!P)-CMP__UVRtedIXiE3D0qT)mYR1tLQv{=oeN|k-F2W86ubAkd65|Xzc!4-92C1Q`m}1lxv)U zJT-czmTDeRugT|t92?k@RZ?^KGTu#Qd@XEqvl+aBGFxtj&t!v)fL({hiaC2B^yfk%iA`Bm+8$9;7fqf=zg!EtJO#F)fA4PgL9)vV%fI z|6ha-(sq8p4GkMWs?#@%yI|kZxI;$ewc8UYdO*OK?epTqMRho%yH~WzD3`Ncr-7o* zH$v`XD#SQR>a{q#_}^yjTmxhJk+p{ka5{K?$N7uppNZ;pP)=e=hYc}Ua{$%Tz*-!W zd%^1wpeW4YUejVpO)>_Y`1w(268la~OGyK65=>>OB^{p;qs%e`$hV!8LjqAS*3N74 zDi+LVBV5j)F*05&m1aq*>|xtap>ZA@&F}L!4y~(1f{*-)^jB|lPzcQMteM^BRe@5V zHSAuYX$J{A(}~u2#4FzaAb6T6qzI9#^HG8u$N|?Bp2V-(U>va`GV#%*WenTn0f5jaFKDLrpj6x zOlTNIT^}4*w(gr(9|4?0fe-)+K1U=tC7iD9#B|PXZpU!=<+Ay%&i!IdQBDi)`!DO= zw`YK8!WS?blpt^>`i%g#FHyULzV;W@hvj11$b_$V#iYj?9btLEp#$DN=&xJRC7~Su z=~yCJ#yVlhU-v(e6M;K@m2emLFMRRJnc^vD!ssg;04fonI@BJ%sM=z_S;jzmi@8>yU?rB+sXHPV>u_db}L)Kf&m%^{{R4uX>waUpI7&A*Q#3$w)chUkT z>r1+zRJ?ChY?AmdV)v#$M-m~N#}`cyzWZ!#!`PED zL;YF6kWz#WY)?)X^rDdXH=x{t`trtuyp`I-W;x>!U_vQ7UPxwrA2aM6V}gnNeEszy z)xyu~V5$3_fJ*Re5kq4K$(l-;T(LARP=nj~2Gpv_UF_$}Gw8AH`c3bdM+?gN3LASZ z;Pvmp=6!5W`+^ih_Dde)y8-tR26>1P161h9T&A~l7YFal%Y(6-^C6W{n8XsUER`Bw z=Rn`8dO&Ei;7VVv<=nN*(?G^QS;*D~B7QqDJr#$O-N3fs*ul z|JK~97cYaYRA@%qmJQt1yt6KBdn^W zU+g`1ZX~#Q$?Dm!T#i`$qbC&^muEUWNHu2OUq8$`pAI}dl&c)ql~1fpTxi*djE)Le z+IwOIb%_-W137LE$80Kt(EY;$osTH>@1GTnOK#D0bl!hNOwmJ#qiG)L1GpDGKK~&u zQ+#Qi!>6yh`mPBOmY}IC6>p|QQdv9dTa96iFj(@vsKks>%nV+OZE;~B(7z{eyL*w| zd4sTiCU>KDBW3bCZi{X++NE%dU?e%}3)9or7h(UVx=4bAj71bRay;@=*znQQt~_LI z+`*G;yko;A%`U^PW358@FL1|eAJ-|n_rVmKa_^mEIAewRa%lglanbXhIJQvOAkwYW zYy2_}KbQVE=o}}TG4$9*0uaws5IeI@!;Okz#>CiJxbE8qnKZ+bAGr5#M8F{tA{&S{ z0a&+&XztZ|b^a1Qgf^aEQRiCKpGD711%#qPDwGjt~m0Wo7CTxDr%>uVH%N z;a2fH1PYr$SE`9`X1IgP$2-P2e~H_77xrqO$RrG<@Xbn2x`qhLB|e&|H^Ti0mh}C0 zHNOkEfT5|q#4&u%uI=9(ypzXiaxo~cH3MylqLQa9piDwg?|DSXXAo-y1n_QkF%bE5 ztRGw@FZTiq7sV03WmWONUu}s~@v~1d0>!BbW5z7c8*HH{kJtgMq|6jBq<6aKU-&{~ zU=$VO-I-I=)*5ghF)Z5DVY4G&KofPEoGRCsE^*c!tIGN^8MotEe&Iu%Y2F6Gr2Kot z^3}xj2}T59e+Hf$x_fp;((k~H(P7}~q~7$j+DF7DyaLuT2f(X6P1sY!;H$(?gbyno2*ebGDSzVB zxeksShmLFXkKIvOW_)*Zl~G| zVNEU$evd**W|Yq&T4SxbKwovnlsM`j%DIJ}IU&lhh93Hl1uSWyGh%?vDmO?>^&&$f(Z!5m{)y+EWqR)0A}d*los zYI^VlJMdvg6}nFq<^4y?=juF~q51pd3rh<2H<6u4*=Xmz}JduO817Kw1I*ycs;6<))Rwpm2&n5%n{V?8G8iT``p`BQic&4=J zW<3V{O_;-{o~8XQlZ9ki-PKrxYIG+K$Z?X*nlJShz1LDOPG z^jwUGEjc^dU(ga}s8uKp6K07e=ltBi@ltE(po;11;VP>;0gb>Vr?H4iLuQc^KKlD& z_@AwHz_$dNCC4c_vw5ksIaXJe<9c>~jW&+Y(~Sf9)(HS~ixh-hxzv@_lp8YQVXt^( zb$qJHm`5#nc0CrDm7{kM3=PymI>n>XRb}Sj82#O8Ojk;c!D+esd?yX{<9qa+_xg%! zX6McIyxh0T&a*0ij@tmMy>H96E?SmV@Oh+l;X>N+Xbq|K2HL{AbqaW9r9#=CjJrdG zOa&6X1=_fC`9ngEp7;B>S?M7zB#6Y6=i6DKD|lQVv-92D_lmmiZ_AG7?)p`%<3NA4 z10~i@txwsyf8A#(Js;u(tOY2=IKXRaYDAY8dR2i$1F9Do;J;n`hi8Tm7-zU>fqggV zB}3JEFEF!xJ73BO{?iLAlNi(+{zGkOntjeAtYUeUDRe+1f;>tRjQpf1q~qCEbMsej ziAFq3knv>U?CxWgu~2&0+-%rU6G(}V4PRCW_xcVpylHx9HV$Hpe0(W7@c-m;5?m2; z5*z;Jd*+Ed2A^0T;ghL}ug)(YnVsyU(NH7T7ms!yS)J6If8eC3L7LvOHJJR zd6uf|VIynI)@wj_p?lXcWP5YFDfsMVtoX~f^(pua+9u=7Lu)cFA>jz z59C_d&cb>88PGkY)}>eHA%15hDB3sI)AoK zx5I)!y9#DK8_W)y*8Ue!5|{iK-ubLW`?Wo9hi=cDsXT2~q5h}-8+83QVC9PUuOuml zco9%Xm7btc*9YtQ_IoaLw1Ae}>O zY$Drk2_Evo=AZUA*kQM07igbJJ(r#z!dp7_PK?pLugYxL@GN zyWdB+w$ENF+O1RN?rYwC@2k?)u>e_Uhy>+p-yZ3n-gMfFay+Vgo#TP)$?FCy&wDvW zTs>0QtaAMR8Td?yj|3aVM)-{0)zffj$l8>+#xWx`NsbgXE8gaeDuwZQGE>}3%@>Md zNVQK*`Btg3kK1XnyAm_4Eaemchclg%Pco?KP)5Ztswe*(EjM#?-xYj(qFw6Ln7iy% znq6+`;x!dx5#{;V4f_U;pZ*sRAtA-S$HMtZ_x+C?)2YG?neiZNh}|td(!lO|{!4PZ zQ;mhWv?g$XawZU9cKgNRvHJ;EbvB5D$(H=Z*Gc<(cQ727 z&RrgSn1yFJAt!-0X*@B3%5s6!(35>! z)2)_JL#NkHNsQ~&q~il-GL?MQ{fn|-@z}AU5ClS~%z9!yv0vsjCNeQgWd5&qHT>Fz*@%Qh&oU+iPB_edpl?2X|` zh}1zMUxj5t1ZmH8vCs?+iW$0{6wO*DhqUUjnpuAE%^8=(bA8cYzVs;N!z|~$!qLA2 zTi9J|*XJOv$6W7VqMQm`!cBR;JP>P_ej@&PnJWSK3Btz@33&O?x%*Av?kp0woJuqx zo4S)|j}ii17ZRrDlN8(K7djsjDqvgzfUtgq4$i>qsji9Qx*;^w?ZycUPd4Md62LSap+G??vzC=?6LtRtefE!;9Ro8~Z{? z0U4~tXs_i3vQN(b^;TuZ++zU~4GGO<0nr90Ub_*G;Gcy=C*g;!;koiXzZel)pr6&z zh!vV^IE5R1yUg(0_4jg|Uq*kzMLWyQ18gO%>%ib+QE))W60x&fx1&j9u@Nh_{U;?L z(#Wi+aTxm#)hUTI_-XDLgA^#yy+itPUe_biTQ`r$TFixX0w@<_X3iKD&A8oV_y;lK zr^dSnyaV1F;&_Mf*K%05R1EayGH5q;!0E=7oqdiYqs&4|2aQmLh7V4;Q)|BmDp z3+}iV&7@>KL0cCJJi{*DypTI}ohMwr5@)AuxqsQgndrjCTL#=U?cgwB*#uF%xOX*B zD`xr+k)X1P1m?6)@jMG-H#eY9(0202EfW;x4 z(*6Ug1KG7&y-6x_EzR9z*~I|bZSN#cLmk6sqRmtzQAW#^hr;IS&JG~>S_D#d8;05* z>@3n-oLuzUVEwcioiU)x-oeDK)EMG>e0!cJGuI2A!I22tW#$!PjOV{gyK*W1)Sj~Y z(rX9o#t#1#dY=!i@tUJV46m{^7RYO&TM@Atbe-aH`~y$7eDGur6?t}`3!*8b>z9OQ z2ElR_LGX(UWTYsBQAdR`Otub)4KE)S9<%BRxZ;#~YFiQ1ixL-}54qFsEQ=IrF2F}3 z1ZYXXAD*=UZ{R-R$LtM3vesaB9?QDmC*4Ut=zdNByrIaZZqMzs^bXt_or#((f4T}v zS8C2S%as<3iD32YTL^IHxoEt{Rk@em+pq&u0-1!_}rTFlem`huuE*`hqOJL7b z|AnU$K2dA0;Gfy99f9w1S1<~33)$RIgxOdLk`;rwg7m)_aS&{MCAf-@!}qm-H!e6s zcz+&st3cD)?d86}KV@=^pv7L(Pm;7X45d_#Jc1mDSL-H=xLvRJx_JLp6;D_qL9%Vn zLs9v?3sh@~6NQ!i1MK47<*KPt7>g8$Yrjhcsd$zI&boWI3Prc|lJDRsucUVBXLEw8 z1q3Da{1@TAfdan)Kr5%b&ym+A{;5pMpl7Yv>d!$J6JE7MuF<(pn-&l_Ox`*v+I0RZ zCta#GPh(35DV$uPS4uRk%HOc@hw*yOO+iO3@|v+O^l}p0Di}Ket)A~8(`1rEWN6>6 z@df8-i_h81Z}bc-(0oG64m;bLV;PtT{{&s%hqG8e1MS9>0A_|ZiC!{^!#LwyymrpN zKpCP8nD>K;7~a&ej~|GxCaaILX}5pNg+MPZ##`^ksm+2 zQFqa{*jQ911%j>kX4rgzo;*;mj-w2W_Wu{cva)d8>^nC~>4Nr8+~a7pw+p zAR9S1RoMkcI2gAB@N&iKu_53W9v(U9PS33P7f3F6qTZ56JdKhlI<}mSzgJPxcHcnNm_IzL4H)v zf=L7bSHAm>y_tp|cr^iLawo;qbFIie72dd*JP!0MsdYIIumz6}W@0tL@KoT^Y@I6? zj`d@n9V(7YMBeZt1z?=QP1&fW_rwksX>~Y-VWneT@rWyW6tqo=Wrd+#O?|OllK(yM z+Uqco5raL?R3yD#%&Tbu&fJ!IS(T;A)B#n=nDXD%Mnp0|oDCvh zTuIN`tt;0T9#>ETAC5H1^(^l^ciL)S8W)R4k9brq-8z?itODj&R;wwa8BA@emO<5hR>8#reGU=sBXL8BlD7c z`VAB|-&u{+1Pa3R?7U%jd~87qbe_wbm`$*DGmQD;Kif#&R)L;q{%QSni>yA^;%@Hw z;8pB^cQbGJY3A8rx>M#~`nQv%%8rZv=sD%PF>mX+rM2i~_h3?==c+3IgwbXLZ*|!Y zY1!NZIC5*r!5cv1xeb>YRNCnABgO8U9^7OZz8Emqd?EdI`1;3b(ev)FW%f=ke!d>{FM9U&X2UyFG_ z;Tv1;?!~QDh$I~1MKXx!*|kKWp@USD+gc=#O<<7K{7PJIDH%5e;m@WP5vv~4emwgS zpMN}M)gmQh7J;bop4!{1nHtHAL8JiH*O+cdghw?AyXlF48av%SdQ;tX8@9+sM0Jqy zR*$Ejg@iW#M((rEs3oe|@G^N={lE7zzgZXHYB95d%cc1v5F>}Y6(OE>Jzq-@yp9P#7 zI`65;gfJUYKb)PJ91qg#qDh6QZ0unubQ_1M_pV`JxVD{k+C-G({%UL8htnP}8I?xY zS~9*u5ph&&w3OKuTlmT0$tk0o>}9yL{QmiaHbJEe(tqFL;#kzLls>I|CFFLIFS1wb(lYVdMBhVN#yD6o_x<}&n;ymZ%rA@055pCROu|E!EZ@=-tedlNh zop*h1dQgCCwsmbMd(aeZvp1^oMuD$1&<-mfozebmpeM0tyErXqA-Xk>dJ|sSG(N6X zk{y;xSEXju5Ym;-`yZ^lCakddZvs{ntX0ps6}OdxO;;?# z)z{Q}-uc-O8336?s|ofo5gL@T%uW=QAP5iEZy(DS`w~-$VMS9G!s>it^^Gb5Y@h1Ds0vpW@@=wI8{Qf|EWQrmoSB0t0d9bIk3Io{dv<#lG zVif=NWIpZGdd{(qj8#NG8a?&OskaQi72OzUb4(b04^4%baS{s0%mXL=3tjm(L5q6|4@$LJ!zNYGuxlKZ1t!#79aXO z=C&pZPei2ffP9g*%z{L}yRIbvs$mo8kjOL?72W!|KSn(B(=XxAc2)Zvg5j1YXCQWU z#nnX!yC$Kx zR;(K#-rI(W)FW0loxD?er=gp2;!seDVjuzzZWX5NC1U{<_cQ&69a}VD;b*Hu{1IFA zB^F$vRf<5v!2P$oe(fep@bB#RgTR#gqgjQJK-y@)K3)I;e;EGzC;GMj%ptWrsG3e2 zN}~)&ziXh!FYOijm4@T7xbTHx_DyE4HAp>k=UKK}}M{IVI6Qs)Rp4#kGuE}CwS$DRhnVYda{~CQ2am*i} z`>O@M5!BUuq7fe45HW&m#Q+xrW*q8vQPJ9WN3r-lv_c{^!;b;Y{bB_np)nC9F+DB6 zRMtpCP*oLEVxj0}v1@!pd97mgntyPf5ZkM?MHF}e6RkXyJcx~~(l||*K+dK<9AEEkRvJ(mpL_nF=!Vfn%bW_-s?Vw6ZsI>{RH3Ge&Yu)c~`komZypNKg3X(DHI&f7LJmur{P1iOijPK)Yh&! zf#X@0jon~nqSE{Km%83Dw@&dB08)ZGThpod?aU= zvQmQ*22gr-)fYrQ@F|P`NIb_PbULP(2}-@|M(*_#IS?S$L30D5b zVU-2~$>>>!Uqqwv3r)6^{9^H&xzRIBV<{Tx_hRqS>hhwX<1xZ6juacNBL?)6X01T7 zXFGHr1bglT0pIIt7a651qjX%P@$kT8-cn1Vm0Uhn8WI~0473>Ed&En51_uH|yT-56WM{t1U! z*C+Gjr1>%yJ&V$*yzK>9)v?PF5$Qk-&Dy`zYZG}E%#$#TfVX}eeJeYJ(G2#E=5i)jP0m<~k>``SWZWe4HyAV{{rb<6Bf(wCSRNa>*l}@n)CW;>d|^>%?TLur?PZ z&AVQTPRkv-0sRi1kd>EHsMV&M}?5pAzm0YvjDIepd zZ5oxG6Zk-NcZ(S3)UtG?%89QDpvFhsQ-AoLAC^(Th$W6myRnKeF)-|PNPb70$RA7c zqZP6a+16{X`mIADED6GDOPBAL#JkWk^9amii91^_MdYsqk1s?=4Gw40h|#Ny(zc8t zfb9JwPs*x#QPw%(r0UycvqMfQhM278QQqFLdDIM+Qa+gMK4CQ9FM>Ukq> zZD&CI&%;S6ja;uoiqqPp$}jpiJ;Ar zF!)!e4DQ3$*cu@6xMx>HU7t{foDHpI%+GM~iqHP4Ukn*-i)*Y2qllNP2h+3Wk)w`f z7-oYY#0C?!h}>+m@arDs*q!(L*NSp5vo<9X6$7ItA1FRU>_)@2LqgLX?EdsX9lFGS zrXEtj@>4S($xf;Yz(%mIHBh{a9E7ITElvZKSLMW(x^#PU zJZbyLhSWwN#A$#Quipb=@Sn9AnigAl01YO>KV%dp7-BtyXWbF)+Lj;F7uzLafx6Y| zI;Bw}9MypWvuQpZ$80SLPKSUpg&_SgnLO6e_;~83m9QdzLD30`99bmbPM{C6mn!l* z@49t0H6b7)NLL3yGlXH#t1`?}hC9IBYvH#hv|MZt3StT+s#uzxmV{gZ9^NG?4}0`g zORXxQebj-cvqRw2u6bd~mqK2t8t?o+EHOZz+lHPerO3_N;cRX1MZgB@>i#&_ncL%S z(uUWrrwz;F=bK+g{1EvtPeLx}y*3k3^s%a{%j~ePf3HT*#aK;im zmaImZ5ZSdgcgHRs4!xFgU^umf{|oc$q55>Q*6$5cUO-gB5hgRxo%vy44 z_0JtLialwxR#_yd^}sA5-pT#zR>Ddiau!+mIJgwq<~(~NJ(S{*fc38C&{w-i?}KrT z7v9zZSc#`Y%vS*~i406hlfBd@yu3Qwtxu>4uM=wP(M%m+l5>P?mbLGfm8l6CPviAb zjnaOb6R+vQ&I!8U(TT5-z~qlXyue52XLu2{Fysa^NXMUp(Kt6)tdNm(b;#KNSx~5x z77na<398J>u4em$JllJl4wld8J7kC3jq)B6Kl4q5l*KEsNbh|bcwqQp)EUh0HF6Jf zn$eD~Wf-MDzi_NQHOygyDkLy=kK?FZxEqqW;$(I0kd1M!>G!83HWer}PodxYv(Nv` zQzIv>BN2!T&^U>#M;@n)u3PzeCX27a`(t0?Mm;|xfBrs<@d9Kgj_|Q@+8o|S3!n47 z7WzugHy1KZ?t1|D$2gw_aAytiDZ=mD;SRBVZP0*frm$BKH34C z{4mpH)jh3LC--gAy(+Gz743xNFV9SMio>8L$xd2vM1d!A+qo_nwHMyT9p`gqNMA1* ztN-c%_w9P$?7J?h-sVPpL7wh|aRESxmaBDW%krEo=emkcG?RB627E6bzWjS(eM3!V zWPi^VN3`Yto>9ds6vT`J`QhTBM_sUD%(?fsSO}v@jSR-L1qin3ePc|rPL4g_VUGr+ zN0It*Flx3q-Y8$&-6j*%Q#AH}_fabISQFUT%H>#%cyALDEE;fPsE-dYGo&j*qMq3&5}@^u zw|ym8%#V)ApVv2KKCv^NqO0WD(~9|rIg<3^j^tLX#J&v$t$KLP3*n4cG=DIeV{J(P zQ@IQf#0+m(mE-6OclF>IR?S7_&@hU>5*({io@{CZM;2#Dp`_jnGBp_sXk-U+p%;$F z#8#n@-_d`qx%q92^Bj6o@kxg8pohPAOz1y4BTB4yQ`fbCzVDWu;#lmcr2hHj8M2@SJ1|bh-?bfRQxw6 zead9M)GM#rvOFT5bM#D3I6g%dpEk3eA57{^IcOd+)7mgyR<^Ye)k6N5VX!8(*eD4h zuEbEn`P=2q$0#P<(IE|_F*jdI z;#MILmM;1ac!kJrN00|hiiP-sEav@?$(_qG9VyWMD74ge^>$k-`s|b+P2PX1F+una zHX_Owq!r+$liPh?f&cXpc+}$G=XrN^;ipkA2n9A$-pj&C!0lMsFMJYiEnw66;~kjt zWIBoD;e#BLHDefp*{|l8R7Ki4YW@fZlk^-;vDIv9t&{Etm7s2TJd|1}?{EF-u6%S{ zDGcqUS2YN$>ekg^EtmYcCoDg14;F7XRzui zvxuosHwrpPxtznw~XNR+&N9nbTj zb~Lg4&Q4M5dgJ?k;05z<5B)HI=&Y_VI$=sQ3rg(slc_xE82w)outSt3lz-ST5Onfv zOGtNHcciF*lt?;(6XvvWnNzPLR^t#cpXrl^9jA644=8Tg6Cw4EbstC&TRLd4>dYTg zmr+AZkr+N*CaO<70l%9Yz^A#vZsohDF<7?Q`o_O|3CGnERz@i0)~fQjZU}gx`7shzHLM!Mft<5Oe z_11iz>udS%Qb%9LBs9BxRlQ1^?yhT;j_FKnn(OPyi9Q3bVF|v`5Sx1U?d$hJKi+%ZiLzf4zHD{p|-#!I5fy#6((a=*7Y)_PK z_NH@hWXWq}*oBHN>}a>oX$1B>JjBZ@in!{+0+C$vDFI zeif`Y1P){eHbs+#_BQL%qv=wa2iCv8HvJhLnshQxt%vkhL$7tvusvjLv4YGPd$`_r z1R)r~8MS&HnDx^3n8tYxHd6;|E~Z>dO+j`0SA+zMCmRthC}~L;1n_I$7)^~2JouQ3 zr#+?S^VNEJ`l$VN2Ia{?tqZ`?L5#Box3PH1)N3<}3S9_)=$v#TO)_yds&663Jnl5Mp1+So zxCRVBBrfa~9WF);9a{r4!su`l2qJIUs|?@nmb4$^n7^L=V+iT(>}Y}LsUj+BJb2$K zmuNgz6^!fj*zN}bTC3oxgiHKGmks(L5Xs@5BZ%01e3{t(%2Q=a9sV042gCZ0d2zzy(-BBzqMMu z2yp^Jg$mb$$P1q`5sau*B8VThFUA~gy(Ny6kXg95xZgppb^}2RO1A@vs9i9@)%}iQ zV6GY#F12@^L#`FrlWuYzGV-N1j2~(vY2kCuKAD?GUm6S#n)4X&#(Ko&H%RSUswfX6!CD3_JMshqJOu|IiFo-nj?x zj!mzp$d%J#V?93NAFalscap=z;9`KdQCY#33H2a|&Q5i#o1PxhyyNi94rLvZ{?t#H)jzjXc*5caI!Eb3vGB6}BS?pswqSGf%`6f2|&>_CSc`b)K&^54MWZw;9 zQ#%Iahh@dri?j;00(O4D9J7$SVYxB1OE5%t#iaD0nrcP##6mAwdKU4Y1|>WbgOVwZ zy|}?>@Q$*Qu#yDT#6Wnxs?Y7Mu~U5&9y61E5E1cLevp2O zPUZQN^jJUB!rb$%L^a7|RC8YQ7yM4xey$h);(biS7G;9+eP!bM2#4OXN~e7eU!>xs znZ6S^RK39p!PbaW0nALRW5{o>w+A2*Lw-813jDBQ5)&~HR|gs@0YIkr2joG^a1qmA z*21s!vkGn$(@LV0_I}CcmRwmHS9x6u>|%1wFZ-jQL$mH0zy0&HAq3SOyq=Dl)zZ@5 zE2rwj<&ndA)4n4yJvAiYJ`rx7#?vPN9-i%Z4VQHuzmI>2IN88v(JBDg+$ec>2_$-7 z19eFiNqo^A`Gf9c3H;97XBjLxFY{}HNhQa>vb#}A*cy(v4Rjw0DMbIX`cE&VYX+MW znG-~aP8tjWi{fU2&SkjcPsq>?t8>QNQ**yv1nb#R&R=41hqAx$QJ{o;4hh|u)Vs4! zC}R>4p@k96slSrK+UaOa(QNRGfa^QHq!#T1b)=i9zpa-?HHzGpbWujT^ZS!XgtISi zumiBh%LOtkhFwf*Zd=JGO(juIfGjUF`NK^&_u5yTA8+-2-6y0lJ0c z=QpEu4uxt5Ja`$AE1Z8%q-+^J13SuV^pDo$aZ)w+5HV*xkUgHlUhQ#VF7TNyQa$?> zEclu?f&Qbi<;k3;A|X%m!1BP??iP?h zu%9S$LMqc@86ct$vYJ-GXIBP7o)*0kL2M)9CZ}5p7ykSekCI2Ax5&R1Gv4`Qk zI?Dlbw$(dXq)tbPL!4$qOMPBd{36V%=%1dJ59XtU77JA*e7=mt?4iQ3Bf!{EF-{P3 z7M9Z2QQFVrjFV66kDHlERH$?V9ji?vDUIF(ypq4wV_P9h?r=1vDyRIhLwRMFHO$*U zY5n8$_viBu79=r1-Ydz;qJU`<*fP5s+HQ>w;ZLsQ6Axu$`cVktV@To^R4K*`EbhlzXfTymqCuLb1lHcy+DP6c zdK>Op<4P+S|6?tHi>dN-eIn=-Ffcww7wo?TU-SZov4=N&geodW8!RIQV@T}4__dqW$?iWr z^+S1cYmfsSXItZH&;bFBC=v$J2h89{te7ggCN2~VB`um+fnz)FPiYm;?1D(Tj1ITi z7bvYeE%zs7)Mk7&QDEZ}l9j0y4JYo0)wVPrFaCpq6@A{$W2u~74)0B2${YAz)mgD zSikBe4M)X_qp3|ZM6dkY{nE2Sgm8bMf5cX>ehh9jU={gD)R|lc!C3PtNlvhunR-5B zzCx|=IzuhWZmA_%RQjusdLIZe0aAzvmw^rcWbru7y9D}Y?yb;-St%yI@aYu;@7($g z@9t)*f`*>o3RGZuy!G}XqS&(gOEk}`#4MY4lWn+2dSPPGm{qiFhil2rm)|)19Qz?A z?~jeap*5jaw4pJ~m&XH>my)btF>Th@&-Q!VayRam>3m@Bvx>tv(*W7%fG9CIm8l%P z!hiCON1lBlTx49r3AUVIl?n1h#W#b_p&R9P2EQ}phB)t9umh-Ha&^4>=6isHVwNhHdZ`vrh@z?8(kSea>fSImGqv1acyZUJPftm7CKD` z2~wy%Mw^2R_Uiv6M5D^D?YZMhhRD& z!zc07v3_)P=D-3r-1eEu7T1Sj1UQYYd2$gQ_t1uJ8tuCAF`yad3Om2xW7@aG8kBrY zHVzfw_9axV(O`oV6*yzYKbs=RfRlt35-#jevJsH0V%L!@xcg@`0@o6Eo$=LZzmD3` zu&={EF^8wc$A-@$7FIAfPrUCXJU2C7Y?u@h8&)W=o(@4{!tSE3Tydi(nxe_# zh@6tvy+b^Xr80ikON@H5ufyR!ItTKqL41u_$@p)2^{aQ%FI11H?lA-*=V|(dMSEob zv8b5hvs}6VwuyS*7XGO*)_hNKUS(F8xemud@ej7E>dq&{Q~kJVymTbUYbAc?%sP2J)A^8&FASsS9_uI+x zGL>tKtZ%|wO`qFrPqCT@_E5NDgL03>*jTmaFOwZcx<_p2+JQM%^CA*-TB|#67Dx(gwX-;!6~Csvv6$>djDWHx0g zV2dq2ht)P9zedWu=oi%8wt2uYY!aBsjE16Te>J_ck51J5(R+8D zuU9`d%R0e6>X1<+EP30@Gn_fmrO!_?)b!16hI*Rj0WqSQoG zUpwnMv9Wo7=2vIJ525u%hw)P=yFKQ>T1QwE$KGU{;LUM?FCKHcFnPbF?qHDistD&J z=AqD(ZY7E71izkQ?)nym>j{^U-gV*7H>utj;i18^pWsFQ zdvtvi1jH^U`7c4y13PBUZAm?y7TSZZLewYf0h%9%_P)gT93xIp9j?1vA#jn?3MPj~ zodGnG8-x1g3RW?)!{&qj5`Ua9|udtdig>lEkdTtfnzf~REd`ar7((w~u5DU<>d>0j@i`~!CS!56;gP@*9dzkmB3Sw*VzZ%NJnVN;GHg( zMJ()s*F&}%L$#t;;D!Jg^+lQfy~w_0Za(6V{()2#GZ;sEef38Uv`4ma=B$+lR#xlBql01?T7K(EmOCH74`H4~oi>@a#{t9YR$18x3ovOYl@B1UY zTN^&Whp*~D;d6E=(sA5LOM$n2TMS$+ngSzUb^ZH#&-VI!bOy*~q3!CY2Y#$twQ^^| z$N-SfSd2@Ao$s}vzJ)06U}Wi5aSZ7)Z(SMGAZ*Fz5{TRy$l_v>^5Gva(XtC1vr?*( zvgsyi&7M#8TYslQPniZm|5{rI{WKRx;V_WO=@*d}uwo=FBLu4W)=vhcH|bR_SGtNI zlu1VMN!j;$nB@^YNHX6l4%h+S$*14eWFsj?nWEjyea_ApWYi8iB_Ea(68>pH*8VMd z-??}dbGiK6;+-T2JDJ~*%-%}E$EIi_e(O4wh1XWJO`^co>o)|BCae=N2 zRo<1B5cZ>tk}3Snckfn88Baii=jya-!=>_V=3$1vnL2VIf6FN~W!F7!bQXe~(1|J5 zF*j}>mrtRiU^9M4V0#e3>Qo|{1o5Qo?0}2TYqR!fG%xYnbGz?xFXB*r;&UlFj~c@jfuX(G&VCQw%3D93-!eXi7F>RU$uQa#()dp)dz^psjWAY@olw9qREQUgiSmt?#GB9O5;CiEWK!glvT$H>* z!*`^>WjF|p$$1sBWSb62YLLQu5p%9@sS+A0XK7aIEXgfn#wS> zg(67=$q2DMU|511!Fiazxs2kImwuUrfhiA9M`-j)Zryi+rBh^pP?BD^yfD$_KEE269I5or1j3CazvR z8dw#l-rrq~B44g<)_2G8g!?Q?{8kw03|J8{zcFxHv_n2a)nFm{RaBUIgnuzNe%Wt87W*67s(ueo+BFdTk`$JBT+TaG?uS=G&=Bv2YxF9}j)x%!w0O z0t(#>+<)}amhS{&ovm+lhhxL7)|WnMPJsLj;-pfM?IlnX7s1{PtF3gutz~FyxY=r3 z3Gxgz#Z>@XBr$-$oiGxoT)wDDT6Bki4QH}|bNDVmMTJrnO@h|ZZ~#X99%ymix1K)z zgCAFDx0q~_n((EN?FaiQ<$-4dMI8!%_CUQ&_v!ZRxACLrX-9}O7VyP zZWiNj-ENJRYDHa+@cNT#%bXGJKoihn397z6@dIoL@&{`CN6d6i!`JC9Fj97mVH!_1 z)nuaXogy3^{Id)Evw(yZ_PfJ@^MY)OY~Ka)>iw04G9(L?0{(yBQ@!d%PgZc#fL%+v zX1|T|(?L(>71JkRrMV8Pj|iH7kUUDOZ1s)N2~@NEDvw; zrRuPKX3sP=N=CEKT5ZOD()B6XvzMl-fC-F{EOBz zKF8ju$bcIUE@UhF3e(AU8FWn8xS89#{%87EQ1h_C+oKl#(bt(HGl-MfYoy^1rqCPj zgP)F#h1w)Y8)#wlbkxm*YA6VcrOLV;KQCULD;T<6TN9Lo0ClV+QZqrB_Pb0EYw?jE zeWc+_9o%v6I8Aw$2(=IE7RlvIGi!jfmN&wmBoaXNRyyu4XSD^>AF_D3z$ahn-R75y zK)S4Ks!qfuJk@8;xr59G9=r?mGohQ)14iF6q>Xztay<(*$HHFCr=gse!A`bicE^px(hU867AYTQ@OQhci}ikWu*j+v{=5Rjgm|te7D`CwoKrjzgU)*{E9x=rva| z?fpM=tVoH7Y(R)klKG(B=FPwJ8jxM}sy2Cl9dphk3yCSYmV=Gx&Kx;wHsV%gE6im} zq|n=K@X`ajDldc7xotxE>N7Dmula+1>win^5<%~Mf&FkY$xLxG?paZ zwx){QvMtNg+ZzPxFvsrw0*}}2^HtF9Qvgh`E|SwYmQRwo54I|6ZOu3Q(`A=ON7I1JnKxfjNfO>i+NabPS#?|0)W%A0HLdSsm^%rRz*#UBXV;ymA zbc7>~FYHFLmvE0~vKtG+h#J-b^Wp9np1w3e$sSV^0k?95eFGZDmiRyLei3oeEJbAt z(isO3L0i)@9pi>+%*(TMmg9JZPp>sjp=9hKevA%`_VMl{^UgFUk~!^wMwlbabqBNd zkmKenHxzRzUI~o3C`6j0ed32-3T}sa)*ydbsywC(I6tcK`yQ25*8Ymr&&F=$6=yyo<8Y2i@;Ml| z8X36}ypkNyy`scN<7$ys-tfnHyx7yw&SRVU(sl0yZZUI#er1&)q#`qyW{1@&l!~?j zAhDD%@nvoS^6sFo_wc}@jrz;2X0vSq;GXWx@zkR8n$Q>UWls*r+{x%iqh+M}K=;4u z44uTZO^-Fr3gdBYa|`|9PwnxY52JsfLieWvHoVY>(8S=UqU8wMS`$h_vXcG@0B z1PDoaF6do+)pRWI`rF=wB7%qZeCU7O_kwPh!^EDUOZX|fe)+z61$RJqu_~rz)b1or zBzIc+I-|B^K^dG~T9?uj6PZoAd>maS$ZYblK`9+g>yx#B|GZt4QT~-4_B2i^!&P=O z_hAbwn7r(lI9zP=vDDvw$Z5#FoTvy!Cyx@oQNmA6{lR=KoNH9AYb(uzU;pSvM6*ME z4D_ND5ZC|}fQ;dXR{ptQ>@W`W)3W<{si0^+n=?}7W(a(@)kJRjZISa#nJL+!M zA-TS(4mk2GT|0X^r8tMXz5Yw^A@5PvHqre31&$&lrdqzk>Ng2A(JKwkKW^1VtJjv} zFAX6%W1}_VCXI7hfUOuhq+wxGkSfo;WI|M#)(8?0u?!Ntx_6n3%ZaX`^4WhaoQAlRvEI5K~c#62Gz`&qj< zq$`Z!#RvU)^1|;YpFhY0t)+}JRX@5FQ%Z8FCCZi)x*@4e08Ybp{mcE&n3(xj z;i=e&q$BQx>dVQET&=~oTJe=O?xpVe&k^is(9qhT5-$kdx>wE27OYa3!)m?@c2f2o#o)=@hDtsnf{Wn*o|Q0YI(BbfNdX z&$03#J6alv{0f_N=Kdpc@?#S5jy0Z}Voo0Ep;qmbZ!NCwv@8g9b@vYc!7SVZVk0s< zjB5>RKMQP#3{fSbble*N#b=C!#V)PhB}iVQ59bSTjW;cmT;>BlRB;GG>?C(I+hH_4 z>!VQ6cbxU{BpWw;X1VY0fxc4)!@o|8p>9~kb|M#wgIrYp53B=U=3S{mZz{ZMOB>6p z;Vmb6keZG)mGt9!(T3JhgYNWq-ovj&%E5{xJan{GUjUP8Y~|FXw4N}77(&044ssI` zrkrg&exa^~T)17+!<&bm*_{{q8U$5kGlc6!+J5FZjQ1SJ6(*?u8mCDCmt(Xm?bv=q zpfY7fZFOmQ-Yg5LQ~T5cO<%=R2!uPDt;a*-UIX0*U2~Qujde}_y<8pHSCY!0A&_xn zHkNzr4>lPk3e~`=k@e9NVd)c(jXjnL&eEuR5ha+mA$tF5z7jx6VS2^mu76HaGXn-2 zzpms^OlNTigx-_Ab#hVWvXX;%9h1a_;+2!cFQyiH0 zY{qJOA0?&VYq>vGY_FGW=tA1nug{VPmhZxEOomSWBQ=8zBaj2piWaaaz(J2UAyps;xUq!J!&0SpLt)3{& z)EJFh_Vv+Bh~9BGW4#VI#}8}W2?r17l1S~8kI6h$@dNiq0y zIkTkpk0bG!5#t}W!y?E4*4UgxFrAn3e2rGVI9-JObSI5q+t4VT{KpE?);3?p&sYNj z;sXa*TyiBU4Gec&t^Q^wcU9J6sflRP?0)1^cIUlhkJiHRwFBErrEvSgc$_u1x0D(X znZVz$73conR8Kr>@yz;??EYBz{fThXxMhUf!_@C|;$}Y2yErp@l|NQ%FX+?bJ`P3 zH_`5SJV58MeSz0@x%$2h>uqtxvR#C@E^T;|9$Z&@UO#j>qgPH~o*x%|HlEFr)C>)J ze{?*OMp}bgJ^y=n7$r?c8@oVoU)3|{iyyKUt21=$XO8&^Ja|xnaUFZ)xl5+kgxGet zpj2zw7jW^77{0z$D#>P;uKDbLIVhKkS_(z`Fi7CN7>GbwN!csKNV+aiNLBt7*O?!l zr)$rfim2p6d}BSl>ALF`7{KLMymW-7I%4c=>{<(zR=SlP^Qe`bm!$sd(#oe*qtpKk zv_IdCuJZ{T8I#>XpgytttmZJ5Y{Z?E-s$orFEBqHW<3FwtJ&zOPVPYVs&iB)_3H{X zU_#Q9w8CWHO+wkq3o(kK!e`Ee%U0|3fLSlcx2ccZUFesC1@!W@{weZJ>|!UPhN1j= z!jf@a?d)4|lhnCfndA}YXM#g~PjED}ehdvZlChI>?MiA>!Tl9^2%=iq;H+A8K79D| zJy-6$xlmcR2CmoWv1fYPAomrSTR&TWESlL}S(Z%}IUI8H!gg}AsSALKdeE^sXn>>KLCDZCrnLjS@};=3YyD0zYW5B4_%2|4$? zNQOJ>r{V(WyVZs63nbB?-J;puTBGBy2BJT-v@5v6Y;hw+Kn`TMA^R5FUp-6)W-}$FLPcHU+D4|u(wj= z*MHHe>;H?cVcKi;f=Zdfr|{RrgT2PJ#(#5z{L+V%0cY6gtdXHDHZuctjjfr_Us>gh zS{ve!igm$?AP8^{V!yz4&AnGIo;~RAJ&#x>xpOPxMS{K`H^{=L#r;%M5IPGcTQc~Z z--3T@=$al?C>Q@|b5X{LXwMrJy4#_L%8nJnE;SH6NxwU;+)cH1D`Ds*g+^^+SmebQ>Yc}SbK79Sc6J4s*tBI$QaR4+ZrI)$dsRk9S zNPOF0RHFa|l>6b=00lI4UPd>Ae_7*yKGF0wF|dT(h^_qs%`Gt#Lv{Xr7hWNzuZIyk zm(m4m!ITbTy6;e(Sb&4u-#>?pcs#zPbyEcMh#fdVCOWX zcp{X9vR;OTrMFJwK_J1g#eZeo#Y_DMlU5*tEWoTnBA?M_VU*w>pyj3J*DW=`Mu*QX z_`-){>8wR1P4iJjF#yjU!rPta3-nCh%X8nYoK~|2Gpw|949dTn53ND~$@|W2xWEjAezJZ+YVWx2PIea@ zxVnO4CL@cPvtOwQS7(BeD)P!O7QAs$x{v=sy;uQ2zvX>8;lDR|!E3CeL@$-c*3W8C zdk$S)&w6l*3USN=_yVSPOHUrL*C|I057wfhyV!RGqDU|z5{Y~4W}Y!a?0G(g&%=S& z4zm%&4V^O5^PMS&Mr35b+3ZoQ)`04q-I|Fo-B@aWtjleyECxm)#I3*lKcgbT3c%>R z{cRCs+uBL+sn&IvWO3E$WOWfYUy$+kx@jZ>Nwy;YbBYuVb1si3r)Wh4d{HduZl;BHu(9Acc?wu%xPna)E5atfu7{MaAS`eX+d}gr{~JRWNkm9IfBEjO&c)&` zZiyr=W&<#Ohja?g%wYZ@Cgv{lQErY2jn$sm+!WuOce$xl_@C(Vq^U6;4e^xGigPN= z4^Ztr_SUBh#f`$jW2SE#Q_*sZIod}p_Q_TJmTZ+g$9q!U2QChR%OlKPi5&(pFAn0# zh)rLVYkQZLF*)>JhubA!PYhcLz6UK~`vbB3==;TY@*(<{t(cB6xXUp+uNzt~y8cOk za~w6w7y2uBaXr?>tEG*DLKzmIRN?X(J&oagpZCWd%%X3pYT<}OIcv97N_=)QeH?H| zxp*r08Pc5Pt^X{~vgu??*+0JB5UPIi$rV0FRW@Gax-_?X}Qu7Np&S`=paM|5)Vp+MX51sEU z8+IX*h?VMNd^V-#qkw&?;Qd=(Dw6Tl+V>gOyGilK=vfno_->xAFd1-eJLzckHSOMD z%~7VJ#4w53S8xz;3{QrwUr&nboE>R<`U^`Ct(S7)Z{$jkxz7|K0g`uapIb+G-+G3% zh)<==^RNr3vyQ_-C@u5Ap%&&x`{3PS0AGrVG&n9A^ptp#eODR z7OB)RXkWrlIIQdwRw2B@ss&{qhE?msxGE*18r1pP>YUhi8Cd8QMszuPz6oZeQipn# zdr%d(5~FUes%vYB@0`j1&8G=v9rF4TN9{x48O%DZrKnCklUpclQt1Q9W^rUdm)q(; zs$Qfb>*N-+D_$!UPD#wwj`d2L9f#9o`%7vRRw7U!u4?stPq0<0$mEQb;SR~{R(pdo zIrm%sR=fmk3bj2|-Y>-~?Ixzb?V-)S+4$pY(1L4&Oj7fCZU`A=c%%FqA+5mMAa7NS z#~QY%sf$a0a?^8tqxS{$@}`GiOu!+xX_jUiY83tTIG;4P9m-T0rY%bwjrfHu0wN$} z1E~M-_h(9Ie^I`3qPfQ%`%B@GUJo-7nZ5w#mQCH_h(IFM|2FH*V-Vuj%clurn@^6{ zh4qoy$NW0Vbe1}jBTf3-XdDsC4fJXfp_B}dwH=f=P;{`jbJGcAd&D2FA-#0Ii>ijO z^!Mi0YHgG(tm6rg6zLUleB?N1;KfS=G%5s>An8-gGmuwHJNq9SYB*3!H*20IU|)wF zPf(2D=y3NXh4mzo$g!bCLZ4gk@y6J1metDZR&3ao;XDF{+0KX}Q~3X(^ueLxJje&Z z14&XZd35}iOG$l05qE-_=xo&^i%9IH^X4ayLqV=_C^q>|vx9m!JFZ(el6bM?iU`D=t{B0z+A@p(P(a$JbG|9 z^J#Uw?^eel=3VK`WY)Kt7}|0FoyX|Py5lcBMM*rZ*m8o#bE67MssJxsJb~hcDYS>R z8w8O-r+)w0R8Rt?&lm(RUchdFN{e}ANXLrx9{N2J35zWXxV}DZJptBZ=HcXpl`UGK zW8z|XaDa#;SxLr#JEC|V#wV)d08(|Rol&01x4{m^+2U)W0p&k0-7`8Y8V|&AtP1ns z4()l+UaXIRjLp*H%9_EH=tMby=ap|hL24&&8T~pSaD3OuBgg)+fwWLW@CsRVD=;vY z(HAgzY;nf>;G2@{{%w=98^7~v;p7ikoot-<&{c8uCZdDm$H{L%zn{Dwsk(qV(yT?G z)6dIDl+oJ*zsFkV{Q~iAoZ9&%dlx$iH1wi$VGJ&zd5?>G%-2;!OevWj=k71IP)zaz zEkacVrS~H>YQq*$Jm1yJ_$i#e2m-9tU!?Pbu&gsL z9<^aU*fNffU}uF1{gg#pGYfBo^@9oR9!1QJ--z@RLq+$eH`Yn`voj}mR2r^50ghj%Bs-+$8bVDUB-@|EkGDh_~;l@|q8trUOo z+x_rgiSyM-`%uUhylkGUN`HpQ&rr+p!PQauP(u4@vYf*l+s~%|y4^xei094Kjy6^Q zXNe^hM;$HG;9?I;#w0hYBXM2W3@Y%Gr0F^-m6%;T=w9%7mOFM_@)?eT)o-)u?D1IJ2=)236a}$*!D z|1#8YH0-=q%b8YVetoGT`gH`v7oe22ZkaT=P_r(@)A{y7{XD~C>t<27-JG$$-oalq zQnUME^qsixxbl7xv3sc??u=7Bq1MH{W%0(+ulM%ipg~J{l{%`JjOcI(6*oyiI>-E# zDxY>%4N#oJ7PYQpUZ#!SG9;*J$~1U;yL|oB^LH8otR|25hl2=|$T?()sLeKiq54#1 zWzM-ns|NA(tQOH}qQq-uOZo}s5cWsBu?cv`)J#p^koS^rSe_HdwEC)qr!-f+DIaO( zZu{4pM=J`P{_&G1(?bibmVMWG<}in^09c5~sLH#F(zxqUv^^2#>V0*ZcB1RS&Os!J zia^_M=gqG0rRp7jk@kS3Yz~xZR`OcnPaAoTe7a|x5nHH_nSfR1r~O>s?>*8_q!C## z57)aqC8W6P>ewAEWBLA+C0P_~y~{D*8djqTCS_#FoM)_Iu*9c8Cy{lTY}|FV)B9iDdh$0Mf56MDAvR^)Nvg^>m9cFHlB zb<{S;1dipcZ~73Nyg*>f%=_@n-#k(~q-8>)K2Y__JG2ADfg233>pX4ywh-FC89mDm zobQZugT*@_J$7`+_}j^X&Yt}h9)rMn93X~;Q3E_$(cn9prpdBw7N0`@m1Z>^?dmtw zvD=y77WV&6@|GfW&Fj5F&zr3o(j8(gXJoAf9-Dk#;8#z>Ku)#2*jrYaE&Um6p+1-s zEbmI;NV*7c-RVE69f2scA%WqtFn`Z#F<{LX zrM;i2Cp*2EG#C5yxesWEVdhR0&dG`Wks-Q~U+d~_S7 z7P$j)Y!j}qIbK7+cd1t!27jgAbCx73D`;&Hfd)lPYo3H%N+-qyx_B>2751yqVf~*E z$FkW$Hb;}V@*>udv!^&NQwN940kLyQzxR2a)`$)e>SVXytawd55*CA!QQiT4>w5v8 z`I`>IzfT#nnTNHf?>|E0r~Z=|56Z<@TemqQ#oLkdDtl075vZefCBj$Xrl<8I_4#Q_ z&Sgk^GYrMlo(stZ7(zr-dlCs8-Rw3yf`ybvL_t7Icja}# zlq1Ya2g?|LD~as@&6MY(QRG0P{t1(IRPMFgh^qpPt)u#Uo8f9XbeLiRA_)${P;OaO z6}+_`a002H_J_~*?H{4Xbu$zHmZl}Pj;Dka&xf}IK8jf21Mq(lKN~o(4JWF;9H>4# z^ZLxD#O-TP!5^u(E#R=@mj(*h%A7&oG`Jb+f;8?;c&NeajD{lpSiOv3aVw8#Q1mUh zgZmdT5!GM>t}}}aK6%*47_hzn(AhlA6?|hP7SCrseB%vWQ22EW(wgLtgwTh{og=;z zR>iunZxkC7AqV?5VGlW-%sDi|TT-I2?DZ&)zo{TI@9Y%&1e7uURzJ^D24G8fVR-Y5 z^q<~4+tVwT5~tQochC_Qx($tbMi+GtTf6_478B~F%H$u=u2trr@w_aIG#ywCSthleN9HD=L{-uJ6 z_?WS@i$(+G+cZ<0Ls|Xc6|DCXqEB@J+2RVz>40(K-4}H086~!*qmwF!i=p{dv8%PO z(10Edj>F1>ot!djQY$^dJPdAxUcVr*HxSLL3+KyB4d{=fcSd^07qi-LZ@52k;#llw z9!$1CpXN>4A{v`uBer49b1oiIx-dPJArDOzCQNaTh@+al_CX*AnMU198F*c7xcitE zk$%J`i_(ldwXe~>UjDRRx51g3=o$qhV{f|n##}oBG#!;$#ol}jmlWn7rN#P<9D|TX z0&TEjz$zN~-R>l400!d>X5(~1)Plt6CQdU;x-AMpRD37@Ss`2^tv0>nTB#$M&Ac?` zDJvRZt_u?d)=DNjT30G0E}8ZpmGC=~iLTx3SEzT_^n2f}rn>?)O1%tAB=!lfn^Z0! zoR&0-C0fsinYjnx@4%7Lv^F*=h(Dvv2drg1_)R1uyV$qL=Y^o>8i%homS?Z{C$@`N z+ws!0O3D6B-em;6kl}2EvMlwXnHsRsDr zzU_s~7l4Bn2W>iO6Yy={^^y!?$GijDbD{t$OrWRrfTkvf*-QL|nlU5l$-`QDMisXD8>o)^N9pA-0 z-#Gt>yRgE?-jM+xC43suapV3iGW*iVat{BA=RIn_Ts#W4ELq*s`5}D`Oi5tMh|rXH zK6;v2=-U`nr`v^c(iT;_uuU{qI;;PfYa7+_KkBle-khO;N5rf7V7f?BUh_YiO5yE2 ze`bGTdZ&ewkdhWP49bG|&8j@~KY6Zw3gapOuN_e6p$*Yp>z~hcYZ1@K?vkuXP{H(%d*PUx(sWXKB*KI#KF_?eUbH91+LKOO{t?%d06jx12 z#z53baNB*bgRPL}`M1z6iMzxe%;{E0=4BJKIt;Kw2Ug1`WEPG?&5&_Sny6JtOP8%jwkgjO$D$olI56 z7v{_;p_RP`MnuBlP4uz#mKw0`$}kVj8~hBU!u6WMe)c%bVY^qylt`dLQK5_Aam_)4 zEoI5m#^`k?Z;%(DRM*hW$$k7tp7x;Kt+Rz|A4@7j2b=ix=4J@l*h2}ZrgiTw(@x1j z#>?cL&b=`A25X6{pX6Atr0DXpo1QjiG0r9rwCq z%Mn+FG0-5>=l@Yl zNnt`$x3ku9y_Je?_Kh!C{wv~1Suj&kMfzha3#D{=0hKJ+ui&ZFbEyWE%B==ay5uy} z^N;FqwE1JIL>5n02FuUz>Tw3yZ}ROdq~3(r78*1-G&{~xL0~(5sNA!Yc)s40Wh2D2 znHv{x57sWN0y+~h;4ARtKaFl)AH*PoG_;}(YRzT=*UsMFypr(cLG<-X|Ge>~$@Hse z6!x|>rO7HO^TnM-8a04ZN$K}PJ#d6~JO1%v`<(4cFY76W+b#dzlKo}Q&=V&L_~cZ4 z4V?f=HBp6%f4S^tf0{exsO45AJjcl1qY*m4#HYy{yn~DLIzoRGhXz`&smP7GtU~8o zs%?tM5OPtH$ap>~VZ0$rs;GjlNfY5qk{#uS*ESfIijf5OZ@u;T8&Xuym!4Mh=1Z{k zU71IPJM)+`2dooLGR4~nT$oswp2u#%|k z^Bo0-(EF5T6YSf7b!6~WCek?`ut`h@8{}3R4EPEuMk|=ancjZsFV7-Z`L2) z^wp?#XL=M+D06f3C3~72-jEukP%*j~Hp*q^F9rbfWyXr?WZCMDR~PZv2%2uTcOzEC z^FQ0xK!eVuWTB%#BESowzxnvI@#Wbv;KyV9{H!YH_oiW;B_y(BDbPR)ljaXVEr1RT z^8glgD4$Ahrc!J;)^GLPGxKENGcNKq%+w6u_Gdj4il)?-gU^hk-`z z&P>#EHc0Uqb6J~9aC`te@1O4p_KPKifeKWh>}W$^2f&us`ocNI%h70P_3 z?a?cW%I}DJF3ptCAMb@5HSi@idiC-4f=hF5-ktMWs?cURav;$EM?;ytXGS>)q%Xh< z?#qI*8ggQCh=UUInT02Bx7*5psh5AjUH7?9usFG;J`W!3-RdGxzZ21TW zshCi_a8z@6F%HTgC$6z8^R4isq7s9A(gftIOPM#5lY|A*(@+g(CWVeXu-*NUns}Th&lgzl z41pgF|Gd)p)8f>|oG+_e@+|&)W9b#?d$dWCn1$jKyf^#u>u=#@>ka8iWrwA8x}CRc zYw+$bg%npQ|I>DEiw?q%P}lqM`i_Q80`1nIr+NP%2Zn{=1yk>lJLS)p*Fa#}LbW!H zgqnC(zxb+<{UyWR!P|5fwzIrG=k!Os+FA2?hWdXKG*Ii4vLO$1yAs;Gw6nGK=gq*E znGW1qDOdijGf;6s!wv$Qq|@&?)IlRw%bZuT9wZAf$5Xc;fjx#GpcZm$#6QoAveIpV-wRn&mf7 zQ2)Dgg2G=#q3xLCeREz7tdRls+IAjPx6EF6`Ca|DR6wJHQA9-;=fG^o0~nkxPl)0XEao&IR0bu zfsp9Em^odU78c>+I#6JbZv>z{lnAp}-`dOh1CQ^kNO_BnJWrQoxgXfhj>RhD=NG|5 zDgrG-mo|iNT_1ClTQ%AoEM!hc&zhGZ>jM?|a-VI#ZhZE;@V)0a0dYv^u)y|#qrbe3 zQyJdO+Rt30>$@iOXFT>g(mlN?r&yRHFA6TdW&0DAXe!=hVezTbl_NLWkQ_p9^MAz` zz-ZxZu!5F6n14sS_Q(kr269#}VN%~i`70Qjeq}435QL2?+znLNg>mg6t;gOP#kZk- zb~Q@AQx}un%W&P}hw$0Gl7Gt=^Le{`>UNVfLvD(Hm4-{gksjhdCQgJ4P(8Ijlp1ge zlmiNnVXXV#h<9?;O6jQ~O7nFiECbMjHl^Zd%7YTiZ|q5+1*Cp%G>A+;n!xxZoyo=* zKNi$}vD_GJiXhJW`E*!UQS^E8*ze#%-$`J>b~If?YzQ}7s;a9GiF`9WD2Yef&*<5- zn?f*3N^dGeuu51jMH+gI4#?LPRbOmKvh4&dO-Ag-w?d26)Mcwn_pRwXPXtmi7F7k) zMTwE5(*A~*S*JQ=X^`KI9crN)RJr>RCso`hDzKw^n5gp+Y@&gD9UP#HJE9!d$(&*c2k?)Q1@70=7nxpsy}r6_s@lT)3^QGSUcr6#G^S%p)(G^ z$;O|1|L8AU85c-e4?;gl+F;s`c*8WuC|N3XVX>XAtg>22`KA79$X~ALV>o!H&gzsZ zsROdGX%&}6mq|MQpACp-Gg2)>=%2Ok&S&WgZ$C}NMAd|#fsc`!Tpek_uq7j!%oOUl z@aM)`vZ<>E%q)h+^tR0ob6_8Bg#tJqq)n&5Le2(FpHuXuaPaQbmY#efU5ErqUfGj?lw9S6;Iwr+1}f0&$TjmK zzvT2+2bh|EDrRj%_`+j}9ZhK{w%)He(O`0jV^`-Bp6nWZBsc*4yT2{{MG#CP3!2H_ zS=LUOL62}oV^pc`cgj=cn~+^!*>Y`OY$4|Y^tE@qxrYZO{)6?#Ez&%Zr`IOn5ibnX z7VPV$uU=m+S@DE1FtWCDIbT8MMNf8P^MAUH<~mS}Hmba^q}790j9r+%xp&41=iFB` z8e{vsb|A8m1|S#`$*`t0_5Su$NdBFlq8O#&zoHV=QB4`fyi5Kx9ho4m%@X&VPuC!1 zK{|!u9f{C-29u*HP+%1u)wC3b-~TG-RBbU-yj+D~eCy$s3~X|GG?MYr#^zARa8@=v zQOm6mx3@-*$g-$;e)7Edesx6gx%fwa*@;CrQFxUZm&|mGwBCy+%S@6tcL?FSLvskA zdNn5^8kwP|4e#-L>NE<;nfEoUw_BXeUj(|v~Bw=`NfJh&}_{_D!!Gm0M{*w z?#3cSn*=GcoE8Cv(n%IAH@Fq;105Zu_oV< zBP;1x!UI3%Ham<@OsBVSC_{_mWLqY6bgG{Xm+SjQR2L8NsrozyfaWbm=qo(mdg#AC zp$k=hNA{v===fgaF>xrlvC@nGklS!6dv1cFFT09PIrn(v`W~rn418rbCdd|toMqCJ zTE-}fKP?{a{oc7Mg#^A(5NCZiI?Z~pKGAa084L?|?!BOmuco;3g6$8j=E9m{>Zz*!(;iZZ(EF z-^IIVU3eayi6)hE6JfVB{@dl{qt)}5UlG7&`Dcf) zy35T(1-F7KY%|VL){HgbJ;y=s&sm$RxBRwGttpWssUOKnUS{40z6B;;Y@>!e;n&x% zD-*!a#9kXBurA#FmEYz{Q0!_o9!(1Vpr`FVP!%1BKZK<<%MNTZDaD53RMHMF{aTi3 zt89A$uAUP9A#gST9R9cD_O^*K!AK_veU<a7b53HV+OmztlDfzRU+j19Zm1+x6kFMHa?} z0=czbug~^vig#cmp#WuIk&3@-$)4i(vnODhd+^osYoyASZYqw1R*1i+Z|L-l0WN;Ruo+1#I|8Z-J@rd=M6QHv zjmle;$@u~lIEa42R2W1~EEp1$HP!i=I_(jw%`39k>yTnWZWQqtI5*gbzoEb58gFKB z{};Y_avVJwe5(9dKD3?7wxOJ>FsS)oB@qG!Mt*}PHL#C1;!0ebgZJx&>dOI2h%cd+ zZsf!wbReJ{7B4eqIZi|o4q2l zrl|)eeRGEdB8DsrZ)U4|BybO1^&`kD06#3i0eH>uh)NZt;g_tFcc(#0QgHYO zh8z)w*lIf6wP_pjGQ|E9;sy$QH$BL-D;%wdWOAs_9NMHCy(J*&iXGxyMM3c*#B`&X z&@F_c#^gtV0>D8?ciR^TBBJM3oKKkW=%zimk?C)E*D4D8)$(51j??)SmwSLTVW-Kv z&Hbc=H0X1t)&oo2rhLPz0QaOTm`-8OaZ|02KDJD|VDmVT7a)&&0q16*?rT1S_0}-^ zS+Qvkm0keljvTQw2wQ>nKr+A;$l5=rPBZbt0uCJHo;&zFeY(BinssgJ;Yg5Zt$}OQ zru#0qDl_H}x!)0*nFKnMFBl~-&_Q_XBB~(Y7L2m(;LJ6N@iMo?J8-Ebl1<(8ZjZ*E zreV*Gb$~*~Du6T?Ldj6ALa6~<3&7mX_;x_BCvY$mfON!omBtW*Aj41R%S0irJ4s;I z!_2wLkSE9aR3&5FPP0#s;VCV5m4bUI!P3~M)h0VRJ^W|w4D*g_ayNV!$~z2OSyaRP z;01$vS09f>8`hi27ic^lzOK@utX2JWLUSFsJuUXgXjsDVUTj78+EZ2FGRaGWaZMkY zJwaG4U2D)^Es+ED0||P92K~bK6riPUu3hVKq;hVuiy~$!;&LRNz-ZA-(E-B*VL|@s z>{!5q2OPBA;QD*CgS}9a;Fxj^et>Y35dAih3F|F zP8dSwCWCW5zR8~gebBathi@O=P6>R0B?H{KAR9}&0)>=r86I_{&6NKg-Trmj-!S42 zoG9fhyOU#FA=l2@!kx}UW&YP;x>=PzBG-uE7Xz~2N21N;D6sQ7rp2y%JA%9UG;4Gt zA41g3vxG~}ntZGIP#=I{aJs#~%vg7!8<0G(Yi2kTR&T(|T;%?dMo|MgwA-y0UkM&j3?_yx z><06a&A(De5gHTLvw*Lx$XGQ2NsFe8TmDp%+(Nvn3WT|BVCd&mn0!$aiaN84;NdVc zlZW_l_qdOT|2KOAKpF5x}Rl4saDtCD~r| zy2tX>0CDRG|0b7CUtCJtP2+{_f>RiM$elQq@e&pA0!2AlLFK`a< zuhPoTT1LB%jE0`naL%<%z>-hPoNwNopDp%e>omRH@HNCpM_Tgm#B_wnOrIteEqY+f zc{n#Eh%_2oxm|XLf=KltA})%ZoMd4USqIBh47acf9P&ImSB&>p7${>Rr?)26RU@}E zhWY)xEFcm1v3l|QCmkXv*zO~tpZ3h26J&DVhYC|h@W)S5+5n(%_nf$W&6-)luo;G3BAR z<2xQ`ly$*AFL6jE6|%FWkg<3GL|>C?eAOS-3v0d3WYi|S0cwxY?O)tT{fnNE%cB<$ zU^(eJ>Po4d%>y}lz-8XAdgeeemtyVipvzb}secDa0z-9#_utz5^O@ic_`g81?&zrEhAV*m{6 zoM0@X*UM+Ao=tD|E$cIEhF_J+-BErCHxOd!)rel}3ixsV>%H8RzU8Kl!6SLe=u2Ge zpaZpDzfc*UL9vrKhWn)IG~WaLf)u+-96HdA0vG5N9wS{nuD)wL=4SCC%DexDkA{?0mj@8#7Wd`5z{@2bqWm1ig6 zMKtU`N9|T_9eM8LiglIpY^^IJ>yKE$ALg+rySHx;dV=-7t76O$J{)e!x?Mk=kI}(% zZ%FZa@?@5YuOt`>Vp$LYmNGWALmvFMPg{>JLIbbMo@+l%Tz+~@CF~n zL?*#IbDy;Di$~N3cq_+o4-HXgW$=-4WFRV`BYL_tAtc%67990Yvk(* zA)p&wTv(?H(~EdCqrui!%tbWA!P!&7_m+^M^o>_1Fn|aELUOT8+;R3V(5f22AU!+f z9yN1h11vt3(|ite!|y%Cs=Y`0d-4LLZoHL;MXS-*+($X;se``5DdH1=@Y83wn?DGn? zmY^wdQ*L@D!*lBD>yGAsysBq+|A{tQv6e|;o5@9lFkU9q+`@K-O|()6oTd8x$k;h^ zTJQND&*q^HzddmeF<`--)lL^LzZbe@H}5Dy!#P%#W~y$Rm(3yeQL;9!zzk4um^IW-Mq&~Jtt|070 zZBx=+Ld4o;^$?!!XNz7OAV}=W#`<>V$Uwmnyq2h4@hrVcksmwonll#Zjm?QLQ{m$u zIaopbUllZHChiit7l4eV@y#H5Uv@{(*!TaEuEQVdKaAfYGrKb4&MJO(PG*I;vm<21 zp^VVkd!CGtWbbv6y}FY7UD1zPwn^&1v;YNKJg;@WpQLQo0D7 z%N;FJwqpdGgLF1~Y7YM7})Plloq z+ZnZ#b~TGcp+L~DR9=~1<_QF?1`07HfP_jp*^&3GDNYbL*DCt^{HhMZJpp*4-nkvdpqXAwuI4sY(yUzIXKEvFx zb|9>^hk{e5`DbUNhpM1m>SUrtkjByWVL58@hakBjzKUEr> ziCl8uMx4ZLfJJ0dhT7}xBMRvsW4DDWh`RSf%xEQymggMXuGeLd6jy*lPuost zVhZc|?j&{yb}+cdUOyjku6KYs^O2a8rlwc{Ot7h$*fqG%Q8Q5VYNi5rl+$1-59@h)lSW>l}hU#m;HO*Q6Tt$5Z6#+&eVXE zehTlOMX~>P*^@`~;mp}wy~|e8h2DK0aVZj%aJ2puC)|LMcdZ@&FI5<#>XfNtbEsOi zQqa}5vXfv)UB|>-x2qb{xmhBsY#_LKZW#TAVX*4>%Qi;TQS9ZM7oETj-b`QN4b}Or zfI3jQ>0Y4@>1mq&YLTxFOw>A+$5@>Q-B#YeD63<=doL!lW65`G`b1O?u{-k}BVSJy@pzXLp`?4FWY+vYR<$}y*Nw!rHdfr%u}(YU zX#lxDopZe!H5(drlj$w}J*q<9_m}$W>1humggN&(8%!9e0l=Sl$2gTMY9w_i71arI zE9h}jCY>|td&#b2_GG`@v^HNgY-|J;wImL0cx=>Crxb%23ZkZ^KR{C}Hmc8>#-9BrXxwYom zPoL*%Z1h@OICtNoZn$Ew+G?18u)KOauIse~Q?-IhNG7ozM1iZ1P9 zqB~sYvi(Z%fC!VK4K$4*YJLrqf!dyPgU& zF(Xga)uDCnquYb}WtM@UAJ|4(!0g`Mb|tNf{K22wDTE>!?dFGDGoEF~7RQMKjtzWG zglqu3fLniYYPD|xua;Rf*tH@2-`xT{T3UfL5o?4d~Zm`ucVa-0on*NGfX(bVcP(B?RvnN*rJo5LAky4qFds;Mb zM|fch6N?%18A4AD-j=OsiHUOPr@9U|ATHbhVPtDag@ln~r-{xAnw`qf#03mnJ^UNC zh*W4KRDXht-fq}mn~HD?z%9lROhc6Ma@*5RO5mg9gt$FFdI{c39>nVmAr~!9ApQs^ zgUNf*LASdEu|5)-nfqI+cB(I;__G&wtIU)gmEVSJPO$08bGu%@9Em&iLNCoN9<1_3 zj^Y*{T`tJ&(lWd+-tp6PVXb4(rjJ%Xk49qi>$cWl&;L#>&FGVSn^A0Jig7fLvu1_Z zl1Kw>WxslKGyeMCw*N_wXsu>BLJ)^B@S@Ws$eX3TD$vuIJN20cq_(y{SHnyzNK&j& zlQs91&hV(%c#$$!FIO3Of$R|VrUc;&g757%+DB=RDo~8eHNQIkO~TFk>~tO3s-PC& z!C(8xPEz?kk+FdO@sn{y5C7aSs0s815hlGV@?vM>-%;Cs^2BVU*4+gXfca45>BHfi zI3AyFx&5Hn8Nz>A3Ur zO!&G+c#+?_qUJ&%CuaPRLTx;2i4p!fQ@f$Z4VF8jlgvU>{TFNjVi!sE;l^~0z+=mr zMseq5VAKNAIj$V`2)?3 zCt?MWA7y|cbb4VaZ1oa!`?KgbMhyQr>hXV%c2ewGFp zLeQi8ht)r0N)JxfUyWWIXKxWiQNF-81=D$wDya}ql#>6Wtj&bPPE!AflIoeAQKawMI#Ev# ziR9DDv60Q=N#uV}J2z_4MvLq-PZT@LpL{Y74ZX-BzmSsZ3(9`SxO=v95zKaLOZz`+ z0P9aYdYqmaabrxM4pmk6d;N5Ien9MTir&vy$j~>2 zcQ`H#U$B^9R*7OaE6!0->bo1?$eZ2rjhX`4NTIjzNnr(=37;oG!51GP298SO|7tB7}gT`#?X169jBB~IP%a-Sx=N8~4abmK0a)bOW`4UZ3q zI7T!bao8Cu>eg&CN4rryzUzMbzPmD>e;&(GZf{}y^De#+*_Y1OnJ$PEFQi7Ub?>ui zTo5`eQK!PO5%vy%k|c5&cNSYu&20r%eususDJBK#hkE1!2xeJ zfZ;d#{`bn)y!H^M6HBNrNX({LB8%s-mbi9r(W|Xu!b9yY_70qN#r$<-CRsO8VqiV% z*c8_&Ot_-e4v^LoUiaky!869ggQ{(7^dFxGl)vhzH1kNINa_Q3l+BUN^?Q7V?Nb;I zruhv$wDxb-bk_v>E_eETqy6}{3AaWba-HZL1bFfQ^U8IZB^SBUun!JFN+NCO#3j~P z{e-b~2F<@&J7WRH!~G|R6~rJPifEe51tKwk*X55%)8Ch0@4&I0(oBEqs`j-s^d@13 zS!CO9naeGK?XEHbbuD*4e)*{iWt;M99;vD!Jp7{lBwihzV94!6nU6}i<~5ZoPMy<( z1ar7*8>rT?pa0_G@FIK@b}rWI%`dwWwRvDry??24P)ZD`VH1>P0ysnd#V^2t zQO%xF4^!DynQ|j&zb)oV`v)8`7sV3Ud5H!R9_E{6FndxXdif{00$>ie5yW~kFfreXl5X^ zFmw>CGLw&)6ayGuXqRrfkuDl34qSXx$t(EHM+zyX$Qt!LUgRYd7PtOOkg>oac{+k` z=$CgQ#^sK9TG)umJcyMSHyGDEm5)bE-?ZzjY97E!Xq5e;-IAbV zKi(JEa}%D+xFR^q6Yr>Ia|SnNlOdsMM=TVL=AKXPPZfy@XVJN6pqp>^J}^V*JkHX7 zwV$55oT4rFG!3BfJfSy5QFA7aJl=&d><*Y!k=H z%d=*7WMg#cy1J(xY^FmiFQDR1Zwv1;jp`7_E5i9>*b7rmlPimMuhQU51%F`W`&-3$^5TVY`DK zoQPS`za;-yon&YJefy_N8Na`8`ZqTwxFXyhRL9_e57RHGl};g>mmCtoZ;>(!(4nloE7)`+2ioUe5dtyaVz4*a6rHQR%FH z^^sn9)gRDu%D8$<@7-1p7jURq@9s+0o_5N^JOnQ>Xp7Rtf-?7Vi`XN5MEAcKd&bzF zNy(JDeba^&$No2)JG9}EKqa_2gPj+hTtITml*s@vMpZpHaWJ$ zVA#B+wDHF6Y*%hiB9LC1@5}so!iRr3CnI#Yzco%&FO`H4VRv{lF$K=G^!!`Qrjj6$ zR~4jcbWuYC%UM|hd3>-OD{-F;FIMAgaXQFct16fgkF#J8{b9MweES5jqzli{L*!;{ zNvH94PW-f2d`#SO4C^XO`! diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go new file mode 100644 index 0000000000..4aaab8bf97 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -0,0 +1,180 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/utesting" +) + +//var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") +var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + +func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { + sendConn := s.setupConnection(t) + t.Logf("sending tx: %v %v %v\n", tx.Hash().String(), tx.GasPrice(), tx.Gas()) + // Send the transaction + if err := sendConn.Write(Transactions([]*types.Transaction{tx})); err != nil { + t.Fatal(err) + } + time.Sleep(100 * time.Millisecond) + recvConn := s.setupConnection(t) + // Wait for the transaction announcement + switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { + case *Transactions: + recTxs := *msg + if len(recTxs) < 1 { + t.Fatalf("received transactions do not match send: %v", recTxs) + } + if tx.Hash() != recTxs[len(recTxs)-1].Hash() { + t.Fatalf("received transactions do not match send: got %v want %v", recTxs, tx) + } + case *NewPooledTransactionHashes: + txHashes := *msg + if len(txHashes) < 1 { + t.Fatalf("received transactions do not match send: %v", txHashes) + } + if tx.Hash() != txHashes[len(txHashes)-1] { + t.Fatalf("wrong announcement received, wanted %v got %v", tx, txHashes) + } + default: + t.Fatalf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg)) + } +} + +func sendFailingTx(t *utesting.T, s *Suite, tx *types.Transaction) { + sendConn, recvConn := s.setupConnection(t), s.setupConnection(t) + // Wait for a transaction announcement + switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { + case *NewPooledTransactionHashes: + break + default: + t.Logf("unexpected message, logging: %v", pretty.Sdump(msg)) + } + // Send the transaction + if err := sendConn.Write(Transactions([]*types.Transaction{tx})); err != nil { + t.Fatal(err) + } + // Wait for another transaction announcement + switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { + case *Transactions: + t.Fatalf("Received unexpected transaction announcement: %v", msg) + case *NewPooledTransactionHashes: + t.Fatalf("Received unexpected pooledTx announcement: %v", msg) + case *Error: + // Transaction should not be announced -> wait for timeout + return + default: + t.Fatalf("unexpected message in sendFailingTx: %s", pretty.Sdump(msg)) + } +} + +func unknownTx(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce()+1, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) + return signWithFaucet(t, txNew) +} + +func getNextTxFromChain(t *utesting.T, s *Suite) *types.Transaction { + // Get a new transaction + var tx *types.Transaction + for _, blocks := range s.fullChain.blocks[s.chain.Len():] { + txs := blocks.Transactions() + if txs.Len() != 0 { + tx = txs[0] + break + } + } + if tx == nil { + t.Fatal("could not find transaction") + } + return tx +} + +func getOldTxFromChain(t *utesting.T, s *Suite) *types.Transaction { + var tx *types.Transaction + for _, blocks := range s.fullChain.blocks[:s.chain.Len()-1] { + txs := blocks.Transactions() + if txs.Len() != 0 { + tx = txs[0] + break + } + } + if tx == nil { + t.Fatal("could not find transaction") + } + return tx +} + +func invalidNonceTx(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce()-2, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) + return signWithFaucet(t, txNew) +} + +func hugeAmount(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + amount := largeNumber(2) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce(), to, amount, tx.Gas(), tx.GasPrice(), tx.Data()) + return signWithFaucet(t, txNew) +} + +func hugeGasPrice(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + gasPrice := largeNumber(2) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), gasPrice, tx.Data()) + return signWithFaucet(t, txNew) +} + +func hugeData(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), tx.GasPrice(), largeBuffer(2)) + return signWithFaucet(t, txNew) +} + +func signWithFaucet(t *utesting.T, tx *types.Transaction) *types.Transaction { + signer := types.HomesteadSigner{} + signedTx, err := types.SignTx(tx, signer, faucetKey) + if err != nil { + t.Fatalf("could not sign tx: %v\n", err) + } + return signedTx +} diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index a20e88c372..f768d61d5a 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -100,13 +100,9 @@ type NewBlockHashes []struct { func (nbh NewBlockHashes) Code() int { return 17 } -// NewBlock is the network packet for the block propagation message. -type NewBlock struct { - Block *types.Block - TD *big.Int -} +type Transactions []*types.Transaction -func (nb NewBlock) Code() int { return 23 } +func (t Transactions) Code() int { return 18 } // GetBlockHeaders represents a block header query. type GetBlockHeaders struct { @@ -122,6 +118,29 @@ type BlockHeaders []*types.Header func (bh BlockHeaders) Code() int { return 20 } +// GetBlockBodies represents a GetBlockBodies request +type GetBlockBodies []common.Hash + +func (gbb GetBlockBodies) Code() int { return 21 } + +// BlockBodies is the network packet for block content distribution. +type BlockBodies []*types.Body + +func (bb BlockBodies) Code() int { return 22 } + +// NewBlock is the network packet for the block propagation message. +type NewBlock struct { + Block *types.Block + TD *big.Int +} + +func (nb NewBlock) Code() int { return 23 } + +// NewPooledTransactionHashes is the network packet for the tx hash propagation message. +type NewPooledTransactionHashes [][32]byte + +func (nb NewPooledTransactionHashes) Code() int { return 24 } + // HashOrNumber is a combined field for specifying an origin block. type hashOrNumber struct { Hash common.Hash // Block hash from which to retrieve headers (excludes Number) @@ -158,16 +177,6 @@ func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error { return err } -// GetBlockBodies represents a GetBlockBodies request -type GetBlockBodies []common.Hash - -func (gbb GetBlockBodies) Code() int { return 21 } - -// BlockBodies is the network packet for block content distribution. -type BlockBodies []*types.Body - -func (bb BlockBodies) Code() int { return 22 } - // Conn represents an individual connection with a peer type Conn struct { *rlpx.Conn @@ -205,6 +214,10 @@ func (c *Conn) Read() Message { msg = new(NewBlock) case (NewBlockHashes{}).Code(): msg = new(NewBlockHashes) + case (Transactions{}).Code(): + msg = new(Transactions) + case (NewPooledTransactionHashes{}).Code(): + msg = new(NewPooledTransactionHashes) default: return errorf("invalid message code: %d", code) } From e7db1dbc96fb366c13e05ee9b3b0a57ba26ca49b Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 30 Nov 2020 18:58:47 +0100 Subject: [PATCH 037/235] p2p/nodestate: fix deadlock during shutdown of les server (#21927) This PR fixes a deadlock reported here: #21925 The cause is that many operations may be pending, but if the close happens, only one of them gets awoken and exits, the others remain waiting for a signal that never comes. --- p2p/nodestate/nodestate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/nodestate/nodestate.go b/p2p/nodestate/nodestate.go index ab28b47a15..def93bac43 100644 --- a/p2p/nodestate/nodestate.go +++ b/p2p/nodestate/nodestate.go @@ -725,7 +725,7 @@ func (ns *NodeStateMachine) opFinish() { } ns.opPending = nil ns.opFlag = false - ns.opWait.Signal() + ns.opWait.Broadcast() } // Operation calls the given function as an operation callback. This allows the caller From a2795c8055988ce231ff4abf1731514c75c4be5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Tue, 1 Dec 2020 10:03:41 +0100 Subject: [PATCH 038/235] les: fix nodiscover option (#21906) --- cmd/utils/flags.go | 4 +++- eth/backend.go | 2 +- eth/discovery.go | 5 ++--- les/client.go | 2 +- les/enr_entry.go | 5 ++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 56880768f9..051bdd6308 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1584,7 +1584,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(RPCGlobalTxFeeCapFlag.Name) { cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCapFlag.Name) } - if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) { + if ctx.GlobalIsSet(NoDiscoverFlag.Name) { + cfg.DiscoveryURLs = []string{} + } else if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) { urls := ctx.GlobalString(DNSDiscoveryFlag.Name) if urls == "" { cfg.DiscoveryURLs = []string{} diff --git a/eth/backend.go b/eth/backend.go index 01e6cadd1f..03b0b319b7 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -208,7 +208,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) - eth.dialCandidates, err = eth.setupDiscovery(&stack.Config().P2P) + eth.dialCandidates, err = eth.setupDiscovery() if err != nil { return nil, err } diff --git a/eth/discovery.go b/eth/discovery.go index 48f6159017..e7a281d356 100644 --- a/eth/discovery.go +++ b/eth/discovery.go @@ -19,7 +19,6 @@ package eth import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" @@ -65,8 +64,8 @@ func (eth *Ethereum) currentEthEntry() *ethEntry { } // setupDiscovery creates the node discovery source for the eth protocol. -func (eth *Ethereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) { - if cfg.NoDiscovery || len(eth.config.DiscoveryURLs) == 0 { +func (eth *Ethereum) setupDiscovery() (enode.Iterator, error) { + if len(eth.config.DiscoveryURLs) == 0 { return nil, nil } client := dnsdisc.NewClient(dnsdisc.Config{}) diff --git a/les/client.go b/les/client.go index a2f7c56dfd..37250d076f 100644 --- a/les/client.go +++ b/les/client.go @@ -112,7 +112,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { } peers.subscribe((*vtSubscription)(leth.valueTracker)) - dnsdisc, err := leth.setupDiscovery(&stack.Config().P2P) + dnsdisc, err := leth.setupDiscovery() if err != nil { return nil, err } diff --git a/les/enr_entry.go b/les/enr_entry.go index 11e6273be5..8f0169bee1 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -17,7 +17,6 @@ package les import ( - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" @@ -35,8 +34,8 @@ func (e lesEntry) ENRKey() string { } // setupDiscovery creates the node discovery source for the eth protocol. -func (eth *LightEthereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) { - if cfg.NoDiscovery || len(eth.config.DiscoveryURLs) == 0 { +func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) { + if len(eth.config.DiscoveryURLs) == 0 { return nil, nil } client := dnsdisc.NewClient(dnsdisc.Config{}) From 908c18073a4df12866c84c4ff5f63430e28a62d0 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 2 Dec 2020 16:17:59 +0800 Subject: [PATCH 039/235] params: update CHTs (#21941) --- params/config.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/params/config.go b/params/config.go index ade81408a8..588d848f1c 100644 --- a/params/config.go +++ b/params/config.go @@ -74,10 +74,10 @@ var ( // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 336, - SectionHead: common.HexToHash("0xd42b78902b6527a80337bf1bc372a3ccc3db97e9cc7cf421ca047ae9076c716b"), - CHTRoot: common.HexToHash("0xd97f3b30f7e0cb958e4c67c53ec27745e5a165e33e56821b86523dfee62b783a"), - BloomRoot: common.HexToHash("0xf3cbfd070fababfe2adc9b23fc02c731f6ca2cce6646b3ede4ef2db06092ccce"), + SectionIndex: 345, + SectionHead: common.HexToHash("0x5453bab878704adebc934b41fd214a07ea7a72b8572ff088dca7f7956cd0ef28"), + CHTRoot: common.HexToHash("0x7693d432595846c094f47cb37f5c868b0b7b1968fc6b0fc411ded1345fdaffab"), + BloomRoot: common.HexToHash("0x8b0e7895bc39840d8dac857e26bdf3d0a07684b0b962b252546659e0337a9f70"), } // MainnetCheckpointOracle contains a set of configs for the main network oracle. @@ -113,10 +113,10 @@ var ( // RopstenTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. RopstenTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 269, - SectionHead: common.HexToHash("0x290a9eb65e65c64601d1b05522533ed502098a246736b348502a170818a33d64"), - CHTRoot: common.HexToHash("0x530ebac02264227277d0a16b0819ef96a2011a6e1e66523ebff8040f4a3437ca"), - BloomRoot: common.HexToHash("0x480cd5b3198a0767022902130546854a2e8867cce573c1cf0ce54e67a7bf5efb"), + SectionIndex: 279, + SectionHead: common.HexToHash("0x4a4912848d4c06090097073357c10015d11c6f4544a0f93cbdd584701c3b7d58"), + CHTRoot: common.HexToHash("0x9053b7867ae921e80a4e2f5a4b15212e4af3d691ca712fb33dc150e9c6ea221c"), + BloomRoot: common.HexToHash("0x3dc04cb1be7ddc271f3f83469b47b76184a79d7209ef51d85b1539ea6d25a645"), } // RopstenCheckpointOracle contains a set of configs for the Ropsten test network oracle. @@ -155,10 +155,10 @@ var ( // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. RinkebyTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 223, - SectionHead: common.HexToHash("0x03ca0d5e3a931c77cd7a97bbaa2d9e4edc4549c621dc1d223a29f10c86a4a16a"), - CHTRoot: common.HexToHash("0x6573dbdd91b2958b446bd04d67c23e5f14b2510ac96e8df1b6a894dc49e37c6c"), - BloomRoot: common.HexToHash("0x28a35042a4e88efbac55fe566faf7fce000dc436f17fd4cb4b081c9cd793e1a7"), + SectionIndex: 232, + SectionHead: common.HexToHash("0x8170fca4039b11a008c11f9996ff112151cbb17411437bb2f86288e11158b2f0"), + CHTRoot: common.HexToHash("0x4526560d92ae1b3a6d3ee780c3ad289ba2bbf1b5da58d9ea107f2f26412b631f"), + BloomRoot: common.HexToHash("0x82a889098a35d6a21ea8894d35a1db69b94bad61b988bbe5ae4601437320e331"), } // RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle. @@ -195,10 +195,10 @@ var ( // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 107, - SectionHead: common.HexToHash("0xff3ae39199fa191894de419e7f673c8627aa8cc7af924b90f36635b6add375f2"), - CHTRoot: common.HexToHash("0x27d59d60c652425b6b593a882f55a4ff57f24e470a810a6e3c8ba71833a20220"), - BloomRoot: common.HexToHash("0x3c14066d8bb3733780c06b8165768dbb9dd23b75f56012fe5f2fb3c2fb70cadb"), + SectionIndex: 116, + SectionHead: common.HexToHash("0xf2d200f636f213c9c7bb4e747ff564813da7708253037103aef3d8be5203c5e1"), + CHTRoot: common.HexToHash("0xb0ac83e2ccf6c2776945e099c4e3df50fe6200499c8b2045c34cafdf57d15087"), + BloomRoot: common.HexToHash("0xfb580ad1c611230a4bfc56534f58bcb156d028bc6ce70e35403dc019c7c02d90"), } // GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle. From e9e86aeacbfc810016c2451e8b21616a3ea0b927 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 2 Dec 2020 12:49:20 +0100 Subject: [PATCH 040/235] eth: fix error in tracing if reexec is set (#21830) * eth: fix error in tracing if reexec is set * eth: change pointer embedding to value-embedding --- eth/api_tracer.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 804d26b0b9..2497c8d95e 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -65,7 +65,7 @@ type TraceConfig struct { // StdTraceConfig holds extra parameters to standard-json trace functions. type StdTraceConfig struct { - *vm.LogConfig + vm.LogConfig Reexec *uint64 TxHash common.Hash } @@ -549,9 +549,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block txHash common.Hash ) if config != nil { - if config.LogConfig != nil { - logConfig = *config.LogConfig - } + logConfig = config.LogConfig txHash = config.TxHash } logConfig.Debug = true @@ -576,7 +574,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block chainConfigCopy := new(params.ChainConfig) *chainConfigCopy = *chainConfig chainConfig = chainConfigCopy - if yolov2 := config.Overrides.YoloV2Block; yolov2 != nil { + if yolov2 := config.LogConfig.Overrides.YoloV2Block; yolov2 != nil { chainConfig.YoloV2Block = yolov2 canon = false } From 0b2f1446bbece9bb4e7abef0bf8de1a267aba654 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 2 Dec 2020 16:42:38 +0100 Subject: [PATCH 041/235] go.mod: update github.com/golang/snappy(#21934) This updates the snappy library depency to include a fix for a Go 1.16 incompatibility issue. --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 9a35f6447f..0f47809ef6 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-stack/stack v1.8.0 github.com/golang/protobuf v1.4.2 - github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 + github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 diff --git a/go.sum b/go.sum index dedae7bc7a..d1bd608585 100644 --- a/go.sum +++ b/go.sum @@ -97,6 +97,8 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 h1:lMm2hD9Fy0ynom5+85/pbdkiYcBqM1JWmhpAXLmy0fw= github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 h1:ur2rms48b3Ep1dxh7aUV2FZEQ8jEVO2F6ILKx8ofkAg= +github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= From d7a64dc02b788b9a77904108b49d19a7b4a1a224 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 3 Dec 2020 13:16:20 +0100 Subject: [PATCH 042/235] cmd/devp2p: add node filter for snap + fix arg error (#21950) --- cmd/devp2p/nodesetcmd.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go index 228d3319e3..ba97405abc 100644 --- a/cmd/devp2p/nodesetcmd.go +++ b/cmd/devp2p/nodesetcmd.go @@ -95,6 +95,7 @@ var filterFlags = map[string]nodeFilterC{ "-min-age": {1, minAgeFilter}, "-eth-network": {1, ethFilter}, "-les-server": {0, lesFilter}, + "-snap": {0, snapFilter}, } func parseFilters(args []string) ([]nodeFilter, error) { @@ -104,15 +105,15 @@ func parseFilters(args []string) ([]nodeFilter, error) { if !ok { return nil, fmt.Errorf("invalid filter %q", args[0]) } - if len(args) < fc.narg { - return nil, fmt.Errorf("filter %q wants %d arguments, have %d", args[0], fc.narg, len(args)) + if len(args)-1 < fc.narg { + return nil, fmt.Errorf("filter %q wants %d arguments, have %d", args[0], fc.narg, len(args)-1) } - filter, err := fc.fn(args[1:]) + filter, err := fc.fn(args[1 : 1+fc.narg]) if err != nil { return nil, fmt.Errorf("%s: %v", args[0], err) } filters = append(filters, filter) - args = args[fc.narg+1:] + args = args[1+fc.narg:] } return filters, nil } @@ -191,3 +192,13 @@ func lesFilter(args []string) (nodeFilter, error) { } return f, nil } + +func snapFilter(args []string) (nodeFilter, error) { + f := func(n nodeJSON) bool { + var snap struct { + _ []rlp.RawValue `rlp:"tail"` + } + return n.N.Load(enr.WithEntry("snap", &snap)) == nil + } + return f, nil +} From 62cedb3aabf5db260b74830285975116ba464e86 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Fri, 4 Dec 2020 09:54:07 +0200 Subject: [PATCH 043/235] core/vm/runtime: remove duplicated line (#21956) This line is duplicated, though it doesn't cause any issues. --- core/vm/runtime/runtime.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 8abd378cef..2586535f9d 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -118,7 +118,6 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { cfg.State.AddAddressToAccessList(address) for _, addr := range vmenv.ActivePrecompiles() { cfg.State.AddAddressToAccessList(addr) - cfg.State.AddAddressToAccessList(addr) } } cfg.State.CreateAccount(address) From 7770e41cb5fcc386a7d2329d1187174839122f24 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 4 Dec 2020 12:22:19 +0100 Subject: [PATCH 044/235] core: improve contextual information on core errors (#21869) A lot of times when we hit 'core' errors, example: invalid tx, the information provided is insufficient. We miss several pieces of information: what account has nonce too high, and what transaction in that block was offending? This PR adds that information, using the new type of wrapped errors. It also adds a testcase which (partly) verifies the output from the errors. The first commit changes all usage of direct equality-checks on core errors, into using errors.Is. The second commit adds contextual information. This wraps most of the core errors with more information, and also wraps it one more time in stateprocessor, to further provide tx index and tx hash, if such a tx is encoutered in a block. The third commit uses the chainmaker to try to generate chains with such errors in them, thus triggering the errors and checking that the generated string meets expectations. --- accounts/abi/bind/backends/simulated.go | 2 +- core/blockchain_test.go | 3 +- core/state_processor.go | 4 +- core/state_processor_test.go | 152 ++++++++++++++++++++++++ core/state_transition.go | 21 ++-- core/tx_pool_test.go | 4 +- light/lightchain_test.go | 3 +- miner/worker.go | 10 +- 8 files changed, 179 insertions(+), 20 deletions(-) create mode 100644 core/state_processor_test.go diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 695d81bf03..4be98adfca 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -479,7 +479,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs b.pendingState.RevertToSnapshot(snapshot) if err != nil { - if err == core.ErrIntrinsicGas { + if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit } return true, nil, err // Bail out diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 7ec62b11dd..d60a235981 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -17,6 +17,7 @@ package core import ( + "errors" "fmt" "io/ioutil" "math/big" @@ -468,7 +469,7 @@ func testBadHashes(t *testing.T, full bool) { _, err = blockchain.InsertHeaderChain(headers, 1) } - if err != ErrBlacklistedHash { + if !errors.Is(err, ErrBlacklistedHash) { t.Errorf("error mismatch: have: %v, want: %v", err, ErrBlacklistedHash) } } diff --git a/core/state_processor.go b/core/state_processor.go index d992dfb9cb..cdc86a694e 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -17,6 +17,8 @@ package core import ( + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" @@ -76,7 +78,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg statedb.Prepare(tx.Hash(), block.Hash(), i) receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, header, tx, usedGas, vmenv) if err != nil { - return nil, nil, 0, err + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) diff --git a/core/state_processor_test.go b/core/state_processor_test.go new file mode 100644 index 0000000000..6e63975ac1 --- /dev/null +++ b/core/state_processor_test.go @@ -0,0 +1,152 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + "golang.org/x/crypto/sha3" +) + +// TestStateProcessorErrors tests the output from the 'core' errors +// as defined in core/error.go. These errors are generated when the +// blockchain imports bad blocks, meaning blocks which have valid headers but +// contain invalid transactions +func TestStateProcessorErrors(t *testing.T) { + var ( + signer = types.HomesteadSigner{} + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: params.TestChainConfig, + } + genesis = gspec.MustCommit(db) + blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + ) + defer blockchain.Stop() + var makeTx = func(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, testKey) + return tx + } + for i, tt := range []struct { + txs []*types.Transaction + want string + }{ + { + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + }, + want: "could not apply tx 1 [0x36bfa6d14f1cd35a1be8cc2322982a595fabc0e799f09c1de3bad7bd5b1f7626]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1", + }, + { + txs: []*types.Transaction{ + makeTx(100, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + }, + want: "could not apply tx 0 [0x51cd272d41ef6011d8138e18bf4043797aca9b713c7d39a97563f9bbe6bdbe6f]: nonce too high: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0", + }, + { + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), 21000000, nil, nil), + }, + want: "could not apply tx 0 [0x54c58b530824b0bb84b7a98183f08913b5d74e1cebc368515ef3c65edf8eb56a]: gas limit reached", + }, + { + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(1), params.TxGas, nil, nil), + }, + want: "could not apply tx 0 [0x3094b17498940d92b13baccf356ce8bfd6f221e926abc903d642fa1466c5b50e]: insufficient funds for transfer: address 0x71562b71999873DB5b286dF957af199Ec94617F7", + }, + { + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(0xffffff), nil), + }, + want: "could not apply tx 0 [0xaa3f7d86802b1f364576d9071bf231e31d61b392d306831ac9cf706ff5371ce0]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 0 want 352321515000", + }, + { + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + makeTx(1, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + makeTx(2, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + makeTx(3, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(0), nil), + }, + want: "could not apply tx 3 [0x836fab5882205362680e49b311a20646de03b630920f18ec6ee3b111a2cf6835]: intrinsic gas too low: have 20000, want 21000", + }, + // The last 'core' error is ErrGasUintOverflow: "gas uint64 overflow", but in order to + // trigger that one, we'd have to allocate a _huge_ chunk of data, such that the + // multiplication len(data) +gas_per_byte overflows uint64. Not testable at the moment + } { + block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs) + _, err := blockchain.InsertChain(types.Blocks{block}) + if err == nil { + t.Fatal("block imported without errors") + } + if have, want := err.Error(), tt.want; have != want { + t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want) + } + } +} + +// GenerateBadBlock constructs a "block" which contains the transactions. The transactions are not expected to be +// valid, and no proper post-state can be made. But from the perspective of the blockchain, the block is sufficiently +// valid to be considered for import: +// - valid pow (fake), ancestry, difficulty, gaslimit etc +func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions) *types.Block { + header := &types.Header{ + ParentHash: parent.Hash(), + Coinbase: parent.Coinbase(), + Difficulty: engine.CalcDifficulty(&fakeChainReader{params.TestChainConfig}, parent.Time()+10, &types.Header{ + Number: parent.Number(), + Time: parent.Time(), + Difficulty: parent.Difficulty(), + UncleHash: parent.UncleHash(), + }), + GasLimit: CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()), + Number: new(big.Int).Add(parent.Number(), common.Big1), + Time: parent.Time() + 10, + UncleHash: types.EmptyUncleHash, + } + var receipts []*types.Receipt + + // The post-state result doesn't need to be correct (this is a bad block), but we do need something there + // Preferably something unique. So let's use a combo of blocknum + txhash + hasher := sha3.NewLegacyKeccak256() + hasher.Write(header.Number.Bytes()) + var cumulativeGas uint64 + for _, tx := range txs { + txh := tx.Hash() + hasher.Write(txh[:]) + receipt := types.NewReceipt(nil, false, cumulativeGas+tx.Gas()) + receipt.TxHash = tx.Hash() + receipt.GasUsed = tx.Gas() + receipts = append(receipts, receipt) + cumulativeGas += tx.Gas() + } + header.Root = common.BytesToHash(hasher.Sum(nil)) + // Assemble and return the final block for sealing + return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)) +} diff --git a/core/state_transition.go b/core/state_transition.go index 72e8b02a1c..a8d1936c1f 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,6 +17,7 @@ package core import ( + "fmt" "math" "math/big" @@ -174,8 +175,8 @@ func (st *StateTransition) to() common.Address { func (st *StateTransition) buyGas() error { mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) - if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 { - return ErrInsufficientFunds + if have, want := st.state.GetBalance(st.msg.From()), mgval; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want) } if err := st.gp.SubGas(st.msg.Gas()); err != nil { return err @@ -190,11 +191,13 @@ func (st *StateTransition) buyGas() error { func (st *StateTransition) preCheck() error { // Make sure this transaction's nonce is correct. if st.msg.CheckNonce() { - nonce := st.state.GetNonce(st.msg.From()) - if nonce < st.msg.Nonce() { - return ErrNonceTooHigh - } else if nonce > st.msg.Nonce() { - return ErrNonceTooLow + stNonce := st.state.GetNonce(st.msg.From()) + if msgNonce := st.msg.Nonce(); stNonce < msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh, + st.msg.From().Hex(), msgNonce, stNonce) + } else if stNonce > msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow, + st.msg.From().Hex(), msgNonce, stNonce) } } return st.buyGas() @@ -240,13 +243,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { return nil, err } if st.gas < gas { - return nil, ErrIntrinsicGas + return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas) } st.gas -= gas // Check clause 6 if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) { - return nil, ErrInsufficientFundsForTransfer + return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex()) } var ( ret []byte diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index dbc49d4f9c..246b3977d3 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -242,7 +242,7 @@ func TestInvalidTransactions(t *testing.T) { from, _ := deriveSender(tx) pool.currentState.AddBalance(from, big.NewInt(1)) - if err := pool.AddRemote(tx); err != ErrInsufficientFunds { + if err := pool.AddRemote(tx); !errors.Is(err, ErrInsufficientFunds) { t.Error("expected", ErrInsufficientFunds) } @@ -255,7 +255,7 @@ func TestInvalidTransactions(t *testing.T) { pool.currentState.SetNonce(from, 1) pool.currentState.AddBalance(from, big.NewInt(0xffffffffffffff)) tx = transaction(0, 100000, key) - if err := pool.AddRemote(tx); err != ErrNonceTooLow { + if err := pool.AddRemote(tx); !errors.Is(err, ErrNonceTooLow) { t.Error("expected", ErrNonceTooLow) } diff --git a/light/lightchain_test.go b/light/lightchain_test.go index 70d2e70c18..2aed08d74e 100644 --- a/light/lightchain_test.go +++ b/light/lightchain_test.go @@ -18,6 +18,7 @@ package light import ( "context" + "errors" "math/big" "testing" @@ -321,7 +322,7 @@ func TestBadHeaderHashes(t *testing.T) { var err error headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10) core.BadHashes[headers[2].Hash()] = true - if _, err = bc.InsertHeaderChain(headers, 1); err != core.ErrBlacklistedHash { + if _, err = bc.InsertHeaderChain(headers, 1); !errors.Is(err, core.ErrBlacklistedHash) { t.Errorf("error mismatch: have: %v, want %v", err, core.ErrBlacklistedHash) } } diff --git a/miner/worker.go b/miner/worker.go index 16f4c1c313..d5813c18a8 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -793,23 +793,23 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin w.current.state.Prepare(tx.Hash(), common.Hash{}, w.current.tcount) logs, err := w.commitTransaction(tx, coinbase) - switch err { - case core.ErrGasLimitReached: + switch { + case errors.Is(err, core.ErrGasLimitReached): // Pop the current out-of-gas transaction without shifting in the next from the account log.Trace("Gas limit exceeded for current block", "sender", from) txs.Pop() - case core.ErrNonceTooLow: + case errors.Is(err, core.ErrNonceTooLow): // New head notification data race between the transaction pool and miner, shift log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) txs.Shift() - case core.ErrNonceTooHigh: + case errors.Is(err, core.ErrNonceTooHigh): // Reorg notification data race between the transaction pool and miner, skip account = log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce()) txs.Pop() - case nil: + case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account coalescedLogs = append(coalescedLogs, logs...) w.current.tcount++ From 15339cf1c9af2b1242c2574869fa7afca1096cdf Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 4 Dec 2020 15:01:47 +0100 Subject: [PATCH 045/235] cmd/geth: implement vulnerability check (#21859) * cmd/geth: implement vulnerability check * cmd/geth: use minisign to verify vulnerability feed * cmd/geth: add the test too * cmd/geth: more minisig/signify testing * cmd/geth: support multiple pubfiles for signing * cmd/geth: add @holiman minisig pubkey * cmd/geth: polishes on vulnerability check * cmd/geth: fix ineffassign linter nit * cmd/geth: add CVE to version check struct * cmd/geth/testdata: add missing testfile * cmd/geth: add more keys to versionchecker * cmd/geth: support file:// URLs in version check * cmd/geth: improve key ID printing when signature check fails Co-authored-by: Felix Lange --- cmd/geth/main.go | 1 + cmd/geth/misccmd.go | 27 +++ cmd/geth/testdata/vcheck/data.json | 61 +++++++ cmd/geth/testdata/vcheck/data2.json | 62 +++++++ .../vulnerabilities.json.minisig.1 | 4 + .../vulnerabilities.json.minisig.2 | 4 + .../vulnerabilities.json.minisig.3 | 4 + cmd/geth/testdata/vcheck/minisign.pub | 2 + cmd/geth/testdata/vcheck/minisign.sec | 2 + .../vcheck/signify-sigs/data.json.sig | 2 + cmd/geth/testdata/vcheck/signifykey.pub | 2 + cmd/geth/testdata/vcheck/signifykey.sec | 2 + .../sigs/vulnerabilities.json.minisig.1 | 4 + .../sigs/vulnerabilities.json.minisig.2 | 4 + .../sigs/vulnerabilities.json.minisig.3 | 4 + cmd/geth/version_check.go | 169 ++++++++++++++++++ cmd/geth/version_check_test.go | 79 ++++++++ 17 files changed, 433 insertions(+) create mode 100644 cmd/geth/testdata/vcheck/data.json create mode 100644 cmd/geth/testdata/vcheck/data2.json create mode 100644 cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 create mode 100644 cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 create mode 100644 cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 create mode 100644 cmd/geth/testdata/vcheck/minisign.pub create mode 100644 cmd/geth/testdata/vcheck/minisign.sec create mode 100644 cmd/geth/testdata/vcheck/signify-sigs/data.json.sig create mode 100644 cmd/geth/testdata/vcheck/signifykey.pub create mode 100644 cmd/geth/testdata/vcheck/signifykey.sec create mode 100644 cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 create mode 100644 cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 create mode 100644 cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 create mode 100644 cmd/geth/version_check.go create mode 100644 cmd/geth/version_check_test.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0f50989208..e587a5f86d 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -242,6 +242,7 @@ func init() { makecacheCommand, makedagCommand, versionCommand, + versionCheckCommand, licenseCommand, // See config.go dumpConfigCommand, diff --git a/cmd/geth/misccmd.go b/cmd/geth/misccmd.go index 0e7ee96513..967df2ada0 100644 --- a/cmd/geth/misccmd.go +++ b/cmd/geth/misccmd.go @@ -31,6 +31,18 @@ import ( ) var ( + VersionCheckUrlFlag = cli.StringFlag{ + Name: "check.url", + Usage: "URL to use when checking vulnerabilities", + Value: "https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json", + } + VersionCheckVersionFlag = cli.StringFlag{ + Name: "check.version", + Usage: "Version to check", + Value: fmt.Sprintf("Geth/v%v/%v-%v/%v", + params.VersionWithCommit(gitCommit, gitDate), + runtime.GOOS, runtime.GOARCH, runtime.Version()), + } makecacheCommand = cli.Command{ Action: utils.MigrateFlags(makecache), Name: "makecache", @@ -65,6 +77,21 @@ Regular users do not need to execute it. Category: "MISCELLANEOUS COMMANDS", Description: ` The output of this command is supposed to be machine-readable. +`, + } + versionCheckCommand = cli.Command{ + Action: utils.MigrateFlags(versionCheck), + Flags: []cli.Flag{ + VersionCheckUrlFlag, + VersionCheckVersionFlag, + }, + Name: "version-check", + Usage: "Checks (online) whether the current version suffers from any known security vulnerabilities", + ArgsUsage: "", + Category: "MISCELLANEOUS COMMANDS", + Description: ` +The version-check command fetches vulnerability-information from https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json, +and displays information about any security vulnerabilities that affect the currently executing version. `, } licenseCommand = cli.Command{ diff --git a/cmd/geth/testdata/vcheck/data.json b/cmd/geth/testdata/vcheck/data.json new file mode 100644 index 0000000000..e7ee2bf7e4 --- /dev/null +++ b/cmd/geth/testdata/vcheck/data.json @@ -0,0 +1,61 @@ +[ + { + "name": "CorruptedDAG", + "uid": "GETH-2020-01", + "summary": "Mining nodes will generate erroneous PoW on epochs > `385`.", + "description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.", + "links": [ + "https://github.com/ethereum/go-ethereum/pull/21793", + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49" + ], + "introduced": "v1.6.0", + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Medium", + "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.2(1|2|3)-.*" + }, + { + "name": "GoCrash", + "uid": "GETH-2020-02", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`", + "description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM", + "https://github.com/golang/go/issues/42552" + ], + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$" + }, + { + "name": "ShallowCopy", + "uid": "GETH-2020-03", + "summary": "A consensus flaw in Geth, related to `datacopy` precompile", + "description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.7", + "fixed": "v1.9.17", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$" + }, + { + "name": "GethCrash", + "uid": "GETH-2020-04", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing", + "description": "Full details to be disclosed at a later date", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.16", + "fixed": "v1.9.18", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9.(16|17).*$" + } +] diff --git a/cmd/geth/testdata/vcheck/data2.json b/cmd/geth/testdata/vcheck/data2.json new file mode 100644 index 0000000000..75a31677a1 --- /dev/null +++ b/cmd/geth/testdata/vcheck/data2.json @@ -0,0 +1,62 @@ +[ + { + "name": "CorruptedDAG", + "uid": "GETH-2020-01", + "summary": "Mining nodes will generate erroneous PoW on epochs > `385`.", + "description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.", + "links": [ + "https://github.com/ethereum/go-ethereum/pull/21793", + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49" + ], + "introduced": "v1.6.0", + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Medium", + "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.2(1|2|3)-.*", + "CVE": "correct" + }, + { + "name": "GoCrash", + "uid": "GETH-2020-02", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`", + "description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM", + "https://github.com/golang/go/issues/42552" + ], + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$" + }, + { + "name": "ShallowCopy", + "uid": "GETH-2020-03", + "summary": "A consensus flaw in Geth, related to `datacopy` precompile", + "description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.7", + "fixed": "v1.9.17", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$" + }, + { + "name": "GethCrash", + "uid": "GETH-2020-04", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing", + "description": "Full details to be disclosed at a later date", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.16", + "fixed": "v1.9.18", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9.(16|17).*$" + } +] diff --git a/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 new file mode 100644 index 0000000000..f9066d4fe0 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: timestamp:1605618622 file:vulnerabilities.json +osAPs4QPdDkmiWQxqeMIzYv/b+ZGxJ+19Sbrk1Cpq4t2gHBT+lqFtwL3OCzKWWyjGRTmHfsVGBYpzEdPRQ0/BQ== diff --git a/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 new file mode 100644 index 0000000000..a89a83d21a --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 @@ -0,0 +1,4 @@ +untrusted comment: Here's a comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 new file mode 100644 index 0000000000..6fd33b19a3 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 @@ -0,0 +1,4 @@ +untrusted comment: One more (untrusted) comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/minisign.pub b/cmd/geth/testdata/vcheck/minisign.pub new file mode 100644 index 0000000000..183dce5f6b --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisign.pub @@ -0,0 +1,2 @@ +untrusted comment: minisign public key 284E00B52C269624 +RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp diff --git a/cmd/geth/testdata/vcheck/minisign.sec b/cmd/geth/testdata/vcheck/minisign.sec new file mode 100644 index 0000000000..5c50715b20 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisign.sec @@ -0,0 +1,2 @@ +untrusted comment: minisign encrypted secret key +RWRTY0Iyz8kmPMKrqk6DCtlO9a33akKiaOQG1aLolqDxs52qvPoAAAACAAAAAAAAAEAAAAAArEiggdvyn6+WzTprirLtgiYQoU+ihz/HyGgjhuF+Pz2ddMduyCO+xjCHeq+vgVVW039fbsI8hW6LRGJZLBKV5/jdxCXAVVQE7qTQ6xpEdO0z8Z731/pV1hlspQXG2PNd16NMtwd9dWw= diff --git a/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig b/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig new file mode 100644 index 0000000000..3d5fcacf9a --- /dev/null +++ b/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig @@ -0,0 +1,2 @@ +untrusted comment: verify with ./signifykey.pub +RWSKLNhZb0KdAbhRUhW2LQZXdnwttu2SYhM9EuC4mMgOJB85h7/YIPupf8/ldTs4N8e9Y/fhgdY40q5LQpt5IFC62fq0v8U1/w8= diff --git a/cmd/geth/testdata/vcheck/signifykey.pub b/cmd/geth/testdata/vcheck/signifykey.pub new file mode 100644 index 0000000000..328f973ab4 --- /dev/null +++ b/cmd/geth/testdata/vcheck/signifykey.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/ diff --git a/cmd/geth/testdata/vcheck/signifykey.sec b/cmd/geth/testdata/vcheck/signifykey.sec new file mode 100644 index 0000000000..3279a2e58b --- /dev/null +++ b/cmd/geth/testdata/vcheck/signifykey.sec @@ -0,0 +1,2 @@ +untrusted comment: signify secret key +RWRCSwAAACpLQDLawSQCtI7eAVIvaiHzjTsTyJsfV5aKLNhZb0KdAWeICXJGa93/bHAcsY6jUh9I8RdEcDWEoGxmaXZC+IdVBPxDpkix9fBRGEUdKWHi3dOfqME0YRzErWI5AVg3cRw= diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 new file mode 100644 index 0000000000..f9066d4fe0 --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: timestamp:1605618622 file:vulnerabilities.json +osAPs4QPdDkmiWQxqeMIzYv/b+ZGxJ+19Sbrk1Cpq4t2gHBT+lqFtwL3OCzKWWyjGRTmHfsVGBYpzEdPRQ0/BQ== diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 new file mode 100644 index 0000000000..a89a83d21a --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 @@ -0,0 +1,4 @@ +untrusted comment: Here's a comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 new file mode 100644 index 0000000000..6fd33b19a3 --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 @@ -0,0 +1,4 @@ +untrusted comment: One more (untrusted) comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/version_check.go b/cmd/geth/version_check.go new file mode 100644 index 0000000000..2101a69e98 --- /dev/null +++ b/cmd/geth/version_check.go @@ -0,0 +1,169 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" + + "github.com/ethereum/go-ethereum/log" + "github.com/jedisct1/go-minisign" + "gopkg.in/urfave/cli.v1" +) + +var gethPubKeys []string = []string{ + //@holiman, minisign public key FB1D084D39BAEC24 + "RWQk7Lo5TQgd+wxBNZM+Zoy+7UhhMHaWKzqoes9tvSbFLJYZhNTbrIjx", + //minisign public key 138B1CA303E51687 + "RWSHFuUDoxyLEzjszuWZI1xStS66QTyXFFZG18uDfO26CuCsbckX1e9J", + //minisign public key FD9813B2D2098484 + "RWSEhAnSshOY/b+GmaiDkObbCWefsAoavjoLcPjBo1xn71yuOH5I+Lts", +} + +type vulnJson struct { + Name string + Uid string + Summary string + Description string + Links []string + Introduced string + Fixed string + Published string + Severity string + Check string + CVE string +} + +func versionCheck(ctx *cli.Context) error { + url := ctx.String(VersionCheckUrlFlag.Name) + version := ctx.String(VersionCheckVersionFlag.Name) + log.Info("Checking vulnerabilities", "version", version, "url", url) + return checkCurrent(url, version) +} + +func checkCurrent(url, current string) error { + var ( + data []byte + sig []byte + err error + ) + if data, err = fetch(url); err != nil { + return fmt.Errorf("could not retrieve data: %w", err) + } + if sig, err = fetch(fmt.Sprintf("%v.minisig", url)); err != nil { + return fmt.Errorf("could not retrieve signature: %w", err) + } + if err = verifySignature(gethPubKeys, data, sig); err != nil { + return err + } + var vulns []vulnJson + if err = json.Unmarshal(data, &vulns); err != nil { + return err + } + allOk := true + for _, vuln := range vulns { + r, err := regexp.Compile(vuln.Check) + if err != nil { + return err + } + if r.MatchString(current) { + allOk = false + fmt.Printf("## Vulnerable to %v (%v)\n\n", vuln.Uid, vuln.Name) + fmt.Printf("Severity: %v\n", vuln.Severity) + fmt.Printf("Summary : %v\n", vuln.Summary) + fmt.Printf("Fixed in: %v\n", vuln.Fixed) + if len(vuln.CVE) > 0 { + fmt.Printf("CVE: %v\n", vuln.CVE) + } + if len(vuln.Links) > 0 { + fmt.Printf("References:\n") + for _, ref := range vuln.Links { + fmt.Printf("\t- %v\n", ref) + } + } + fmt.Println() + } + } + if allOk { + fmt.Println("No vulnerabilities found") + } + return nil +} + +// fetch makes an HTTP request to the given url and returns the response body +func fetch(url string) ([]byte, error) { + if filep := strings.TrimPrefix(url, "file://"); filep != url { + return ioutil.ReadFile(filep) + } + res, err := http.Get(url) + if err != nil { + return nil, err + } + defer res.Body.Close() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + return body, nil +} + +// verifySignature checks that the sigData is a valid signature of the given +// data, for pubkey GethPubkey +func verifySignature(pubkeys []string, data, sigdata []byte) error { + sig, err := minisign.DecodeSignature(string(sigdata)) + if err != nil { + return err + } + // find the used key + var key *minisign.PublicKey + for _, pubkey := range pubkeys { + pub, err := minisign.NewPublicKey(pubkey) + if err != nil { + // our pubkeys should be parseable + return err + } + if pub.KeyId != sig.KeyId { + continue + } + key = &pub + break + } + if key == nil { + log.Info("Signing key not trusted", "keyid", keyID(sig.KeyId), "error", err) + return errors.New("signature could not be verified") + } + if ok, err := key.Verify(data, sig); !ok || err != nil { + log.Info("Verification failed error", "keyid", keyID(key.KeyId), "error", err) + return errors.New("signature could not be verified") + } + return nil +} + +// keyID turns a binary minisign key ID into a hex string. +// Note: key IDs are printed in reverse byte order. +func keyID(id [8]byte) string { + var rev [8]byte + for i := range id { + rev[len(rev)-1-i] = id[i] + } + return fmt.Sprintf("%X", rev) +} diff --git a/cmd/geth/version_check_test.go b/cmd/geth/version_check_test.go new file mode 100644 index 0000000000..4afe8ddc83 --- /dev/null +++ b/cmd/geth/version_check_test.go @@ -0,0 +1,79 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "encoding/json" + "io/ioutil" + "path/filepath" + "testing" +) + +func TestVerification(t *testing.T) { + // Signatures generated with `minisign` + t.Run("minisig", func(t *testing.T) { + // For this test, the pubkey is in testdata/minisign.pub + // (the privkey is `minisign.sec`, if we want to expand this test. Password 'test' ) + pub := "RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp" + testVerification(t, pub, "./testdata/vcheck/minisig-sigs/") + }) + // Signatures generated with `signify-openbsd` + t.Run("signify-openbsd", func(t *testing.T) { + t.Skip("This currently fails, minisign expects 4 lines of data, signify provides only 2") + // For this test, the pubkey is in testdata/signifykey.pub + // (the privkey is `signifykey.sec`, if we want to expand this test. Password 'test' ) + pub := "RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/" + testVerification(t, pub, "./testdata/vcheck/signify-sigs/") + }) +} + +func testVerification(t *testing.T, pubkey, sigdir string) { + // Data to verify + data, err := ioutil.ReadFile("./testdata/vcheck/data.json") + if err != nil { + t.Fatal(err) + } + // Signatures, with and without comments, both trusted and untrusted + files, err := ioutil.ReadDir(sigdir) + if err != nil { + t.Fatal(err) + } + for _, f := range files { + sig, err := ioutil.ReadFile(filepath.Join(sigdir, f.Name())) + if err != nil { + t.Fatal(err) + } + err = verifySignature([]string{pubkey}, data, sig) + if err != nil { + t.Fatal(err) + } + } +} + +func TestJson(t *testing.T) { + data, _ := ioutil.ReadFile("./testdata/vcheck/data2.json") + var vulns []vulnJson + if err := json.Unmarshal(data, &vulns); err != nil { + t.Fatal(err) + } + if len(vulns) == 0 { + t.Fatal("expected data, got none") + } + if have, want := vulns[0].CVE, "correct"; have != want { + t.Errorf("have %v, want %v", have, want) + } +} From 581c028d181cc4582c8435ced5c0b104ac63346f Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Mon, 7 Dec 2020 13:04:27 +0000 Subject: [PATCH 046/235] les: cosmetic rewrite of the arm64 float bug workaround (#21960) * les: revert arm float bug workaround to check go 1.15 * add traces to reproduce outside travis * simpler workaround --- les/utils/expiredvalue.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/les/utils/expiredvalue.go b/les/utils/expiredvalue.go index 55e82cee48..1a2b3d995e 100644 --- a/les/utils/expiredvalue.go +++ b/les/utils/expiredvalue.go @@ -88,8 +88,9 @@ func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { if base >= 0 || uint64(-base) <= e.Base { // This is a temporary fix to circumvent a golang // uint conversion issue on arm64, which needs to - // be investigated further. FIXME - e.Base = uint64(int64(e.Base) + int64(base)) + // be investigated further. More details at: + // https://github.com/golang/go/issues/43047 + e.Base += uint64(int64(base)) return amount } net := int64(-float64(e.Base) / factor) From 6a4e730003d4adec28fa14baa78020c8c8b53887 Mon Sep 17 00:00:00 2001 From: Steve Ruckdashel Date: Tue, 8 Dec 2020 03:47:56 -0600 Subject: [PATCH 047/235] crypto/secp256k1: add workaround for go mod vendor (#21735) Go won't vendor C files if there are no Go files present in the directory. Workaround is to add dummy Go files. Fixes: #20232 --- crypto/secp256k1/dummy.go | 20 +++++++++++++++++++ .../secp256k1/libsecp256k1/contrib/dummy.go | 7 +++++++ crypto/secp256k1/libsecp256k1/dummy.go | 7 +++++++ .../secp256k1/libsecp256k1/include/dummy.go | 7 +++++++ crypto/secp256k1/libsecp256k1/src/dummy.go | 7 +++++++ .../libsecp256k1/src/modules/dummy.go | 7 +++++++ .../libsecp256k1/src/modules/ecdh/dummy.go | 7 +++++++ .../src/modules/recovery/dummy.go | 7 +++++++ 8 files changed, 69 insertions(+) create mode 100644 crypto/secp256k1/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/contrib/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/include/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/src/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go diff --git a/crypto/secp256k1/dummy.go b/crypto/secp256k1/dummy.go new file mode 100644 index 0000000000..c0f2ee52c0 --- /dev/null +++ b/crypto/secp256k1/dummy.go @@ -0,0 +1,20 @@ +// +build dummy + +// This file is part of a workaround for `go mod vendor` which won't vendor +// C files if there's no Go file in the same directory. +// This would prevent the crypto/secp256k1/libsecp256k1/include/secp256k1.h file to be vendored. +// +// This Go file imports the c directory where there is another dummy.go file which +// is the second part of this workaround. +// +// These two files combined make it so `go mod vendor` behaves correctly. +// +// See this issue for reference: https://github.com/golang/go/issues/26366 + +package secp256k1 + +import ( + _ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/include" + _ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/src" + _ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/src/modules/recovery" +) diff --git a/crypto/secp256k1/libsecp256k1/contrib/dummy.go b/crypto/secp256k1/libsecp256k1/contrib/dummy.go new file mode 100644 index 0000000000..fda594be99 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/contrib/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package contrib diff --git a/crypto/secp256k1/libsecp256k1/dummy.go b/crypto/secp256k1/libsecp256k1/dummy.go new file mode 100644 index 0000000000..379b16992f --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package libsecp256k1 diff --git a/crypto/secp256k1/libsecp256k1/include/dummy.go b/crypto/secp256k1/libsecp256k1/include/dummy.go new file mode 100644 index 0000000000..5af540c73c --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/include/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package include diff --git a/crypto/secp256k1/libsecp256k1/src/dummy.go b/crypto/secp256k1/libsecp256k1/src/dummy.go new file mode 100644 index 0000000000..65868f38a8 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package src diff --git a/crypto/secp256k1/libsecp256k1/src/modules/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/dummy.go new file mode 100644 index 0000000000..3c7a696439 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package module diff --git a/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go new file mode 100644 index 0000000000..b6fc38327e --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package ecdh diff --git a/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go new file mode 100644 index 0000000000..b9491f0cb9 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package recovery From ed0670cb17a96aafeb9eaaeb9765a42fb6bb5663 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Dec 2020 14:44:56 +0100 Subject: [PATCH 048/235] accounts/abi/bind: allow specifying signer on transactOpts (#21356) This commit enables users to specify which signer they want to use while creating their transactOpts. Previously all contract interactions used the homestead signer. Now a user can specify whether they want to sign with homestead or EIP155 and specify the chainID which adds another layer of security. Closes #16484 --- accounts/abi/bind/auth.go | 92 ++++++++++++++++++-- accounts/abi/bind/backends/simulated.go | 2 + accounts/abi/bind/backends/simulated_test.go | 12 +-- accounts/abi/bind/base.go | 4 +- accounts/abi/bind/bind_test.go | 34 ++++---- contracts/checkpointoracle/oracle_test.go | 2 +- les/sync_test.go | 6 +- les/test_helper.go | 3 +- mobile/bind.go | 12 ++- 9 files changed, 127 insertions(+), 40 deletions(-) diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index c891b0a3e9..1190772676 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -21,6 +21,7 @@ import ( "errors" "io" "io/ioutil" + "math/big" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/external" @@ -28,11 +29,21 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" ) +// ErrNoChainID is returned whenever the user failed to specify a chain id. +var ErrNoChainID = errors.New("no chain id specified") + +// ErrNotAuthorized is returned when an account is not properly unlocked. +var ErrNotAuthorized = errors.New("not authorized to sign this account") + // NewTransactor is a utility method to easily create a transaction signer from // an encrypted json key stream and the associated passphrase. +// +// Deprecated: Use NewTransactorWithChainID instead. func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { + log.Warn("WARNING: NewTransactor has been deprecated in favour of NewTransactorWithChainID") json, err := ioutil.ReadAll(keyin) if err != nil { return nil, err @@ -45,13 +56,17 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { } // NewKeyStoreTransactor is a utility method to easily create a transaction signer from -// a decrypted key from a keystore. +// an decrypted key from a keystore. +// +// Deprecated: Use NewKeyStoreTransactorWithChainID instead. func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) { + log.Warn("WARNING: NewKeyStoreTransactor has been deprecated in favour of NewTransactorWithChainID") + signer := types.HomesteadSigner{} return &TransactOpts{ From: account.Address, - Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { if address != account.Address { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) if err != nil { @@ -64,13 +79,17 @@ func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account // NewKeyedTransactor is a utility method to easily create a transaction signer // from a single private key. +// +// Deprecated: Use NewKeyedTransactorWithChainID instead. func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { + log.Warn("WARNING: NewKeyedTransactor has been deprecated in favour of NewKeyedTransactorWithChainID") keyAddr := crypto.PubkeyToAddress(key.PublicKey) + signer := types.HomesteadSigner{} return &TransactOpts{ From: keyAddr, - Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { if address != keyAddr { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) if err != nil { @@ -81,14 +100,73 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { } } +// NewTransactorWithChainID is a utility method to easily create a transaction signer from +// an encrypted json key stream and the associated passphrase. +func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.Int) (*TransactOpts, error) { + json, err := ioutil.ReadAll(keyin) + if err != nil { + return nil, err + } + key, err := keystore.DecryptKey(json, passphrase) + if err != nil { + return nil, err + } + return NewKeyedTransactorWithChainID(key.PrivateKey, chainID) +} + +// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from +// an decrypted key from a keystore. +func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) { + if chainID == nil { + return nil, ErrNoChainID + } + signer := types.NewEIP155Signer(chainID) + return &TransactOpts{ + From: account.Address, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != account.Address { + return nil, ErrNotAuthorized + } + signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + }, nil +} + +// NewKeyedTransactorWithChainID is a utility method to easily create a transaction signer +// from a single private key. +func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*TransactOpts, error) { + keyAddr := crypto.PubkeyToAddress(key.PublicKey) + if chainID == nil { + return nil, ErrNoChainID + } + signer := types.NewEIP155Signer(chainID) + return &TransactOpts{ + From: keyAddr, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != keyAddr { + return nil, ErrNotAuthorized + } + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + }, nil +} + // NewClefTransactor is a utility method to easily create a transaction signer // with a clef backend. func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) *TransactOpts { return &TransactOpts{ From: account.Address, - Signer: func(signer types.Signer, address common.Address, transaction *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) { if address != account.Address { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } return clef.SignTx(account, transaction, nil) // Clef enforces its own chain id }, diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 4be98adfca..6e87e037f0 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -74,6 +74,7 @@ type SimulatedBackend struct { // NewSimulatedBackendWithDatabase creates a new binding backend based on the given database // and uses a simulated blockchain for testing purposes. +// A simulated backend always uses chainID 1337. func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc} genesis.MustCommit(database) @@ -91,6 +92,7 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis // NewSimulatedBackend creates a new binding backend using a simulated blockchain // for testing purposes. +// A simulated backend always uses chainID 1337. func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, gasLimit) } diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index e2597cca01..64ddf8bb2c 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -39,7 +39,7 @@ import ( func TestSimulatedBackend(t *testing.T) { var gasLimit uint64 = 8000029 key, _ := crypto.GenerateKey() // nolint: gosec - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) genAlloc := make(core.GenesisAlloc) genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)} @@ -411,7 +411,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) - opts := bind.NewKeyedTransactor(key) + opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether)}}, 10000000) defer sim.Close() @@ -888,7 +888,7 @@ func TestSimulatedBackend_PendingCodeAt(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - auth := bind.NewKeyedTransactor(testKey) + auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) @@ -924,7 +924,7 @@ func TestSimulatedBackend_CodeAt(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - auth := bind.NewKeyedTransactor(testKey) + auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) @@ -956,7 +956,7 @@ func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - contractAuth := bind.NewKeyedTransactor(testKey) + contractAuth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v", err) @@ -1043,7 +1043,7 @@ func TestSimulatedBackend_CallContractRevert(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - contractAuth := bind.NewKeyedTransactor(testKey) + contractAuth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(reverterBin), sim) if err != nil { t.Errorf("could not deploy contract: %v", err) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 9e6d898eaf..f5a6fe22fc 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -32,7 +32,7 @@ import ( // SignerFn is a signer function callback when a contract requires a method to // sign the transaction before submission. -type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Transaction, error) +type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) // CallOpts is the collection of options to fine tune a contract call request. type CallOpts struct { @@ -256,7 +256,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i if opts.Signer == nil { return nil, errors.New("no signer to authorize the transaction with") } - signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx) + signedTx, err := opts.Signer(opts.From, rawTx) if err != nil { return nil, err } diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 8bfbf30b53..1a8a17e45e 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -296,7 +296,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -351,7 +351,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -397,7 +397,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -455,7 +455,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -503,7 +503,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -598,7 +598,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -648,7 +648,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -723,7 +723,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -817,7 +817,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1007,7 +1007,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1142,7 +1142,7 @@ var bindTests = []struct { ` key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1284,7 +1284,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1350,7 +1350,7 @@ var bindTests = []struct { ` // Initialize test accounts key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1444,7 +1444,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer sim.Close() - transactOpts := bind.NewKeyedTransactor(key) + transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) _, _, _, err := DeployIdentifierCollision(transactOpts, sim) if err != nil { t.Fatalf("failed to deploy contract: %v", err) @@ -1506,7 +1506,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer sim.Close() - transactOpts := bind.NewKeyedTransactor(key) + transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) _, _, c1, err := DeployContractOne(transactOpts, sim) if err != nil { t.Fatal("Failed to deploy contract") @@ -1563,7 +1563,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1632,7 +1632,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 1000000) defer sim.Close() - opts := bind.NewKeyedTransactor(key) + opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) _, _, c, err := DeployNewFallbacks(opts, sim) if err != nil { t.Fatalf("Failed to deploy contract: %v", err) diff --git a/contracts/checkpointoracle/oracle_test.go b/contracts/checkpointoracle/oracle_test.go index 817954d11a..1218481929 100644 --- a/contracts/checkpointoracle/oracle_test.go +++ b/contracts/checkpointoracle/oracle_test.go @@ -178,7 +178,7 @@ func TestCheckpointRegister(t *testing.T) { contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{accounts[0].addr: {Balance: big.NewInt(1000000000)}, accounts[1].addr: {Balance: big.NewInt(1000000000)}, accounts[2].addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer contractBackend.Close() - transactOpts := bind.NewKeyedTransactor(accounts[0].key) + transactOpts, _ := bind.NewKeyedTransactorWithChainID(accounts[0].key, big.NewInt(1337)) // 3 trusted signers, threshold 2 contractAddr, _, c, err := contract.DeployCheckpointOracle(transactOpts, contractBackend, []common.Address{accounts[0].addr, accounts[1].addr, accounts[2].addr}, sectionSize, processConfirms, big.NewInt(2)) diff --git a/les/sync_test.go b/les/sync_test.go index ffce4d8df2..6924e7b43a 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -80,7 +80,8 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { + auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337)) + if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(auth, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { t.Error("register checkpoint failed", err) } server.backend.Commit() @@ -162,7 +163,8 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { + auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337)) + if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(auth, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { t.Error("register checkpoint failed", err) } server.backend.Commit() diff --git a/les/test_helper.go b/les/test_helper.go index 5a8d64f767..d108a8dace 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -112,7 +112,8 @@ func prepare(n int, backend *backends.SimulatedBackend) { switch i { case 0: // deploy checkpoint contract - registrarAddr, _, _, _ = contract.DeployCheckpointOracle(bind.NewKeyedTransactor(bankKey), backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) + auth, _ := bind.NewKeyedTransactorWithChainID(bankKey, big.NewInt(1337)) + registrarAddr, _, _, _ = contract.DeployCheckpointOracle(auth, backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) // bankUser transfers some ether to user1 nonce, _ := backend.PendingNonceAt(ctx, bankAddr) tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10000), params.TxGas, nil, nil), signer, bankKey) diff --git a/mobile/bind.go b/mobile/bind.go index afa97b5382..e32d864aa5 100644 --- a/mobile/bind.go +++ b/mobile/bind.go @@ -40,7 +40,7 @@ type MobileSigner struct { } func (s *MobileSigner) Sign(addr *Address, unsignedTx *Transaction) (signedTx *Transaction, _ error) { - sig, err := s.sign(types.EIP155Signer{}, addr.address, unsignedTx.tx) + sig, err := s.sign(addr.address, unsignedTx.tx) if err != nil { return nil, err } @@ -82,12 +82,16 @@ func NewTransactOpts() *TransactOpts { // NewKeyedTransactOpts is a utility method to easily create a transaction signer // from a single private key. -func NewKeyedTransactOpts(keyJson []byte, passphrase string) (*TransactOpts, error) { +func NewKeyedTransactOpts(keyJson []byte, passphrase string, chainID *big.Int) (*TransactOpts, error) { key, err := keystore.DecryptKey(keyJson, passphrase) if err != nil { return nil, err } - return &TransactOpts{*bind.NewKeyedTransactor(key.PrivateKey)}, nil + auth, err := bind.NewKeyedTransactorWithChainID(key.PrivateKey, chainID) + if err != nil { + return nil, err + } + return &TransactOpts{*auth}, nil } func (opts *TransactOpts) GetFrom() *Address { return &Address{opts.opts.From} } @@ -106,7 +110,7 @@ func (opts *TransactOpts) GetGasLimit() int64 { return int64(opts.opts.GasLimi func (opts *TransactOpts) SetFrom(from *Address) { opts.opts.From = from.address } func (opts *TransactOpts) SetNonce(nonce int64) { opts.opts.Nonce = big.NewInt(nonce) } func (opts *TransactOpts) SetSigner(s Signer) { - opts.opts.Signer = func(signer types.Signer, addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + opts.opts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { sig, err := s.Sign(&Address{addr}, &Transaction{tx}) if err != nil { return nil, err From bd848aad7c4e1f7d1eaecd9ea7ee23785090768a Mon Sep 17 00:00:00 2001 From: "Li, Cheng" Date: Tue, 8 Dec 2020 13:19:09 -0500 Subject: [PATCH 049/235] common: improve printing of Hash and Address (#21834) Both Hash and Address have a String method, which returns the value as hex with 0x prefix. They also had a Format method which tried to print the value using printf of []byte. The way Format worked was at odds with String though, leading to a situation where fmt.Sprintf("%v", hash) returned the decimal notation and hash.String() returned a hex string. This commit makes it consistent again. Both types now support the %v, %s, %q format verbs for 0x-prefixed hex output. %x, %X creates unprefixed hex output. %d is also supported and returns the decimal notation "[1 2 3...]". For Address, the case of hex characters in %v, %s, %q output is determined using the EIP-55 checksum. Using %x, %X with Address disables checksumming. Co-authored-by: Felix Lange --- common/types.go | 94 +++++++++++++++++++----- common/types_test.go | 166 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+), 18 deletions(-) diff --git a/common/types.go b/common/types.go index 94cf622e8c..d920e8b1f1 100644 --- a/common/types.go +++ b/common/types.go @@ -17,6 +17,7 @@ package common import ( + "bytes" "database/sql/driver" "encoding/hex" "encoding/json" @@ -84,10 +85,34 @@ func (h Hash) String() string { return h.Hex() } -// Format implements fmt.Formatter, forcing the byte slice to be formatted as is, -// without going through the stringer interface used for logging. +// Format implements fmt.Formatter. +// Hash supports the %v, %s, %v, %x, %X and %d format verbs. func (h Hash) Format(s fmt.State, c rune) { - fmt.Fprintf(s, "%"+string(c), h[:]) + hexb := make([]byte, 2+len(h)*2) + copy(hexb, "0x") + hex.Encode(hexb[2:], h[:]) + + switch c { + case 'x', 'X': + if !s.Flag('#') { + hexb = hexb[2:] + } + if c == 'X' { + hexb = bytes.ToUpper(hexb) + } + fallthrough + case 'v', 's': + s.Write(hexb) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(hexb) + s.Write(q) + case 'd': + fmt.Fprint(s, ([len(h)]byte)(h)) + default: + fmt.Fprintf(s, "%%!%c(hash=%x)", c, h) + } } // UnmarshalText parses a hash in hex syntax. @@ -208,35 +233,68 @@ func (a Address) Hash() Hash { return BytesToHash(a[:]) } // Hex returns an EIP55-compliant hex string representation of the address. func (a Address) Hex() string { - unchecksummed := hex.EncodeToString(a[:]) + return string(a.checksumHex()) +} + +// String implements fmt.Stringer. +func (a Address) String() string { + return a.Hex() +} + +func (a *Address) checksumHex() []byte { + buf := a.hex() + + // compute checksum sha := sha3.NewLegacyKeccak256() - sha.Write([]byte(unchecksummed)) + sha.Write(buf[2:]) hash := sha.Sum(nil) - - result := []byte(unchecksummed) - for i := 0; i < len(result); i++ { - hashByte := hash[i/2] + for i := 2; i < len(buf); i++ { + hashByte := hash[(i-2)/2] if i%2 == 0 { hashByte = hashByte >> 4 } else { hashByte &= 0xf } - if result[i] > '9' && hashByte > 7 { - result[i] -= 32 + if buf[i] > '9' && hashByte > 7 { + buf[i] -= 32 } } - return "0x" + string(result) + return buf[:] } -// String implements fmt.Stringer. -func (a Address) String() string { - return a.Hex() +func (a Address) hex() []byte { + var buf [len(a)*2 + 2]byte + copy(buf[:2], "0x") + hex.Encode(buf[2:], a[:]) + return buf[:] } -// Format implements fmt.Formatter, forcing the byte slice to be formatted as is, -// without going through the stringer interface used for logging. +// Format implements fmt.Formatter. +// Address supports the %v, %s, %v, %x, %X and %d format verbs. func (a Address) Format(s fmt.State, c rune) { - fmt.Fprintf(s, "%"+string(c), a[:]) + switch c { + case 'v', 's': + s.Write(a.checksumHex()) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(a.checksumHex()) + s.Write(q) + case 'x', 'X': + // %x disables the checksum. + hex := a.hex() + if !s.Flag('#') { + hex = hex[2:] + } + if c == 'X' { + hex = bytes.ToUpper(hex) + } + s.Write(hex) + case 'd': + fmt.Fprint(s, ([len(a)]byte)(a)) + default: + fmt.Fprintf(s, "%%!%c(address=%x)", c, a) + } } // SetBytes sets the address to the value of b. diff --git a/common/types_test.go b/common/types_test.go index fffd673c6e..318e985f87 100644 --- a/common/types_test.go +++ b/common/types_test.go @@ -17,8 +17,10 @@ package common import ( + "bytes" "database/sql/driver" "encoding/json" + "fmt" "math/big" "reflect" "strings" @@ -371,3 +373,167 @@ func TestAddress_Value(t *testing.T) { }) } } + +func TestAddress_Format(t *testing.T) { + b := []byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + } + var addr Address + addr.SetBytes(b) + + tests := []struct { + name string + out string + want string + }{ + { + name: "println", + out: fmt.Sprintln(addr), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15\n", + }, + { + name: "print", + out: fmt.Sprint(addr), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15", + }, + { + name: "printf-s", + out: func() string { + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "%s", addr) + return buf.String() + }(), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15", + }, + { + name: "printf-q", + out: fmt.Sprintf("%q", addr), + want: `"0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15"`, + }, + { + name: "printf-x", + out: fmt.Sprintf("%x", addr), + want: "b26f2b342aab24bcf63ea218c6a9274d30ab9a15", + }, + { + name: "printf-X", + out: fmt.Sprintf("%X", addr), + want: "B26F2B342AAB24BCF63EA218C6A9274D30AB9A15", + }, + { + name: "printf-#x", + out: fmt.Sprintf("%#x", addr), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15", + }, + { + name: "printf-v", + out: fmt.Sprintf("%v", addr), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15", + }, + // The original default formatter for byte slice + { + name: "printf-d", + out: fmt.Sprintf("%d", addr), + want: "[178 111 43 52 42 171 36 188 246 62 162 24 198 169 39 77 48 171 154 21]", + }, + // Invalid format char. + { + name: "printf-t", + out: fmt.Sprintf("%t", addr), + want: "%!t(address=b26f2b342aab24bcf63ea218c6a9274d30ab9a15)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.out != tt.want { + t.Errorf("%s does not render as expected:\n got %s\nwant %s", tt.name, tt.out, tt.want) + } + }) + } +} + +func TestHash_Format(t *testing.T) { + var hash Hash + hash.SetBytes([]byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + 0x10, 0x00, + }) + + tests := []struct { + name string + out string + want string + }{ + { + name: "println", + out: fmt.Sprintln(hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000\n", + }, + { + name: "print", + out: fmt.Sprint(hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-s", + out: func() string { + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "%s", hash) + return buf.String() + }(), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-q", + out: fmt.Sprintf("%q", hash), + want: `"0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000"`, + }, + { + name: "printf-x", + out: fmt.Sprintf("%x", hash), + want: "b26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-X", + out: fmt.Sprintf("%X", hash), + want: "B26F2B342AAB24BCF63EA218C6A9274D30AB9A15A218C6A9274D30AB9A151000", + }, + { + name: "printf-#x", + out: fmt.Sprintf("%#x", hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-#X", + out: fmt.Sprintf("%#X", hash), + want: "0XB26F2B342AAB24BCF63EA218C6A9274D30AB9A15A218C6A9274D30AB9A151000", + }, + { + name: "printf-v", + out: fmt.Sprintf("%v", hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + // The original default formatter for byte slice + { + name: "printf-d", + out: fmt.Sprintf("%d", hash), + want: "[178 111 43 52 42 171 36 188 246 62 162 24 198 169 39 77 48 171 154 21 162 24 198 169 39 77 48 171 154 21 16 0]", + }, + // Invalid format char. + { + name: "printf-t", + out: fmt.Sprintf("%t", hash), + want: "%!t(hash=b26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.out != tt.want { + t.Errorf("%s does not render as expected:\n got %s\nwant %s", tt.name, tt.out, tt.want) + } + }) + } +} From 40b6ccf383cba1471971767039a3071b8c57d28b Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 9 Dec 2020 11:13:02 +0100 Subject: [PATCH 050/235] core,les: headerchain import in batches (#21471) * core: add test for headerchain inserts * core, light: write headerchains in batches * core: change to one callback per batch of inserted headers + review concerns * core: error-check on batch write * core: unexport writeHeaders * core: remove callback parameter in InsertHeaderChain The semantics of InsertHeaderChain are now much simpler: it is now an all-or-nothing operation. The new WriteStatus return value allows callers to check for the canonicality of the insertion. This change simplifies use of HeaderChain in package les, where the callback was previously used to post chain events. * core: skip some hashing when writing headers * core: less hashing in header validation * core: fix headerchain flaw regarding blacklisted hashes Co-authored-by: Felix Lange --- core/blockchain.go | 8 +- core/headerchain.go | 292 +++++++++++++++++++++++---------------- core/headerchain_test.go | 115 +++++++++++++++ light/lightchain.go | 34 ++--- 4 files changed, 309 insertions(+), 140 deletions(-) create mode 100644 core/headerchain_test.go diff --git a/core/blockchain.go b/core/blockchain.go index c52be68354..bc1db49f37 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2438,12 +2438,8 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i bc.wg.Add(1) defer bc.wg.Done() - - whFunc := func(header *types.Header) error { - _, err := bc.hc.WriteHeader(header) - return err - } - return bc.hc.InsertHeaderChain(chain, whFunc, start) + _, err := bc.hc.InsertHeaderChain(chain, start) + return 0, err } // CurrentHeader retrieves the current head header of the canonical chain. The diff --git a/core/headerchain.go b/core/headerchain.go index 7c1aff99d8..dc354549c0 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -129,118 +129,192 @@ func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 { return number } -// WriteHeader writes a header into the local chain, given that its parent is -// already known. If the total difficulty of the newly inserted header becomes -// greater than the current known TD, the canonical chain is re-routed. +type headerWriteResult struct { + status WriteStatus + ignored int + imported int + lastHash common.Hash + lastHeader *types.Header +} + +// WriteHeaders writes a chain of headers into the local chain, given that the parents +// are already known. If the total difficulty of the newly inserted chain becomes +// greater than the current known TD, the canonical chain is reorged. // // Note: This method is not concurrent-safe with inserting blocks simultaneously // into the chain, as side effects caused by reorganisations cannot be emulated // without the real blocks. Hence, writing headers directly should only be done // in two scenarios: pure-header mode of operation (light clients), or properly // separated header/block phases (non-archive clients). -func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, err error) { - // Cache some values to prevent constant recalculation +func (hc *HeaderChain) writeHeaders(headers []*types.Header) (result *headerWriteResult, err error) { + if len(headers) == 0 { + return &headerWriteResult{}, nil + } + ptd := hc.GetTd(headers[0].ParentHash, headers[0].Number.Uint64()-1) + if ptd == nil { + return &headerWriteResult{}, consensus.ErrUnknownAncestor + } var ( - hash = header.Hash() - number = header.Number.Uint64() + lastNumber = headers[0].Number.Uint64() - 1 // Last successfully imported number + lastHash = headers[0].ParentHash // Last imported header hash + newTD = new(big.Int).Set(ptd) // Total difficulty of inserted chain + + lastHeader *types.Header + inserted []numberHash // Ephemeral lookup of number/hash for the chain + firstInserted = -1 // Index of the first non-ignored header ) - // Calculate the total difficulty of the header - ptd := hc.GetTd(header.ParentHash, number-1) - if ptd == nil { - return NonStatTy, consensus.ErrUnknownAncestor - } - head := hc.CurrentHeader().Number.Uint64() - localTd := hc.GetTd(hc.currentHeaderHash, head) - externTd := new(big.Int).Add(header.Difficulty, ptd) - - // Irrelevant of the canonical status, write the td and header to the database - // - // Note all the components of header(td, hash->number index and header) should - // be written atomically. - headerBatch := hc.chainDb.NewBatch() - rawdb.WriteTd(headerBatch, hash, number, externTd) - rawdb.WriteHeader(headerBatch, header) - if err := headerBatch.Write(); err != nil { - log.Crit("Failed to write header into disk", "err", err) + + batch := hc.chainDb.NewBatch() + for i, header := range headers { + var hash common.Hash + // The headers have already been validated at this point, so we already + // know that it's a contiguous chain, where + // headers[i].Hash() == headers[i+1].ParentHash + if i < len(headers)-1 { + hash = headers[i+1].ParentHash + } else { + hash = header.Hash() + } + number := header.Number.Uint64() + newTD.Add(newTD, header.Difficulty) + + // If the header is already known, skip it, otherwise store + if !hc.HasHeader(hash, number) { + // Irrelevant of the canonical status, write the TD and header to the database. + rawdb.WriteTd(batch, hash, number, newTD) + hc.tdCache.Add(hash, new(big.Int).Set(newTD)) + + rawdb.WriteHeader(batch, header) + inserted = append(inserted, numberHash{number, hash}) + hc.headerCache.Add(hash, header) + hc.numberCache.Add(hash, number) + if firstInserted < 0 { + firstInserted = i + } + } + lastHeader, lastHash, lastNumber = header, hash, number + } + + // Skip the slow disk write of all headers if interrupted. + if hc.procInterrupt() { + log.Debug("Premature abort during headers import") + return &headerWriteResult{}, errors.New("aborted") } + // Commit to disk! + if err := batch.Write(); err != nil { + log.Crit("Failed to write headers", "error", err) + } + batch.Reset() + + var ( + head = hc.CurrentHeader().Number.Uint64() + localTD = hc.GetTd(hc.currentHeaderHash, head) + status = SideStatTy + ) // If the total difficulty is higher than our known, add it to the canonical chain // Second clause in the if statement reduces the vulnerability to selfish mining. // Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf - reorg := externTd.Cmp(localTd) > 0 - if !reorg && externTd.Cmp(localTd) == 0 { - if header.Number.Uint64() < head { + reorg := newTD.Cmp(localTD) > 0 + if !reorg && newTD.Cmp(localTD) == 0 { + if lastNumber < head { reorg = true - } else if header.Number.Uint64() == head { + } else if lastNumber == head { reorg = mrand.Float64() < 0.5 } } + // If the parent of the (first) block is already the canon header, + // we don't have to go backwards to delete canon blocks, but + // simply pile them onto the existing chain + chainAlreadyCanon := headers[0].ParentHash == hc.currentHeaderHash if reorg { // If the header can be added into canonical chain, adjust the // header chain markers(canonical indexes and head header flag). // // Note all markers should be written atomically. - - // Delete any canonical number assignments above the new head - markerBatch := hc.chainDb.NewBatch() - for i := number + 1; ; i++ { - hash := rawdb.ReadCanonicalHash(hc.chainDb, i) - if hash == (common.Hash{}) { - break + markerBatch := batch // we can reuse the batch to keep allocs down + if !chainAlreadyCanon { + // Delete any canonical number assignments above the new head + for i := lastNumber + 1; ; i++ { + hash := rawdb.ReadCanonicalHash(hc.chainDb, i) + if hash == (common.Hash{}) { + break + } + rawdb.DeleteCanonicalHash(markerBatch, i) + } + // Overwrite any stale canonical number assignments, going + // backwards from the first header in this import + var ( + headHash = headers[0].ParentHash // inserted[0].parent? + headNumber = headers[0].Number.Uint64() - 1 // inserted[0].num-1 ? + headHeader = hc.GetHeader(headHash, headNumber) + ) + for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash { + rawdb.WriteCanonicalHash(markerBatch, headHash, headNumber) + headHash = headHeader.ParentHash + headNumber = headHeader.Number.Uint64() - 1 + headHeader = hc.GetHeader(headHash, headNumber) + } + // If some of the older headers were already known, but obtained canon-status + // during this import batch, then we need to write that now + // Further down, we continue writing the staus for the ones that + // were not already known + for i := 0; i < firstInserted; i++ { + hash := headers[i].Hash() + num := headers[i].Number.Uint64() + rawdb.WriteCanonicalHash(markerBatch, hash, num) + rawdb.WriteHeadHeaderHash(markerBatch, hash) } - rawdb.DeleteCanonicalHash(markerBatch, i) } - - // Overwrite any stale canonical number assignments - var ( - headHash = header.ParentHash - headNumber = header.Number.Uint64() - 1 - headHeader = hc.GetHeader(headHash, headNumber) - ) - for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash { - rawdb.WriteCanonicalHash(markerBatch, headHash, headNumber) - - headHash = headHeader.ParentHash - headNumber = headHeader.Number.Uint64() - 1 - headHeader = hc.GetHeader(headHash, headNumber) + // Extend the canonical chain with the new headers + for _, hn := range inserted { + rawdb.WriteCanonicalHash(markerBatch, hn.hash, hn.number) + rawdb.WriteHeadHeaderHash(markerBatch, hn.hash) } - // Extend the canonical chain with the new header - rawdb.WriteCanonicalHash(markerBatch, hash, number) - rawdb.WriteHeadHeaderHash(markerBatch, hash) if err := markerBatch.Write(); err != nil { log.Crit("Failed to write header markers into disk", "err", err) } + markerBatch.Reset() // Last step update all in-memory head header markers - hc.currentHeaderHash = hash - hc.currentHeader.Store(types.CopyHeader(header)) - headHeaderGauge.Update(header.Number.Int64()) + hc.currentHeaderHash = lastHash + hc.currentHeader.Store(types.CopyHeader(lastHeader)) + headHeaderGauge.Update(lastHeader.Number.Int64()) + // Chain status is canonical since this insert was a reorg. + // Note that all inserts which have higher TD than existing are 'reorg'. status = CanonStatTy - } else { - status = SideStatTy } - hc.tdCache.Add(hash, externTd) - hc.headerCache.Add(hash, header) - hc.numberCache.Add(hash, number) - return -} -// WhCallback is a callback function for inserting individual headers. -// A callback is used for two reasons: first, in a LightChain, status should be -// processed and light chain events sent, while in a BlockChain this is not -// necessary since chain events are sent after inserting blocks. Second, the -// header writes should be protected by the parent chain mutex individually. -type WhCallback func(*types.Header) error + if len(inserted) == 0 { + status = NonStatTy + } + return &headerWriteResult{ + status: status, + ignored: len(headers) - len(inserted), + imported: len(inserted), + lastHash: lastHash, + lastHeader: lastHeader, + }, nil +} func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) (int, error) { // Do a sanity check that the provided chain is actually ordered and linked for i := 1; i < len(chain); i++ { - if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != chain[i-1].Hash() { + parentHash := chain[i-1].Hash() + if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != parentHash { // Chain broke ancestry, log a message (programming error) and skip insertion log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", chain[i].Hash(), - "parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", chain[i-1].Hash()) + "parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", parentHash) return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, chain[i-1].Number, - chain[i-1].Hash().Bytes()[:4], i, chain[i].Number, chain[i].Hash().Bytes()[:4], chain[i].ParentHash[:4]) + parentHash.Bytes()[:4], i, chain[i].Number, chain[i].Hash().Bytes()[:4], chain[i].ParentHash[:4]) + } + // If the header is a banned one, straight out abort + if BadHashes[parentHash] { + return i - 1, ErrBlacklistedHash + } + // If it's the last header in the cunk, we need to check it too + if i == len(chain)-1 && BadHashes[chain[i].Hash()] { + return i, ErrBlacklistedHash } } @@ -263,16 +337,12 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) defer close(abort) // Iterate over the headers and ensure they all check out - for i, header := range chain { + for i := range chain { // If the chain is terminating, stop processing blocks if hc.procInterrupt() { log.Debug("Premature abort during headers verification") return 0, errors.New("aborted") } - // If the header is a banned one, straight out abort - if BadHashes[header.Hash()] { - return i, ErrBlacklistedHash - } // Otherwise wait for headers checks and ensure they pass if err := <-results; err != nil { return i, err @@ -282,55 +352,41 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) return 0, nil } -// InsertHeaderChain attempts to insert the given header chain in to the local -// chain, possibly creating a reorg. If an error is returned, it will return the -// index number of the failing header as well an error describing what went wrong. +// InsertHeaderChain inserts the given headers. // -// The verify parameter can be used to fine tune whether nonce verification -// should be done or not. The reason behind the optional check is because some -// of the header retrieval mechanisms already need to verfy nonces, as well as -// because nonces can be verified sparsely, not needing to check each. -func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, writeHeader WhCallback, start time.Time) (int, error) { - // Collect some import statistics to report on - stats := struct{ processed, ignored int }{} - // All headers passed verification, import them into the database - for i, header := range chain { - // Short circuit insertion if shutting down - if hc.procInterrupt() { - log.Debug("Premature abort during headers import") - return i, errors.New("aborted") - } - // If the header's already known, skip it, otherwise store - hash := header.Hash() - if hc.HasHeader(hash, header.Number.Uint64()) { - externTd := hc.GetTd(hash, header.Number.Uint64()) - localTd := hc.GetTd(hc.currentHeaderHash, hc.CurrentHeader().Number.Uint64()) - if externTd == nil || externTd.Cmp(localTd) <= 0 { - stats.ignored++ - continue - } - } - if err := writeHeader(header); err != nil { - return i, err - } - stats.processed++ +// The validity of the headers is NOT CHECKED by this method, i.e. they need to be +// validated by ValidateHeaderChain before calling InsertHeaderChain. +// +// This insert is all-or-nothing. If this returns an error, no headers were written, +// otherwise they were all processed successfully. +// +// The returned 'write status' says if the inserted headers are part of the canonical chain +// or a side chain. +func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time) (WriteStatus, error) { + if hc.procInterrupt() { + return 0, errors.New("aborted") } - // Report some public statistics so the user has a clue what's going on - last := chain[len(chain)-1] + res, err := hc.writeHeaders(chain) + // Report some public statistics so the user has a clue what's going on context := []interface{}{ - "count", stats.processed, "elapsed", common.PrettyDuration(time.Since(start)), - "number", last.Number, "hash", last.Hash(), + "count", res.imported, + "elapsed", common.PrettyDuration(time.Since(start)), + } + if err != nil { + context = append(context, "err", err) } - if timestamp := time.Unix(int64(last.Time), 0); time.Since(timestamp) > time.Minute { - context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) + if last := res.lastHeader; last != nil { + context = append(context, "number", last.Number, "hash", res.lastHash) + if timestamp := time.Unix(int64(last.Time), 0); time.Since(timestamp) > time.Minute { + context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) + } } - if stats.ignored > 0 { - context = append(context, []interface{}{"ignored", stats.ignored}...) + if res.ignored > 0 { + context = append(context, []interface{}{"ignored", res.ignored}...) } log.Info("Imported new block headers", context...) - - return 0, nil + return res.status, err } // GetBlockHashesFromHash retrieves a number of block hashes starting at a given diff --git a/core/headerchain_test.go b/core/headerchain_test.go new file mode 100644 index 0000000000..0aa25efd1f --- /dev/null +++ b/core/headerchain_test.go @@ -0,0 +1,115 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "errors" + "fmt" + "testing" + "time" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +func verifyUnbrokenCanonchain(hc *HeaderChain) error { + h := hc.CurrentHeader() + for { + canonHash := rawdb.ReadCanonicalHash(hc.chainDb, h.Number.Uint64()) + if exp := h.Hash(); canonHash != exp { + return fmt.Errorf("Canon hash chain broken, block %d got %x, expected %x", + h.Number, canonHash[:8], exp[:8]) + } + // Verify that we have the TD + if td := rawdb.ReadTd(hc.chainDb, canonHash, h.Number.Uint64()); td == nil { + return fmt.Errorf("Canon TD missing at block %d", h.Number) + } + if h.Number.Uint64() == 0 { + break + } + h = hc.GetHeader(h.ParentHash, h.Number.Uint64()-1) + } + return nil +} + +func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus WriteStatus, wantErr error) { + t.Helper() + + status, err := hc.InsertHeaderChain(chain, time.Now()) + if status != wantStatus { + t.Errorf("wrong write status from InsertHeaderChain: got %v, want %v", status, wantStatus) + } + // Always verify that the header chain is unbroken + if err := verifyUnbrokenCanonchain(hc); err != nil { + t.Fatal(err) + } + if !errors.Is(err, wantErr) { + t.Fatalf("unexpected error from InsertHeaderChain: %v", err) + } +} + +// This test checks status reporting of InsertHeaderChain. +func TestHeaderInsertion(t *testing.T) { + var ( + db = rawdb.NewMemoryDatabase() + genesis = new(Genesis).MustCommit(db) + ) + + hc, err := NewHeaderChain(db, params.AllEthashProtocolChanges, ethash.NewFaker(), func() bool { return false }) + if err != nil { + t.Fatal(err) + } + // chain A: G->A1->A2...A128 + chainA := makeHeaderChain(genesis.Header(), 128, ethash.NewFaker(), db, 10) + // chain B: G->A1->B2...B128 + chainB := makeHeaderChain(chainA[0], 128, ethash.NewFaker(), db, 10) + log.Root().SetHandler(log.StdoutHandler) + + // Inserting 64 headers on an empty chain, expecting + // 1 callbacks, 1 canon-status, 0 sidestatus, + testInsert(t, hc, chainA[:64], CanonStatTy, nil) + + // Inserting 64 identical headers, expecting + // 0 callbacks, 0 canon-status, 0 sidestatus, + testInsert(t, hc, chainA[:64], NonStatTy, nil) + + // Inserting the same some old, some new headers + // 1 callbacks, 1 canon, 0 side + testInsert(t, hc, chainA[32:96], CanonStatTy, nil) + + // Inserting side blocks, but not overtaking the canon chain + testInsert(t, hc, chainB[0:32], SideStatTy, nil) + + // Inserting more side blocks, but we don't have the parent + testInsert(t, hc, chainB[34:36], NonStatTy, consensus.ErrUnknownAncestor) + + // Inserting more sideblocks, overtaking the canon chain + testInsert(t, hc, chainB[32:97], CanonStatTy, nil) + + // Inserting more A-headers, taking back the canonicality + testInsert(t, hc, chainA[90:100], CanonStatTy, nil) + + // And B becomes canon again + testInsert(t, hc, chainB[97:107], CanonStatTy, nil) + + // And B becomes even longer + testInsert(t, hc, chainB[107:128], CanonStatTy, nil) +} diff --git a/light/lightchain.go b/light/lightchain.go index 6fc321ae0b..ca6fbfac49 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -396,24 +396,26 @@ func (lc *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i lc.wg.Add(1) defer lc.wg.Done() - var events []interface{} - whFunc := func(header *types.Header) error { - status, err := lc.hc.WriteHeader(header) - - switch status { - case core.CanonStatTy: - log.Debug("Inserted new header", "number", header.Number, "hash", header.Hash()) - events = append(events, core.ChainEvent{Block: types.NewBlockWithHeader(header), Hash: header.Hash()}) - - case core.SideStatTy: - log.Debug("Inserted forked header", "number", header.Number, "hash", header.Hash()) - events = append(events, core.ChainSideEvent{Block: types.NewBlockWithHeader(header)}) - } - return err + status, err := lc.hc.InsertHeaderChain(chain, start) + if err != nil || len(chain) == 0 { + return 0, err + } + + // Create chain event for the new head block of this insertion. + var ( + events = make([]interface{}, 0, 1) + lastHeader = chain[len(chain)-1] + block = types.NewBlockWithHeader(lastHeader) + ) + switch status { + case core.CanonStatTy: + events = append(events, core.ChainEvent{Block: block, Hash: block.Hash()}) + case core.SideStatTy: + events = append(events, core.ChainSideEvent{Block: block}) } - i, err := lc.hc.InsertHeaderChain(chain, whFunc, start) lc.postChainEvents(events) - return i, err + + return 0, err } // CurrentHeader retrieves the current head header of the canonical chain. The From 915643a3e5a487899d925250b922824fb3d65708 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 9 Dec 2020 13:59:24 +0100 Subject: [PATCH 051/235] cmd/geth: add test to verify regexps in version check (#21962) --- .../{data2.json => vulnerabilities.json} | 22 ++++--- cmd/geth/version_check_test.go | 63 +++++++++++++++++-- 2 files changed, 72 insertions(+), 13 deletions(-) rename cmd/geth/testdata/vcheck/{data2.json => vulnerabilities.json} (79%) diff --git a/cmd/geth/testdata/vcheck/data2.json b/cmd/geth/testdata/vcheck/vulnerabilities.json similarity index 79% rename from cmd/geth/testdata/vcheck/data2.json rename to cmd/geth/testdata/vcheck/vulnerabilities.json index 75a31677a1..36509f95a9 100644 --- a/cmd/geth/testdata/vcheck/data2.json +++ b/cmd/geth/testdata/vcheck/vulnerabilities.json @@ -7,28 +7,32 @@ "links": [ "https://github.com/ethereum/go-ethereum/pull/21793", "https://blog.ethereum.org/2020/11/12/geth_security_release/", - "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49" + "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-v592-xf75-856p" ], "introduced": "v1.6.0", "fixed": "v1.9.24", "published": "2020-11-12", "severity": "Medium", - "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.2(1|2|3)-.*", - "CVE": "correct" + "CVE": "CVE-2020-26240", + "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.\\d-.*|Geth\\/v1\\.9\\.1.*|Geth\\/v1\\.9\\.2(0|1|2|3)-.*" }, { - "name": "GoCrash", + "name": "Denial of service due to Go CVE-2020-28362", "uid": "GETH-2020-02", "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`", "description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.", "links": [ "https://blog.ethereum.org/2020/11/12/geth_security_release/", "https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM", - "https://github.com/golang/go/issues/42552" + "https://github.com/golang/go/issues/42552", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-m6gx-rhvj-fh52" ], + "introduced": "v0.0.0", "fixed": "v1.9.24", "published": "2020-11-12", "severity": "Critical", + "CVE": "CVE-2020-28362", "check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$" }, { @@ -37,12 +41,14 @@ "summary": "A consensus flaw in Geth, related to `datacopy` precompile", "description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.", "links": [ - "https://blog.ethereum.org/2020/11/12/geth_security_release/" + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-69v6-xc2j-r2jf" ], "introduced": "v1.9.7", "fixed": "v1.9.17", "published": "2020-11-12", "severity": "Critical", + "CVE": "CVE-2020-26241", "check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$" }, { @@ -51,12 +57,14 @@ "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing", "description": "Full details to be disclosed at a later date", "links": [ - "https://blog.ethereum.org/2020/11/12/geth_security_release/" + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-jm5c-rv3w-w83m" ], "introduced": "v1.9.16", "fixed": "v1.9.18", "published": "2020-11-12", "severity": "Critical", + "CVE": "CVE-2020-26242", "check": "Geth\\/v1\\.9.(16|17).*$" } ] diff --git a/cmd/geth/version_check_test.go b/cmd/geth/version_check_test.go index 4afe8ddc83..0f056d1967 100644 --- a/cmd/geth/version_check_test.go +++ b/cmd/geth/version_check_test.go @@ -18,8 +18,12 @@ package main import ( "encoding/json" + "fmt" "io/ioutil" "path/filepath" + "regexp" + "strconv" + "strings" "testing" ) @@ -64,16 +68,63 @@ func testVerification(t *testing.T, pubkey, sigdir string) { } } -func TestJson(t *testing.T) { - data, _ := ioutil.ReadFile("./testdata/vcheck/data2.json") +func versionUint(v string) int { + mustInt := func(s string) int { + a, err := strconv.Atoi(s) + if err != nil { + panic(v) + } + return a + } + components := strings.Split(strings.TrimPrefix(v, "v"), ".") + a := mustInt(components[0]) + b := mustInt(components[1]) + c := mustInt(components[2]) + return a*100*100 + b*100 + c +} + +// TestMatching can be used to check that the regexps are correct +func TestMatching(t *testing.T) { + data, _ := ioutil.ReadFile("./testdata/vcheck/vulnerabilities.json") var vulns []vulnJson if err := json.Unmarshal(data, &vulns); err != nil { t.Fatal(err) } - if len(vulns) == 0 { - t.Fatal("expected data, got none") + check := func(version string) { + vFull := fmt.Sprintf("Geth/%v-unstable-15339cf1-20201204/linux-amd64/go1.15.4", version) + for _, vuln := range vulns { + r, err := regexp.Compile(vuln.Check) + vulnIntro := versionUint(vuln.Introduced) + vulnFixed := versionUint(vuln.Fixed) + current := versionUint(version) + if err != nil { + t.Fatal(err) + } + if vuln.Name == "Denial of service due to Go CVE-2020-28362" { + // this one is not tied to geth-versions + continue + } + if vulnIntro <= current && vulnFixed > current { + // Should be vulnerable + if !r.MatchString(vFull) { + t.Errorf("Should be vulnerable, version %v, intro: %v, fixed: %v %v %v", + version, vuln.Introduced, vuln.Fixed, vuln.Name, vuln.Check) + } + } else { + if r.MatchString(vFull) { + t.Errorf("Should not be flagged vulnerable, version %v, intro: %v, fixed: %v %v %d %d %d", + version, vuln.Introduced, vuln.Fixed, vuln.Name, vulnIntro, current, vulnFixed) + } + } + + } } - if have, want := vulns[0].CVE, "correct"; have != want { - t.Errorf("have %v, want %v", have, want) + for major := 1; major < 2; major++ { + for minor := 0; minor < 30; minor++ { + for patch := 0; patch < 30; patch++ { + vShort := fmt.Sprintf("v%d.%d.%d", major, minor, patch) + check(vShort) + } + } } } From f935b1d5426b7c4147914b9f120b07f1f983f0ef Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 9 Dec 2020 15:43:36 +0100 Subject: [PATCH 052/235] crypto/signify, build: fix archive signing with signify (#21977) This fixes some issues in crypto/signify and makes release signing work. The archive signing step in ci.go used getenvBase64, which decodes the key data. This is incorrect here because crypto/signify already base64-decodes the key. --- .travis.yml | 26 ++++----- build/ci.go | 14 +++-- crypto/signify/signify.go | 100 ++++++++++++++------------------- crypto/signify/signify_test.go | 8 +-- 4 files changed, 66 insertions(+), 82 deletions(-) diff --git a/.travis.yml b/.travis.yml index d37458792a..1268c6d657 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,22 +67,22 @@ jobs: script: # Build for the primary platforms that Trusty can manage - go run build/ci.go install -dlgo - - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go install -dlgo -arch 386 - - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # Switch over GCC to cross compilation (breaks 386, hence why do it here only) - sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross - sudo ln -s /usr/include/asm-generic /usr/include/asm - GOARM=5 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc - - GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - GOARM=6 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc - - GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - GOARM=7 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabihf-gcc - - GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc - - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # This builder does the Linux Azure MIPS xgo uploads - stage: build @@ -100,19 +100,19 @@ jobs: script: - go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done - - go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mipsle --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mipsle; do mv -f "${bin}" "${bin/-linux-mipsle/}"; done - - go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mips64 --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips64; do mv -f "${bin}" "${bin/-linux-mips64/}"; done - - go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mips64le --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips64le; do mv -f "${bin}" "${bin/-linux-mips64le/}"; done - - go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # This builder does the Android Maven and Azure uploads - stage: build @@ -151,7 +151,7 @@ jobs: - mkdir -p $GOPATH/src/github.com/ethereum - ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum - - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -signify ANDROID_SIGNIFY_KEY -deploy https://oss.sonatype.org -upload gethstore/builds + - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -signify SIGNIFY_KEY -deploy https://oss.sonatype.org -upload gethstore/builds # This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads - stage: build @@ -167,7 +167,7 @@ jobs: submodules: false # avoid cloning ethereum/tests script: - go run build/ci.go install -dlgo - - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify OSX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # Build the iOS framework and upload it to CocoaPods and Azure - gem uninstall cocoapods -a -x @@ -182,7 +182,7 @@ jobs: # Workaround for https://github.com/golang/go/issues/23749 - export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc' - - go run build/ci.go xcode -signer IOS_SIGNING_KEY -signify IOS_SIGNIFY_KEY -deploy trunk -upload gethstore/builds + - go run build/ci.go xcode -signer IOS_SIGNING_KEY -signify SIGNIFY_KEY -deploy trunk -upload gethstore/builds # These builders run the tests - stage: build diff --git a/build/ci.go b/build/ci.go index 951b21f910..15946c257e 100644 --- a/build/ci.go +++ b/build/ci.go @@ -58,7 +58,7 @@ import ( "time" "github.com/cespare/cp" - signifyPkg "github.com/ethereum/go-ethereum/crypto/signify" + "github.com/ethereum/go-ethereum/crypto/signify" "github.com/ethereum/go-ethereum/internal/build" "github.com/ethereum/go-ethereum/params" ) @@ -449,7 +449,7 @@ func archiveBasename(arch string, archiveVersion string) string { return platform + "-" + archiveVersion } -func archiveUpload(archive string, blobstore string, signer string, signify string) error { +func archiveUpload(archive string, blobstore string, signer string, signifyVar string) error { // If signing was requested, generate the signature files if signer != "" { key := getenvBase64(signer) @@ -457,9 +457,11 @@ func archiveUpload(archive string, blobstore string, signer string, signify stri return err } } - if signify != "" { - key := getenvBase64(string(signify)) - if err := signifyPkg.SignifySignFile(archive, archive+".sig", string(key), "verify with geth.pub", fmt.Sprintf("%d", time.Now().UTC().Unix())); err != nil { + if signifyVar != "" { + key := os.Getenv(signifyVar) + untrustedComment := "verify with geth-release.pub" + trustedComment := fmt.Sprintf("%s (%s)", archive, time.Now().UTC().Format(time.RFC1123)) + if err := signify.SignFile(archive, archive+".sig", key, untrustedComment, trustedComment); err != nil { return err } } @@ -478,7 +480,7 @@ func archiveUpload(archive string, blobstore string, signer string, signify stri return err } } - if signify != "" { + if signifyVar != "" { if err := build.AzureBlobstoreUpload(archive+".sig", filepath.Base(archive+".sig"), auth); err != nil { return err } diff --git a/crypto/signify/signify.go b/crypto/signify/signify.go index e86c4f09b0..7ba9705491 100644 --- a/crypto/signify/signify.go +++ b/crypto/signify/signify.go @@ -20,99 +20,81 @@ package signify import ( + "bytes" + "crypto/ed25519" "encoding/base64" "errors" "fmt" "io/ioutil" - "os" "strings" "time" - - "crypto/ed25519" ) var ( - errInvalidKeyHeader = errors.New("Incorrect key header") + errInvalidKeyHeader = errors.New("incorrect key header") errInvalidKeyLength = errors.New("invalid, key length != 104") ) -func parsePrivateKey(key string) (ed25519.PrivateKey, []byte, []byte, error) { +func parsePrivateKey(key string) (k ed25519.PrivateKey, header []byte, keyNum []byte, err error) { keydata, err := base64.StdEncoding.DecodeString(key) if err != nil { return nil, nil, nil, err } - if len(keydata) != 104 { return nil, nil, nil, errInvalidKeyLength } - if string(keydata[:2]) != "Ed" { return nil, nil, nil, errInvalidKeyHeader } - return ed25519.PrivateKey(keydata[40:]), keydata[:2], keydata[32:40], nil } -func commentHasManyLines(comment string) bool { - firstLFIndex := strings.IndexByte(comment, 10) - return (firstLFIndex >= 0 && firstLFIndex < len(comment)-1) -} - -// SignifySignFile creates a signature of the input file. -func SignifySignFile(input string, output string, key string, unTrustedComment string, trustedComment string) error { - in, err := os.Open(input) - if err != nil { - return err +// SignFile creates a signature of the input file. +// +// This accepts base64 keys in the format created by the 'signify' tool. +// The signature is written to the 'output' file. +func SignFile(input string, output string, key string, untrustedComment string, trustedComment string) error { + // Pre-check comments and ensure they're set to something. + if strings.IndexByte(untrustedComment, '\n') >= 0 { + return errors.New("untrusted comment must not contain newline") } - defer in.Close() - - out, err := os.Create(output) - if err != nil { - return err + if strings.IndexByte(trustedComment, '\n') >= 0 { + return errors.New("trusted comment must not contain newline") + } + if untrustedComment == "" { + untrustedComment = "verify with " + input + ".pub" + } + if trustedComment == "" { + trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix()) } - defer out.Close() - skey, header, keyNum, err := parsePrivateKey(key) + filedata, err := ioutil.ReadFile(input) if err != nil { return err } - - filedata, err := ioutil.ReadAll(in) + skey, header, keyNum, err := parsePrivateKey(key) if err != nil { return err } + // Create the main data signature. rawSig := ed25519.Sign(skey, filedata) - - var sigdata []byte - sigdata = append(sigdata, header...) - sigdata = append(sigdata, keyNum...) - sigdata = append(sigdata, rawSig...) - - // Check that the trusted comment fits in one line - if commentHasManyLines(unTrustedComment) { - return errors.New("untrusted comment must fit on a single line") - } - - if unTrustedComment == "" { - unTrustedComment = "verify with " + input + ".pub" - } - out.WriteString(fmt.Sprintf("untrusted comment: %s\n%s\n", unTrustedComment, base64.StdEncoding.EncodeToString(sigdata))) - - // Add the trusted comment if unavailable - if trustedComment == "" { - trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix()) - } - - // Check that the trusted comment fits in one line - if commentHasManyLines(trustedComment) { - return errors.New("trusted comment must fit on a single line") - } - - var sigAndComment []byte - sigAndComment = append(sigAndComment, rawSig...) - sigAndComment = append(sigAndComment, []byte(trustedComment)...) - out.WriteString(fmt.Sprintf("trusted comment: %s\n%s\n", trustedComment, base64.StdEncoding.EncodeToString(ed25519.Sign(skey, sigAndComment)))) - - return nil + var dataSig []byte + dataSig = append(dataSig, header...) + dataSig = append(dataSig, keyNum...) + dataSig = append(dataSig, rawSig...) + + // Create the comment signature. + var commentSigInput []byte + commentSigInput = append(commentSigInput, rawSig...) + commentSigInput = append(commentSigInput, []byte(trustedComment)...) + commentSig := ed25519.Sign(skey, commentSigInput) + + // Create the output file. + var out = new(bytes.Buffer) + fmt.Fprintln(out, "untrusted comment:", untrustedComment) + fmt.Fprintln(out, base64.StdEncoding.EncodeToString(dataSig)) + fmt.Fprintln(out, "trusted comment:", trustedComment) + fmt.Fprintln(out, base64.StdEncoding.EncodeToString(commentSig)) + return ioutil.WriteFile(output, out.Bytes(), 0644) } diff --git a/crypto/signify/signify_test.go b/crypto/signify/signify_test.go index af77eaf227..615d4e6527 100644 --- a/crypto/signify/signify_test.go +++ b/crypto/signify/signify_test.go @@ -52,7 +52,7 @@ func TestSignify(t *testing.T) { t.Fatal(err) } - err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "clé", "croissants") + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "clé", "croissants") if err != nil { t.Fatal(err) } @@ -96,7 +96,7 @@ func TestSignifyTrustedCommentTooManyLines(t *testing.T) { t.Fatal(err) } - err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "crois\nsants") + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "crois\nsants") if err == nil || err.Error() == "" { t.Fatalf("should have errored on a multi-line trusted comment, got %v", err) } @@ -121,7 +121,7 @@ func TestSignifyTrustedCommentTooManyLinesLF(t *testing.T) { t.Fatal(err) } - err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "crois\rsants", "") + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "crois\rsants", "") if err != nil { t.Fatal(err) } @@ -146,7 +146,7 @@ func TestSignifyTrustedCommentEmpty(t *testing.T) { t.Fatal(err) } - err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "") + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "") if err != nil { t.Fatal(err) } From 817a3fb5622c8704116e9847661c16f9f3d785c6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 9 Dec 2020 20:21:31 +0100 Subject: [PATCH 053/235] p2p/enode: avoid crashing for invalid IP (#21981) The database panicked for invalid IPs. This is usually no problem because all code paths leading to node DB access verify the IP, but it's dangerous because improper validation can turn this panic into a DoS vulnerability. The quick fix here is to just turn database accesses using invalid IP into a noop. This isn't great, but I'm planning to remove the node DB for discv5 long-term, so it should be fine to have this quick fix for half a year. Fixes #21849 --- p2p/enode/nodedb.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index 643fbe5711..d62f383f0b 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -61,6 +61,10 @@ const ( dbVersion = 9 ) +var ( + errInvalidIP = errors.New("invalid IP") +) + var zeroIP = make(net.IP, 16) // DB is the node database, storing previously seen nodes and any collected metadata about @@ -359,16 +363,25 @@ func (db *DB) expireNodes() { // LastPingReceived retrieves the time of the last ping packet received from // a remote node. func (db *DB) LastPingReceived(id ID, ip net.IP) time.Time { + if ip = ip.To16(); ip == nil { + return time.Time{} + } return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePing)), 0) } // UpdateLastPingReceived updates the last time we tried contacting a remote node. func (db *DB) UpdateLastPingReceived(id ID, ip net.IP, instance time.Time) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } return db.storeInt64(nodeItemKey(id, ip, dbNodePing), instance.Unix()) } // LastPongReceived retrieves the time of the last successful pong from remote node. func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time { + if ip = ip.To16(); ip == nil { + return time.Time{} + } // Launch expirer db.ensureExpirer() return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePong)), 0) @@ -376,26 +389,41 @@ func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time { // UpdateLastPongReceived updates the last pong time of a node. func (db *DB) UpdateLastPongReceived(id ID, ip net.IP, instance time.Time) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } return db.storeInt64(nodeItemKey(id, ip, dbNodePong), instance.Unix()) } // FindFails retrieves the number of findnode failures since bonding. func (db *DB) FindFails(id ID, ip net.IP) int { + if ip = ip.To16(); ip == nil { + return 0 + } return int(db.fetchInt64(nodeItemKey(id, ip, dbNodeFindFails))) } // UpdateFindFails updates the number of findnode failures since bonding. func (db *DB) UpdateFindFails(id ID, ip net.IP, fails int) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } return db.storeInt64(nodeItemKey(id, ip, dbNodeFindFails), int64(fails)) } // FindFailsV5 retrieves the discv5 findnode failure counter. func (db *DB) FindFailsV5(id ID, ip net.IP) int { + if ip = ip.To16(); ip == nil { + return 0 + } return int(db.fetchInt64(v5Key(id, ip, dbNodeFindFails))) } // UpdateFindFailsV5 stores the discv5 findnode failure counter. func (db *DB) UpdateFindFailsV5(id ID, ip net.IP, fails int) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } return db.storeInt64(v5Key(id, ip, dbNodeFindFails), int64(fails)) } From 9f6bb492bbcd6def92d0bc195faeb751e1591535 Mon Sep 17 00:00:00 2001 From: gary rong Date: Thu, 10 Dec 2020 21:33:52 +0800 Subject: [PATCH 054/235] les, light: remove untrusted header retrieval in ODR (#21907) * les, light: remove untrusted header retrieval in ODR * les: polish * light: check the hash equality in odr --- les/client_handler.go | 62 +++++++++++++++++++++++++++++++++++++------ les/odr.go | 22 ++++++++------- les/odr_requests.go | 52 +++++++++++++++--------------------- les/retrieve.go | 9 +++++++ les/sync.go | 6 ++--- les/sync_test.go | 2 +- light/odr.go | 11 +++----- light/odr_util.go | 39 +++++++++++---------------- light/trie.go | 4 +++ 9 files changed, 125 insertions(+), 82 deletions(-) diff --git a/les/client_handler.go b/les/client_handler.go index 77a0ea5c6f..d7ca1c54f5 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -17,6 +17,7 @@ package les import ( + "context" "math/big" "sync" "sync/atomic" @@ -200,14 +201,23 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { p.fcServer.ReceivedReply(resp.ReqID, resp.BV) p.answeredRequest(resp.ReqID) - // Filter out any explicitly requested headers, deliver the rest to the downloader - filter := len(headers) == 1 - if filter { - headers = h.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers) - } - if len(headers) != 0 || !filter { - if err := h.downloader.DeliverHeaders(p.id, headers); err != nil { - log.Debug("Failed to deliver headers", "err", err) + // Filter out the explicitly requested header by the retriever + if h.backend.retriever.requested(resp.ReqID) { + deliverMsg = &Msg{ + MsgType: MsgBlockHeaders, + ReqID: resp.ReqID, + Obj: resp.Headers, + } + } else { + // Filter out any explicitly requested headers, deliver the rest to the downloader + filter := len(headers) == 1 + if filter { + headers = h.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers) + } + if len(headers) != 0 || !filter { + if err := h.downloader.DeliverHeaders(p.id, headers); err != nil { + log.Debug("Failed to deliver headers", "err", err) + } } } case BlockBodiesMsg: @@ -394,6 +404,42 @@ func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip return nil } +// RetrieveSingleHeaderByNumber requests a single header by the specified block +// number. This function will wait the response until it's timeout or delivered. +func (pc *peerConnection) RetrieveSingleHeaderByNumber(context context.Context, number uint64) (*types.Header, error) { + reqID := genReqID() + rq := &distReq{ + getCost: func(dp distPeer) uint64 { + peer := dp.(*serverPeer) + return peer.getRequestCost(GetBlockHeadersMsg, 1) + }, + canSend: func(dp distPeer) bool { + return dp.(*serverPeer) == pc.peer + }, + request: func(dp distPeer) func() { + peer := dp.(*serverPeer) + cost := peer.getRequestCost(GetBlockHeadersMsg, 1) + peer.fcServer.QueuedRequest(reqID, cost) + return func() { peer.requestHeadersByNumber(reqID, number, 1, 0, false) } + }, + } + var header *types.Header + if err := pc.handler.backend.retriever.retrieve(context, reqID, rq, func(peer distPeer, msg *Msg) error { + if msg.MsgType != MsgBlockHeaders { + return errInvalidMessageType + } + headers := msg.Obj.([]*types.Header) + if len(headers) != 1 { + return errInvalidEntryCount + } + header = headers[0] + return nil + }, nil); err != nil { + return nil, err + } + return header, nil +} + // downloaderPeerNotify implements peerSetNotify type downloaderPeerNotify clientHandler diff --git a/les/odr.go b/les/odr.go index f8469cc103..2c36f512de 100644 --- a/les/odr.go +++ b/les/odr.go @@ -24,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/light" - "github.com/ethereum/go-ethereum/log" ) // LesOdr implements light.OdrBackend @@ -83,7 +82,8 @@ func (odr *LesOdr) IndexerConfig() *light.IndexerConfig { } const ( - MsgBlockBodies = iota + MsgBlockHeaders = iota + MsgBlockBodies MsgCode MsgReceipts MsgProofsV2 @@ -122,13 +122,17 @@ func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err erro return func() { lreq.Request(reqID, p) } }, } - sent := mclock.Now() - if err = odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err == nil { - // retrieved from network, store in db - req.StoreResult(odr.db) + + defer func(sent mclock.AbsTime) { + if err != nil { + return + } requestRTT.Update(time.Duration(mclock.Now() - sent)) - } else { - log.Debug("Failed to retrieve data from network", "err", err) + }(mclock.Now()) + + if err := odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err != nil { + return err } - return + req.StoreResult(odr.db) + return nil } diff --git a/les/odr_requests.go b/les/odr_requests.go index 3704436a03..eb1d3602e0 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -327,9 +327,6 @@ func (r *ChtRequest) CanSend(peer *serverPeer) bool { peer.lock.RLock() defer peer.lock.RUnlock() - if r.Untrusted { - return peer.headInfo.Number >= r.BlockNum && peer.id == r.PeerId - } return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize } @@ -369,39 +366,34 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error { if err := rlp.DecodeBytes(headerEnc, header); err != nil { return errHeaderUnavailable } - // Verify the CHT - // Note: For untrusted CHT request, there is no proof response but - // header data. - var node light.ChtNode - if !r.Untrusted { - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) - - reads := &readTraceDB{db: nodeSet} - value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads) - if err != nil { - return fmt.Errorf("merkle proof verification failed: %v", err) - } - if len(reads.reads) != nodeSet.KeyCount() { - return errUselessNodes - } + var ( + node light.ChtNode + encNumber [8]byte + ) + binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) - if err := rlp.DecodeBytes(value, &node); err != nil { - return err - } - if node.Hash != header.Hash() { - return errCHTHashMismatch - } - if r.BlockNum != header.Number.Uint64() { - return errCHTNumberMismatch - } + reads := &readTraceDB{db: nodeSet} + value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads) + if err != nil { + return fmt.Errorf("merkle proof verification failed: %v", err) + } + if len(reads.reads) != nodeSet.KeyCount() { + return errUselessNodes + } + if err := rlp.DecodeBytes(value, &node); err != nil { + return err + } + if node.Hash != header.Hash() { + return errCHTHashMismatch + } + if r.BlockNum != header.Number.Uint64() { + return errCHTNumberMismatch } // Verifications passed, store and return r.Header = header r.Proof = nodeSet - r.Td = node.Td // For untrusted request, td here is nil, todo improve the les/2 protocol - + r.Td = node.Td return nil } diff --git a/les/retrieve.go b/les/retrieve.go index 4f77004f20..ca4f867ea8 100644 --- a/les/retrieve.go +++ b/les/retrieve.go @@ -155,6 +155,15 @@ func (rm *retrieveManager) sendReq(reqID uint64, req *distReq, val validatorFunc return r } +// requested reports whether the request with given reqid is sent by the retriever. +func (rm *retrieveManager) requested(reqId uint64) bool { + rm.lock.RLock() + defer rm.lock.RUnlock() + + _, ok := rm.sentReqs[reqId] + return ok +} + // deliver is called by the LES protocol manager to deliver reply messages to waiting requests func (rm *retrieveManager) deliver(peer distPeer, msg *Msg) error { rm.lock.RLock() diff --git a/les/sync.go b/les/sync.go index d2568d45bc..ad3a0e0f3c 100644 --- a/les/sync.go +++ b/les/sync.go @@ -56,8 +56,8 @@ func (h *clientHandler) validateCheckpoint(peer *serverPeer) error { defer cancel() // Fetch the block header corresponding to the checkpoint registration. - cp := peer.checkpoint - header, err := light.GetUntrustedHeaderByNumber(ctx, h.backend.odr, peer.checkpointNumber, peer.id) + wrapPeer := &peerConnection{handler: h, peer: peer} + header, err := wrapPeer.RetrieveSingleHeaderByNumber(ctx, peer.checkpointNumber) if err != nil { return err } @@ -66,7 +66,7 @@ func (h *clientHandler) validateCheckpoint(peer *serverPeer) error { if err != nil { return err } - events := h.backend.oracle.Contract().LookupCheckpointEvents(logs, cp.SectionIndex, cp.Hash()) + events := h.backend.oracle.Contract().LookupCheckpointEvents(logs, peer.checkpoint.SectionIndex, peer.checkpoint.Hash()) if len(events) == 0 { return errInvalidCheckpoint } diff --git a/les/sync_test.go b/les/sync_test.go index 6924e7b43a..2eb0f88bf9 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -53,7 +53,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { time.Sleep(10 * time.Millisecond) } } - // Generate 512+4 blocks (totally 1 CHT sections) + // Generate 128+1 blocks (totally 1 CHT sections) server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, 0, false, false, true) defer tearDown() diff --git a/light/odr.go b/light/odr.go index 7016ef8ef2..bb243f9152 100644 --- a/light/odr.go +++ b/light/odr.go @@ -135,8 +135,6 @@ func (req *ReceiptsRequest) StoreResult(db ethdb.Database) { // ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie type ChtRequest struct { - Untrusted bool // Indicator whether the result retrieved is trusted or not - PeerId string // The specified peer id from which to retrieve data. Config *IndexerConfig ChtNum, BlockNum uint64 ChtRoot common.Hash @@ -148,12 +146,9 @@ type ChtRequest struct { // StoreResult stores the retrieved data in local database func (req *ChtRequest) StoreResult(db ethdb.Database) { hash, num := req.Header.Hash(), req.Header.Number.Uint64() - - if !req.Untrusted { - rawdb.WriteHeader(db, req.Header) - rawdb.WriteTd(db, hash, num, req.Td) - rawdb.WriteCanonicalHash(db, hash, num) - } + rawdb.WriteHeader(db, req.Header) + rawdb.WriteTd(db, hash, num, req.Td) + rawdb.WriteCanonicalHash(db, hash, num) } // BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure diff --git a/light/odr_util.go b/light/odr_util.go index aec0c7b69f..ec2d1e6491 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -19,20 +19,23 @@ package light import ( "bytes" "context" + "errors" "math/big" "github.com/ethereum/go-ethereum/common" "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/crypto" "github.com/ethereum/go-ethereum/rlp" ) -var sha3Nil = crypto.Keccak256Hash(nil) +// errNonCanonicalHash is returned if the requested chain data doesn't belong +// to the canonical chain. ODR can only retrieve the canonical chain data covered +// by the CHT or Bloom trie for verification. +var errNonCanonicalHash = errors.New("hash is not currently canonical") // GetHeaderByNumber retrieves the canonical block header corresponding to the -// given number. +// given number. The returned header is proven by local CHT. func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) { // Try to find it in the local database first. db := odr.Database() @@ -63,25 +66,6 @@ func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*typ return r.Header, nil } -// GetUntrustedHeaderByNumber retrieves specified block header without -// correctness checking. Note this function should only be used in light -// client checkpoint syncing. -func GetUntrustedHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64, peerId string) (*types.Header, error) { - // todo(rjl493456442) it's a hack to retrieve headers which is not covered - // by CHT. Fix it in LES4 - r := &ChtRequest{ - BlockNum: number, - ChtNum: number / odr.IndexerConfig().ChtSize, - Untrusted: true, - PeerId: peerId, - Config: odr.IndexerConfig(), - } - if err := odr.Retrieve(ctx, r); err != nil { - return nil, err - } - return r.Header, nil -} - // GetCanonicalHash retrieves the canonical block hash corresponding to the number. func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) { hash := rawdb.ReadCanonicalHash(odr.Database(), number) @@ -102,10 +86,13 @@ func GetTd(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) if td != nil { return td, nil } - _, err := GetHeaderByNumber(ctx, odr, number) + header, err := GetHeaderByNumber(ctx, odr, number) if err != nil { return nil, err } + if header.Hash() != hash { + return nil, errNonCanonicalHash + } // -> td mapping already be stored in db, get it. return rawdb.ReadTd(odr.Database(), hash, number), nil } @@ -120,6 +107,9 @@ func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number ui if err != nil { return nil, errNoHeader } + if header.Hash() != hash { + return nil, errNonCanonicalHash + } r := &BlockRequest{Hash: hash, Number: number, Header: header} if err := odr.Retrieve(ctx, r); err != nil { return nil, err @@ -167,6 +157,9 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num if err != nil { return nil, errNoHeader } + if header.Hash() != hash { + return nil, errNonCanonicalHash + } r := &ReceiptsRequest{Hash: hash, Number: number, Header: header} if err := odr.Retrieve(ctx, r); err != nil { return nil, err diff --git a/light/trie.go b/light/trie.go index 3eb05f4a3f..0516b94486 100644 --- a/light/trie.go +++ b/light/trie.go @@ -30,6 +30,10 @@ import ( "github.com/ethereum/go-ethereum/trie" ) +var ( + sha3Nil = crypto.Keccak256Hash(nil) +) + func NewState(ctx context.Context, head *types.Header, odr OdrBackend) *state.StateDB { state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr), nil) return state From b44f24e3e6644a2034ce23037827b21aa59e441d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 10 Dec 2020 14:48:32 +0100 Subject: [PATCH 055/235] core, trie: speed up some tests with quadratic processing flaw (#21987) This commit fixes a flaw in two testcases, and brings down the exec-time from ~40s to ~8s for trie/TestIncompleteSync. The checkConsistency was performed over and over again on the complete set of nodes, not just the recently added, turning it into a quadratic runtime. --- core/state/sync_test.go | 30 ++++++++++++++---------------- trie/sync_test.go | 7 ++----- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/core/state/sync_test.go b/core/state/sync_test.go index deb4b52b4c..9c4867093d 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -62,7 +62,8 @@ func makeTestState() (Database, common.Hash, []*testAccount) { } if i%5 == 0 { for j := byte(0); j < 5; j++ { - obj.SetState(db, crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j}), crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j})) + hash := crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j}) + obj.SetState(db, hash, hash) } } state.updateStateObject(obj) @@ -401,15 +402,14 @@ func TestIncompleteStateSync(t *testing.T) { // Create a random state to copy srcDb, srcRoot, srcAccounts := makeTestState() - // isCode reports whether the hash is contract code hash. - isCode := func(hash common.Hash) bool { - for _, acc := range srcAccounts { - if hash == crypto.Keccak256Hash(acc.code) { - return true - } + // isCodeLookup to save some hashing + var isCode = make(map[common.Hash]struct{}) + for _, acc := range srcAccounts { + if len(acc.code) > 0 { + isCode[crypto.Keccak256Hash(acc.code)] = struct{}{} } - return false } + isCode[common.BytesToHash(emptyCodeHash)] = struct{}{} checkTrieConsistency(srcDb.TrieDB().DiskDB().(ethdb.Database), srcRoot) // Create a destination state and sync with the scheduler @@ -447,15 +447,13 @@ func TestIncompleteStateSync(t *testing.T) { batch.Write() for _, result := range results { added = append(added, result.Hash) - } - // Check that all known sub-tries added so far are complete or missing entirely. - for _, hash := range added { - if isCode(hash) { + // Check that all known sub-tries added so far are complete or missing entirely. + if _, ok := isCode[result.Hash]; ok { continue } // Can't use checkStateConsistency here because subtrie keys may have odd // length and crash in LeafKey. - if err := checkTrieConsistency(dstDb, hash); err != nil { + if err := checkTrieConsistency(dstDb, result.Hash); err != nil { t.Fatalf("state inconsistent: %v", err) } } @@ -466,9 +464,9 @@ func TestIncompleteStateSync(t *testing.T) { // Sanity check that removing any node from the database is detected for _, node := range added[1:] { var ( - key = node.Bytes() - code = isCode(node) - val []byte + key = node.Bytes() + _, code = isCode[node] + val []byte ) if code { val = rawdb.ReadCode(dstDb, node) diff --git a/trie/sync_test.go b/trie/sync_test.go index 39e0f9575e..cb3283875d 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -377,7 +377,6 @@ func TestIncompleteSync(t *testing.T) { nodes, _, codes := sched.Missing(1) queue := append(append([]common.Hash{}, nodes...), codes...) - for len(queue) > 0 { // Fetch a batch of trie nodes results := make([]SyncResult, len(queue)) @@ -401,10 +400,8 @@ func TestIncompleteSync(t *testing.T) { batch.Write() for _, result := range results { added = append(added, result.Hash) - } - // Check that all known sub-tries in the synced trie are complete - for _, root := range added { - if err := checkTrieConsistency(triedb, root); err != nil { + // Check that all known sub-tries in the synced trie are complete + if err := checkTrieConsistency(triedb, result.Hash); err != nil { t.Fatalf("trie inconsistent: %v", err) } } From 004541098d28ed2d76ac4aa524b0e3fa80140f97 Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 11 Dec 2020 00:20:55 +0800 Subject: [PATCH 056/235] les: introduce forkID (#21974) * les: introduce forkID * les: address comment --- les/client_handler.go | 28 ++++++++++++++++------------ les/peer.go | 27 ++++++++++++++++++++++----- les/peer_test.go | 23 +++++++++++++++++++++-- les/protocol.go | 5 ++++- les/server_handler.go | 6 +++++- 5 files changed, 68 insertions(+), 21 deletions(-) diff --git a/les/client_handler.go b/les/client_handler.go index d7ca1c54f5..6de5766961 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/light" @@ -37,6 +38,7 @@ import ( // responses. type clientHandler struct { ulc *ulc + forkFilter forkid.Filter checkpoint *params.TrustedCheckpoint fetcher *lightFetcher downloader *downloader.Downloader @@ -49,6 +51,7 @@ type clientHandler struct { func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.TrustedCheckpoint, backend *LightEthereum) *clientHandler { handler := &clientHandler{ + forkFilter: forkid.NewFilter(backend.blockchain), checkpoint: checkpoint, backend: backend, closeCh: make(chan struct{}), @@ -103,7 +106,8 @@ func (h *clientHandler) handle(p *serverPeer) error { p.Log().Debug("Light Ethereum peer connected", "name", p.Name()) // Execute the LES handshake - if err := p.Handshake(h.backend.blockchain.Genesis().Hash()); err != nil { + forkid := forkid.NewID(h.backend.blockchain.Config(), h.backend.genesis, h.backend.blockchain.CurrentHeader().Number.Uint64()) + if err := p.Handshake(h.backend.blockchain.Genesis().Hash(), forkid, h.forkFilter); err != nil { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err } @@ -154,8 +158,8 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { var deliverMsg *Msg // Handle the message depending on its contents - switch msg.Code { - case AnnounceMsg: + switch { + case msg.Code == AnnounceMsg: p.Log().Trace("Received announce message") var req announceData if err := msg.Decode(&req); err != nil { @@ -188,7 +192,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { p.updateHead(req.Hash, req.Number, req.Td) h.fetcher.announce(p, &req) } - case BlockHeadersMsg: + case msg.Code == BlockHeadersMsg: p.Log().Trace("Received block header response message") var resp struct { ReqID, BV uint64 @@ -220,7 +224,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { } } } - case BlockBodiesMsg: + case msg.Code == BlockBodiesMsg: p.Log().Trace("Received block bodies response") var resp struct { ReqID, BV uint64 @@ -236,7 +240,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Data, } - case CodeMsg: + case msg.Code == CodeMsg: p.Log().Trace("Received code response") var resp struct { ReqID, BV uint64 @@ -252,7 +256,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Data, } - case ReceiptsMsg: + case msg.Code == ReceiptsMsg: p.Log().Trace("Received receipts response") var resp struct { ReqID, BV uint64 @@ -268,7 +272,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Receipts, } - case ProofsV2Msg: + case msg.Code == ProofsV2Msg: p.Log().Trace("Received les/2 proofs response") var resp struct { ReqID, BV uint64 @@ -284,7 +288,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Data, } - case HelperTrieProofsMsg: + case msg.Code == HelperTrieProofsMsg: p.Log().Trace("Received helper trie proof response") var resp struct { ReqID, BV uint64 @@ -300,7 +304,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Data, } - case TxStatusMsg: + case msg.Code == TxStatusMsg: p.Log().Trace("Received tx status response") var resp struct { ReqID, BV uint64 @@ -316,11 +320,11 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Status, } - case StopMsg: + case msg.Code == StopMsg && p.version >= lpv3: p.freeze() h.backend.retriever.frozen(p) p.Log().Debug("Service stopped") - case ResumeMsg: + case msg.Code == ResumeMsg && p.version >= lpv3: var bv uint64 if err := msg.Decode(&bv); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) diff --git a/les/peer.go b/les/peer.go index 2b0117bedc..31ee8f7f04 100644 --- a/les/peer.go +++ b/les/peer.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les/flowcontrol" @@ -246,7 +247,7 @@ func (p *peerCommons) sendReceiveHandshake(sendList keyValueList) (keyValueList, // network IDs, difficulties, head and genesis blocks. Besides the basic handshake // fields, server and client can exchange and resolve some specified fields through // two callback functions. -func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, sendCallback func(*keyValueList), recvCallback func(keyValueMap) error) error { +func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, sendCallback func(*keyValueList), recvCallback func(keyValueMap) error) error { p.lock.Lock() defer p.lock.Unlock() @@ -262,6 +263,12 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g send = send.add("headNum", headNum) send = send.add("genesisHash", genesis) + // If the protocol version is beyond les4, then pass the forkID + // as well. Check http://eips.ethereum.org/EIPS/eip-2124 for more + // spec detail. + if p.version >= lpv4 { + send = send.add("forkID", forkID) + } // Add client-specified or server-specified fields if sendCallback != nil { sendCallback(&send) @@ -295,6 +302,16 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g if int(rVersion) != p.version { return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version) } + // Check forkID if the protocol version is beyond the les4 + if p.version >= lpv4 { + var forkID forkid.ID + if err := recv.get("forkID", &forkID); err != nil { + return err + } + if err := forkFilter(forkID); err != nil { + return errResp(ErrForkIDRejected, "%v", err) + } + } if recvCallback != nil { return recvCallback(recv) } @@ -561,10 +578,10 @@ func (p *serverPeer) updateHead(hash common.Hash, number uint64, td *big.Int) { // Handshake executes the les protocol handshake, negotiating version number, // network IDs and genesis blocks. -func (p *serverPeer) Handshake(genesis common.Hash) error { +func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter forkid.Filter) error { // Note: there is no need to share local head with a server but older servers still // require these fields so we announce zero values. - return p.handshake(common.Big0, common.Hash{}, 0, genesis, func(lists *keyValueList) { + return p.handshake(common.Big0, common.Hash{}, 0, genesis, forkid, forkFilter, func(lists *keyValueList) { // Add some client-specific handshake fields // // Enable signed announcement randomly even the server is not trusted. @@ -944,11 +961,11 @@ func (p *clientPeer) freezeClient() { // Handshake executes the les protocol handshake, negotiating version number, // network IDs, difficulties, head and genesis blocks. -func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, server *LesServer) error { +func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, server *LesServer) error { // Note: clientPeer.headInfo should contain the last head announced to the client by us. // The values announced in the handshake are dummy values for compatibility reasons and should be ignored. p.headInfo = blockInfo{Hash: head, Number: headNum, Td: td} - return p.handshake(td, head, headNum, genesis, func(lists *keyValueList) { + return p.handshake(td, head, headNum, genesis, forkID, forkFilter, func(lists *keyValueList) { // Add some information which services server can offer. if !server.config.UltraLightOnlyAnnounce { *lists = (*lists).add("serveHeaders", nil) diff --git a/les/peer_test.go b/les/peer_test.go index 6d3c7f9755..d6551ce6b6 100644 --- a/les/peer_test.go +++ b/les/peer_test.go @@ -26,8 +26,13 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" ) type testServerPeerSub struct { @@ -91,6 +96,14 @@ func TestPeerSubscription(t *testing.T) { checkPeers(sub.unregCh) } +type fakeChain struct{} + +func (f *fakeChain) Config() *params.ChainConfig { return params.MainnetChainConfig } +func (f *fakeChain) Genesis() *types.Block { + return core.DefaultGenesisBlock().ToBlock(rawdb.NewMemoryDatabase()) +} +func (f *fakeChain) CurrentHeader() *types.Header { return &types.Header{Number: big.NewInt(10000000)} } + func TestHandshake(t *testing.T) { // Create a message pipe to communicate through app, net := p2p.MsgPipe() @@ -110,15 +123,21 @@ func TestHandshake(t *testing.T) { head = common.HexToHash("deadbeef") headNum = uint64(10) genesis = common.HexToHash("cafebabe") + + chain1, chain2 = &fakeChain{}, &fakeChain{} + forkID1 = forkid.NewID(chain1.Config(), chain1.Genesis().Hash(), chain1.CurrentHeader().Number.Uint64()) + forkID2 = forkid.NewID(chain2.Config(), chain2.Genesis().Hash(), chain2.CurrentHeader().Number.Uint64()) + filter1, filter2 = forkid.NewFilter(chain1), forkid.NewFilter(chain2) ) + go func() { - errCh1 <- peer1.handshake(td, head, headNum, genesis, func(list *keyValueList) { + errCh1 <- peer1.handshake(td, head, headNum, genesis, forkID1, filter1, func(list *keyValueList) { var announceType uint64 = announceTypeSigned *list = (*list).add("announceType", announceType) }, nil) }() go func() { - errCh2 <- peer2.handshake(td, head, headNum, genesis, nil, func(recv keyValueMap) error { + errCh2 <- peer2.handshake(td, head, headNum, genesis, forkID2, filter2, nil, func(recv keyValueMap) error { var reqType uint64 err := recv.get("announceType", &reqType) if err != nil { diff --git a/les/protocol.go b/les/protocol.go index 19a9561ce9..aebe0f2c04 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -34,6 +34,7 @@ import ( const ( lpv2 = 2 lpv3 = 3 + lpv4 = 4 ) // Supported versions of the les protocol (first is primary) @@ -44,7 +45,7 @@ var ( ) // Number of implemented message corresponding to different protocol versions. -var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24} +var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24, lpv4: 24} const ( NetworkId = 1 @@ -150,6 +151,7 @@ const ( ErrInvalidResponse ErrTooManyTimeouts ErrMissingKey + ErrForkIDRejected ) func (e errCode) String() string { @@ -172,6 +174,7 @@ var errorToString = map[int]string{ ErrInvalidResponse: "Invalid response", ErrTooManyTimeouts: "Too many request timeouts", ErrMissingKey: "Key missing from list", + ErrForkIDRejected: "ForkID rejected", } // announceData is the network packet for the block announcements. diff --git a/les/server_handler.go b/les/server_handler.go index c0600b3686..f965e3fc64 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -66,6 +67,7 @@ var ( // serverHandler is responsible for serving light client and process // all incoming light requests. type serverHandler struct { + forkFilter forkid.Filter blockchain *core.BlockChain chainDb ethdb.Database txpool *core.TxPool @@ -81,6 +83,7 @@ type serverHandler struct { func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb ethdb.Database, txpool *core.TxPool, synced func() bool) *serverHandler { handler := &serverHandler{ + forkFilter: forkid.NewFilter(blockchain), server: server, blockchain: blockchain, chainDb: chainDb, @@ -121,8 +124,9 @@ func (h *serverHandler) handle(p *clientPeer) error { hash = head.Hash() number = head.Number.Uint64() td = h.blockchain.GetTd(hash, number) + forkID = forkid.NewID(h.blockchain.Config(), h.blockchain.Genesis().Hash(), h.blockchain.CurrentBlock().NumberU64()) ) - if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), h.server); err != nil { + if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), forkID, h.forkFilter, h.server); err != nil { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err } From 1d1f5fea4a3327dd59e39bf1324501f9b43f3354 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 11 Dec 2020 09:02:55 +0100 Subject: [PATCH 057/235] build: upgrade to Go 1.15.6 (#21986) --- build/checksums.txt | 20 ++++++++++++-------- build/ci.go | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/build/checksums.txt b/build/checksums.txt index 32b376519f..a7a6a657e9 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,13 +1,17 @@ # This file contains sha256 checksums of optional build dependencies. -c1076b90cf94b73ebed62a81d802cd84d43d02dea8c07abdc922c57a071c84f1 go1.15.5.src.tar.gz -359a4334b8c8f5e3067e5a76f16419791ac3fef4613d8e8e1eac0b9719915f6d go1.15.5.darwin-amd64.tar.gz -4c8179d406136979724c71732009c7e2e7c794dbeaaa2a043c00da34d4be0559 go1.15.5.linux-386.tar.gz -9a58494e8da722c3aef248c9227b0e9c528c7318309827780f16220998180a0d go1.15.5.linux-amd64.tar.gz -a72a0b036beb4193a0214bca3fca4c5d68a38a4ccf098c909f7ce8bf08567c48 go1.15.5.linux-arm64.tar.gz -5ea6456620d3efed5dda99238c7f23866eafdd915e5348736e631bc283c0238a go1.15.5.linux-armv6l.tar.gz -d812436c7e3482ba3c97172edf26afaf35aca60a5621ff4a5f6a08386505ab9c go1.15.5.windows-386.zip -1d24be3a200201a74be25e4134fbec467750e834e84e9c7789a9fc13248c5507 go1.15.5.windows-amd64.zip +890bba73c5e2b19ffb1180e385ea225059eb008eb91b694875dd86ea48675817 go1.15.6.src.tar.gz +940a73b45993a3bae5792cf324140dded34af97c548af4864d22fd6d49f3bd9f go1.15.6.darwin-amd64.tar.gz +ad187f02158b9a9013ef03f41d14aa69c402477f178825a3940280814bcbb755 go1.15.6.linux-386.tar.gz +3918e6cc85e7eaaa6f859f1bdbaac772e7a825b0eb423c63d3ae68b21f84b844 go1.15.6.linux-amd64.tar.gz +f87515b9744154ffe31182da9341d0a61eb0795551173d242c8cad209239e492 go1.15.6.linux-arm64.tar.gz +40ba9a57764e374195018ef37c38a5fbac9bbce908eab436370631a84bfc5788 go1.15.6.linux-armv6l.tar.gz +5872eff6746a0a5f304272b27cbe9ce186f468454e95749cce01e903fbfc0e17 go1.15.6.windows-386.zip +b7b3808bb072c2bab73175009187fd5a7f20ffe0a31739937003a14c5c4d9006 go1.15.6.windows-amd64.zip +9d9dd5c217c1392f1b2ed5e03e1c71bf4cf8553884e57a38e68fd37fdcfe31a8 go1.15.6.freebsd-386.tar.gz +609f065d855aed5a0b40ef0245aacbcc0b4b7882dc3b1e75ae50576cf25265ee go1.15.6.freebsd-amd64.tar.gz +d4174fc217e749ac049eacc8827df879689f2246ac230d04991ae7df336f7cb2 go1.15.6.linux-ppc64le.tar.gz +839cc6b67687d8bb7cb044e4a9a2eac0c090765cc8ec55ffe714dfb7cd51cf3a go1.15.6.linux-s390x.tar.gz d998a84eea42f2271aca792a7b027ca5c1edfcba229e8e5a844c9ac3f336df35 golangci-lint-1.27.0-linux-armv7.tar.gz bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint.exe-1.27.0-windows-amd64.zip diff --git a/build/ci.go b/build/ci.go index 15946c257e..73d8961629 100644 --- a/build/ci.go +++ b/build/ci.go @@ -152,7 +152,7 @@ var ( // This is the version of go that will be downloaded by // // go run ci.go install -dlgo - dlgoVersion = "1.15.5" + dlgoVersion = "1.15.6" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) From e7872729012a4871397307b12cc3f4772ffcbec6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 11 Dec 2020 08:59:12 +0100 Subject: [PATCH 058/235] params: go-ethereum v1.9.25 stable --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index a2ea188170..3237b51f85 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 25 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 25 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From fc0662bb2372f9a94dea60c3dcf122afd6bda55f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 11 Dec 2020 08:59:46 +0100 Subject: [PATCH 059/235] params: begin v1.9.26 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 3237b51f85..b6d6bd3f1d 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 25 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 26 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From 1a715d7db57997307d309a498e8f819dd08725ad Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Fri, 11 Dec 2020 09:28:01 +0000 Subject: [PATCH 060/235] les: rework float conversion on arm64 and other architectures (#21994) The previous fix #21960 converted the float to an intermediate signed int, before attempting the uint conversion. Although this works, this doesn't guarantee that other architectures will work the same. --- les/utils/expiredvalue.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/les/utils/expiredvalue.go b/les/utils/expiredvalue.go index 1a2b3d995e..3fd52616fa 100644 --- a/les/utils/expiredvalue.go +++ b/les/utils/expiredvalue.go @@ -86,11 +86,15 @@ func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { e.Exp = integer } if base >= 0 || uint64(-base) <= e.Base { - // This is a temporary fix to circumvent a golang - // uint conversion issue on arm64, which needs to - // be investigated further. More details at: + // The conversion from negative float64 to + // uint64 is undefined in golang, and doesn't + // work with ARMv8. More details at: // https://github.com/golang/go/issues/43047 - e.Base += uint64(int64(base)) + if base >= 0 { + e.Base += uint64(base) + } else { + e.Base -= uint64(-base) + } return amount } net := int64(-float64(e.Base) / factor) From 62dc59c2bd6c80b711e873300d7cb91afa91e830 Mon Sep 17 00:00:00 2001 From: lzhfromustc <43191155+lzhfromustc@users.noreply.github.com> Date: Fri, 11 Dec 2020 04:29:42 -0500 Subject: [PATCH 061/235] miner, test: fix potential goroutine leak (#21989) In miner/worker.go, there are two goroutine using channel w.newWorkCh: newWorkerLoop() sends to this channel, and mainLoop() receives from this channel. Only the receive operation is in a select. However, w.exitCh may be closed by another goroutine. This is fine for the receive since receive is in select, but if the send operation is blocking, then it will block forever. This commit puts the send in a select, so it won't block even if w.exitCh is closed. Similarly, there are two goroutines using channel errc: the parent that runs the test receives from it, and the child created at line 573 sends to it. If the parent goroutine exits too early by calling t.Fatalf() at line 614, then the child goroutine will be blocked at line 574 forever. This commit adds 1 buffer to errc. Now send will not block, and receive is not influenced because receive still needs to wait for the send. --- eth/downloader/downloader_test.go | 2 +- miner/worker.go | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 7645f04e4f..5e46042ae4 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -569,7 +569,7 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) { <-proceed } // Start a synchronisation concurrently - errc := make(chan error) + errc := make(chan error, 1) go func() { errc <- tester.sync("peer", nil, mode) }() diff --git a/miner/worker.go b/miner/worker.go index d5813c18a8..5f07affdc4 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -347,7 +347,11 @@ func (w *worker) newWorkLoop(recommit time.Duration) { atomic.StoreInt32(interrupt, s) } interrupt = new(int32) - w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp} + select { + case w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp}: + case <-w.exitCh: + return + } timer.Reset(recommit) atomic.StoreInt32(&w.newTxs, 0) } From b47f4ca5cf3adf7c29e9ee00a6056196f295763c Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Fri, 11 Dec 2020 15:05:39 +0530 Subject: [PATCH 062/235] cmd/faucet: use Twitter API instead of scraping webpage (#21850) This PR adds support for using Twitter API to query the tweet and author details. There are two reasons behind this change: - Twitter will be deprecating the legacy website on 15th December. The current method is expected to stop working then. - More importantly, the current system uses Twitter handle for spam protection but the Twitter handle can be changed via automated calls. This allows bots to use the same tweet to withdraw funds infinite times as long as they keep changing their handle between every request. The Rinkeby as well as the Goerli faucet are being actively drained via this method. This PR changes the spam protection to be based on Twitter IDs instead of usernames. A user can not change their Twitter ID. --- cmd/faucet/faucet.go | 93 +++++++++++++++++++++++++++++++----- cmd/puppeth/module_faucet.go | 7 +++ cmd/puppeth/wizard_faucet.go | 23 +++++++++ 3 files changed, 110 insertions(+), 13 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index eaf0dc30c1..d7927ac491 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -83,6 +83,8 @@ var ( noauthFlag = flag.Bool("noauth", false, "Enables funding requests without authentication") logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet") + + twitterBearerToken = flag.String("twitter.token", "", "Twitter bearer token to authenticate with the twitter API") ) var ( @@ -443,6 +445,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } // Retrieve the Ethereum address to fund, the requesting user and a profile picture var ( + id string username string avatar string address common.Address @@ -462,11 +465,13 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } continue case strings.HasPrefix(msg.URL, "https://twitter.com/"): - username, avatar, address, err = authTwitter(msg.URL) + id, username, avatar, address, err = authTwitter(msg.URL, *twitterBearerToken) case strings.HasPrefix(msg.URL, "https://www.facebook.com/"): username, avatar, address, err = authFacebook(msg.URL) + id = username case *noauthFlag: username, avatar, address, err = authNoAuth(msg.URL) + id = username default: //lint:ignore ST1005 This error is to be displayed in the browser err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues") @@ -486,7 +491,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { fund bool timeout time.Time ) - if timeout = f.timeouts[username]; time.Now().After(timeout) { + if timeout = f.timeouts[id]; time.Now().After(timeout) { // User wasn't funded recently, create the funding transaction amount := new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether) amount = new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(5), big.NewInt(int64(msg.Tier)), nil)) @@ -520,7 +525,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { timeout := time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute grace := timeout / 288 // 24h timeout => 5m grace - f.timeouts[username] = time.Now().Add(timeout - grace) + f.timeouts[id] = time.Now().Add(timeout - grace) fund = true } f.lock.Unlock() @@ -684,23 +689,32 @@ func sendSuccess(conn *websocket.Conn, msg string) error { } // authTwitter tries to authenticate a faucet request using Twitter posts, returning -// the username, avatar URL and Ethereum address to fund on success. -func authTwitter(url string) (string, string, common.Address, error) { +// the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success. +func authTwitter(url string, token string) (string, string, string, common.Address, error) { // Ensure the user specified a meaningful URL, no fancy nonsense parts := strings.Split(url, "/") if len(parts) < 4 || parts[len(parts)-2] != "status" { //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("Invalid Twitter status URL") + return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL") + } + + // Twitter's API isn't really friendly with direct links. + // It is restricted to 300 queries / 15 minute with an app api key. + // Anything more will require read only authorization from the users and that we want to avoid. + + // If twitter bearer token is provided, use the twitter api + if token != "" { + return authTwitterWithToken(parts[len(parts)-1], token) } - // Twitter's API isn't really friendly with direct links. Still, we don't - // want to do ask read permissions from users, so just load the public posts + + // Twiter API token isn't provided so we just load the public posts // and scrape it for the Ethereum address and profile URL. We need to load // the mobile page though since the main page loads tweet contents via JS. url = strings.Replace(url, "https://twitter.com/", "https://mobile.twitter.com/", 1) res, err := http.Get(url) if err != nil { - return "", "", common.Address{}, err + return "", "", "", common.Address{}, err } defer res.Body.Close() @@ -708,24 +722,77 @@ func authTwitter(url string) (string, string, common.Address, error) { parts = strings.Split(res.Request.URL.String(), "/") if len(parts) < 4 || parts[len(parts)-2] != "status" { //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("Invalid Twitter status URL") + return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL") } username := parts[len(parts)-3] body, err := ioutil.ReadAll(res.Body) if err != nil { - return "", "", common.Address{}, err + return "", "", "", common.Address{}, err } address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body))) if address == (common.Address{}) { //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("No Ethereum address found to fund") + return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") } var avatar string if parts = regexp.MustCompile("src=\"([^\"]+twimg.com/profile_images[^\"]+)\"").FindStringSubmatch(string(body)); len(parts) == 2 { avatar = parts[1] } - return username + "@twitter", avatar, address, nil + return username + "@twitter", username, avatar, address, nil +} + +// authTwitterWithToken tries to authenticate a faucet request using Twitter's API, returning +// the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success. +func authTwitterWithToken(tweetID string, token string) (string, string, string, common.Address, error) { + // Strip any query parameters from the tweet id + sanitizedTweetID := strings.Split(tweetID, "?")[0] + + // Ensure numeric tweetID + if !regexp.MustCompile("^[0-9]+$").MatchString(sanitizedTweetID) { + return "", "", "", common.Address{}, errors.New("Invalid Tweet URL") + } + + // Query the tweet details from Twitter + url := fmt.Sprintf("https://api.twitter.com/2/tweets/%s?expansions=author_id&user.fields=profile_image_url", sanitizedTweetID) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", "", "", common.Address{}, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + res, err := http.DefaultClient.Do(req) + if err != nil { + return "", "", "", common.Address{}, err + } + defer res.Body.Close() + + var result struct { + Data struct { + AuthorID string `json:"author_id"` + ID string `json:"id"` + Text string `json:"text"` + } `json:"data"` + Includes struct { + Users []struct { + ProfileImageURL string `json:"profile_image_url"` + Username string `json:"username"` + ID string `json:"id"` + Name string `json:"name"` + } `json:"users"` + } `json:"includes"` + } + + err = json.NewDecoder(res.Body).Decode(&result) + if err != nil { + return "", "", "", common.Address{}, err + } + + address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(result.Data.Text)) + if address == (common.Address{}) { + //lint:ignore ST1005 This error is to be displayed in the browser + return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") + } + return result.Data.AuthorID + "@twitter", result.Includes.Users[0].Username, result.Includes.Users[0].ProfileImageURL, address, nil } // authFacebook tries to authenticate a faucet request using Facebook posts, diff --git a/cmd/puppeth/module_faucet.go b/cmd/puppeth/module_faucet.go index 987bed14aa..2527e137f2 100644 --- a/cmd/puppeth/module_faucet.go +++ b/cmd/puppeth/module_faucet.go @@ -46,6 +46,7 @@ ENTRYPOINT [ \ "--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \ "--account.json", "/account.json", "--account.pass", "/account.pass" \ {{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}} \ + {{if .TwitterToken}}, "--twitter.token", "{{.TwitterToken}}", ]` // faucetComposefile is the docker-compose.yml file required to deploy and maintain @@ -71,6 +72,7 @@ services: - FAUCET_TIERS={{.FaucetTiers}} - CAPTCHA_TOKEN={{.CaptchaToken}} - CAPTCHA_SECRET={{.CaptchaSecret}} + - TWITTER_TOKEN={{.TwitterToken}} - NO_AUTH={{.NoAuth}}{{if .VHost}} - VIRTUAL_HOST={{.VHost}} - VIRTUAL_PORT=8080{{end}} @@ -103,6 +105,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config "FaucetMinutes": config.minutes, "FaucetTiers": config.tiers, "NoAuth": config.noauth, + "TwitterToken": config.twitterToken, }) files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() @@ -120,6 +123,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config "FaucetMinutes": config.minutes, "FaucetTiers": config.tiers, "NoAuth": config.noauth, + "TwitterToken": config.twitterToken, }) files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() @@ -152,6 +156,7 @@ type faucetInfos struct { noauth bool captchaToken string captchaSecret string + twitterToken string } // Report converts the typed struct into a plain string->string map, containing @@ -165,6 +170,7 @@ func (info *faucetInfos) Report() map[string]string { "Funding cooldown (base tier)": fmt.Sprintf("%d mins", info.minutes), "Funding tiers": strconv.Itoa(info.tiers), "Captha protection": fmt.Sprintf("%v", info.captchaToken != ""), + "Using Twitter API": fmt.Sprintf("%v", info.twitterToken != ""), "Ethstats username": info.node.ethstats, } if info.noauth { @@ -243,5 +249,6 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) { captchaToken: infos.envvars["CAPTCHA_TOKEN"], captchaSecret: infos.envvars["CAPTCHA_SECRET"], noauth: infos.envvars["NO_AUTH"] == "true", + twitterToken: infos.envvars["TWITTER_TOKEN"], }, nil } diff --git a/cmd/puppeth/wizard_faucet.go b/cmd/puppeth/wizard_faucet.go index 9f753ad68b..47e05cd9c1 100644 --- a/cmd/puppeth/wizard_faucet.go +++ b/cmd/puppeth/wizard_faucet.go @@ -102,6 +102,29 @@ func (w *wizard) deployFaucet() { infos.captchaSecret = w.readPassword() } } + + // Accessing the twitter api requires a bearer token, request it + if infos.twitterToken != "" { + fmt.Println() + fmt.Println("Reuse previous twitter API Bearer token (y/n)? (default = yes)") + if !w.readDefaultYesNo(true) { + infos.twitterToken = "" + } + } + if infos.twitterToken == "" { + // No previous twitter token (or old one discarded) + fmt.Println() + fmt.Println("Enable twitter API (y/n)? (default = no)") + if !w.readDefaultYesNo(false) { + log.Warn("The faucet will fallback to using direct calls") + } else { + // Twitter api explicitly requested, read the bearer token + fmt.Println() + fmt.Printf("What is the twitter API Bearer token?\n") + infos.twitterToken = w.readString() + } + } + // Figure out where the user wants to store the persistent data fmt.Println() if infos.node.datadir == "" { From 88c696240dc7dfd99588d9e2ef0b04f03a06d1a5 Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 11 Dec 2020 17:44:57 +0800 Subject: [PATCH 063/235] core/txpool: remove "local" notion from the txpool price heap (#21478) * core: separate the local notion from the pricedHeap * core: add benchmarks * core: improve tests * core: address comments * core: degrade the panic to error message * core: fix typo * core: address comments * core: address comment * core: use PEAK instead of POP * core: address comments --- core/tx_list.go | 152 +++++++++++++++------------------ core/tx_pool.go | 195 +++++++++++++++++++++++++++++++++---------- core/tx_pool_test.go | 69 ++++++++++++--- 3 files changed, 271 insertions(+), 145 deletions(-) diff --git a/core/tx_list.go b/core/tx_list.go index cdd3df14c5..894640d570 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -24,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" ) // nonceHeap is a heap.Interface implementation over 64bit unsigned integers for @@ -439,24 +438,29 @@ func (h *priceHeap) Pop() interface{} { } // txPricedList is a price-sorted heap to allow operating on transactions pool -// contents in a price-incrementing way. +// contents in a price-incrementing way. It's built opon the all transactions +// in txpool but only interested in the remote part. It means only remote transactions +// will be considered for tracking, sorting, eviction, etc. type txPricedList struct { - all *txLookup // Pointer to the map of all transactions - items *priceHeap // Heap of prices of all the stored transactions - stales int // Number of stale price points to (re-heap trigger) + all *txLookup // Pointer to the map of all transactions + remotes *priceHeap // Heap of prices of all the stored **remote** transactions + stales int // Number of stale price points to (re-heap trigger) } // newTxPricedList creates a new price-sorted transaction heap. func newTxPricedList(all *txLookup) *txPricedList { return &txPricedList{ - all: all, - items: new(priceHeap), + all: all, + remotes: new(priceHeap), } } // Put inserts a new transaction into the heap. -func (l *txPricedList) Put(tx *types.Transaction) { - heap.Push(l.items, tx) +func (l *txPricedList) Put(tx *types.Transaction, local bool) { + if local { + return + } + heap.Push(l.remotes, tx) } // Removed notifies the prices transaction list that an old transaction dropped @@ -465,121 +469,95 @@ func (l *txPricedList) Put(tx *types.Transaction) { func (l *txPricedList) Removed(count int) { // Bump the stale counter, but exit if still too low (< 25%) l.stales += count - if l.stales <= len(*l.items)/4 { + if l.stales <= len(*l.remotes)/4 { return } // Seems we've reached a critical number of stale transactions, reheap - reheap := make(priceHeap, 0, l.all.Count()) - - l.stales, l.items = 0, &reheap - l.all.Range(func(hash common.Hash, tx *types.Transaction) bool { - *l.items = append(*l.items, tx) - return true - }) - heap.Init(l.items) + l.Reheap() } // Cap finds all the transactions below the given price threshold, drops them // from the priced list and returns them for further removal from the entire pool. -func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transactions { +// +// Note: only remote transactions will be considered for eviction. +func (l *txPricedList) Cap(threshold *big.Int) types.Transactions { drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop - save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep - - for len(*l.items) > 0 { + for len(*l.remotes) > 0 { // Discard stale transactions if found during cleanup - tx := heap.Pop(l.items).(*types.Transaction) - if l.all.Get(tx.Hash()) == nil { + cheapest := (*l.remotes)[0] + if l.all.GetRemote(cheapest.Hash()) == nil { // Removed or migrated + heap.Pop(l.remotes) l.stales-- continue } // Stop the discards if we've reached the threshold - if tx.GasPriceIntCmp(threshold) >= 0 { - save = append(save, tx) + if cheapest.GasPriceIntCmp(threshold) >= 0 { break } - // Non stale transaction found, discard unless local - if local.containsTx(tx) { - save = append(save, tx) - } else { - drop = append(drop, tx) - } - } - for _, tx := range save { - heap.Push(l.items, tx) + heap.Pop(l.remotes) + drop = append(drop, cheapest) } return drop } // Underpriced checks whether a transaction is cheaper than (or as cheap as) the -// lowest priced transaction currently being tracked. -func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) bool { - // Local transactions cannot be underpriced - if local.containsTx(tx) { - return false - } +// lowest priced (remote) transaction currently being tracked. +func (l *txPricedList) Underpriced(tx *types.Transaction) bool { // Discard stale price points if found at the heap start - for len(*l.items) > 0 { - head := []*types.Transaction(*l.items)[0] - if l.all.Get(head.Hash()) == nil { + for len(*l.remotes) > 0 { + head := []*types.Transaction(*l.remotes)[0] + if l.all.GetRemote(head.Hash()) == nil { // Removed or migrated l.stales-- - heap.Pop(l.items) + heap.Pop(l.remotes) continue } break } // Check if the transaction is underpriced or not - if len(*l.items) == 0 { - log.Error("Pricing query for empty pool") // This cannot happen, print to catch programming errors - return false + if len(*l.remotes) == 0 { + return false // There is no remote transaction at all. } - cheapest := []*types.Transaction(*l.items)[0] + // If the remote transaction is even cheaper than the + // cheapest one tracked locally, reject it. + cheapest := []*types.Transaction(*l.remotes)[0] return cheapest.GasPriceCmp(tx) >= 0 } // Discard finds a number of most underpriced transactions, removes them from the // priced list and returns them for further removal from the entire pool. -func (l *txPricedList) Discard(slots int, local *accountSet) types.Transactions { - // If we have some local accountset, those will not be discarded - if !local.empty() { - // In case the list is filled to the brim with 'local' txs, we do this - // little check to avoid unpacking / repacking the heap later on, which - // is very expensive - discardable := 0 - for _, tx := range *l.items { - if !local.containsTx(tx) { - discardable++ - } - if discardable >= slots { - break - } - } - if slots > discardable { - slots = discardable - } - } - if slots == 0 { - return nil - } - drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop - save := make(types.Transactions, 0, len(*l.items)-slots) // Local underpriced transactions to keep - - for len(*l.items) > 0 && slots > 0 { +// +// Note local transaction won't be considered for eviction. +func (l *txPricedList) Discard(slots int, force bool) (types.Transactions, bool) { + drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop + for len(*l.remotes) > 0 && slots > 0 { // Discard stale transactions if found during cleanup - tx := heap.Pop(l.items).(*types.Transaction) - if l.all.Get(tx.Hash()) == nil { + tx := heap.Pop(l.remotes).(*types.Transaction) + if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated l.stales-- continue } - // Non stale transaction found, discard unless local - if local.containsTx(tx) { - save = append(save, tx) - } else { - drop = append(drop, tx) - slots -= numSlots(tx) + // Non stale transaction found, discard it + drop = append(drop, tx) + slots -= numSlots(tx) + } + // If we still can't make enough room for the new transaction + if slots > 0 && !force { + for _, tx := range drop { + heap.Push(l.remotes, tx) } + return nil, false } - for _, tx := range save { - heap.Push(l.items, tx) - } - return drop + return drop, true +} + +// Reheap forcibly rebuilds the heap based on the current remote transaction set. +func (l *txPricedList) Reheap() { + reheap := make(priceHeap, 0, l.all.RemoteCount()) + + l.stales, l.remotes = 0, &reheap + l.all.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool { + *l.remotes = append(*l.remotes, tx) + return true + }, false, true) // Only iterate remotes + heap.Init(l.remotes) } diff --git a/core/tx_pool.go b/core/tx_pool.go index e3ffe103cf..4a17c31ca8 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -63,6 +63,10 @@ var ( // configured for the transaction pool. ErrUnderpriced = errors.New("transaction underpriced") + // ErrTxPoolOverflow is returned if the transaction pool is full and can't accpet + // another remote transaction. + ErrTxPoolOverflow = errors.New("txpool is full") + // ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced // with a different one without the required price bump. ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") @@ -105,6 +109,7 @@ var ( validTxMeter = metrics.NewRegisteredMeter("txpool/valid", nil) invalidTxMeter = metrics.NewRegisteredMeter("txpool/invalid", nil) underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil) + overflowedTxMeter = metrics.NewRegisteredMeter("txpool/overflowed", nil) pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil) queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) @@ -421,7 +426,7 @@ func (pool *TxPool) SetGasPrice(price *big.Int) { defer pool.mu.Unlock() pool.gasPrice = price - for _, tx := range pool.priced.Cap(price, pool.locals) { + for _, tx := range pool.priced.Cap(price) { pool.removeTx(tx.Hash(), false) } log.Info("Transaction pool price threshold updated", "price", price) @@ -536,7 +541,6 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { return ErrInvalidSender } // Drop non-local transactions under our own minimal accepted gas price - local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 { return ErrUnderpriced } @@ -575,22 +579,36 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e knownTxMeter.Mark(1) return false, ErrAlreadyKnown } + // Make the local flag. If it's from local source or it's from the network but + // the sender is marked as local previously, treat it as the local transaction. + isLocal := local || pool.locals.containsTx(tx) + // If the transaction fails basic validation, discard it - if err := pool.validateTx(tx, local); err != nil { + if err := pool.validateTx(tx, isLocal); err != nil { log.Trace("Discarding invalid transaction", "hash", hash, "err", err) invalidTxMeter.Mark(1) return false, err } // If the transaction pool is full, discard underpriced transactions - if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue { + if uint64(pool.all.Count()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { // If the new transaction is underpriced, don't accept it - if !local && pool.priced.Underpriced(tx, pool.locals) { + if !isLocal && pool.priced.Underpriced(tx) { log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice()) underpricedTxMeter.Mark(1) return false, ErrUnderpriced } - // New transaction is better than our worse ones, make room for it - drop := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), pool.locals) + // New transaction is better than our worse ones, make room for it. + // If it's a local transaction, forcibly discard all available transactions. + // Otherwise if we can't make enough room for new one, abort the operation. + drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal) + + // Special case, we still can't make the room for the new remote one. + if !isLocal && !success { + log.Trace("Discarding overflown transaction", "hash", hash) + overflowedTxMeter.Mark(1) + return false, ErrTxPoolOverflow + } + // Kick out the underpriced remote transactions. for _, tx := range drop { log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice()) underpricedTxMeter.Mark(1) @@ -612,8 +630,8 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e pool.priced.Removed(1) pendingReplaceMeter.Mark(1) } - pool.all.Add(tx) - pool.priced.Put(tx) + pool.all.Add(tx, isLocal) + pool.priced.Put(tx, isLocal) pool.journalTx(from, tx) pool.queueTxEvent(tx) log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) @@ -623,18 +641,17 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e return old != nil, nil } // New transaction isn't replacing a pending one, push into queue - replaced, err = pool.enqueueTx(hash, tx) + replaced, err = pool.enqueueTx(hash, tx, isLocal, true) if err != nil { return false, err } // Mark local addresses and journal local transactions - if local { - if !pool.locals.contains(from) { - log.Info("Setting new local account", "address", from) - pool.locals.add(from) - } + if local && !pool.locals.contains(from) { + log.Info("Setting new local account", "address", from) + pool.locals.add(from) + pool.priced.Removed(pool.all.RemoteToLocals(pool.locals)) // Migrate the remotes if it's marked as local first time. } - if local || pool.locals.contains(from) { + if isLocal { localGauge.Inc(1) } pool.journalTx(from, tx) @@ -646,7 +663,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e // enqueueTx inserts a new transaction into the non-executable transaction queue. // // Note, this method assumes the pool lock is held! -func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, error) { +func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) { // Try to insert the transaction into the future queue from, _ := types.Sender(pool.signer, tx) // already validated if pool.queue[from] == nil { @@ -667,9 +684,14 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er // Nothing was replaced, bump the queued counter queuedGauge.Inc(1) } - if pool.all.Get(hash) == nil { - pool.all.Add(tx) - pool.priced.Put(tx) + // If the transaction isn't in lookup set but it's expected to be there, + // show the error log. + if pool.all.Get(hash) == nil && !addAll { + log.Error("Missing transaction in lookup set, please report the issue", "hash", hash) + } + if addAll { + pool.all.Add(tx, local) + pool.priced.Put(tx, local) } // If we never record the heartbeat, do it right now. if _, exist := pool.beats[from]; !exist { @@ -718,11 +740,6 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T // Nothing was replaced, bump the pending counter pendingGauge.Inc(1) } - // Failsafe to work around direct pending inserts (tests) - if pool.all.Get(hash) == nil { - pool.all.Add(tx) - pool.priced.Put(tx) - } // Set the potentially new pending nonce and notify any subsystems of the new tx pool.pendingNonces.set(addr, tx.Nonce()+1) @@ -904,7 +921,8 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { } // Postpone any invalidated transactions for _, tx := range invalids { - pool.enqueueTx(tx.Hash(), tx) + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(tx.Hash(), tx, false, false) } // Update the account nonce if needed pool.pendingNonces.setIfLower(addr, tx.Nonce()) @@ -1408,7 +1426,9 @@ func (pool *TxPool) demoteUnexecutables() { for _, tx := range invalids { hash := tx.Hash() log.Trace("Demoting pending transaction", "hash", hash) - pool.enqueueTx(hash, tx) + + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(hash, tx, false, false) } pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) if pool.locals.contains(addr) { @@ -1420,7 +1440,9 @@ func (pool *TxPool) demoteUnexecutables() { for _, tx := range gapped { hash := tx.Hash() log.Error("Demoting invalidated transaction", "hash", hash) - pool.enqueueTx(hash, tx) + + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(hash, tx, false, false) } pendingGauge.Dec(int64(len(gapped))) // This might happen in a reorg, so log it to the metering @@ -1519,8 +1541,8 @@ func (as *accountSet) merge(other *accountSet) { as.cache = nil } -// txLookup is used internally by TxPool to track transactions while allowing lookup without -// mutex contention. +// txLookup is used internally by TxPool to track transactions while allowing +// lookup without mutex contention. // // Note, although this type is properly protected against concurrent access, it // is **not** a type that should ever be mutated or even exposed outside of the @@ -1528,27 +1550,43 @@ func (as *accountSet) merge(other *accountSet) { // internal mechanisms. The sole purpose of the type is to permit out-of-bound // peeking into the pool in TxPool.Get without having to acquire the widely scoped // TxPool.mu mutex. +// +// This lookup set combines the notion of "local transactions", which is useful +// to build upper-level structure. type txLookup struct { - all map[common.Hash]*types.Transaction - slots int - lock sync.RWMutex + slots int + lock sync.RWMutex + locals map[common.Hash]*types.Transaction + remotes map[common.Hash]*types.Transaction } // newTxLookup returns a new txLookup structure. func newTxLookup() *txLookup { return &txLookup{ - all: make(map[common.Hash]*types.Transaction), + locals: make(map[common.Hash]*types.Transaction), + remotes: make(map[common.Hash]*types.Transaction), } } -// Range calls f on each key and value present in the map. -func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction) bool) { +// Range calls f on each key and value present in the map. The callback passed +// should return the indicator whether the iteration needs to be continued. +// Callers need to specify which set (or both) to be iterated. +func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) { t.lock.RLock() defer t.lock.RUnlock() - for key, value := range t.all { - if !f(key, value) { - break + if local { + for key, value := range t.locals { + if !f(key, value, true) { + return + } + } + } + if remote { + for key, value := range t.remotes { + if !f(key, value, false) { + return + } } } } @@ -1558,15 +1596,50 @@ func (t *txLookup) Get(hash common.Hash) *types.Transaction { t.lock.RLock() defer t.lock.RUnlock() - return t.all[hash] + if tx := t.locals[hash]; tx != nil { + return tx + } + return t.remotes[hash] } -// Count returns the current number of items in the lookup. +// GetLocal returns a transaction if it exists in the lookup, or nil if not found. +func (t *txLookup) GetLocal(hash common.Hash) *types.Transaction { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.locals[hash] +} + +// GetRemote returns a transaction if it exists in the lookup, or nil if not found. +func (t *txLookup) GetRemote(hash common.Hash) *types.Transaction { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.remotes[hash] +} + +// Count returns the current number of transactions in the lookup. func (t *txLookup) Count() int { t.lock.RLock() defer t.lock.RUnlock() - return len(t.all) + return len(t.locals) + len(t.remotes) +} + +// LocalCount returns the current number of local transactions in the lookup. +func (t *txLookup) LocalCount() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return len(t.locals) +} + +// RemoteCount returns the current number of remote transactions in the lookup. +func (t *txLookup) RemoteCount() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return len(t.remotes) } // Slots returns the current number of slots used in the lookup. @@ -1578,14 +1651,18 @@ func (t *txLookup) Slots() int { } // Add adds a transaction to the lookup. -func (t *txLookup) Add(tx *types.Transaction) { +func (t *txLookup) Add(tx *types.Transaction, local bool) { t.lock.Lock() defer t.lock.Unlock() t.slots += numSlots(tx) slotsGauge.Update(int64(t.slots)) - t.all[tx.Hash()] = tx + if local { + t.locals[tx.Hash()] = tx + } else { + t.remotes[tx.Hash()] = tx + } } // Remove removes a transaction from the lookup. @@ -1593,10 +1670,36 @@ func (t *txLookup) Remove(hash common.Hash) { t.lock.Lock() defer t.lock.Unlock() - t.slots -= numSlots(t.all[hash]) + tx, ok := t.locals[hash] + if !ok { + tx, ok = t.remotes[hash] + } + if !ok { + log.Error("No transaction found to be deleted", "hash", hash) + return + } + t.slots -= numSlots(tx) slotsGauge.Update(int64(t.slots)) - delete(t.all, hash) + delete(t.locals, hash) + delete(t.remotes, hash) +} + +// RemoteToLocals migrates the transactions belongs to the given locals to locals +// set. The assumption is held the locals set is thread-safe to be used. +func (t *txLookup) RemoteToLocals(locals *accountSet) int { + t.lock.Lock() + defer t.lock.Unlock() + + var migrated int + for hash, tx := range t.remotes { + if locals.containsTx(tx) { + t.locals[hash] = tx + delete(t.remotes, hash) + migrated += 1 + } + } + return migrated } // numSlots calculates the number of slots needed for a single transaction. diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 246b3977d3..47d3830b06 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -107,10 +107,11 @@ func validateTxPoolInternals(pool *TxPool) error { if total := pool.all.Count(); total != pending+queued { return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued) } - if priced := pool.priced.items.Len() - pool.priced.stales; priced != pending+queued { - return fmt.Errorf("total priced transaction count %d != %d pending + %d queued", priced, pending, queued) + pool.priced.Reheap() + priced, remote := pool.priced.remotes.Len(), pool.all.RemoteCount() + if priced != remote { + return fmt.Errorf("total priced transaction count %d != %d", priced, remote) } - // Ensure the next nonce to assign is the correct one for addr, txs := range pool.pending { // Find the last transaction @@ -280,7 +281,7 @@ func TestTransactionQueue(t *testing.T) { pool.currentState.AddBalance(from, big.NewInt(1000)) <-pool.requestReset(nil, nil) - pool.enqueueTx(tx.Hash(), tx) + pool.enqueueTx(tx.Hash(), tx, false, true) <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) if len(pool.pending) != 1 { t.Error("expected valid txs to be 1 is", len(pool.pending)) @@ -289,7 +290,7 @@ func TestTransactionQueue(t *testing.T) { tx = transaction(1, 100, key) from, _ = deriveSender(tx) pool.currentState.SetNonce(from, 2) - pool.enqueueTx(tx.Hash(), tx) + pool.enqueueTx(tx.Hash(), tx, false, true) <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { @@ -313,9 +314,9 @@ func TestTransactionQueue2(t *testing.T) { pool.currentState.AddBalance(from, big.NewInt(1000)) pool.reset(nil, nil) - pool.enqueueTx(tx1.Hash(), tx1) - pool.enqueueTx(tx2.Hash(), tx2) - pool.enqueueTx(tx3.Hash(), tx3) + pool.enqueueTx(tx1.Hash(), tx1, false, true) + pool.enqueueTx(tx2.Hash(), tx2, false, true) + pool.enqueueTx(tx3.Hash(), tx3, false, true) pool.promoteExecutables([]common.Address{from}) if len(pool.pending) != 1 { @@ -488,12 +489,21 @@ func TestTransactionDropping(t *testing.T) { tx11 = transaction(11, 200, key) tx12 = transaction(12, 300, key) ) + pool.all.Add(tx0, false) + pool.priced.Put(tx0, false) pool.promoteTx(account, tx0.Hash(), tx0) + + pool.all.Add(tx1, false) + pool.priced.Put(tx1, false) pool.promoteTx(account, tx1.Hash(), tx1) + + pool.all.Add(tx2, false) + pool.priced.Put(tx2, false) pool.promoteTx(account, tx2.Hash(), tx2) - pool.enqueueTx(tx10.Hash(), tx10) - pool.enqueueTx(tx11.Hash(), tx11) - pool.enqueueTx(tx12.Hash(), tx12) + + pool.enqueueTx(tx10.Hash(), tx10, false, true) + pool.enqueueTx(tx11.Hash(), tx11, false, true) + pool.enqueueTx(tx12.Hash(), tx12, false, true) // Check that pre and post validations leave the pool as is if pool.pending[account].Len() != 3 { @@ -1964,7 +1974,7 @@ func benchmarkFuturePromotion(b *testing.B, size int) { for i := 0; i < size; i++ { tx := transaction(uint64(1+i), 100000, key) - pool.enqueueTx(tx.Hash(), tx) + pool.enqueueTx(tx.Hash(), tx, false, true) } // Benchmark the speed of pool validation b.ResetTimer() @@ -2007,3 +2017,38 @@ func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) { } } } + +func BenchmarkInsertRemoteWithAllLocals(b *testing.B) { + // Allocate keys for testing + key, _ := crypto.GenerateKey() + account := crypto.PubkeyToAddress(key.PublicKey) + + remoteKey, _ := crypto.GenerateKey() + remoteAddr := crypto.PubkeyToAddress(remoteKey.PublicKey) + + locals := make([]*types.Transaction, 4096+1024) // Occupy all slots + for i := 0; i < len(locals); i++ { + locals[i] = transaction(uint64(i), 100000, key) + } + remotes := make([]*types.Transaction, 1000) + for i := 0; i < len(remotes); i++ { + remotes[i] = pricedTransaction(uint64(i), 100000, big.NewInt(2), remoteKey) // Higher gasprice + } + // Benchmark importing the transactions into the queue + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + pool, _ := setupTxPool() + pool.currentState.AddBalance(account, big.NewInt(100000000)) + for _, local := range locals { + pool.AddLocal(local) + } + b.StartTimer() + // Assign a high enough balance for testing + pool.currentState.AddBalance(remoteAddr, big.NewInt(100000000)) + for i := 0; i < len(remotes); i++ { + pool.AddRemotes([]*types.Transaction{remotes[i]}) + } + pool.Stop() + } +} From efe6dd29042b36d543420a422fc21d123f1e67e3 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 11 Dec 2020 11:06:44 +0100 Subject: [PATCH 064/235] consensus/ethash: implement faster difficulty calculators (#21976) This PR adds re-written difficulty calculators, which are based on uint256. It also adds a fuzzer + oss-fuzz integration for the new fuzzer. It does differential fuzzing between the new and old calculators. Note: this PR does not actually enable the new calculators. --- consensus/ethash/consensus.go | 5 + consensus/ethash/consensus_test.go | 102 +++++++++++ consensus/ethash/difficulty.go | 193 ++++++++++++++++++++ oss-fuzz.sh | 1 + tests/fuzzers/difficulty/debug/main.go | 23 +++ tests/fuzzers/difficulty/difficulty-fuzz.go | 145 +++++++++++++++ 6 files changed, 469 insertions(+) create mode 100644 consensus/ethash/difficulty.go create mode 100644 tests/fuzzers/difficulty/debug/main.go create mode 100644 tests/fuzzers/difficulty/difficulty-fuzz.go diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index bdc02098af..8e401af7ca 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -485,6 +485,11 @@ func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int { return diff } +// Exported for fuzzing +var FrontierDifficultyCalulator = calcDifficultyFrontier +var HomesteadDifficultyCalulator = calcDifficultyHomestead +var DynamicDifficultyCalculator = makeDifficultyCalculator + // VerifySeal implements consensus.Engine, checking whether the given block satisfies // the PoW difficulty requirements. func (ethash *Ethash) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error { diff --git a/consensus/ethash/consensus_test.go b/consensus/ethash/consensus_test.go index 675737d9e1..6f6dc79fd8 100644 --- a/consensus/ethash/consensus_test.go +++ b/consensus/ethash/consensus_test.go @@ -17,12 +17,15 @@ package ethash import ( + "encoding/binary" "encoding/json" "math/big" + "math/rand" "os" "path/filepath" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -84,3 +87,102 @@ func TestCalcDifficulty(t *testing.T) { } } } + +func randSlice(min, max uint32) []byte { + var b = make([]byte, 4) + rand.Read(b) + a := binary.LittleEndian.Uint32(b) + size := min + a%(max-min) + out := make([]byte, size) + rand.Read(out) + return out +} + +func TestDifficultyCalculators(t *testing.T) { + rand.Seed(2) + for i := 0; i < 5000; i++ { + // 1 to 300 seconds diff + var timeDelta = uint64(1 + rand.Uint32()%3000) + diffBig := big.NewInt(0).SetBytes(randSlice(2, 10)) + if diffBig.Cmp(params.MinimumDifficulty) < 0 { + diffBig.Set(params.MinimumDifficulty) + } + //rand.Read(difficulty) + header := &types.Header{ + Difficulty: diffBig, + Number: new(big.Int).SetUint64(rand.Uint64() % 50_000_000), + Time: rand.Uint64() - timeDelta, + } + if rand.Uint32()&1 == 0 { + header.UncleHash = types.EmptyUncleHash + } + bombDelay := new(big.Int).SetUint64(rand.Uint64() % 50_000_000) + for i, pair := range []struct { + bigFn func(time uint64, parent *types.Header) *big.Int + u256Fn func(time uint64, parent *types.Header) *big.Int + }{ + {FrontierDifficultyCalulator, CalcDifficultyFrontierU256}, + {HomesteadDifficultyCalulator, CalcDifficultyHomesteadU256}, + {DynamicDifficultyCalculator(bombDelay), MakeDifficultyCalculatorU256(bombDelay)}, + } { + time := header.Time + timeDelta + want := pair.bigFn(time, header) + have := pair.u256Fn(time, header) + if want.BitLen() > 256 { + continue + } + if want.Cmp(have) != 0 { + t.Fatalf("pair %d: want %x have %x\nparent.Number: %x\np.Time: %x\nc.Time: %x\nBombdelay: %v\n", i, want, have, + header.Number, header.Time, time, bombDelay) + } + } + } +} + +func BenchmarkDifficultyCalculator(b *testing.B) { + x1 := makeDifficultyCalculator(big.NewInt(1000000)) + x2 := MakeDifficultyCalculatorU256(big.NewInt(1000000)) + h := &types.Header{ + ParentHash: common.Hash{}, + UncleHash: types.EmptyUncleHash, + Difficulty: big.NewInt(0xffffff), + Number: big.NewInt(500000), + Time: 1000000, + } + b.Run("big-frontier", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + calcDifficultyFrontier(1000014, h) + } + }) + b.Run("u256-frontier", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + CalcDifficultyFrontierU256(1000014, h) + } + }) + b.Run("big-homestead", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + calcDifficultyHomestead(1000014, h) + } + }) + b.Run("u256-homestead", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + CalcDifficultyHomesteadU256(1000014, h) + } + }) + b.Run("big-generic", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + x1(1000014, h) + } + }) + b.Run("u256-generic", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + x2(1000014, h) + } + }) +} diff --git a/consensus/ethash/difficulty.go b/consensus/ethash/difficulty.go new file mode 100644 index 0000000000..59c4ac7419 --- /dev/null +++ b/consensus/ethash/difficulty.go @@ -0,0 +1,193 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethash + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/holiman/uint256" +) + +const ( + // frontierDurationLimit is for Frontier: + // The decision boundary on the blocktime duration used to determine + // whether difficulty should go up or down. + frontierDurationLimit = 13 + // minimumDifficulty The minimum that the difficulty may ever be. + minimumDifficulty = 131072 + // expDiffPeriod is the exponential difficulty period + expDiffPeriodUint = 100000 + // difficultyBoundDivisorBitShift is the bound divisor of the difficulty (2048), + // This constant is the right-shifts to use for the division. + difficultyBoundDivisor = 11 +) + +// CalcDifficultyFrontierU256 is the difficulty adjustment algorithm. It returns the +// difficulty that a new block should have when created at time given the parent +// block's time and difficulty. The calculation uses the Frontier rules. +func CalcDifficultyFrontierU256(time uint64, parent *types.Header) *big.Int { + /* + Algorithm + block_diff = pdiff + pdiff / 2048 * (1 if time - ptime < 13 else -1) + int(2^((num // 100000) - 2)) + + Where: + - pdiff = parent.difficulty + - ptime = parent.time + - time = block.timestamp + - num = block.number + */ + + pDiff := uint256.NewInt() + pDiff.SetFromBig(parent.Difficulty) // pDiff: pdiff + adjust := pDiff.Clone() + adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048 + + if time-parent.Time < frontierDurationLimit { + pDiff.Add(pDiff, adjust) + } else { + pDiff.Sub(pDiff, adjust) + } + if pDiff.LtUint64(minimumDifficulty) { + pDiff.SetUint64(minimumDifficulty) + } + // 'pdiff' now contains: + // pdiff + pdiff / 2048 * (1 if time - ptime < 13 else -1) + + if periodCount := (parent.Number.Uint64() + 1) / expDiffPeriodUint; periodCount > 1 { + // diff = diff + 2^(periodCount - 2) + expDiff := adjust.SetOne() + expDiff.Lsh(expDiff, uint(periodCount-2)) // expdiff: 2 ^ (periodCount -2) + pDiff.Add(pDiff, expDiff) + } + return pDiff.ToBig() +} + +// CalcDifficultyHomesteadU256 is the difficulty adjustment algorithm. It returns +// the difficulty that a new block should have when created at time given the +// parent block's time and difficulty. The calculation uses the Homestead rules. +func CalcDifficultyHomesteadU256(time uint64, parent *types.Header) *big.Int { + /* + https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md + Algorithm: + block_diff = pdiff + pdiff / 2048 * max(1 - (time - ptime) / 10, -99) + 2 ^ int((num / 100000) - 2)) + + Our modification, to use unsigned ints: + block_diff = pdiff - pdiff / 2048 * max((time - ptime) / 10 - 1, 99) + 2 ^ int((num / 100000) - 2)) + + Where: + - pdiff = parent.difficulty + - ptime = parent.time + - time = block.timestamp + - num = block.number + */ + + pDiff := uint256.NewInt() + pDiff.SetFromBig(parent.Difficulty) // pDiff: pdiff + adjust := pDiff.Clone() + adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048 + + x := (time - parent.Time) / 10 // (time - ptime) / 10) + var neg = true + if x == 0 { + x = 1 + neg = false + } else if x >= 100 { + x = 99 + } else { + x = x - 1 + } + z := new(uint256.Int).SetUint64(x) + adjust.Mul(adjust, z) // adjust: (pdiff / 2048) * max((time - ptime) / 10 - 1, 99) + if neg { + pDiff.Sub(pDiff, adjust) // pdiff - pdiff / 2048 * max((time - ptime) / 10 - 1, 99) + } else { + pDiff.Add(pDiff, adjust) // pdiff + pdiff / 2048 * max((time - ptime) / 10 - 1, 99) + } + if pDiff.LtUint64(minimumDifficulty) { + pDiff.SetUint64(minimumDifficulty) + } + // for the exponential factor, a.k.a "the bomb" + // diff = diff + 2^(periodCount - 2) + if periodCount := (1 + parent.Number.Uint64()) / expDiffPeriodUint; periodCount > 1 { + expFactor := adjust.Lsh(adjust.SetOne(), uint(periodCount-2)) + pDiff.Add(pDiff, expFactor) + } + return pDiff.ToBig() +} + +// MakeDifficultyCalculatorU256 creates a difficultyCalculator with the given bomb-delay. +// the difficulty is calculated with Byzantium rules, which differs from Homestead in +// how uncles affect the calculation +func MakeDifficultyCalculatorU256(bombDelay *big.Int) func(time uint64, parent *types.Header) *big.Int { + // Note, the calculations below looks at the parent number, which is 1 below + // the block number. Thus we remove one from the delay given + bombDelayFromParent := bombDelay.Uint64() - 1 + return func(time uint64, parent *types.Header) *big.Int { + /* + https://github.com/ethereum/EIPs/issues/100 + pDiff = parent.difficulty + BLOCK_DIFF_FACTOR = 9 + a = pDiff + (pDiff // BLOCK_DIFF_FACTOR) * adj_factor + b = min(parent.difficulty, MIN_DIFF) + child_diff = max(a,b ) + */ + x := (time - parent.Time) / 9 // (block_timestamp - parent_timestamp) // 9 + c := uint64(1) // if parent.unclehash == emptyUncleHashHash + if parent.UncleHash != types.EmptyUncleHash { + c = 2 + } + xNeg := x >= c + if xNeg { + // x is now _negative_ adjustment factor + x = x - c // - ( (t-p)/p -( 2 or 1) ) + } else { + x = c - x // (2 or 1) - (t-p)/9 + } + if x > 99 { + x = 99 // max(x, 99) + } + // parent_diff + (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)) + y := new(uint256.Int) + y.SetFromBig(parent.Difficulty) // y: p_diff + pDiff := y.Clone() // pdiff: p_diff + z := new(uint256.Int).SetUint64(x) //z : +-adj_factor (either pos or negative) + y.Rsh(y, difficultyBoundDivisor) // y: p__diff / 2048 + z.Mul(y, z) // z: (p_diff / 2048 ) * (+- adj_factor) + + if xNeg { + y.Sub(pDiff, z) // y: parent_diff + parent_diff/2048 * adjustment_factor + } else { + y.Add(pDiff, z) // y: parent_diff + parent_diff/2048 * adjustment_factor + } + // minimum difficulty can ever be (before exponential factor) + if y.LtUint64(minimumDifficulty) { + y.SetUint64(minimumDifficulty) + } + // calculate a fake block number for the ice-age delay + // Specification: https://eips.ethereum.org/EIPS/eip-1234 + var pNum = parent.Number.Uint64() + if pNum >= bombDelayFromParent { + if fakeBlockNumber := pNum - bombDelayFromParent; fakeBlockNumber >= 2*expDiffPeriodUint { + z.SetOne() + z.Lsh(z, uint(fakeBlockNumber/expDiffPeriodUint-2)) + y.Add(z, y) + } + } + return y.ToBig() + } +} diff --git a/oss-fuzz.sh b/oss-fuzz.sh index e0a293a6d6..e060ea88e1 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -57,6 +57,7 @@ compile_fuzzer tests/fuzzers/txfetcher Fuzz fuzzTxfetcher compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie +compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul diff --git a/tests/fuzzers/difficulty/debug/main.go b/tests/fuzzers/difficulty/debug/main.go new file mode 100644 index 0000000000..23516b3a0d --- /dev/null +++ b/tests/fuzzers/difficulty/debug/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/tests/fuzzers/difficulty" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: debug ") + os.Exit(1) + } + crasher := os.Args[1] + data, err := ioutil.ReadFile(crasher) + if err != nil { + fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) + os.Exit(1) + } + difficulty.Fuzz(data) +} diff --git a/tests/fuzzers/difficulty/difficulty-fuzz.go b/tests/fuzzers/difficulty/difficulty-fuzz.go new file mode 100644 index 0000000000..e4c5dcf57c --- /dev/null +++ b/tests/fuzzers/difficulty/difficulty-fuzz.go @@ -0,0 +1,145 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package difficulty + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/types" +) + +type fuzzer struct { + input io.Reader + exhausted bool + debugging bool +} + +func (f *fuzzer) read(size int) []byte { + out := make([]byte, size) + if _, err := f.input.Read(out); err != nil { + f.exhausted = true + } + return out +} + +func (f *fuzzer) readSlice(min, max int) []byte { + var a uint16 + binary.Read(f.input, binary.LittleEndian, &a) + size := min + int(a)%(max-min) + out := make([]byte, size) + if _, err := f.input.Read(out); err != nil { + f.exhausted = true + } + return out +} + +func (f *fuzzer) readUint64(min, max uint64) uint64 { + if min == max { + return min + } + var a uint64 + if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { + f.exhausted = true + } + a = min + a%(max-min) + return a +} +func (f *fuzzer) readBool() bool { + return f.read(1)[0]&0x1 == 0 +} + +// The function must return +// 1 if the fuzzer should increase priority of the +// given input during subsequent fuzzing (for example, the input is lexically +// correct and was parsed successfully); +// -1 if the input must not be added to corpus even if gives new coverage; and +// 0 otherwise +// other values are reserved for future use. +func Fuzz(data []byte) int { + f := fuzzer{ + input: bytes.NewReader(data), + exhausted: false, + } + return f.fuzz() +} + +var minDifficulty = big.NewInt(0x2000) + +type calculator func(time uint64, parent *types.Header) *big.Int + +func (f *fuzzer) fuzz() int { + // A parent header + header := &types.Header{} + if f.readBool() { + header.UncleHash = types.EmptyUncleHash + } + // Difficulty can range between 0x2000 (2 bytes) and up to 32 bytes + { + diff := new(big.Int).SetBytes(f.readSlice(2, 32)) + if diff.Cmp(minDifficulty) < 0 { + diff.Set(minDifficulty) + } + header.Difficulty = diff + } + // Number can range between 0 and up to 32 bytes (but not so that the child exceeds it) + { + // However, if we use astronomic numbers, then the bomb exp karatsuba calculation + // in the legacy methods) + // times out, so we limit it to fit within reasonable bounds + number := new(big.Int).SetBytes(f.readSlice(0, 4)) // 4 bytes: 32 bits: block num max 4 billion + header.Number = number + } + // Both parent and child time must fit within uint64 + var time uint64 + { + childTime := f.readUint64(1, 0xFFFFFFFFFFFFFFFF) + //fmt.Printf("childTime: %x\n",childTime) + delta := f.readUint64(1, childTime) + //fmt.Printf("delta: %v\n", delta) + pTime := childTime - delta + header.Time = pTime + time = childTime + } + // Bomb delay will never exceed uint64 + bombDelay := new(big.Int).SetUint64(f.readUint64(1, 0xFFFFFFFFFFFFFFFe)) + + if f.exhausted { + return 0 + } + + for i, pair := range []struct { + bigFn calculator + u256Fn calculator + }{ + {ethash.FrontierDifficultyCalulator, ethash.CalcDifficultyFrontierU256}, + {ethash.HomesteadDifficultyCalulator, ethash.CalcDifficultyHomesteadU256}, + {ethash.DynamicDifficultyCalculator(bombDelay), ethash.MakeDifficultyCalculatorU256(bombDelay)}, + } { + want := pair.bigFn(time, header) + have := pair.u256Fn(time, header) + if want.Cmp(have) != 0 { + panic(fmt.Sprintf("pair %d: want %x have %x\nparent.Number: %x\np.Time: %x\nc.Time: %x\nBombdelay: %v\n", i, want, have, + header.Number, header.Time, time, bombDelay)) + } + } + return 1 +} From c49aae987040b3c0b846c5acb006fdba1eae282b Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 11 Dec 2020 16:49:44 +0200 Subject: [PATCH 065/235] consensus: refactor FinalizeAndAssemble to use Finalize (#21993) --- consensus/clique/clique.go | 5 ++--- consensus/ethash/consensus.go | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index c05f84cc2e..6c667804d8 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -561,9 +561,8 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade // FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set, // nor block rewards given, and returns the final block. func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { - // No block rewards in PoA, so the state remains as is and uncles are dropped - header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - header.UncleHash = types.CalcUncleHash(nil) + // Finalize block + c.Finalize(chain, header, state, txs, uncles) // Assemble and return the final block for sealing return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)), nil diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 8e401af7ca..ae0905ee3a 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -584,9 +584,8 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types. // FinalizeAndAssemble implements consensus.Engine, accumulating the block and // uncle rewards, setting the final state and assembling the block. func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { - // Accumulate any block and uncle rewards and commit the final state root - accumulateRewards(chain.Config(), state, header, uncles) - header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + // Finalize block + ethash.Finalize(chain, header, state, txs, uncles) // Header seems complete, assemble into a block and return return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie)), nil From 4d48980e74e7925d40fb89683eac0b43f3540d77 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 11 Dec 2020 15:56:00 +0100 Subject: [PATCH 066/235] core, eth, les: implement unclean-shutdown marker (#21893) This PR implements unclean shutdown marker. Every time geth boots, it adds a timestamp to a list of timestamps in the database. This list is capped at 10. At a clean shutdown, the timestamp is removed again. Thus, when geth exits unclean, the marker remains, and at boot up we show the most recent unclean shutdowns to the user, which makes it easier to diagnose root-causes to certain problems. Co-authored-by: Nagy Salem --- core/rawdb/accessors_metadata.go | 63 +++++++++++++++++++++++++++++++- core/rawdb/database.go | 2 +- core/rawdb/schema.go | 6 ++- eth/backend.go | 15 ++++++++ les/client.go | 14 +++++++ 5 files changed, 95 insertions(+), 5 deletions(-) diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index 14a302a127..079e335fa6 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -18,6 +18,7 @@ package rawdb import ( "encoding/json" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" @@ -30,7 +31,7 @@ import ( func ReadDatabaseVersion(db ethdb.KeyValueReader) *uint64 { var version uint64 - enc, _ := db.Get(databaseVerisionKey) + enc, _ := db.Get(databaseVersionKey) if len(enc) == 0 { return nil } @@ -47,7 +48,7 @@ func WriteDatabaseVersion(db ethdb.KeyValueWriter, version uint64) { if err != nil { log.Crit("Failed to encode database version", "err", err) } - if err = db.Put(databaseVerisionKey, enc); err != nil { + if err = db.Put(databaseVersionKey, enc); err != nil { log.Crit("Failed to store the database version", "err", err) } } @@ -79,3 +80,61 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha log.Crit("Failed to store chain config", "err", err) } } + +// crashList is a list of unclean-shutdown-markers, for rlp-encoding to the +// database +type crashList struct { + Discarded uint64 // how many ucs have we deleted + Recent []uint64 // unix timestamps of 10 latest unclean shutdowns +} + +const crashesToKeep = 10 + +// PushUncleanShutdownMarker appends a new unclean shutdown marker and returns +// the previous data +// - a list of timestamps +// - a count of how many old unclean-shutdowns have been discarded +func PushUncleanShutdownMarker(db ethdb.KeyValueStore) ([]uint64, uint64, error) { + var uncleanShutdowns crashList + // Read old data + if data, err := db.Get(uncleanShutdownKey); err != nil { + log.Warn("Error reading unclean shutdown markers", "error", err) + } else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil { + return nil, 0, err + } + var discarded = uncleanShutdowns.Discarded + var previous = make([]uint64, len(uncleanShutdowns.Recent)) + copy(previous, uncleanShutdowns.Recent) + // Add a new (but cap it) + uncleanShutdowns.Recent = append(uncleanShutdowns.Recent, uint64(time.Now().Unix())) + if count := len(uncleanShutdowns.Recent); count > crashesToKeep+1 { + numDel := count - (crashesToKeep + 1) + uncleanShutdowns.Recent = uncleanShutdowns.Recent[numDel:] + uncleanShutdowns.Discarded += uint64(numDel) + } + // And save it again + data, _ := rlp.EncodeToBytes(uncleanShutdowns) + if err := db.Put(uncleanShutdownKey, data); err != nil { + log.Warn("Failed to write unclean-shutdown marker", "err", err) + return nil, 0, err + } + return previous, discarded, nil +} + +// PopUncleanShutdownMarker removes the last unclean shutdown marker +func PopUncleanShutdownMarker(db ethdb.KeyValueStore) { + var uncleanShutdowns crashList + // Read old data + if data, err := db.Get(uncleanShutdownKey); err != nil { + log.Warn("Error reading unclean shutdown markers", "error", err) + } else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil { + log.Error("Error decoding unclean shutdown markers", "error", err) // Should mos def _not_ happen + } + if l := len(uncleanShutdowns.Recent); l > 0 { + uncleanShutdowns.Recent = uncleanShutdowns.Recent[:l-1] + } + data, _ := rlp.EncodeToBytes(uncleanShutdowns) + if err := db.Put(uncleanShutdownKey, data); err != nil { + log.Warn("Failed to clear unclean-shutdown marker", "err", err) + } +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index b1ac3e9587..b01a31ebcd 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -355,7 +355,7 @@ func InspectDatabase(db ethdb.Database) error { bloomTrieNodes.Add(size) default: var accounted bool - for _, meta := range [][]byte{databaseVerisionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey} { + for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey} { if bytes.Equal(key, meta) { metadata.Add(size) accounted = true diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index dbc5025d5d..cff27b4bb0 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -27,8 +27,8 @@ import ( // The fields below define the low level database schema prefixing. var ( - // databaseVerisionKey tracks the current database version. - databaseVerisionKey = []byte("DatabaseVersion") + // databaseVersionKey tracks the current database version. + databaseVersionKey = []byte("DatabaseVersion") // headHeaderKey tracks the latest known header's hash. headHeaderKey = []byte("LastHeader") @@ -81,6 +81,8 @@ var ( preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db + uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db + // Chain index prefixes (use `i` + single byte to avoid mixing data types). BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress diff --git a/eth/backend.go b/eth/backend.go index 03b0b319b7..bb4275b92c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -24,6 +24,7 @@ import ( "runtime" "sync" "sync/atomic" + "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" @@ -220,6 +221,19 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { stack.RegisterAPIs(eth.APIs()) stack.RegisterProtocols(eth.Protocols()) stack.RegisterLifecycle(eth) + // Check for unclean shutdown + if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil { + log.Error("Could not update unclean-shutdown-marker list", "error", err) + } else { + if discards > 0 { + log.Warn("Old unclean shutdowns found", "count", discards) + } + for _, tstamp := range uncleanShutdowns { + t := time.Unix(int64(tstamp), 0) + log.Warn("Unclean shutdown detected", "booted", t, + "age", common.PrettyAge(t)) + } + } return eth, nil } @@ -543,6 +557,7 @@ func (s *Ethereum) Stop() error { s.miner.Stop() s.blockchain.Stop() s.engine.Close() + rawdb.PopUncleanShutdownMarker(s.chainDb) s.chainDb.Close() s.eventMux.Stop() return nil diff --git a/les/client.go b/les/client.go index 37250d076f..47997a098b 100644 --- a/les/client.go +++ b/les/client.go @@ -178,6 +178,19 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { stack.RegisterProtocols(leth.Protocols()) stack.RegisterLifecycle(leth) + // Check for unclean shutdown + if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil { + log.Error("Could not update unclean-shutdown-marker list", "error", err) + } else { + if discards > 0 { + log.Warn("Old unclean shutdowns found", "count", discards) + } + for _, tstamp := range uncleanShutdowns { + t := time.Unix(int64(tstamp), 0) + log.Warn("Unclean shutdown detected", "booted", t, + "age", common.PrettyAge(t)) + } + } return leth, nil } @@ -313,6 +326,7 @@ func (s *LightEthereum) Stop() error { s.engine.Close() s.pruner.close() s.eventMux.Stop() + rawdb.PopUncleanShutdownMarker(s.chainDb) s.chainDb.Close() s.wg.Wait() log.Info("Light ethereum stopped") From 38c1d592b7121f26dc661c1bc2bf0e32eba9d888 Mon Sep 17 00:00:00 2001 From: Connor Stein Date: Sat, 12 Dec 2020 04:16:34 -0500 Subject: [PATCH 067/235] abi/bind: fix error-handling in generated wrappers for functions returning structs (#22005) Fixes the template used when generating code, which in some scenarios would lead to panic instead of returning an error. --- accounts/abi/bind/bind_test.go | 39 ++++++++++++++++++++++++++++++++++ accounts/abi/bind/template.go | 3 +++ 2 files changed, 42 insertions(+) diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 1a8a17e45e..4a504516bb 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -569,6 +569,45 @@ var bindTests = []struct { nil, nil, }, + { + `NonExistentStruct`, + ` + contract NonExistentStruct { + function Struct() public view returns(uint256 a, uint256 b) { + return (10, 10); + } + } + `, + []string{`6080604052348015600f57600080fd5b5060888061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063d5f6622514602d575b600080fd5b6033604c565b6040805192835260208301919091528051918290030190f35b600a809156fea264697066735822beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef64736f6c6343decafe0033`}, + []string{`[{"inputs":[],"name":"Struct","outputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"stateMutability":"pure","type":"function"}]`}, + ` + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + `, + ` + // Create a simulator and wrap a non-deployed contract + + sim := backends.NewSimulatedBackend(core.GenesisAlloc{}, uint64(10000000000)) + defer sim.Close() + + nonexistent, err := NewNonExistentStruct(common.Address{}, sim) + if err != nil { + t.Fatalf("Failed to access non-existent contract: %v", err) + } + // Ensure that contract calls fail with the appropriate error + if res, err := nonexistent.Struct(nil); err == nil { + t.Fatalf("Call succeeded on non-existent contract: %v", res) + } else if (err != bind.ErrNoCode) { + t.Fatalf("Error mismatch: have %v, want %v", err, bind.ErrNoCode) + } + `, + nil, + nil, + nil, + nil, + }, // Tests that gas estimation works for contracts with weird gas mechanics too. { `FunkyGasPattern`, diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 8dac11f79f..351eabd258 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -304,6 +304,9 @@ var ( err := _{{$contract.Type}}.contract.Call(opts, &out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) {{if .Structured}} outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} }) + if err != nil { + return *outstruct, err + } {{range $i, $t := .Normalized.Outputs}} outstruct.{{.Name}} = out[{{$i}}].({{bindtype .Type $structs}}){{end}} From 00d10e610f9fef56b5ee9c27f7fe7c842eba2e9b Mon Sep 17 00:00:00 2001 From: Shiming Date: Sun, 13 Dec 2020 00:36:32 +0800 Subject: [PATCH 068/235] cmd/abigen: clarify abigen alias flag usage (#21875) * doc: clarify abigen alias flag usage update the `abigen --alias` flag help info, give an example to make it more clear related issue: https://github.com/ethereum/go-ethereum/issues/21846 * Update cmd/abigen/main.go Co-authored-by: ligi Co-authored-by: Martin Holst Swende Co-authored-by: ligi --- cmd/abigen/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index a74b0396d4..7b3b35e4e5 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -96,7 +96,7 @@ var ( } aliasFlag = cli.StringFlag{ Name: "alias", - Usage: "Comma separated aliases for function and event renaming, e.g. foo=bar", + Usage: "Comma separated aliases for function and event renaming, e.g. original1=alias1, original2=alias2", } ) From 017831dd5b33a68076aed7c9ff05e62b0dcb5f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 14 Dec 2020 11:27:15 +0200 Subject: [PATCH 069/235] core, eth: split eth package, implement snap protocol (#21482) This commit splits the eth package, separating the handling of eth and snap protocols. It also includes the capability to run snap sync (https://github.com/ethereum/devp2p/blob/master/caps/snap.md) , but does not enable it by default. Co-authored-by: Marius van der Wijden Co-authored-by: Martin Holst Swende --- cmd/geth/misccmd.go | 2 - cmd/utils/flags.go | 29 +- core/blockchain.go | 8 +- core/blockchain_snapshot_test.go | 2 +- core/forkid/forkid.go | 9 + core/rawdb/accessors_snapshot.go | 21 + core/rawdb/schema.go | 3 + core/state/snapshot/generate.go | 2 +- core/state/statedb.go | 24 +- eth/api_backend.go | 6 +- eth/api_test.go | 6 + eth/backend.go | 60 +- eth/config.go | 3 +- eth/discovery.go | 9 +- eth/downloader/downloader.go | 108 +- eth/downloader/downloader_test.go | 114 +- eth/downloader/modes.go | 9 +- eth/downloader/peer.go | 14 +- eth/downloader/queue.go | 49 +- eth/downloader/statesync.go | 31 +- eth/gen_config.go | 10 +- eth/handler.go | 867 ++---- eth/handler_eth.go | 218 ++ eth/handler_eth_test.go | 740 +++++ eth/handler_snap.go | 48 + eth/handler_test.go | 736 +---- eth/helper_test.go | 231 -- eth/peer.go | 804 +----- eth/peerset.go | 301 ++ eth/protocol.go | 221 -- eth/protocol_test.go | 459 --- eth/protocols/eth/broadcast.go | 195 ++ eth/protocols/eth/discovery.go | 65 + eth/protocols/eth/handler.go | 512 ++++ eth/protocols/eth/handler_test.go | 519 ++++ eth/protocols/eth/handshake.go | 107 + eth/protocols/eth/handshake_test.go | 91 + eth/protocols/eth/peer.go | 429 +++ eth/protocols/eth/peer_test.go | 61 + eth/protocols/eth/protocol.go | 279 ++ eth/protocols/eth/protocol_test.go | 68 + eth/protocols/snap/discovery.go | 32 + eth/protocols/snap/handler.go | 490 ++++ eth/protocols/snap/peer.go | 111 + eth/protocols/snap/protocol.go | 218 ++ eth/protocols/snap/sync.go | 2481 +++++++++++++++++ eth/sync.go | 129 +- eth/sync_test.go | 50 +- ethstats/ethstats.go | 14 +- graphql/graphql.go | 4 - graphql/schema.go | 2 - internal/ethapi/api.go | 17 +- internal/ethapi/backend.go | 1 - les/client.go | 2 +- les/enr_entry.go | 4 +- les/handler_test.go | 2 +- les/peer.go | 13 +- les/server_handler.go | 2 +- tests/block_test_util.go | 2 +- ...1c14030f26872e57bf1481084f151d71eed8161c-1 | Bin 0 -> 16005 bytes ...27e54254422543060a13ea8a4bc913d768e4adb6-2 | Bin 0 -> 15965 bytes ...6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 | Bin 0 -> 15976 bytes ...a67e63bc0c0004bd009944a6061297cb7d4ac238-1 | Bin 0 -> 14980 bytes ...ae892bbae0a843950bc8316496e595b1a194c009-4 | Bin 0 -> 15977 bytes ...ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 | Bin 0 -> 16000 bytes ...f50a6d57a46d30184aa294af5b252ab9701af7c9-2 | Bin 0 -> 1748 bytes tests/fuzzers/rangeproof/corpus/random.dat | Bin 0 -> 16000 bytes tests/fuzzers/rangeproof/debug/main.go | 41 + tests/fuzzers/rangeproof/rangeproof-fuzzer.go | 218 ++ trie/notary.go | 57 + trie/proof.go | 104 +- trie/proof_test.go | 104 +- trie/sync_bloom.go | 6 +- trie/trie.go | 39 +- 74 files changed, 8224 insertions(+), 3389 deletions(-) create mode 100644 eth/handler_eth.go create mode 100644 eth/handler_eth_test.go create mode 100644 eth/handler_snap.go delete mode 100644 eth/helper_test.go create mode 100644 eth/peerset.go delete mode 100644 eth/protocol.go delete mode 100644 eth/protocol_test.go create mode 100644 eth/protocols/eth/broadcast.go create mode 100644 eth/protocols/eth/discovery.go create mode 100644 eth/protocols/eth/handler.go create mode 100644 eth/protocols/eth/handler_test.go create mode 100644 eth/protocols/eth/handshake.go create mode 100644 eth/protocols/eth/handshake_test.go create mode 100644 eth/protocols/eth/peer.go create mode 100644 eth/protocols/eth/peer_test.go create mode 100644 eth/protocols/eth/protocol.go create mode 100644 eth/protocols/eth/protocol_test.go create mode 100644 eth/protocols/snap/discovery.go create mode 100644 eth/protocols/snap/handler.go create mode 100644 eth/protocols/snap/peer.go create mode 100644 eth/protocols/snap/protocol.go create mode 100644 eth/protocols/snap/sync.go create mode 100644 tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1 create mode 100644 tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2 create mode 100644 tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 create mode 100644 tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1 create mode 100644 tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4 create mode 100644 tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 create mode 100644 tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2 create mode 100644 tests/fuzzers/rangeproof/corpus/random.dat create mode 100644 tests/fuzzers/rangeproof/debug/main.go create mode 100644 tests/fuzzers/rangeproof/rangeproof-fuzzer.go create mode 100644 trie/notary.go diff --git a/cmd/geth/misccmd.go b/cmd/geth/misccmd.go index 967df2ada0..b347d31d97 100644 --- a/cmd/geth/misccmd.go +++ b/cmd/geth/misccmd.go @@ -25,7 +25,6 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/params" "gopkg.in/urfave/cli.v1" ) @@ -143,7 +142,6 @@ func version(ctx *cli.Context) error { fmt.Println("Git Commit Date:", gitDate) } fmt.Println("Architecture:", runtime.GOARCH) - fmt.Println("Protocol Versions:", eth.ProtocolVersions) fmt.Println("Go Version:", runtime.Version()) fmt.Println("Operating System:", runtime.GOOS) fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 051bdd6308..0b1695d0a5 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -187,7 +187,7 @@ var ( defaultSyncMode = eth.DefaultConfig.SyncMode SyncModeFlag = TextMarshalerFlag{ Name: "syncmode", - Usage: `Blockchain sync mode ("fast", "full", or "light")`, + Usage: `Blockchain sync mode ("fast", "full", "snap" or "light")`, Value: &defaultSyncMode, } GCModeFlag = cli.StringFlag{ @@ -1555,8 +1555,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100 } if !ctx.GlobalIsSet(SnapshotFlag.Name) { - cfg.TrieCleanCache += cfg.SnapshotCache - cfg.SnapshotCache = 0 // Disabled + // If snap-sync is requested, this flag is also required + if cfg.SyncMode == downloader.SnapSync { + log.Info("Snap sync requested, enabling --snapshot") + ctx.Set(SnapshotFlag.Name, "true") + } else { + cfg.TrieCleanCache += cfg.SnapshotCache + cfg.SnapshotCache = 0 // Disabled + } } if ctx.GlobalIsSet(DocRootFlag.Name) { cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name) @@ -1585,16 +1591,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCapFlag.Name) } if ctx.GlobalIsSet(NoDiscoverFlag.Name) { - cfg.DiscoveryURLs = []string{} + cfg.EthDiscoveryURLs, cfg.SnapDiscoveryURLs = []string{}, []string{} } else if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) { urls := ctx.GlobalString(DNSDiscoveryFlag.Name) if urls == "" { - cfg.DiscoveryURLs = []string{} + cfg.EthDiscoveryURLs = []string{} } else { - cfg.DiscoveryURLs = SplitAndTrim(urls) + cfg.EthDiscoveryURLs = SplitAndTrim(urls) } } - // Override any default configs for hard coded networks. switch { case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): @@ -1676,16 +1681,20 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // SetDNSDiscoveryDefaults configures DNS discovery with the given URL if // no URLs are set. func SetDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { - if cfg.DiscoveryURLs != nil { + if cfg.EthDiscoveryURLs != nil { return // already set through flags/config } - protocol := "all" if cfg.SyncMode == downloader.LightSync { protocol = "les" } if url := params.KnownDNSNetwork(genesis, protocol); url != "" { - cfg.DiscoveryURLs = []string{url} + cfg.EthDiscoveryURLs = []string{url} + } + if cfg.SyncMode == downloader.SnapSync { + if url := params.KnownDNSNetwork(genesis, "snap"); url != "" { + cfg.SnapDiscoveryURLs = []string{url} + } } } diff --git a/core/blockchain.go b/core/blockchain.go index bc1db49f37..d9505dcf69 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -659,12 +659,8 @@ func (bc *BlockChain) CurrentBlock() *types.Block { return bc.currentBlock.Load().(*types.Block) } -// Snapshot returns the blockchain snapshot tree. This method is mainly used for -// testing, to make it possible to verify the snapshot after execution. -// -// Warning: There are no guarantees about the safety of using the returned 'snap' if the -// blockchain is simultaneously importing blocks, so take care. -func (bc *BlockChain) Snapshot() *snapshot.Tree { +// Snapshots returns the blockchain snapshot tree. +func (bc *BlockChain) Snapshots() *snapshot.Tree { return bc.snaps } diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index e8d3b2470a..f35dae1678 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -751,7 +751,7 @@ func testSnapshot(t *testing.T, tt *snapshotTest) { t.Fatalf("Failed to recreate chain: %v", err) } chain.InsertChain(newBlocks) - chain.Snapshot().Cap(newBlocks[len(newBlocks)-1].Root(), 0) + chain.Snapshots().Cap(newBlocks[len(newBlocks)-1].Root(), 0) // Simulate the blockchain crash // Don't call chain.Stop here, so that no snapshot diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index c432858617..1bf3406828 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -84,6 +84,15 @@ func NewID(config *params.ChainConfig, genesis common.Hash, head uint64) ID { return ID{Hash: checksumToBytes(hash), Next: next} } +// NewIDWithChain calculates the Ethereum fork ID from an existing chain instance. +func NewIDWithChain(chain Blockchain) ID { + return NewID( + chain.Config(), + chain.Genesis().Hash(), + chain.CurrentHeader().Number.Uint64(), + ) +} + // NewFilter creates a filter that returns if a fork ID should be rejected or not // based on the local chain's status. func NewFilter(chain Blockchain) Filter { diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index 5bd48ad5fa..0a91d9353b 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -175,3 +175,24 @@ func DeleteSnapshotRecoveryNumber(db ethdb.KeyValueWriter) { log.Crit("Failed to remove snapshot recovery number", "err", err) } } + +// ReadSanpshotSyncStatus retrieves the serialized sync status saved at shutdown. +func ReadSanpshotSyncStatus(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(snapshotSyncStatusKey) + return data +} + +// WriteSnapshotSyncStatus stores the serialized sync status to save at shutdown. +func WriteSnapshotSyncStatus(db ethdb.KeyValueWriter, status []byte) { + if err := db.Put(snapshotSyncStatusKey, status); err != nil { + log.Crit("Failed to store snapshot sync status", "err", err) + } +} + +// DeleteSnapshotSyncStatus deletes the serialized sync status saved at the last +// shutdown +func DeleteSnapshotSyncStatus(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotSyncStatusKey); err != nil { + log.Crit("Failed to remove snapshot sync status", "err", err) + } +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index cff27b4bb0..2aabfd3baa 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -57,6 +57,9 @@ var ( // snapshotRecoveryKey tracks the snapshot recovery marker across restarts. snapshotRecoveryKey = []byte("SnapshotRecovery") + // snapshotSyncStatusKey tracks the snapshot sync status across restarts. + snapshotSyncStatusKey = []byte("SnapshotSyncStatus") + // txIndexTailKey tracks the oldest block whose transactions have been indexed. txIndexTailKey = []byte("TransactionIndexTail") diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 92c7640c40..4a2fa78d3a 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -241,7 +241,7 @@ func (dl *diskLayer) generate(stats *generatorStats) { if acc.Root != emptyRoot { storeTrie, err := trie.NewSecure(acc.Root, dl.triedb) if err != nil { - log.Error("Generator failed to access storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err) + log.Error("Generator failed to access storage trie", "root", dl.root, "account", accountHash, "stroot", acc.Root, "err", err) abort := <-dl.genAbort abort <- stats return diff --git a/core/state/statedb.go b/core/state/statedb.go index ed9a82379f..a9d1de2e06 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -314,14 +314,19 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { return common.Hash{} } -// GetProof returns the MerkleProof for a given Account -func (s *StateDB) GetProof(a common.Address) ([][]byte, error) { +// GetProof returns the Merkle proof for a given account. +func (s *StateDB) GetProof(addr common.Address) ([][]byte, error) { + return s.GetProofByHash(crypto.Keccak256Hash(addr.Bytes())) +} + +// GetProofByHash returns the Merkle proof for a given account. +func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) { var proof proofList - err := s.trie.Prove(crypto.Keccak256(a.Bytes()), 0, &proof) + err := s.trie.Prove(addrHash[:], 0, &proof) return proof, err } -// GetStorageProof returns the StorageProof for given key +// GetStorageProof returns the Merkle proof for given storage slot. func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) { var proof proofList trie := s.StorageTrie(a) @@ -332,6 +337,17 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, return proof, err } +// GetStorageProofByHash returns the Merkle proof for given storage slot. +func (s *StateDB) GetStorageProofByHash(a common.Address, key common.Hash) ([][]byte, error) { + var proof proofList + trie := s.StorageTrie(a) + if trie == nil { + return proof, errors.New("storage trie for requested address does not exist") + } + err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) + return proof, err +} + // GetCommittedState retrieves a value from the given account's committed storage trie. func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { stateObject := s.getStateObject(addr) diff --git a/eth/api_backend.go b/eth/api_backend.go index e7f676f178..2f7020475f 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -56,7 +56,7 @@ func (b *EthAPIBackend) CurrentBlock() *types.Block { } func (b *EthAPIBackend) SetHead(number uint64) { - b.eth.protocolManager.downloader.Cancel() + b.eth.handler.downloader.Cancel() b.eth.blockchain.SetHead(number) } @@ -272,10 +272,6 @@ func (b *EthAPIBackend) Downloader() *downloader.Downloader { return b.eth.Downloader() } -func (b *EthAPIBackend) ProtocolVersion() int { - return b.eth.EthVersion() -} - func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { return b.gpo.SuggestPrice(ctx) } diff --git a/eth/api_test.go b/eth/api_test.go index 2c9a2e54e8..b44eed40bc 100644 --- a/eth/api_test.go +++ b/eth/api_test.go @@ -57,6 +57,8 @@ func (h resultHash) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h resultHash) Less(i, j int) bool { return bytes.Compare(h[i].Bytes(), h[j].Bytes()) < 0 } func TestAccountRange(t *testing.T) { + t.Parallel() + var ( statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), nil) state, _ = state.New(common.Hash{}, statedb, nil) @@ -126,6 +128,8 @@ func TestAccountRange(t *testing.T) { } func TestEmptyAccountRange(t *testing.T) { + t.Parallel() + var ( statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) state, _ = state.New(common.Hash{}, statedb, nil) @@ -142,6 +146,8 @@ func TestEmptyAccountRange(t *testing.T) { } func TestStorageRangeAt(t *testing.T) { + t.Parallel() + // Create a state where account 0x010000... has a few storage entries. var ( state, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) diff --git a/eth/backend.go b/eth/backend.go index bb4275b92c..987dee6d55 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -40,6 +40,8 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -48,7 +50,6 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" @@ -59,10 +60,11 @@ type Ethereum struct { config *Config // Handlers - txPool *core.TxPool - blockchain *core.BlockChain - protocolManager *ProtocolManager - dialCandidates enode.Iterator + txPool *core.TxPool + blockchain *core.BlockChain + handler *handler + ethDialCandidates enode.Iterator + snapDialCandidates enode.Iterator // DB interfaces chainDb ethdb.Database // Block chain database @@ -145,7 +147,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { if bcVersion != nil { dbVer = fmt.Sprintf("%d", *bcVersion) } - log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId, "dbversion", dbVer) + log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer) if !config.SkipBcVersionCheck { if bcVersion != nil && *bcVersion > core.BlockChainVersion { @@ -196,7 +198,17 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { if checkpoint == nil { checkpoint = params.TrustedCheckpoints[genesisHash] } - if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist); err != nil { + if eth.handler, err = newHandler(&handlerConfig{ + Database: chainDb, + Chain: eth.blockchain, + TxPool: eth.txPool, + Network: config.NetworkId, + Sync: config.SyncMode, + BloomCache: uint64(cacheLimit), + EventMux: eth.eventMux, + Checkpoint: checkpoint, + Whitelist: config.Whitelist, + }); err != nil { return nil, err } eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) @@ -209,13 +221,16 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) - eth.dialCandidates, err = eth.setupDiscovery() + eth.ethDialCandidates, err = setupDiscovery(eth.config.EthDiscoveryURLs) + if err != nil { + return nil, err + } + eth.snapDialCandidates, err = setupDiscovery(eth.config.SnapDiscoveryURLs) if err != nil { return nil, err } - // Start the RPC service - eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer, eth.NetVersion()) + eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer) // Register the backend on the node stack.RegisterAPIs(eth.APIs()) @@ -310,7 +325,7 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "eth", Version: "1.0", - Service: downloader.NewPublicDownloaderAPI(s.protocolManager.downloader, s.eventMux), + Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux), Public: true, }, { Namespace: "miner", @@ -473,7 +488,7 @@ func (s *Ethereum) StartMining(threads int) error { } // If mining is started, we can disable the transaction rejection mechanism // introduced to speed sync times. - atomic.StoreUint32(&s.protocolManager.acceptTxs, 1) + atomic.StoreUint32(&s.handler.acceptTxs, 1) go s.miner.Start(eb) } @@ -504,21 +519,17 @@ func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } func (s *Ethereum) Engine() consensus.Engine { return s.engine } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) IsListening() bool { return true } // Always listening -func (s *Ethereum) EthVersion() int { return int(ProtocolVersions[0]) } -func (s *Ethereum) NetVersion() uint64 { return s.networkID } -func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } -func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 } +func (s *Ethereum) Downloader() *downloader.Downloader { return s.handler.downloader } +func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.handler.acceptTxs) == 1 } func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } // Protocols returns all the currently configured // network protocols to start. func (s *Ethereum) Protocols() []p2p.Protocol { - protos := make([]p2p.Protocol, len(ProtocolVersions)) - for i, vsn := range ProtocolVersions { - protos[i] = s.protocolManager.makeProtocol(vsn) - protos[i].Attributes = []enr.Entry{s.currentEthEntry()} - protos[i].DialCandidates = s.dialCandidates + protos := eth.MakeProtocols((*ethHandler)(s.handler), s.networkID, s.ethDialCandidates) + if s.config.SnapshotCache > 0 { + protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler), s.snapDialCandidates)...) } return protos } @@ -526,7 +537,7 @@ func (s *Ethereum) Protocols() []p2p.Protocol { // Start implements node.Lifecycle, starting all internal goroutines needed by the // Ethereum protocol implementation. func (s *Ethereum) Start() error { - s.startEthEntryUpdate(s.p2pServer.LocalNode()) + eth.StartENRUpdater(s.blockchain, s.p2pServer.LocalNode()) // Start the bloom bits servicing goroutines s.startBloomHandlers(params.BloomBitsBlocks) @@ -540,7 +551,7 @@ func (s *Ethereum) Start() error { maxPeers -= s.config.LightPeers } // Start the networking layer and the light server if requested - s.protocolManager.Start(maxPeers) + s.handler.Start(maxPeers) return nil } @@ -548,7 +559,7 @@ func (s *Ethereum) Start() error { // Ethereum protocol. func (s *Ethereum) Stop() error { // Stop all the peer-related stuff first. - s.protocolManager.Stop() + s.handler.Stop() // Then stop everything else. s.bloomIndexer.Close() @@ -560,5 +571,6 @@ func (s *Ethereum) Stop() error { rawdb.PopUncleanShutdownMarker(s.chainDb) s.chainDb.Close() s.eventMux.Stop() + return nil } diff --git a/eth/config.go b/eth/config.go index 0d90376d94..77d03e9569 100644 --- a/eth/config.go +++ b/eth/config.go @@ -115,7 +115,8 @@ type Config struct { // This can be set to list of enrtree:// URLs which will be queried for // for nodes to connect to. - DiscoveryURLs []string + EthDiscoveryURLs []string + SnapDiscoveryURLs []string NoPruning bool // Whether to disable pruning and flush everything to disk NoPrefetch bool // Whether to disable prefetching and only load state on demand diff --git a/eth/discovery.go b/eth/discovery.go index e7a281d356..855ce3b0e1 100644 --- a/eth/discovery.go +++ b/eth/discovery.go @@ -63,11 +63,12 @@ func (eth *Ethereum) currentEthEntry() *ethEntry { eth.blockchain.CurrentHeader().Number.Uint64())} } -// setupDiscovery creates the node discovery source for the eth protocol. -func (eth *Ethereum) setupDiscovery() (enode.Iterator, error) { - if len(eth.config.DiscoveryURLs) == 0 { +// setupDiscovery creates the node discovery source for the `eth` and `snap` +// protocols. +func setupDiscovery(urls []string) (enode.Iterator, error) { + if len(urls) == 0 { return nil, nil } client := dnsdisc.NewClient(dnsdisc.Config{}) - return client.NewIterator(eth.config.DiscoveryURLs...) + return client.NewIterator(urls...) } diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 686c1ace14..3123598437 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -38,7 +39,6 @@ import ( ) var ( - MaxHashFetch = 512 // Amount of hashes to be fetched per retrieval request MaxBlockFetch = 128 // Amount of blocks to be fetched per retrieval request MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request MaxSkeletonSize = 128 // Number of header fetches to need for a skeleton assembly @@ -89,7 +89,7 @@ var ( errCancelContentProcessing = errors.New("content processing canceled (requested)") errCanceled = errors.New("syncing canceled (requested)") errNoSyncActive = errors.New("no sync active") - errTooOld = errors.New("peer doesn't speak recent enough protocol version (need version >= 63)") + errTooOld = errors.New("peer doesn't speak recent enough protocol version (need version >= 64)") ) type Downloader struct { @@ -131,20 +131,22 @@ type Downloader struct { ancientLimit uint64 // The maximum block number which can be regarded as ancient data. // Channels - headerCh chan dataPack // [eth/62] Channel receiving inbound block headers - bodyCh chan dataPack // [eth/62] Channel receiving inbound block bodies - receiptCh chan dataPack // [eth/63] Channel receiving inbound receipts - bodyWakeCh chan bool // [eth/62] Channel to signal the block body fetcher of new tasks - receiptWakeCh chan bool // [eth/63] Channel to signal the receipt fetcher of new tasks - headerProcCh chan []*types.Header // [eth/62] Channel to feed the header processor new tasks + headerCh chan dataPack // Channel receiving inbound block headers + bodyCh chan dataPack // Channel receiving inbound block bodies + receiptCh chan dataPack // Channel receiving inbound receipts + bodyWakeCh chan bool // Channel to signal the block body fetcher of new tasks + receiptWakeCh chan bool // Channel to signal the receipt fetcher of new tasks + headerProcCh chan []*types.Header // Channel to feed the header processor new tasks // State sync pivotHeader *types.Header // Pivot block header to dynamically push the syncing state root pivotLock sync.RWMutex // Lock protecting pivot header reads from updates + snapSync bool // Whether to run state sync over the snap protocol + SnapSyncer *snap.Syncer // TODO(karalabe): make private! hack for now stateSyncStart chan *stateSync trackStateReq chan *stateReq - stateCh chan dataPack // [eth/63] Channel receiving inbound node state data + stateCh chan dataPack // Channel receiving inbound node state data // Cancellation and termination cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop) @@ -237,6 +239,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom, headerProcCh: make(chan []*types.Header, 1), quitCh: make(chan struct{}), stateCh: make(chan dataPack), + SnapSyncer: snap.NewSyncer(stateDb, stateBloom), stateSyncStart: make(chan *stateSync), syncStatsState: stateSyncStats{ processed: rawdb.ReadFastTrieProgress(stateDb), @@ -286,19 +289,16 @@ func (d *Downloader) Synchronising() bool { return atomic.LoadInt32(&d.synchronising) > 0 } -// SyncBloomContains tests if the syncbloom filter contains the given hash: -// - false: the bloom definitely does not contain hash -// - true: the bloom maybe contains hash -// -// While the bloom is being initialized (or is closed), all queries will return true. -func (d *Downloader) SyncBloomContains(hash []byte) bool { - return d.stateBloom == nil || d.stateBloom.Contains(hash) -} - // RegisterPeer injects a new download peer into the set of block source to be // used for fetching hashes and blocks from. -func (d *Downloader) RegisterPeer(id string, version int, peer Peer) error { - logger := log.New("peer", id) +func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:16]) + } logger.Trace("Registering sync peer") if err := d.peers.Register(newPeerConnection(id, version, peer, logger)); err != nil { logger.Error("Failed to register sync peer", "err", err) @@ -310,7 +310,7 @@ func (d *Downloader) RegisterPeer(id string, version int, peer Peer) error { } // RegisterLightPeer injects a light client peer, wrapping it so it appears as a regular peer. -func (d *Downloader) RegisterLightPeer(id string, version int, peer LightPeer) error { +func (d *Downloader) RegisterLightPeer(id string, version uint, peer LightPeer) error { return d.RegisterPeer(id, version, &lightPeerWrapper{peer}) } @@ -319,7 +319,13 @@ func (d *Downloader) RegisterLightPeer(id string, version int, peer LightPeer) e // the queue. func (d *Downloader) UnregisterPeer(id string) error { // Unregister the peer from the active peer set and revoke any fetch tasks - logger := log.New("peer", id) + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:16]) + } logger.Trace("Unregistering sync peer") if err := d.peers.Unregister(id); err != nil { logger.Error("Failed to unregister sync peer", "err", err) @@ -381,6 +387,16 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode if mode == FullSync && d.stateBloom != nil { d.stateBloom.Close() } + // If snap sync was requested, create the snap scheduler and switch to fast + // sync mode. Long term we could drop fast sync or merge the two together, + // but until snap becomes prevalent, we should support both. TODO(karalabe). + if mode == SnapSync { + if !d.snapSync { + log.Warn("Enabling snapshot sync prototype") + d.snapSync = true + } + mode = FastSync + } // Reset the queue, peer set and wake channels to clean any internal leftover state d.queue.Reset(blockCacheMaxItems, blockCacheInitialItems) d.peers.Reset() @@ -443,8 +459,8 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I d.mux.Post(DoneEvent{latest}) } }() - if p.version < 63 { - return errTooOld + if p.version < 64 { + return fmt.Errorf("%w, peer version: %d", errTooOld, p.version) } mode := d.getMode() @@ -1910,27 +1926,53 @@ func (d *Downloader) commitPivotBlock(result *fetchResult) error { // DeliverHeaders injects a new batch of block headers received from a remote // node into the download schedule. -func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) (err error) { - return d.deliver(id, d.headerCh, &headerPack{id, headers}, headerInMeter, headerDropMeter) +func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) error { + return d.deliver(d.headerCh, &headerPack{id, headers}, headerInMeter, headerDropMeter) } // DeliverBodies injects a new batch of block bodies received from a remote node. -func (d *Downloader) DeliverBodies(id string, transactions [][]*types.Transaction, uncles [][]*types.Header) (err error) { - return d.deliver(id, d.bodyCh, &bodyPack{id, transactions, uncles}, bodyInMeter, bodyDropMeter) +func (d *Downloader) DeliverBodies(id string, transactions [][]*types.Transaction, uncles [][]*types.Header) error { + return d.deliver(d.bodyCh, &bodyPack{id, transactions, uncles}, bodyInMeter, bodyDropMeter) } // DeliverReceipts injects a new batch of receipts received from a remote node. -func (d *Downloader) DeliverReceipts(id string, receipts [][]*types.Receipt) (err error) { - return d.deliver(id, d.receiptCh, &receiptPack{id, receipts}, receiptInMeter, receiptDropMeter) +func (d *Downloader) DeliverReceipts(id string, receipts [][]*types.Receipt) error { + return d.deliver(d.receiptCh, &receiptPack{id, receipts}, receiptInMeter, receiptDropMeter) } // DeliverNodeData injects a new batch of node state data received from a remote node. -func (d *Downloader) DeliverNodeData(id string, data [][]byte) (err error) { - return d.deliver(id, d.stateCh, &statePack{id, data}, stateInMeter, stateDropMeter) +func (d *Downloader) DeliverNodeData(id string, data [][]byte) error { + return d.deliver(d.stateCh, &statePack{id, data}, stateInMeter, stateDropMeter) +} + +// DeliverSnapPacket is invoked from a peer's message handler when it transmits a +// data packet for the local node to consume. +func (d *Downloader) DeliverSnapPacket(peer *snap.Peer, packet snap.Packet) error { + switch packet := packet.(type) { + case *snap.AccountRangePacket: + hashes, accounts, err := packet.Unpack() + if err != nil { + return err + } + return d.SnapSyncer.OnAccounts(peer, packet.ID, hashes, accounts, packet.Proof) + + case *snap.StorageRangesPacket: + hashset, slotset := packet.Unpack() + return d.SnapSyncer.OnStorage(peer, packet.ID, hashset, slotset, packet.Proof) + + case *snap.ByteCodesPacket: + return d.SnapSyncer.OnByteCodes(peer, packet.ID, packet.Codes) + + case *snap.TrieNodesPacket: + return d.SnapSyncer.OnTrieNodes(peer, packet.ID, packet.Nodes) + + default: + return fmt.Errorf("unexpected snap packet type: %T", packet) + } } // deliver injects a new batch of data received from a remote node. -func (d *Downloader) deliver(id string, destCh chan dataPack, packet dataPack, inMeter, dropMeter metrics.Meter) (err error) { +func (d *Downloader) deliver(destCh chan dataPack, packet dataPack, inMeter, dropMeter metrics.Meter) (err error) { // Update the delivery metrics for both good and failed deliveries inMeter.Mark(int64(packet.Items())) defer func() { diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 5e46042ae4..6578275d0c 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -390,7 +390,7 @@ func (dl *downloadTester) Rollback(hashes []common.Hash) { } // newPeer registers a new block download source into the downloader. -func (dl *downloadTester) newPeer(id string, version int, chain *testChain) error { +func (dl *downloadTester) newPeer(id string, version uint, chain *testChain) error { dl.lock.Lock() defer dl.lock.Unlock() @@ -518,8 +518,6 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng // Tests that simple synchronization against a canonical chain works correctly. // In this test common ancestor lookup should be short circuited and not require // binary searching. -func TestCanonicalSynchronisation63Full(t *testing.T) { testCanonicalSynchronisation(t, 63, FullSync) } -func TestCanonicalSynchronisation63Fast(t *testing.T) { testCanonicalSynchronisation(t, 63, FastSync) } func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonicalSynchronisation(t, 64, FullSync) } func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonicalSynchronisation(t, 64, FastSync) } func TestCanonicalSynchronisation65Full(t *testing.T) { testCanonicalSynchronisation(t, 65, FullSync) } @@ -528,7 +526,7 @@ func TestCanonicalSynchronisation65Light(t *testing.T) { testCanonicalSynchronisation(t, 65, LightSync) } -func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) { +func testCanonicalSynchronisation(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -547,14 +545,12 @@ func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) { // Tests that if a large batch of blocks are being downloaded, it is throttled // until the cached blocks are retrieved. -func TestThrottling63Full(t *testing.T) { testThrottling(t, 63, FullSync) } -func TestThrottling63Fast(t *testing.T) { testThrottling(t, 63, FastSync) } func TestThrottling64Full(t *testing.T) { testThrottling(t, 64, FullSync) } func TestThrottling64Fast(t *testing.T) { testThrottling(t, 64, FastSync) } func TestThrottling65Full(t *testing.T) { testThrottling(t, 65, FullSync) } func TestThrottling65Fast(t *testing.T) { testThrottling(t, 65, FastSync) } -func testThrottling(t *testing.T, protocol int, mode SyncMode) { +func testThrottling(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -632,15 +628,13 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) { // Tests that simple synchronization against a forked chain works correctly. In // this test common ancestor lookup should *not* be short circuited, and a full // binary search should be executed. -func TestForkedSync63Full(t *testing.T) { testForkedSync(t, 63, FullSync) } -func TestForkedSync63Fast(t *testing.T) { testForkedSync(t, 63, FastSync) } func TestForkedSync64Full(t *testing.T) { testForkedSync(t, 64, FullSync) } func TestForkedSync64Fast(t *testing.T) { testForkedSync(t, 64, FastSync) } func TestForkedSync65Full(t *testing.T) { testForkedSync(t, 65, FullSync) } func TestForkedSync65Fast(t *testing.T) { testForkedSync(t, 65, FastSync) } func TestForkedSync65Light(t *testing.T) { testForkedSync(t, 65, LightSync) } -func testForkedSync(t *testing.T, protocol int, mode SyncMode) { +func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -665,15 +659,13 @@ func testForkedSync(t *testing.T, protocol int, mode SyncMode) { // Tests that synchronising against a much shorter but much heavyer fork works // corrently and is not dropped. -func TestHeavyForkedSync63Full(t *testing.T) { testHeavyForkedSync(t, 63, FullSync) } -func TestHeavyForkedSync63Fast(t *testing.T) { testHeavyForkedSync(t, 63, FastSync) } func TestHeavyForkedSync64Full(t *testing.T) { testHeavyForkedSync(t, 64, FullSync) } func TestHeavyForkedSync64Fast(t *testing.T) { testHeavyForkedSync(t, 64, FastSync) } func TestHeavyForkedSync65Full(t *testing.T) { testHeavyForkedSync(t, 65, FullSync) } func TestHeavyForkedSync65Fast(t *testing.T) { testHeavyForkedSync(t, 65, FastSync) } func TestHeavyForkedSync65Light(t *testing.T) { testHeavyForkedSync(t, 65, LightSync) } -func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) { +func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -700,15 +692,13 @@ func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head, ensuring that malicious peers cannot waste resources by feeding // long dead chains. -func TestBoundedForkedSync63Full(t *testing.T) { testBoundedForkedSync(t, 63, FullSync) } -func TestBoundedForkedSync63Fast(t *testing.T) { testBoundedForkedSync(t, 63, FastSync) } func TestBoundedForkedSync64Full(t *testing.T) { testBoundedForkedSync(t, 64, FullSync) } func TestBoundedForkedSync64Fast(t *testing.T) { testBoundedForkedSync(t, 64, FastSync) } func TestBoundedForkedSync65Full(t *testing.T) { testBoundedForkedSync(t, 65, FullSync) } func TestBoundedForkedSync65Fast(t *testing.T) { testBoundedForkedSync(t, 65, FastSync) } func TestBoundedForkedSync65Light(t *testing.T) { testBoundedForkedSync(t, 65, LightSync) } -func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) { +func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -734,15 +724,13 @@ func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head for short but heavy forks too. These are a bit special because they // take different ancestor lookup paths. -func TestBoundedHeavyForkedSync63Full(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FullSync) } -func TestBoundedHeavyForkedSync63Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FastSync) } func TestBoundedHeavyForkedSync64Full(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FullSync) } func TestBoundedHeavyForkedSync64Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FastSync) } func TestBoundedHeavyForkedSync65Full(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FullSync) } func TestBoundedHeavyForkedSync65Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FastSync) } func TestBoundedHeavyForkedSync65Light(t *testing.T) { testBoundedHeavyForkedSync(t, 65, LightSync) } -func testBoundedHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) { +func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -786,15 +774,13 @@ func TestInactiveDownloader63(t *testing.T) { } // Tests that a canceled download wipes all previously accumulated state. -func TestCancel63Full(t *testing.T) { testCancel(t, 63, FullSync) } -func TestCancel63Fast(t *testing.T) { testCancel(t, 63, FastSync) } func TestCancel64Full(t *testing.T) { testCancel(t, 64, FullSync) } func TestCancel64Fast(t *testing.T) { testCancel(t, 64, FastSync) } func TestCancel65Full(t *testing.T) { testCancel(t, 65, FullSync) } func TestCancel65Fast(t *testing.T) { testCancel(t, 65, FastSync) } func TestCancel65Light(t *testing.T) { testCancel(t, 65, LightSync) } -func testCancel(t *testing.T, protocol int, mode SyncMode) { +func testCancel(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -819,15 +805,13 @@ func testCancel(t *testing.T, protocol int, mode SyncMode) { } // Tests that synchronisation from multiple peers works as intended (multi thread sanity test). -func TestMultiSynchronisation63Full(t *testing.T) { testMultiSynchronisation(t, 63, FullSync) } -func TestMultiSynchronisation63Fast(t *testing.T) { testMultiSynchronisation(t, 63, FastSync) } func TestMultiSynchronisation64Full(t *testing.T) { testMultiSynchronisation(t, 64, FullSync) } func TestMultiSynchronisation64Fast(t *testing.T) { testMultiSynchronisation(t, 64, FastSync) } func TestMultiSynchronisation65Full(t *testing.T) { testMultiSynchronisation(t, 65, FullSync) } func TestMultiSynchronisation65Fast(t *testing.T) { testMultiSynchronisation(t, 65, FastSync) } func TestMultiSynchronisation65Light(t *testing.T) { testMultiSynchronisation(t, 65, LightSync) } -func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) { +func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -849,15 +833,13 @@ func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) { // Tests that synchronisations behave well in multi-version protocol environments // and not wreak havoc on other nodes in the network. -func TestMultiProtoSynchronisation63Full(t *testing.T) { testMultiProtoSync(t, 63, FullSync) } -func TestMultiProtoSynchronisation63Fast(t *testing.T) { testMultiProtoSync(t, 63, FastSync) } func TestMultiProtoSynchronisation64Full(t *testing.T) { testMultiProtoSync(t, 64, FullSync) } func TestMultiProtoSynchronisation64Fast(t *testing.T) { testMultiProtoSync(t, 64, FastSync) } func TestMultiProtoSynchronisation65Full(t *testing.T) { testMultiProtoSync(t, 65, FullSync) } func TestMultiProtoSynchronisation65Fast(t *testing.T) { testMultiProtoSync(t, 65, FastSync) } func TestMultiProtoSynchronisation65Light(t *testing.T) { testMultiProtoSync(t, 65, LightSync) } -func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) { +func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -888,15 +870,13 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) { // Tests that if a block is empty (e.g. header only), no body request should be // made, and instead the header should be assembled into a whole block in itself. -func TestEmptyShortCircuit63Full(t *testing.T) { testEmptyShortCircuit(t, 63, FullSync) } -func TestEmptyShortCircuit63Fast(t *testing.T) { testEmptyShortCircuit(t, 63, FastSync) } func TestEmptyShortCircuit64Full(t *testing.T) { testEmptyShortCircuit(t, 64, FullSync) } func TestEmptyShortCircuit64Fast(t *testing.T) { testEmptyShortCircuit(t, 64, FastSync) } func TestEmptyShortCircuit65Full(t *testing.T) { testEmptyShortCircuit(t, 65, FullSync) } func TestEmptyShortCircuit65Fast(t *testing.T) { testEmptyShortCircuit(t, 65, FastSync) } func TestEmptyShortCircuit65Light(t *testing.T) { testEmptyShortCircuit(t, 65, LightSync) } -func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) { +func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -942,15 +922,13 @@ func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) { // Tests that headers are enqueued continuously, preventing malicious nodes from // stalling the downloader by feeding gapped header chains. -func TestMissingHeaderAttack63Full(t *testing.T) { testMissingHeaderAttack(t, 63, FullSync) } -func TestMissingHeaderAttack63Fast(t *testing.T) { testMissingHeaderAttack(t, 63, FastSync) } func TestMissingHeaderAttack64Full(t *testing.T) { testMissingHeaderAttack(t, 64, FullSync) } func TestMissingHeaderAttack64Fast(t *testing.T) { testMissingHeaderAttack(t, 64, FastSync) } func TestMissingHeaderAttack65Full(t *testing.T) { testMissingHeaderAttack(t, 65, FullSync) } func TestMissingHeaderAttack65Fast(t *testing.T) { testMissingHeaderAttack(t, 65, FastSync) } func TestMissingHeaderAttack65Light(t *testing.T) { testMissingHeaderAttack(t, 65, LightSync) } -func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) { +func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -974,15 +952,13 @@ func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) { // Tests that if requested headers are shifted (i.e. first is missing), the queue // detects the invalid numbering. -func TestShiftedHeaderAttack63Full(t *testing.T) { testShiftedHeaderAttack(t, 63, FullSync) } -func TestShiftedHeaderAttack63Fast(t *testing.T) { testShiftedHeaderAttack(t, 63, FastSync) } func TestShiftedHeaderAttack64Full(t *testing.T) { testShiftedHeaderAttack(t, 64, FullSync) } func TestShiftedHeaderAttack64Fast(t *testing.T) { testShiftedHeaderAttack(t, 64, FastSync) } func TestShiftedHeaderAttack65Full(t *testing.T) { testShiftedHeaderAttack(t, 65, FullSync) } func TestShiftedHeaderAttack65Fast(t *testing.T) { testShiftedHeaderAttack(t, 65, FastSync) } func TestShiftedHeaderAttack65Light(t *testing.T) { testShiftedHeaderAttack(t, 65, LightSync) } -func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) { +func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1011,11 +987,10 @@ func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) { // Tests that upon detecting an invalid header, the recent ones are rolled back // for various failure scenarios. Afterwards a full sync is attempted to make // sure no state was corrupted. -func TestInvalidHeaderRollback63Fast(t *testing.T) { testInvalidHeaderRollback(t, 63, FastSync) } func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) } func TestInvalidHeaderRollback65Fast(t *testing.T) { testInvalidHeaderRollback(t, 65, FastSync) } -func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) { +func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1103,15 +1078,13 @@ func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) { // Tests that a peer advertising a high TD doesn't get to stall the downloader // afterwards by not sending any useful hashes. -func TestHighTDStarvationAttack63Full(t *testing.T) { testHighTDStarvationAttack(t, 63, FullSync) } -func TestHighTDStarvationAttack63Fast(t *testing.T) { testHighTDStarvationAttack(t, 63, FastSync) } func TestHighTDStarvationAttack64Full(t *testing.T) { testHighTDStarvationAttack(t, 64, FullSync) } func TestHighTDStarvationAttack64Fast(t *testing.T) { testHighTDStarvationAttack(t, 64, FastSync) } func TestHighTDStarvationAttack65Full(t *testing.T) { testHighTDStarvationAttack(t, 65, FullSync) } func TestHighTDStarvationAttack65Fast(t *testing.T) { testHighTDStarvationAttack(t, 65, FastSync) } func TestHighTDStarvationAttack65Light(t *testing.T) { testHighTDStarvationAttack(t, 65, LightSync) } -func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) { +func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1125,11 +1098,10 @@ func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) { } // Tests that misbehaving peers are disconnected, whilst behaving ones are not. -func TestBlockHeaderAttackerDropping63(t *testing.T) { testBlockHeaderAttackerDropping(t, 63) } func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) } func TestBlockHeaderAttackerDropping65(t *testing.T) { testBlockHeaderAttackerDropping(t, 65) } -func testBlockHeaderAttackerDropping(t *testing.T, protocol int) { +func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { t.Parallel() // Define the disconnection requirement for individual hash fetch errors @@ -1179,15 +1151,13 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol int) { // Tests that synchronisation progress (origin block number, current block number // and highest block number) is tracked and updated correctly. -func TestSyncProgress63Full(t *testing.T) { testSyncProgress(t, 63, FullSync) } -func TestSyncProgress63Fast(t *testing.T) { testSyncProgress(t, 63, FastSync) } func TestSyncProgress64Full(t *testing.T) { testSyncProgress(t, 64, FullSync) } func TestSyncProgress64Fast(t *testing.T) { testSyncProgress(t, 64, FastSync) } func TestSyncProgress65Full(t *testing.T) { testSyncProgress(t, 65, FullSync) } func TestSyncProgress65Fast(t *testing.T) { testSyncProgress(t, 65, FastSync) } func TestSyncProgress65Light(t *testing.T) { testSyncProgress(t, 65, LightSync) } -func testSyncProgress(t *testing.T, protocol int, mode SyncMode) { +func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1263,21 +1233,19 @@ func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.Sync // Tests that synchronisation progress (origin block number and highest block // number) is tracked and updated correctly in case of a fork (or manual head // revertal). -func TestForkedSyncProgress63Full(t *testing.T) { testForkedSyncProgress(t, 63, FullSync) } -func TestForkedSyncProgress63Fast(t *testing.T) { testForkedSyncProgress(t, 63, FastSync) } func TestForkedSyncProgress64Full(t *testing.T) { testForkedSyncProgress(t, 64, FullSync) } func TestForkedSyncProgress64Fast(t *testing.T) { testForkedSyncProgress(t, 64, FastSync) } func TestForkedSyncProgress65Full(t *testing.T) { testForkedSyncProgress(t, 65, FullSync) } func TestForkedSyncProgress65Fast(t *testing.T) { testForkedSyncProgress(t, 65, FastSync) } func TestForkedSyncProgress65Light(t *testing.T) { testForkedSyncProgress(t, 65, LightSync) } -func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) { +func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() defer tester.terminate() - chainA := testChainForkLightA.shorten(testChainBase.len() + MaxHashFetch) - chainB := testChainForkLightB.shorten(testChainBase.len() + MaxHashFetch) + chainA := testChainForkLightA.shorten(testChainBase.len() + MaxHeaderFetch) + chainB := testChainForkLightB.shorten(testChainBase.len() + MaxHeaderFetch) // Set a sync init hook to catch progress changes starting := make(chan struct{}) @@ -1339,15 +1307,13 @@ func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) { // Tests that if synchronisation is aborted due to some failure, then the progress // origin is not updated in the next sync cycle, as it should be considered the // continuation of the previous sync and not a new instance. -func TestFailedSyncProgress63Full(t *testing.T) { testFailedSyncProgress(t, 63, FullSync) } -func TestFailedSyncProgress63Fast(t *testing.T) { testFailedSyncProgress(t, 63, FastSync) } func TestFailedSyncProgress64Full(t *testing.T) { testFailedSyncProgress(t, 64, FullSync) } func TestFailedSyncProgress64Fast(t *testing.T) { testFailedSyncProgress(t, 64, FastSync) } func TestFailedSyncProgress65Full(t *testing.T) { testFailedSyncProgress(t, 65, FullSync) } func TestFailedSyncProgress65Fast(t *testing.T) { testFailedSyncProgress(t, 65, FastSync) } func TestFailedSyncProgress65Light(t *testing.T) { testFailedSyncProgress(t, 65, LightSync) } -func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) { +func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1412,15 +1378,13 @@ func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) { // Tests that if an attacker fakes a chain height, after the attack is detected, // the progress height is successfully reduced at the next sync invocation. -func TestFakedSyncProgress63Full(t *testing.T) { testFakedSyncProgress(t, 63, FullSync) } -func TestFakedSyncProgress63Fast(t *testing.T) { testFakedSyncProgress(t, 63, FastSync) } func TestFakedSyncProgress64Full(t *testing.T) { testFakedSyncProgress(t, 64, FullSync) } func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, FastSync) } func TestFakedSyncProgress65Full(t *testing.T) { testFakedSyncProgress(t, 65, FullSync) } func TestFakedSyncProgress65Fast(t *testing.T) { testFakedSyncProgress(t, 65, FastSync) } func TestFakedSyncProgress65Light(t *testing.T) { testFakedSyncProgress(t, 65, LightSync) } -func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) { +func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1489,31 +1453,15 @@ func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) { // This test reproduces an issue where unexpected deliveries would // block indefinitely if they arrived at the right time. -func TestDeliverHeadersHang(t *testing.T) { - t.Parallel() +func TestDeliverHeadersHang64Full(t *testing.T) { testDeliverHeadersHang(t, 64, FullSync) } +func TestDeliverHeadersHang64Fast(t *testing.T) { testDeliverHeadersHang(t, 64, FastSync) } +func TestDeliverHeadersHang65Full(t *testing.T) { testDeliverHeadersHang(t, 65, FullSync) } +func TestDeliverHeadersHang65Fast(t *testing.T) { testDeliverHeadersHang(t, 65, FastSync) } +func TestDeliverHeadersHang65Light(t *testing.T) { testDeliverHeadersHang(t, 65, LightSync) } - testCases := []struct { - protocol int - syncMode SyncMode - }{ - {63, FullSync}, - {63, FastSync}, - {64, FullSync}, - {64, FastSync}, - {64, LightSync}, - {65, FullSync}, - {65, FastSync}, - {65, LightSync}, - } - for _, tc := range testCases { - t.Run(fmt.Sprintf("protocol %d mode %v", tc.protocol, tc.syncMode), func(t *testing.T) { - t.Parallel() - testDeliverHeadersHang(t, tc.protocol, tc.syncMode) - }) - } -} +func testDeliverHeadersHang(t *testing.T, protocol uint, mode SyncMode) { + t.Parallel() -func testDeliverHeadersHang(t *testing.T, protocol int, mode SyncMode) { master := newTester() defer master.terminate() chain := testChainBase.shorten(15) @@ -1664,15 +1612,13 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { // Tests that peers below a pre-configured checkpoint block are prevented from // being fast-synced from, avoiding potential cheap eclipse attacks. -func TestCheckpointEnforcement63Full(t *testing.T) { testCheckpointEnforcement(t, 63, FullSync) } -func TestCheckpointEnforcement63Fast(t *testing.T) { testCheckpointEnforcement(t, 63, FastSync) } func TestCheckpointEnforcement64Full(t *testing.T) { testCheckpointEnforcement(t, 64, FullSync) } func TestCheckpointEnforcement64Fast(t *testing.T) { testCheckpointEnforcement(t, 64, FastSync) } func TestCheckpointEnforcement65Full(t *testing.T) { testCheckpointEnforcement(t, 65, FullSync) } func TestCheckpointEnforcement65Fast(t *testing.T) { testCheckpointEnforcement(t, 65, FastSync) } func TestCheckpointEnforcement65Light(t *testing.T) { testCheckpointEnforcement(t, 65, LightSync) } -func testCheckpointEnforcement(t *testing.T, protocol int, mode SyncMode) { +func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() // Create a new tester with a particular hard coded checkpoint block diff --git a/eth/downloader/modes.go b/eth/downloader/modes.go index d866ceabce..8ea7876a1f 100644 --- a/eth/downloader/modes.go +++ b/eth/downloader/modes.go @@ -24,7 +24,8 @@ type SyncMode uint32 const ( FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks - FastSync // Quickly download the headers, full sync only at the chain head + FastSync // Quickly download the headers, full sync only at the chain + SnapSync // Download the chain and the state via compact snashots LightSync // Download only the headers and terminate afterwards ) @@ -39,6 +40,8 @@ func (mode SyncMode) String() string { return "full" case FastSync: return "fast" + case SnapSync: + return "snap" case LightSync: return "light" default: @@ -52,6 +55,8 @@ func (mode SyncMode) MarshalText() ([]byte, error) { return []byte("full"), nil case FastSync: return []byte("fast"), nil + case SnapSync: + return []byte("snap"), nil case LightSync: return []byte("light"), nil default: @@ -65,6 +70,8 @@ func (mode *SyncMode) UnmarshalText(text []byte) error { *mode = FullSync case "fast": *mode = FastSync + case "snap": + *mode = SnapSync case "light": *mode = LightSync default: diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index c6671436f9..ba90bf31cb 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -69,7 +69,7 @@ type peerConnection struct { peer Peer - version int // Eth protocol version number to switch strategies + version uint // Eth protocol version number to switch strategies log log.Logger // Contextual logger to add extra infos to peer logs lock sync.RWMutex } @@ -112,7 +112,7 @@ func (w *lightPeerWrapper) RequestNodeData([]common.Hash) error { } // newPeerConnection creates a new downloader peer. -func newPeerConnection(id string, version int, peer Peer, logger log.Logger) *peerConnection { +func newPeerConnection(id string, version uint, peer Peer, logger log.Logger) *peerConnection { return &peerConnection{ id: id, lacking: make(map[common.Hash]struct{}), @@ -457,7 +457,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.headerThroughput } - return ps.idlePeers(63, 65, idle, throughput) + return ps.idlePeers(64, 65, idle, throughput) } // BodyIdlePeers retrieves a flat list of all the currently body-idle peers within @@ -471,7 +471,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.blockThroughput } - return ps.idlePeers(63, 65, idle, throughput) + return ps.idlePeers(64, 65, idle, throughput) } // ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers @@ -485,7 +485,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.receiptThroughput } - return ps.idlePeers(63, 65, idle, throughput) + return ps.idlePeers(64, 65, idle, throughput) } // NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle @@ -499,13 +499,13 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.stateThroughput } - return ps.idlePeers(63, 65, idle, throughput) + return ps.idlePeers(64, 65, idle, throughput) } // idlePeers retrieves a flat list of all currently idle peers satisfying the // protocol version constraints, using the provided function to check idleness. // The resulting set of peers are sorted by their measure throughput. -func (ps *peerSet) idlePeers(minProtocol, maxProtocol int, idleCheck func(*peerConnection) bool, throughput func(*peerConnection) float64) ([]*peerConnection, int) { +func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peerConnection) bool, throughput func(*peerConnection) float64) ([]*peerConnection, int) { ps.lock.RLock() defer ps.lock.RUnlock() diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index d2ec8ba694..2150842f8e 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -113,24 +113,24 @@ type queue struct { mode SyncMode // Synchronisation mode to decide on the block parts to schedule for fetching // Headers are "special", they download in batches, supported by a skeleton chain - headerHead common.Hash // [eth/62] Hash of the last queued header to verify order - headerTaskPool map[uint64]*types.Header // [eth/62] Pending header retrieval tasks, mapping starting indexes to skeleton headers - headerTaskQueue *prque.Prque // [eth/62] Priority queue of the skeleton indexes to fetch the filling headers for - headerPeerMiss map[string]map[uint64]struct{} // [eth/62] Set of per-peer header batches known to be unavailable - headerPendPool map[string]*fetchRequest // [eth/62] Currently pending header retrieval operations - headerResults []*types.Header // [eth/62] Result cache accumulating the completed headers - headerProced int // [eth/62] Number of headers already processed from the results - headerOffset uint64 // [eth/62] Number of the first header in the result cache - headerContCh chan bool // [eth/62] Channel to notify when header download finishes + headerHead common.Hash // Hash of the last queued header to verify order + headerTaskPool map[uint64]*types.Header // Pending header retrieval tasks, mapping starting indexes to skeleton headers + headerTaskQueue *prque.Prque // Priority queue of the skeleton indexes to fetch the filling headers for + headerPeerMiss map[string]map[uint64]struct{} // Set of per-peer header batches known to be unavailable + headerPendPool map[string]*fetchRequest // Currently pending header retrieval operations + headerResults []*types.Header // Result cache accumulating the completed headers + headerProced int // Number of headers already processed from the results + headerOffset uint64 // Number of the first header in the result cache + headerContCh chan bool // Channel to notify when header download finishes // All data retrievals below are based on an already assembles header chain - blockTaskPool map[common.Hash]*types.Header // [eth/62] Pending block (body) retrieval tasks, mapping hashes to headers - blockTaskQueue *prque.Prque // [eth/62] Priority queue of the headers to fetch the blocks (bodies) for - blockPendPool map[string]*fetchRequest // [eth/62] Currently pending block (body) retrieval operations + blockTaskPool map[common.Hash]*types.Header // Pending block (body) retrieval tasks, mapping hashes to headers + blockTaskQueue *prque.Prque // Priority queue of the headers to fetch the blocks (bodies) for + blockPendPool map[string]*fetchRequest // Currently pending block (body) retrieval operations - receiptTaskPool map[common.Hash]*types.Header // [eth/63] Pending receipt retrieval tasks, mapping hashes to headers - receiptTaskQueue *prque.Prque // [eth/63] Priority queue of the headers to fetch the receipts for - receiptPendPool map[string]*fetchRequest // [eth/63] Currently pending receipt retrieval operations + receiptTaskPool map[common.Hash]*types.Header // Pending receipt retrieval tasks, mapping hashes to headers + receiptTaskQueue *prque.Prque // Priority queue of the headers to fetch the receipts for + receiptPendPool map[string]*fetchRequest // Currently pending receipt retrieval operations resultCache *resultStore // Downloaded but not yet delivered fetch results resultSize common.StorageSize // Approximate size of a block (exponential moving average) @@ -690,6 +690,13 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh q.lock.Lock() defer q.lock.Unlock() + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:16]) + } // Short circuit if the data was never requested request := q.headerPendPool[id] if request == nil { @@ -704,10 +711,10 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh accepted := len(headers) == MaxHeaderFetch if accepted { if headers[0].Number.Uint64() != request.From { - log.Trace("First header broke chain ordering", "peer", id, "number", headers[0].Number, "hash", headers[0].Hash(), request.From) + logger.Trace("First header broke chain ordering", "number", headers[0].Number, "hash", headers[0].Hash(), "expected", request.From) accepted = false } else if headers[len(headers)-1].Hash() != target { - log.Trace("Last header broke skeleton structure ", "peer", id, "number", headers[len(headers)-1].Number, "hash", headers[len(headers)-1].Hash(), "expected", target) + logger.Trace("Last header broke skeleton structure ", "number", headers[len(headers)-1].Number, "hash", headers[len(headers)-1].Hash(), "expected", target) accepted = false } } @@ -716,12 +723,12 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh for i, header := range headers[1:] { hash := header.Hash() if want := request.From + 1 + uint64(i); header.Number.Uint64() != want { - log.Warn("Header broke chain ordering", "peer", id, "number", header.Number, "hash", hash, "expected", want) + logger.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", want) accepted = false break } if parentHash != header.ParentHash { - log.Warn("Header broke chain ancestry", "peer", id, "number", header.Number, "hash", hash) + logger.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash) accepted = false break } @@ -731,7 +738,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh } // If the batch of headers wasn't accepted, mark as unavailable if !accepted { - log.Trace("Skeleton filling not accepted", "peer", id, "from", request.From) + logger.Trace("Skeleton filling not accepted", "from", request.From) miss := q.headerPeerMiss[id] if miss == nil { @@ -758,7 +765,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh select { case headerProcCh <- process: - log.Trace("Pre-scheduled new headers", "peer", id, "count", len(process), "from", process[0].Number) + logger.Trace("Pre-scheduled new headers", "count", len(process), "from", process[0].Number) q.headerProced += len(process) default: } diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go index 6745aa54ac..69bd13c2f7 100644 --- a/eth/downloader/statesync.go +++ b/eth/downloader/statesync.go @@ -101,8 +101,16 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { finished []*stateReq // Completed or failed requests timeout = make(chan *stateReq) // Timed out active requests ) - // Run the state sync. log.Trace("State sync starting", "root", s.root) + + defer func() { + // Cancel active request timers on exit. Also set peers to idle so they're + // available for the next sync. + for _, req := range active { + req.timer.Stop() + req.peer.SetNodeDataIdle(int(req.nItems), time.Now()) + } + }() go s.run() defer s.Cancel() @@ -252,8 +260,9 @@ func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []* type stateSync struct { d *Downloader // Downloader instance to access and manage current peerset - sched *trie.Sync // State trie sync scheduler defining the tasks - keccak hash.Hash // Keccak256 hasher to verify deliveries with + root common.Hash // State root currently being synced + sched *trie.Sync // State trie sync scheduler defining the tasks + keccak hash.Hash // Keccak256 hasher to verify deliveries with trieTasks map[common.Hash]*trieTask // Set of trie node tasks currently queued for retrieval codeTasks map[common.Hash]*codeTask // Set of byte code tasks currently queued for retrieval @@ -268,8 +277,6 @@ type stateSync struct { cancelOnce sync.Once // Ensures cancel only ever gets called once done chan struct{} // Channel to signal termination completion err error // Any error hit during sync (set before completion) - - root common.Hash } // trieTask represents a single trie node download task, containing a set of @@ -290,6 +297,7 @@ type codeTask struct { func newStateSync(d *Downloader, root common.Hash) *stateSync { return &stateSync{ d: d, + root: root, sched: state.NewStateSync(root, d.stateDB, d.stateBloom), keccak: sha3.NewLegacyKeccak256(), trieTasks: make(map[common.Hash]*trieTask), @@ -298,7 +306,6 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync { cancel: make(chan struct{}), done: make(chan struct{}), started: make(chan struct{}), - root: root, } } @@ -306,7 +313,12 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync { // it finishes, and finally notifying any goroutines waiting for the loop to // finish. func (s *stateSync) run() { - s.err = s.loop() + close(s.started) + if s.d.snapSync { + s.err = s.d.SnapSyncer.Sync(s.root, s.cancel) + } else { + s.err = s.loop() + } close(s.done) } @@ -318,7 +330,9 @@ func (s *stateSync) Wait() error { // Cancel cancels the sync and waits until it has shut down. func (s *stateSync) Cancel() error { - s.cancelOnce.Do(func() { close(s.cancel) }) + s.cancelOnce.Do(func() { + close(s.cancel) + }) return s.Wait() } @@ -329,7 +343,6 @@ func (s *stateSync) Cancel() error { // pushed here async. The reason is to decouple processing from data receipt // and timeouts. func (s *stateSync) loop() (err error) { - close(s.started) // Listen for new peer events to assign tasks to them newPeer := make(chan *peerConnection, 1024) peerSub := s.d.peers.SubscribeNewPeers(newPeer) diff --git a/eth/gen_config.go b/eth/gen_config.go index b0674c7d77..dd04635eee 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -20,7 +20,7 @@ func (c Config) MarshalTOML() (interface{}, error) { Genesis *core.Genesis `toml:",omitempty"` NetworkId uint64 SyncMode downloader.SyncMode - DiscoveryURLs []string + EthDiscoveryURLs []string NoPruning bool NoPrefetch bool TxLookupLimit uint64 `toml:",omitempty"` @@ -61,7 +61,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.Genesis = c.Genesis enc.NetworkId = c.NetworkId enc.SyncMode = c.SyncMode - enc.DiscoveryURLs = c.DiscoveryURLs + enc.EthDiscoveryURLs = c.EthDiscoveryURLs enc.NoPruning = c.NoPruning enc.NoPrefetch = c.NoPrefetch enc.TxLookupLimit = c.TxLookupLimit @@ -106,7 +106,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { Genesis *core.Genesis `toml:",omitempty"` NetworkId *uint64 SyncMode *downloader.SyncMode - DiscoveryURLs []string + EthDiscoveryURLs []string NoPruning *bool NoPrefetch *bool TxLookupLimit *uint64 `toml:",omitempty"` @@ -156,8 +156,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.SyncMode != nil { c.SyncMode = *dec.SyncMode } - if dec.DiscoveryURLs != nil { - c.DiscoveryURLs = dec.DiscoveryURLs + if dec.EthDiscoveryURLs != nil { + c.EthDiscoveryURLs = dec.EthDiscoveryURLs } if dec.NoPruning != nil { c.NoPruning = *dec.NoPruning diff --git a/eth/handler.go b/eth/handler.go index 5b89986539..76a429f6d3 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -17,9 +17,7 @@ package eth import ( - "encoding/json" "errors" - "fmt" "math" "math/big" "sync" @@ -27,26 +25,22 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/fetcher" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) const ( - softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data. - estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header - // txChanSize is the size of channel listening to NewTxsEvent. // The number is referenced from the size of tx pool. txChanSize = 4096 @@ -56,26 +50,61 @@ var ( syncChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the sync progress challenge ) -func errResp(code errCode, format string, v ...interface{}) error { - return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...)) +// txPool defines the methods needed from a transaction pool implementation to +// support all the operations needed by the Ethereum chain protocols. +type txPool interface { + // Has returns an indicator whether txpool has a transaction + // cached with the given hash. + Has(hash common.Hash) bool + + // Get retrieves the transaction from local txpool with given + // tx hash. + Get(hash common.Hash) *types.Transaction + + // AddRemotes should add the given transactions to the pool. + AddRemotes([]*types.Transaction) []error + + // Pending should return pending transactions. + // The slice should be modifiable by the caller. + Pending() (map[common.Address]types.Transactions, error) + + // SubscribeNewTxsEvent should return an event subscription of + // NewTxsEvent and send events to the given channel. + SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription } -type ProtocolManager struct { +// handlerConfig is the collection of initialization parameters to create a full +// node network handler. +type handlerConfig struct { + Database ethdb.Database // Database for direct sync insertions + Chain *core.BlockChain // Blockchain to serve data from + TxPool txPool // Transaction pool to propagate from + Network uint64 // Network identifier to adfvertise + Sync downloader.SyncMode // Whether to fast or full sync + BloomCache uint64 // Megabytes to alloc for fast sync bloom + EventMux *event.TypeMux // Legacy event mux, deprecate for `feed` + Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges + Whitelist map[uint64]common.Hash // Hard coded whitelist for sync challenged +} + +type handler struct { networkID uint64 forkFilter forkid.Filter // Fork ID filter, constant across the lifetime of the node fastSync uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks) + snapSync uint32 // Flag whether fast sync should operate on top of the snap protocol acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing) checkpointNumber uint64 // Block number for the sync progress validator to cross reference checkpointHash common.Hash // Block hash for the sync progress validator to cross reference - txpool txPool - blockchain *core.BlockChain - chaindb ethdb.Database - maxPeers int + database ethdb.Database + txpool txPool + chain *core.BlockChain + maxPeers int downloader *downloader.Downloader + stateBloom *trie.SyncBloom blockFetcher *fetcher.BlockFetcher txFetcher *fetcher.TxFetcher peers *peerSet @@ -94,29 +123,27 @@ type ProtocolManager struct { chainSync *chainSyncer wg sync.WaitGroup peerWG sync.WaitGroup - - // Test fields or hooks - broadcastTxAnnouncesOnly bool // Testing field, disable transaction propagation } -// NewProtocolManager returns a new Ethereum sub protocol manager. The Ethereum sub protocol manages peers capable -// with the Ethereum network. -func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCheckpoint, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, cacheLimit int, whitelist map[uint64]common.Hash) (*ProtocolManager, error) { +// newHandler returns a handler for all Ethereum chain management protocol. +func newHandler(config *handlerConfig) (*handler, error) { // Create the protocol manager with the base fields - manager := &ProtocolManager{ - networkID: networkID, - forkFilter: forkid.NewFilter(blockchain), - eventMux: mux, - txpool: txpool, - blockchain: blockchain, - chaindb: chaindb, + if config.EventMux == nil { + config.EventMux = new(event.TypeMux) // Nicety initialization for tests + } + h := &handler{ + networkID: config.Network, + forkFilter: forkid.NewFilter(config.Chain), + eventMux: config.EventMux, + database: config.Database, + txpool: config.TxPool, + chain: config.Chain, peers: newPeerSet(), - whitelist: whitelist, + whitelist: config.Whitelist, txsyncCh: make(chan *txsync), quitSync: make(chan struct{}), } - - if mode == downloader.FullSync { + if config.Sync == downloader.FullSync { // The database seems empty as the current block is the genesis. Yet the fast // block is ahead, so fast sync was enabled for this node at a certain point. // The scenarios where this can happen is @@ -125,42 +152,42 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh // * the last fast sync is not finished while user specifies a full sync this // time. But we don't have any recent state for full sync. // In these cases however it's safe to reenable fast sync. - fullBlock, fastBlock := blockchain.CurrentBlock(), blockchain.CurrentFastBlock() + fullBlock, fastBlock := h.chain.CurrentBlock(), h.chain.CurrentFastBlock() if fullBlock.NumberU64() == 0 && fastBlock.NumberU64() > 0 { - manager.fastSync = uint32(1) + h.fastSync = uint32(1) log.Warn("Switch sync mode from full sync to fast sync") } } else { - if blockchain.CurrentBlock().NumberU64() > 0 { + if h.chain.CurrentBlock().NumberU64() > 0 { // Print warning log if database is not empty to run fast sync. log.Warn("Switch sync mode from fast sync to full sync") } else { // If fast sync was requested and our database is empty, grant it - manager.fastSync = uint32(1) + h.fastSync = uint32(1) + if config.Sync == downloader.SnapSync { + h.snapSync = uint32(1) + } } } - // If we have trusted checkpoints, enforce them on the chain - if checkpoint != nil { - manager.checkpointNumber = (checkpoint.SectionIndex+1)*params.CHTFrequency - 1 - manager.checkpointHash = checkpoint.SectionHead + if config.Checkpoint != nil { + h.checkpointNumber = (config.Checkpoint.SectionIndex+1)*params.CHTFrequency - 1 + h.checkpointHash = config.Checkpoint.SectionHead } - // Construct the downloader (long sync) and its backing state bloom if fast // sync is requested. The downloader is responsible for deallocating the state // bloom when it's done. - var stateBloom *trie.SyncBloom - if atomic.LoadUint32(&manager.fastSync) == 1 { - stateBloom = trie.NewSyncBloom(uint64(cacheLimit), chaindb) + if atomic.LoadUint32(&h.fastSync) == 1 { + h.stateBloom = trie.NewSyncBloom(config.BloomCache, config.Database) } - manager.downloader = downloader.New(manager.checkpointNumber, chaindb, stateBloom, manager.eventMux, blockchain, nil, manager.removePeer) + h.downloader = downloader.New(h.checkpointNumber, config.Database, h.stateBloom, h.eventMux, h.chain, nil, h.removePeer) // Construct the fetcher (short sync) validator := func(header *types.Header) error { - return engine.VerifyHeader(blockchain, header, true) + return h.chain.Engine().VerifyHeader(h.chain, header, true) } heighter := func() uint64 { - return blockchain.CurrentBlock().NumberU64() + return h.chain.CurrentBlock().NumberU64() } inserter := func(blocks types.Blocks) (int, error) { // If sync hasn't reached the checkpoint yet, deny importing weird blocks. @@ -169,7 +196,7 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh // the propagated block if the head is too old. Unfortunately there is a corner // case when starting new networks, where the genesis might be ancient (0 unix) // which would prevent full nodes from accepting it. - if manager.blockchain.CurrentBlock().NumberU64() < manager.checkpointNumber { + if h.chain.CurrentBlock().NumberU64() < h.checkpointNumber { log.Warn("Unsynced yet, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash()) return 0, nil } @@ -178,180 +205,88 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh // accept each others' blocks until a restart. Unfortunately we haven't figured // out a way yet where nodes can decide unilaterally whether the network is new // or not. This should be fixed if we figure out a solution. - if atomic.LoadUint32(&manager.fastSync) == 1 { + if atomic.LoadUint32(&h.fastSync) == 1 { log.Warn("Fast syncing, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash()) return 0, nil } - n, err := manager.blockchain.InsertChain(blocks) + n, err := h.chain.InsertChain(blocks) if err == nil { - atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import + atomic.StoreUint32(&h.acceptTxs, 1) // Mark initial sync done on any fetcher import } return n, err } - manager.blockFetcher = fetcher.NewBlockFetcher(false, nil, blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, nil, inserter, manager.removePeer) + h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, h.removePeer) fetchTx := func(peer string, hashes []common.Hash) error { - p := manager.peers.Peer(peer) + p := h.peers.ethPeer(peer) if p == nil { return errors.New("unknown peer") } return p.RequestTxs(hashes) } - manager.txFetcher = fetcher.NewTxFetcher(txpool.Has, txpool.AddRemotes, fetchTx) - - manager.chainSync = newChainSyncer(manager) - - return manager, nil -} - -func (pm *ProtocolManager) makeProtocol(version uint) p2p.Protocol { - length, ok := protocolLengths[version] - if !ok { - panic("makeProtocol for unknown version") - } - - return p2p.Protocol{ - Name: protocolName, - Version: version, - Length: length, - Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - return pm.runPeer(pm.newPeer(int(version), p, rw, pm.txpool.Get)) - }, - NodeInfo: func() interface{} { - return pm.NodeInfo() - }, - PeerInfo: func(id enode.ID) interface{} { - if p := pm.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil { - return p.Info() - } - return nil - }, - } + h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, h.txpool.AddRemotes, fetchTx) + h.chainSync = newChainSyncer(h) + return h, nil } -func (pm *ProtocolManager) removePeer(id string) { - // Short circuit if the peer was already removed - peer := pm.peers.Peer(id) - if peer == nil { - return - } - log.Debug("Removing Ethereum peer", "peer", id) - - // Unregister the peer from the downloader and Ethereum peer set - pm.downloader.UnregisterPeer(id) - pm.txFetcher.Drop(id) - - if err := pm.peers.Unregister(id); err != nil { - log.Error("Peer removal failed", "peer", id, "err", err) - } - // Hard disconnect at the networking layer - if peer != nil { - peer.Peer.Disconnect(p2p.DiscUselessPeer) - } -} - -func (pm *ProtocolManager) Start(maxPeers int) { - pm.maxPeers = maxPeers - - // broadcast transactions - pm.wg.Add(1) - pm.txsCh = make(chan core.NewTxsEvent, txChanSize) - pm.txsSub = pm.txpool.SubscribeNewTxsEvent(pm.txsCh) - go pm.txBroadcastLoop() - - // broadcast mined blocks - pm.wg.Add(1) - pm.minedBlockSub = pm.eventMux.Subscribe(core.NewMinedBlockEvent{}) - go pm.minedBroadcastLoop() - - // start sync handlers - pm.wg.Add(2) - go pm.chainSync.loop() - go pm.txsyncLoop64() // TODO(karalabe): Legacy initial tx echange, drop with eth/64. -} - -func (pm *ProtocolManager) Stop() { - pm.txsSub.Unsubscribe() // quits txBroadcastLoop - pm.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop - - // Quit chainSync and txsync64. - // After this is done, no new peers will be accepted. - close(pm.quitSync) - pm.wg.Wait() - - // Disconnect existing sessions. - // This also closes the gate for any new registrations on the peer set. - // sessions which are already established but not added to pm.peers yet - // will exit when they try to register. - pm.peers.Close() - pm.peerWG.Wait() - - log.Info("Ethereum protocol stopped") -} - -func (pm *ProtocolManager) newPeer(pv int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer { - return newPeer(pv, p, rw, getPooledTx) -} - -func (pm *ProtocolManager) runPeer(p *peer) error { - if !pm.chainSync.handlePeerEvent(p) { +// runEthPeer +func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { + if !h.chainSync.handlePeerEvent(peer) { return p2p.DiscQuitting } - pm.peerWG.Add(1) - defer pm.peerWG.Done() - return pm.handle(p) -} - -// handle is the callback invoked to manage the life cycle of an eth peer. When -// this function terminates, the peer is disconnected. -func (pm *ProtocolManager) handle(p *peer) error { - // Ignore maxPeers if this is a trusted peer - if pm.peers.Len() >= pm.maxPeers && !p.Peer.Info().Network.Trusted { - return p2p.DiscTooManyPeers - } - p.Log().Debug("Ethereum peer connected", "name", p.Name()) + h.peerWG.Add(1) + defer h.peerWG.Done() // Execute the Ethereum handshake var ( - genesis = pm.blockchain.Genesis() - head = pm.blockchain.CurrentHeader() + genesis = h.chain.Genesis() + head = h.chain.CurrentHeader() hash = head.Hash() number = head.Number.Uint64() - td = pm.blockchain.GetTd(hash, number) + td = h.chain.GetTd(hash, number) ) - forkID := forkid.NewID(pm.blockchain.Config(), pm.blockchain.Genesis().Hash(), pm.blockchain.CurrentHeader().Number.Uint64()) - if err := p.Handshake(pm.networkID, td, hash, genesis.Hash(), forkID, pm.forkFilter); err != nil { - p.Log().Debug("Ethereum handshake failed", "err", err) + forkID := forkid.NewID(h.chain.Config(), h.chain.Genesis().Hash(), h.chain.CurrentHeader().Number.Uint64()) + if err := peer.Handshake(h.networkID, td, hash, genesis.Hash(), forkID, h.forkFilter); err != nil { + peer.Log().Debug("Ethereum handshake failed", "err", err) return err } + // Ignore maxPeers if this is a trusted peer + if h.peers.Len() >= h.maxPeers && !peer.Peer.Info().Network.Trusted { + return p2p.DiscTooManyPeers + } + peer.Log().Debug("Ethereum peer connected", "name", peer.Name()) // Register the peer locally - if err := pm.peers.Register(p, pm.removePeer); err != nil { - p.Log().Error("Ethereum peer registration failed", "err", err) + if err := h.peers.registerEthPeer(peer); err != nil { + peer.Log().Error("Ethereum peer registration failed", "err", err) return err } - defer pm.removePeer(p.id) + defer h.removePeer(peer.ID()) + p := h.peers.ethPeer(peer.ID()) + if p == nil { + return errors.New("peer dropped during handling") + } // Register the peer in the downloader. If the downloader considers it banned, we disconnect - if err := pm.downloader.RegisterPeer(p.id, p.version, p); err != nil { + if err := h.downloader.RegisterPeer(peer.ID(), peer.Version(), peer); err != nil { return err } - pm.chainSync.handlePeerEvent(p) + h.chainSync.handlePeerEvent(peer) // Propagate existing transactions. new transactions appearing // after this will be sent via broadcasts. - pm.syncTransactions(p) + h.syncTransactions(peer) // If we have a trusted CHT, reject all peers below that (avoid fast sync eclipse) - if pm.checkpointHash != (common.Hash{}) { + if h.checkpointHash != (common.Hash{}) { // Request the peer's checkpoint header for chain height/weight validation - if err := p.RequestHeadersByNumber(pm.checkpointNumber, 1, 0, false); err != nil { + if err := peer.RequestHeadersByNumber(h.checkpointNumber, 1, 0, false); err != nil { return err } // Start a timer to disconnect if the peer doesn't reply in time p.syncDrop = time.AfterFunc(syncChallengeTimeout, func() { - p.Log().Warn("Checkpoint challenge timed out, dropping", "addr", p.RemoteAddr(), "type", p.Name()) - pm.removePeer(p.id) + peer.Log().Warn("Checkpoint challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Name()) + h.removePeer(peer.ID()) }) // Make sure it's cleaned up if the peer dies off defer func() { @@ -362,474 +297,115 @@ func (pm *ProtocolManager) handle(p *peer) error { }() } // If we have any explicit whitelist block hashes, request them - for number := range pm.whitelist { - if err := p.RequestHeadersByNumber(number, 1, 0, false); err != nil { + for number := range h.whitelist { + if err := peer.RequestHeadersByNumber(number, 1, 0, false); err != nil { return err } } // Handle incoming messages until the connection is torn down - for { - if err := pm.handleMsg(p); err != nil { - p.Log().Debug("Ethereum message handling failed", "err", err) - return err - } - } + return handler(peer) } -// handleMsg is invoked whenever an inbound message is received from a remote -// peer. The remote connection is torn down upon returning any error. -func (pm *ProtocolManager) handleMsg(p *peer) error { - // Read the next message from the remote peer, and ensure it's fully consumed - msg, err := p.rw.ReadMsg() - if err != nil { +// runSnapPeer +func (h *handler) runSnapPeer(peer *snap.Peer, handler snap.Handler) error { + h.peerWG.Add(1) + defer h.peerWG.Done() + + // Register the peer locally + if err := h.peers.registerSnapPeer(peer); err != nil { + peer.Log().Error("Snapshot peer registration failed", "err", err) return err } - if msg.Size > protocolMaxMsgSize { - return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize) - } - defer msg.Discard() - - // Handle the message depending on its contents - switch { - case msg.Code == StatusMsg: - // Status messages should never arrive after the handshake - return errResp(ErrExtraStatusMsg, "uncontrolled status message") - - // Block header query, collect the requested headers and reply - case msg.Code == GetBlockHeadersMsg: - // Decode the complex header query - var query getBlockHeadersData - if err := msg.Decode(&query); err != nil { - return errResp(ErrDecode, "%v: %v", msg, err) - } - hashMode := query.Origin.Hash != (common.Hash{}) - first := true - maxNonCanonical := uint64(100) - - // Gather headers until the fetch or network limits is reached - var ( - bytes common.StorageSize - headers []*types.Header - unknown bool - ) - for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && len(headers) < downloader.MaxHeaderFetch { - // Retrieve the next header satisfying the query - var origin *types.Header - if hashMode { - if first { - first = false - origin = pm.blockchain.GetHeaderByHash(query.Origin.Hash) - if origin != nil { - query.Origin.Number = origin.Number.Uint64() - } - } else { - origin = pm.blockchain.GetHeader(query.Origin.Hash, query.Origin.Number) - } - } else { - origin = pm.blockchain.GetHeaderByNumber(query.Origin.Number) - } - if origin == nil { - break - } - headers = append(headers, origin) - bytes += estHeaderRlpSize - - // Advance to the next header of the query - switch { - case hashMode && query.Reverse: - // Hash based traversal towards the genesis block - ancestor := query.Skip + 1 - if ancestor == 0 { - unknown = true - } else { - query.Origin.Hash, query.Origin.Number = pm.blockchain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) - unknown = (query.Origin.Hash == common.Hash{}) - } - case hashMode && !query.Reverse: - // Hash based traversal towards the leaf block - var ( - current = origin.Number.Uint64() - next = current + query.Skip + 1 - ) - if next <= current { - infos, _ := json.MarshalIndent(p.Peer.Info(), "", " ") - p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) - unknown = true - } else { - if header := pm.blockchain.GetHeaderByNumber(next); header != nil { - nextHash := header.Hash() - expOldHash, _ := pm.blockchain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) - if expOldHash == query.Origin.Hash { - query.Origin.Hash, query.Origin.Number = nextHash, next - } else { - unknown = true - } - } else { - unknown = true - } - } - case query.Reverse: - // Number based traversal towards the genesis block - if query.Origin.Number >= query.Skip+1 { - query.Origin.Number -= query.Skip + 1 - } else { - unknown = true - } - - case !query.Reverse: - // Number based traversal towards the leaf block - query.Origin.Number += query.Skip + 1 - } - } - return p.SendBlockHeaders(headers) + defer h.removePeer(peer.ID()) - case msg.Code == BlockHeadersMsg: - // A batch of headers arrived to one of our previous requests - var headers []*types.Header - if err := msg.Decode(&headers); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // If no headers were received, but we're expencting a checkpoint header, consider it that - if len(headers) == 0 && p.syncDrop != nil { - // Stop the timer either way, decide later to drop or not - p.syncDrop.Stop() - p.syncDrop = nil - - // If we're doing a fast sync, we must enforce the checkpoint block to avoid - // eclipse attacks. Unsynced nodes are welcome to connect after we're done - // joining the network - if atomic.LoadUint32(&pm.fastSync) == 1 { - p.Log().Warn("Dropping unsynced node during fast sync", "addr", p.RemoteAddr(), "type", p.Name()) - return errors.New("unsynced node cannot serve fast sync") - } - } - // Filter out any explicitly requested headers, deliver the rest to the downloader - filter := len(headers) == 1 - if filter { - // If it's a potential sync progress check, validate the content and advertised chain weight - if p.syncDrop != nil && headers[0].Number.Uint64() == pm.checkpointNumber { - // Disable the sync drop timer - p.syncDrop.Stop() - p.syncDrop = nil - - // Validate the header and either drop the peer or continue - if headers[0].Hash() != pm.checkpointHash { - return errors.New("checkpoint hash mismatch") - } - return nil - } - // Otherwise if it's a whitelisted block, validate against the set - if want, ok := pm.whitelist[headers[0].Number.Uint64()]; ok { - if hash := headers[0].Hash(); want != hash { - p.Log().Info("Whitelist mismatch, dropping peer", "number", headers[0].Number.Uint64(), "hash", hash, "want", want) - return errors.New("whitelist block mismatch") - } - p.Log().Debug("Whitelist block verified", "number", headers[0].Number.Uint64(), "hash", want) - } - // Irrelevant of the fork checks, send the header to the fetcher just in case - headers = pm.blockFetcher.FilterHeaders(p.id, headers, time.Now()) - } - if len(headers) > 0 || !filter { - err := pm.downloader.DeliverHeaders(p.id, headers) - if err != nil { - log.Debug("Failed to deliver headers", "err", err) - } - } - - case msg.Code == GetBlockBodiesMsg: - // Decode the retrieval message - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - if _, err := msgStream.List(); err != nil { - return err - } - // Gather blocks until the fetch or network limits is reached - var ( - hash common.Hash - bytes int - bodies []rlp.RawValue - ) - for bytes < softResponseLimit && len(bodies) < downloader.MaxBlockFetch { - // Retrieve the hash of the next block - if err := msgStream.Decode(&hash); err == rlp.EOL { - break - } else if err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Retrieve the requested block body, stopping if enough was found - if data := pm.blockchain.GetBodyRLP(hash); len(data) != 0 { - bodies = append(bodies, data) - bytes += len(data) - } - } - return p.SendBlockBodiesRLP(bodies) - - case msg.Code == BlockBodiesMsg: - // A batch of block bodies arrived to one of our previous requests - var request blockBodiesData - if err := msg.Decode(&request); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Deliver them all to the downloader for queuing - transactions := make([][]*types.Transaction, len(request)) - uncles := make([][]*types.Header, len(request)) - - for i, body := range request { - transactions[i] = body.Transactions - uncles[i] = body.Uncles - } - // Filter out any explicitly requested bodies, deliver the rest to the downloader - filter := len(transactions) > 0 || len(uncles) > 0 - if filter { - transactions, uncles = pm.blockFetcher.FilterBodies(p.id, transactions, uncles, time.Now()) - } - if len(transactions) > 0 || len(uncles) > 0 || !filter { - err := pm.downloader.DeliverBodies(p.id, transactions, uncles) - if err != nil { - log.Debug("Failed to deliver bodies", "err", err) - } - } + if err := h.downloader.SnapSyncer.Register(peer); err != nil { + return err + } + // Handle incoming messages until the connection is torn down + return handler(peer) +} - case p.version >= eth63 && msg.Code == GetNodeDataMsg: - // Decode the retrieval message - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - if _, err := msgStream.List(); err != nil { - return err - } - // Gather state data until the fetch or network limits is reached - var ( - hash common.Hash - bytes int - data [][]byte - ) - for bytes < softResponseLimit && len(data) < downloader.MaxStateFetch { - // Retrieve the hash of the next state entry - if err := msgStream.Decode(&hash); err == rlp.EOL { - break - } else if err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Retrieve the requested state entry, stopping if enough was found - // todo now the code and trienode is mixed in the protocol level, - // separate these two types. - if !pm.downloader.SyncBloomContains(hash[:]) { - // Only lookup the trie node if there's chance that we actually have it - continue - } - entry, err := pm.blockchain.TrieNode(hash) - if len(entry) == 0 || err != nil { - // Read the contract code with prefix only to save unnecessary lookups. - entry, err = pm.blockchain.ContractCodeWithPrefix(hash) - } - if err == nil && len(entry) > 0 { - data = append(data, entry) - bytes += len(entry) - } - } - return p.SendNodeData(data) +func (h *handler) removePeer(id string) { + // Remove the eth peer if it exists + eth := h.peers.ethPeer(id) + if eth != nil { + log.Debug("Removing Ethereum peer", "peer", id) + h.downloader.UnregisterPeer(id) + h.txFetcher.Drop(id) - case p.version >= eth63 && msg.Code == NodeDataMsg: - // A batch of node state data arrived to one of our previous requests - var data [][]byte - if err := msg.Decode(&data); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) + if err := h.peers.unregisterEthPeer(id); err != nil { + log.Error("Peer removal failed", "peer", id, "err", err) } - // Deliver all to the downloader - if err := pm.downloader.DeliverNodeData(p.id, data); err != nil { - log.Debug("Failed to deliver node state data", "err", err) + } + // Remove the snap peer if it exists + snap := h.peers.snapPeer(id) + if snap != nil { + log.Debug("Removing Snapshot peer", "peer", id) + h.downloader.SnapSyncer.Unregister(id) + if err := h.peers.unregisterSnapPeer(id); err != nil { + log.Error("Peer removal failed", "peer", id, "err", err) } + } + // Hard disconnect at the networking layer + if eth != nil { + eth.Peer.Disconnect(p2p.DiscUselessPeer) + } + if snap != nil { + snap.Peer.Disconnect(p2p.DiscUselessPeer) + } +} - case p.version >= eth63 && msg.Code == GetReceiptsMsg: - // Decode the retrieval message - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - if _, err := msgStream.List(); err != nil { - return err - } - // Gather state data until the fetch or network limits is reached - var ( - hash common.Hash - bytes int - receipts []rlp.RawValue - ) - for bytes < softResponseLimit && len(receipts) < downloader.MaxReceiptFetch { - // Retrieve the hash of the next block - if err := msgStream.Decode(&hash); err == rlp.EOL { - break - } else if err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Retrieve the requested block's receipts, skipping if unknown to us - results := pm.blockchain.GetReceiptsByHash(hash) - if results == nil { - if header := pm.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { - continue - } - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(results); err != nil { - log.Error("Failed to encode receipt", "err", err) - } else { - receipts = append(receipts, encoded) - bytes += len(encoded) - } - } - return p.SendReceiptsRLP(receipts) +func (h *handler) Start(maxPeers int) { + h.maxPeers = maxPeers - case p.version >= eth63 && msg.Code == ReceiptsMsg: - // A batch of receipts arrived to one of our previous requests - var receipts [][]*types.Receipt - if err := msg.Decode(&receipts); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Deliver all to the downloader - if err := pm.downloader.DeliverReceipts(p.id, receipts); err != nil { - log.Debug("Failed to deliver receipts", "err", err) - } + // broadcast transactions + h.wg.Add(1) + h.txsCh = make(chan core.NewTxsEvent, txChanSize) + h.txsSub = h.txpool.SubscribeNewTxsEvent(h.txsCh) + go h.txBroadcastLoop() - case msg.Code == NewBlockHashesMsg: - var announces newBlockHashesData - if err := msg.Decode(&announces); err != nil { - return errResp(ErrDecode, "%v: %v", msg, err) - } - // Mark the hashes as present at the remote node - for _, block := range announces { - p.MarkBlock(block.Hash) - } - // Schedule all the unknown hashes for retrieval - unknown := make(newBlockHashesData, 0, len(announces)) - for _, block := range announces { - if !pm.blockchain.HasBlock(block.Hash, block.Number) { - unknown = append(unknown, block) - } - } - for _, block := range unknown { - pm.blockFetcher.Notify(p.id, block.Hash, block.Number, time.Now(), p.RequestOneHeader, p.RequestBodies) - } + // broadcast mined blocks + h.wg.Add(1) + h.minedBlockSub = h.eventMux.Subscribe(core.NewMinedBlockEvent{}) + go h.minedBroadcastLoop() - case msg.Code == NewBlockMsg: - // Retrieve and decode the propagated block - var request newBlockData - if err := msg.Decode(&request); err != nil { - return errResp(ErrDecode, "%v: %v", msg, err) - } - if hash := types.CalcUncleHash(request.Block.Uncles()); hash != request.Block.UncleHash() { - log.Warn("Propagated block has invalid uncles", "have", hash, "exp", request.Block.UncleHash()) - break // TODO(karalabe): return error eventually, but wait a few releases - } - if hash := types.DeriveSha(request.Block.Transactions(), trie.NewStackTrie(nil)); hash != request.Block.TxHash() { - log.Warn("Propagated block has invalid body", "have", hash, "exp", request.Block.TxHash()) - break // TODO(karalabe): return error eventually, but wait a few releases - } - if err := request.sanityCheck(); err != nil { - return err - } - request.Block.ReceivedAt = msg.ReceivedAt - request.Block.ReceivedFrom = p - - // Mark the peer as owning the block and schedule it for import - p.MarkBlock(request.Block.Hash()) - pm.blockFetcher.Enqueue(p.id, request.Block) - - // Assuming the block is importable by the peer, but possibly not yet done so, - // calculate the head hash and TD that the peer truly must have. - var ( - trueHead = request.Block.ParentHash() - trueTD = new(big.Int).Sub(request.TD, request.Block.Difficulty()) - ) - // Update the peer's total difficulty if better than the previous - if _, td := p.Head(); trueTD.Cmp(td) > 0 { - p.SetHead(trueHead, trueTD) - pm.chainSync.handlePeerEvent(p) - } + // start sync handlers + h.wg.Add(2) + go h.chainSync.loop() + go h.txsyncLoop64() // TODO(karalabe): Legacy initial tx echange, drop with eth/64. +} - case msg.Code == NewPooledTransactionHashesMsg && p.version >= eth65: - // New transaction announcement arrived, make sure we have - // a valid and fresh chain to handle them - if atomic.LoadUint32(&pm.acceptTxs) == 0 { - break - } - var hashes []common.Hash - if err := msg.Decode(&hashes); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Schedule all the unknown hashes for retrieval - for _, hash := range hashes { - p.MarkTransaction(hash) - } - pm.txFetcher.Notify(p.id, hashes) +func (h *handler) Stop() { + h.txsSub.Unsubscribe() // quits txBroadcastLoop + h.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop - case msg.Code == GetPooledTransactionsMsg && p.version >= eth65: - // Decode the retrieval message - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - if _, err := msgStream.List(); err != nil { - return err - } - // Gather transactions until the fetch or network limits is reached - var ( - hash common.Hash - bytes int - hashes []common.Hash - txs []rlp.RawValue - ) - for bytes < softResponseLimit { - // Retrieve the hash of the next block - if err := msgStream.Decode(&hash); err == rlp.EOL { - break - } else if err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Retrieve the requested transaction, skipping if unknown to us - tx := pm.txpool.Get(hash) - if tx == nil { - continue - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(tx); err != nil { - log.Error("Failed to encode transaction", "err", err) - } else { - hashes = append(hashes, hash) - txs = append(txs, encoded) - bytes += len(encoded) - } - } - return p.SendPooledTransactionsRLP(hashes, txs) + // Quit chainSync and txsync64. + // After this is done, no new peers will be accepted. + close(h.quitSync) + h.wg.Wait() - case msg.Code == TransactionMsg || (msg.Code == PooledTransactionsMsg && p.version >= eth65): - // Transactions arrived, make sure we have a valid and fresh chain to handle them - if atomic.LoadUint32(&pm.acceptTxs) == 0 { - break - } - // Transactions can be processed, parse all of them and deliver to the pool - var txs []*types.Transaction - if err := msg.Decode(&txs); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - for i, tx := range txs { - // Validate and mark the remote transaction - if tx == nil { - return errResp(ErrDecode, "transaction %d is nil", i) - } - p.MarkTransaction(tx.Hash()) - } - pm.txFetcher.Enqueue(p.id, txs, msg.Code == PooledTransactionsMsg) + // Disconnect existing sessions. + // This also closes the gate for any new registrations on the peer set. + // sessions which are already established but not added to h.peers yet + // will exit when they try to register. + h.peers.close() + h.peerWG.Wait() - default: - return errResp(ErrInvalidMsgCode, "%v", msg.Code) - } - return nil + log.Info("Ethereum protocol stopped") } // BroadcastBlock will either propagate a block to a subset of its peers, or // will only announce its availability (depending what's requested). -func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) { +func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { hash := block.Hash() - peers := pm.peers.PeersWithoutBlock(hash) + peers := h.peers.ethPeersWithoutBlock(hash) // If propagation is requested, send to a subset of the peer if propagate { // Calculate the TD of the block (it's not imported yet, so block.Td is not valid) var td *big.Int - if parent := pm.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1); parent != nil { - td = new(big.Int).Add(block.Difficulty(), pm.blockchain.GetTd(block.ParentHash(), block.NumberU64()-1)) + if parent := h.chain.GetBlock(block.ParentHash(), block.NumberU64()-1); parent != nil { + td = new(big.Int).Add(block.Difficulty(), h.chain.GetTd(block.ParentHash(), block.NumberU64()-1)) } else { log.Error("Propagating dangling block", "number", block.Number(), "hash", hash) return @@ -843,7 +419,7 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) { return } // Otherwise if the block is indeed in out own chain, announce it - if pm.blockchain.HasBlock(hash, block.NumberU64()) { + if h.chain.HasBlock(hash, block.NumberU64()) { for _, peer := range peers { peer.AsyncSendNewBlockHash(block) } @@ -853,15 +429,15 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) { // BroadcastTransactions will propagate a batch of transactions to all peers which are not known to // already have the given transaction. -func (pm *ProtocolManager) BroadcastTransactions(txs types.Transactions, propagate bool) { +func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) { var ( - txset = make(map[*peer][]common.Hash) - annos = make(map[*peer][]common.Hash) + txset = make(map[*ethPeer][]common.Hash) + annos = make(map[*ethPeer][]common.Hash) ) // Broadcast transactions to a batch of peers not knowing about it if propagate { for _, tx := range txs { - peers := pm.peers.PeersWithoutTx(tx.Hash()) + peers := h.peers.ethPeersWithoutTransacion(tx.Hash()) // Send the block to a subset of our peers transfer := peers[:int(math.Sqrt(float64(len(peers))))] @@ -877,13 +453,13 @@ func (pm *ProtocolManager) BroadcastTransactions(txs types.Transactions, propaga } // Otherwise only broadcast the announcement to peers for _, tx := range txs { - peers := pm.peers.PeersWithoutTx(tx.Hash()) + peers := h.peers.ethPeersWithoutTransacion(tx.Hash()) for _, peer := range peers { annos[peer] = append(annos[peer], tx.Hash()) } } for peer, hashes := range annos { - if peer.version >= eth65 { + if peer.Version() >= eth.ETH65 { peer.AsyncSendPooledTransactionHashes(hashes) } else { peer.AsyncSendTransactions(hashes) @@ -892,56 +468,29 @@ func (pm *ProtocolManager) BroadcastTransactions(txs types.Transactions, propaga } // minedBroadcastLoop sends mined blocks to connected peers. -func (pm *ProtocolManager) minedBroadcastLoop() { - defer pm.wg.Done() +func (h *handler) minedBroadcastLoop() { + defer h.wg.Done() - for obj := range pm.minedBlockSub.Chan() { + for obj := range h.minedBlockSub.Chan() { if ev, ok := obj.Data.(core.NewMinedBlockEvent); ok { - pm.BroadcastBlock(ev.Block, true) // First propagate block to peers - pm.BroadcastBlock(ev.Block, false) // Only then announce to the rest + h.BroadcastBlock(ev.Block, true) // First propagate block to peers + h.BroadcastBlock(ev.Block, false) // Only then announce to the rest } } } // txBroadcastLoop announces new transactions to connected peers. -func (pm *ProtocolManager) txBroadcastLoop() { - defer pm.wg.Done() +func (h *handler) txBroadcastLoop() { + defer h.wg.Done() for { select { - case event := <-pm.txsCh: - // For testing purpose only, disable propagation - if pm.broadcastTxAnnouncesOnly { - pm.BroadcastTransactions(event.Txs, false) - continue - } - pm.BroadcastTransactions(event.Txs, true) // First propagate transactions to peers - pm.BroadcastTransactions(event.Txs, false) // Only then announce to the rest + case event := <-h.txsCh: + h.BroadcastTransactions(event.Txs, true) // First propagate transactions to peers + h.BroadcastTransactions(event.Txs, false) // Only then announce to the rest - case <-pm.txsSub.Err(): + case <-h.txsSub.Err(): return } } } - -// NodeInfo represents a short summary of the Ethereum sub-protocol metadata -// known about the host peer. -type NodeInfo struct { - Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4) - Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain - Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block - Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules - Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block -} - -// NodeInfo retrieves some protocol metadata about the running host node. -func (pm *ProtocolManager) NodeInfo() *NodeInfo { - currentBlock := pm.blockchain.CurrentBlock() - return &NodeInfo{ - Network: pm.networkID, - Difficulty: pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64()), - Genesis: pm.blockchain.Genesis().Hash(), - Config: pm.blockchain.Config(), - Head: currentBlock.Hash(), - } -} diff --git a/eth/handler_eth.go b/eth/handler_eth.go new file mode 100644 index 0000000000..84bdac659a --- /dev/null +++ b/eth/handler_eth.go @@ -0,0 +1,218 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "fmt" + "math/big" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/trie" +) + +// ethHandler implements the eth.Backend interface to handle the various network +// packets that are sent as replies or broadcasts. +type ethHandler handler + +func (h *ethHandler) Chain() *core.BlockChain { return h.chain } +func (h *ethHandler) StateBloom() *trie.SyncBloom { return h.stateBloom } +func (h *ethHandler) TxPool() eth.TxPool { return h.txpool } + +// RunPeer is invoked when a peer joins on the `eth` protocol. +func (h *ethHandler) RunPeer(peer *eth.Peer, hand eth.Handler) error { + return (*handler)(h).runEthPeer(peer, hand) +} + +// PeerInfo retrieves all known `eth` information about a peer. +func (h *ethHandler) PeerInfo(id enode.ID) interface{} { + if p := h.peers.ethPeer(id.String()); p != nil { + return p.info() + } + return nil +} + +// AcceptTxs retrieves whether transaction processing is enabled on the node +// or if inbound transactions should simply be dropped. +func (h *ethHandler) AcceptTxs() bool { + return atomic.LoadUint32(&h.acceptTxs) == 1 +} + +// Handle is invoked from a peer's message handler when it receives a new remote +// message that the handler couldn't consume and serve itself. +func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error { + // Consume any broadcasts and announces, forwarding the rest to the downloader + switch packet := packet.(type) { + case *eth.BlockHeadersPacket: + return h.handleHeaders(peer, *packet) + + case *eth.BlockBodiesPacket: + txset, uncleset := packet.Unpack() + return h.handleBodies(peer, txset, uncleset) + + case *eth.NodeDataPacket: + if err := h.downloader.DeliverNodeData(peer.ID(), *packet); err != nil { + log.Debug("Failed to deliver node state data", "err", err) + } + return nil + + case *eth.ReceiptsPacket: + if err := h.downloader.DeliverReceipts(peer.ID(), *packet); err != nil { + log.Debug("Failed to deliver receipts", "err", err) + } + return nil + + case *eth.NewBlockHashesPacket: + hashes, numbers := packet.Unpack() + return h.handleBlockAnnounces(peer, hashes, numbers) + + case *eth.NewBlockPacket: + return h.handleBlockBroadcast(peer, packet.Block, packet.TD) + + case *eth.NewPooledTransactionHashesPacket: + return h.txFetcher.Notify(peer.ID(), *packet) + + case *eth.TransactionsPacket: + return h.txFetcher.Enqueue(peer.ID(), *packet, false) + + case *eth.PooledTransactionsPacket: + return h.txFetcher.Enqueue(peer.ID(), *packet, true) + + default: + return fmt.Errorf("unexpected eth packet type: %T", packet) + } +} + +// handleHeaders is invoked from a peer's message handler when it transmits a batch +// of headers for the local node to process. +func (h *ethHandler) handleHeaders(peer *eth.Peer, headers []*types.Header) error { + p := h.peers.ethPeer(peer.ID()) + if p == nil { + return errors.New("unregistered during callback") + } + // If no headers were received, but we're expencting a checkpoint header, consider it that + if len(headers) == 0 && p.syncDrop != nil { + // Stop the timer either way, decide later to drop or not + p.syncDrop.Stop() + p.syncDrop = nil + + // If we're doing a fast (or snap) sync, we must enforce the checkpoint block to avoid + // eclipse attacks. Unsynced nodes are welcome to connect after we're done + // joining the network + if atomic.LoadUint32(&h.fastSync) == 1 { + peer.Log().Warn("Dropping unsynced node during sync", "addr", peer.RemoteAddr(), "type", peer.Name()) + return errors.New("unsynced node cannot serve sync") + } + } + // Filter out any explicitly requested headers, deliver the rest to the downloader + filter := len(headers) == 1 + if filter { + // If it's a potential sync progress check, validate the content and advertised chain weight + if p.syncDrop != nil && headers[0].Number.Uint64() == h.checkpointNumber { + // Disable the sync drop timer + p.syncDrop.Stop() + p.syncDrop = nil + + // Validate the header and either drop the peer or continue + if headers[0].Hash() != h.checkpointHash { + return errors.New("checkpoint hash mismatch") + } + return nil + } + // Otherwise if it's a whitelisted block, validate against the set + if want, ok := h.whitelist[headers[0].Number.Uint64()]; ok { + if hash := headers[0].Hash(); want != hash { + peer.Log().Info("Whitelist mismatch, dropping peer", "number", headers[0].Number.Uint64(), "hash", hash, "want", want) + return errors.New("whitelist block mismatch") + } + peer.Log().Debug("Whitelist block verified", "number", headers[0].Number.Uint64(), "hash", want) + } + // Irrelevant of the fork checks, send the header to the fetcher just in case + headers = h.blockFetcher.FilterHeaders(peer.ID(), headers, time.Now()) + } + if len(headers) > 0 || !filter { + err := h.downloader.DeliverHeaders(peer.ID(), headers) + if err != nil { + log.Debug("Failed to deliver headers", "err", err) + } + } + return nil +} + +// handleBodies is invoked from a peer's message handler when it transmits a batch +// of block bodies for the local node to process. +func (h *ethHandler) handleBodies(peer *eth.Peer, txs [][]*types.Transaction, uncles [][]*types.Header) error { + // Filter out any explicitly requested bodies, deliver the rest to the downloader + filter := len(txs) > 0 || len(uncles) > 0 + if filter { + txs, uncles = h.blockFetcher.FilterBodies(peer.ID(), txs, uncles, time.Now()) + } + if len(txs) > 0 || len(uncles) > 0 || !filter { + err := h.downloader.DeliverBodies(peer.ID(), txs, uncles) + if err != nil { + log.Debug("Failed to deliver bodies", "err", err) + } + } + return nil +} + +// handleBlockAnnounces is invoked from a peer's message handler when it transmits a +// batch of block announcements for the local node to process. +func (h *ethHandler) handleBlockAnnounces(peer *eth.Peer, hashes []common.Hash, numbers []uint64) error { + // Schedule all the unknown hashes for retrieval + var ( + unknownHashes = make([]common.Hash, 0, len(hashes)) + unknownNumbers = make([]uint64, 0, len(numbers)) + ) + for i := 0; i < len(hashes); i++ { + if !h.chain.HasBlock(hashes[i], numbers[i]) { + unknownHashes = append(unknownHashes, hashes[i]) + unknownNumbers = append(unknownNumbers, numbers[i]) + } + } + for i := 0; i < len(unknownHashes); i++ { + h.blockFetcher.Notify(peer.ID(), unknownHashes[i], unknownNumbers[i], time.Now(), peer.RequestOneHeader, peer.RequestBodies) + } + return nil +} + +// handleBlockBroadcast is invoked from a peer's message handler when it transmits a +// block broadcast for the local node to process. +func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td *big.Int) error { + // Schedule the block for import + h.blockFetcher.Enqueue(peer.ID(), block) + + // Assuming the block is importable by the peer, but possibly not yet done so, + // calculate the head hash and TD that the peer truly must have. + var ( + trueHead = block.ParentHash() + trueTD = new(big.Int).Sub(td, block.Difficulty()) + ) + // Update the peer's total difficulty if better than the previous + if _, td := peer.Head(); trueTD.Cmp(td) > 0 { + peer.SetHead(trueHead, trueTD) + h.chainSync.handlePeerEvent(peer) + } + return nil +} diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go new file mode 100644 index 0000000000..0e5c0c90ee --- /dev/null +++ b/eth/handler_eth_test.go @@ -0,0 +1,740 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "fmt" + "math/big" + "math/rand" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +// testEthHandler is a mock event handler to listen for inbound network requests +// on the `eth` protocol and convert them into a more easily testable form. +type testEthHandler struct { + blockBroadcasts event.Feed + txAnnounces event.Feed + txBroadcasts event.Feed +} + +func (h *testEthHandler) Chain() *core.BlockChain { panic("no backing chain") } +func (h *testEthHandler) StateBloom() *trie.SyncBloom { panic("no backing state bloom") } +func (h *testEthHandler) TxPool() eth.TxPool { panic("no backing tx pool") } +func (h *testEthHandler) AcceptTxs() bool { return true } +func (h *testEthHandler) RunPeer(*eth.Peer, eth.Handler) error { panic("not used in tests") } +func (h *testEthHandler) PeerInfo(enode.ID) interface{} { panic("not used in tests") } + +func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error { + switch packet := packet.(type) { + case *eth.NewBlockPacket: + h.blockBroadcasts.Send(packet.Block) + return nil + + case *eth.NewPooledTransactionHashesPacket: + h.txAnnounces.Send(([]common.Hash)(*packet)) + return nil + + case *eth.TransactionsPacket: + h.txBroadcasts.Send(([]*types.Transaction)(*packet)) + return nil + + case *eth.PooledTransactionsPacket: + h.txBroadcasts.Send(([]*types.Transaction)(*packet)) + return nil + + default: + panic(fmt.Sprintf("unexpected eth packet type in tests: %T", packet)) + } +} + +// Tests that peers are correctly accepted (or rejected) based on the advertised +// fork IDs in the protocol handshake. +func TestForkIDSplit64(t *testing.T) { testForkIDSplit(t, 64) } +func TestForkIDSplit65(t *testing.T) { testForkIDSplit(t, 65) } + +func testForkIDSplit(t *testing.T, protocol uint) { + t.Parallel() + + var ( + engine = ethash.NewFaker() + + configNoFork = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)} + configProFork = ¶ms.ChainConfig{ + HomesteadBlock: big.NewInt(1), + EIP150Block: big.NewInt(2), + EIP155Block: big.NewInt(2), + EIP158Block: big.NewInt(2), + ByzantiumBlock: big.NewInt(3), + } + dbNoFork = rawdb.NewMemoryDatabase() + dbProFork = rawdb.NewMemoryDatabase() + + gspecNoFork = &core.Genesis{Config: configNoFork} + gspecProFork = &core.Genesis{Config: configProFork} + + genesisNoFork = gspecNoFork.MustCommit(dbNoFork) + genesisProFork = gspecProFork.MustCommit(dbProFork) + + chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, configNoFork, engine, vm.Config{}, nil, nil) + chainProFork, _ = core.NewBlockChain(dbProFork, nil, configProFork, engine, vm.Config{}, nil, nil) + + blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil) + blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil) + + ethNoFork, _ = newHandler(&handlerConfig{ + Database: dbNoFork, + Chain: chainNoFork, + TxPool: newTestTxPool(), + Network: 1, + Sync: downloader.FullSync, + BloomCache: 1, + }) + ethProFork, _ = newHandler(&handlerConfig{ + Database: dbProFork, + Chain: chainProFork, + TxPool: newTestTxPool(), + Network: 1, + Sync: downloader.FullSync, + BloomCache: 1, + }) + ) + ethNoFork.Start(1000) + ethProFork.Start(1000) + + // Clean up everything after ourselves + defer chainNoFork.Stop() + defer chainProFork.Stop() + + defer ethNoFork.Stop() + defer ethProFork.Stop() + + // Both nodes should allow the other to connect (same genesis, next fork is the same) + p2pNoFork, p2pProFork := p2p.MsgPipe() + defer p2pNoFork.Close() + defer p2pProFork.Close() + + peerNoFork := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) + peerProFork := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) + defer peerNoFork.Close() + defer peerProFork.Close() + + errc := make(chan error, 2) + go func(errc chan error) { + errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil }) + }(errc) + go func(errc chan error) { + errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil }) + }(errc) + + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err != nil { + t.Fatalf("frontier nofork <-> profork failed: %v", err) + } + case <-time.After(250 * time.Millisecond): + t.Fatalf("frontier nofork <-> profork handler timeout") + } + } + // Progress into Homestead. Fork's match, so we don't care what the future holds + chainNoFork.InsertChain(blocksNoFork[:1]) + chainProFork.InsertChain(blocksProFork[:1]) + + p2pNoFork, p2pProFork = p2p.MsgPipe() + defer p2pNoFork.Close() + defer p2pProFork.Close() + + peerNoFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) + peerProFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) + defer peerNoFork.Close() + defer peerProFork.Close() + + errc = make(chan error, 2) + go func(errc chan error) { + errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil }) + }(errc) + go func(errc chan error) { + errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil }) + }(errc) + + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err != nil { + t.Fatalf("homestead nofork <-> profork failed: %v", err) + } + case <-time.After(250 * time.Millisecond): + t.Fatalf("homestead nofork <-> profork handler timeout") + } + } + // Progress into Spurious. Forks mismatch, signalling differing chains, reject + chainNoFork.InsertChain(blocksNoFork[1:2]) + chainProFork.InsertChain(blocksProFork[1:2]) + + p2pNoFork, p2pProFork = p2p.MsgPipe() + defer p2pNoFork.Close() + defer p2pProFork.Close() + + peerNoFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) + peerProFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) + defer peerNoFork.Close() + defer peerProFork.Close() + + errc = make(chan error, 2) + go func(errc chan error) { + errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil }) + }(errc) + go func(errc chan error) { + errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil }) + }(errc) + + var successes int + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err == nil { + successes++ + if successes == 2 { // Only one side disconnects + t.Fatalf("fork ID rejection didn't happen") + } + } + case <-time.After(250 * time.Millisecond): + t.Fatalf("split peers not rejected") + } + } +} + +// Tests that received transactions are added to the local pool. +func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) } +func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, 65) } + +func testRecvTransactions(t *testing.T, protocol uint) { + t.Parallel() + + // Create a message handler, configure it to accept transactions and watch them + handler := newTestHandler() + defer handler.close() + + handler.handler.acceptTxs = 1 // mark synced to accept transactions + + txs := make(chan core.NewTxsEvent) + sub := handler.txpool.SubscribeNewTxsEvent(txs) + defer sub.Unsubscribe() + + // Create a source peer to send messages through and a sink handler to receive them + p2pSrc, p2pSink := p2p.MsgPipe() + defer p2pSrc.Close() + defer p2pSink.Close() + + src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, handler.txpool) + sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, handler.txpool) + defer src.Close() + defer sink.Close() + + go handler.handler.runEthPeer(sink, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(handler.handler), peer) + }) + // Run the handshake locally to avoid spinning up a source handler + var ( + genesis = handler.chain.Genesis() + head = handler.chain.CurrentBlock() + td = handler.chain.GetTd(head.Hash(), head.NumberU64()) + ) + if err := src.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + // Send the transaction to the sink and verify that it's added to the tx pool + tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + + if err := src.SendTransactions([]*types.Transaction{tx}); err != nil { + t.Fatalf("failed to send transaction: %v", err) + } + select { + case event := <-txs: + if len(event.Txs) != 1 { + t.Errorf("wrong number of added transactions: got %d, want 1", len(event.Txs)) + } else if event.Txs[0].Hash() != tx.Hash() { + t.Errorf("added wrong tx hash: got %v, want %v", event.Txs[0].Hash(), tx.Hash()) + } + case <-time.After(2 * time.Second): + t.Errorf("no NewTxsEvent received within 2 seconds") + } +} + +// This test checks that pending transactions are sent. +func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) } +func TestSendTransactions65(t *testing.T) { testSendTransactions(t, 65) } + +func testSendTransactions(t *testing.T, protocol uint) { + t.Parallel() + + // Create a message handler and fill the pool with big transactions + handler := newTestHandler() + defer handler.close() + + insert := make([]*types.Transaction, 100) + for nonce := range insert { + tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, txsyncPackSize/10)) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + + insert[nonce] = tx + } + go handler.txpool.AddRemotes(insert) // Need goroutine to not block on feed + time.Sleep(250 * time.Millisecond) // Wait until tx events get out of the system (can't use events, tx broadcaster races with peer join) + + // Create a source handler to send messages through and a sink peer to receive them + p2pSrc, p2pSink := p2p.MsgPipe() + defer p2pSrc.Close() + defer p2pSink.Close() + + src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, handler.txpool) + sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, handler.txpool) + defer src.Close() + defer sink.Close() + + go handler.handler.runEthPeer(src, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(handler.handler), peer) + }) + // Run the handshake locally to avoid spinning up a source handler + var ( + genesis = handler.chain.Genesis() + head = handler.chain.CurrentBlock() + td = handler.chain.GetTd(head.Hash(), head.NumberU64()) + ) + if err := sink.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + // After the handshake completes, the source handler should stream the sink + // the transactions, subscribe to all inbound network events + backend := new(testEthHandler) + + anns := make(chan []common.Hash) + annSub := backend.txAnnounces.Subscribe(anns) + defer annSub.Unsubscribe() + + bcasts := make(chan []*types.Transaction) + bcastSub := backend.txBroadcasts.Subscribe(bcasts) + defer bcastSub.Unsubscribe() + + go eth.Handle(backend, sink) + + // Make sure we get all the transactions on the correct channels + seen := make(map[common.Hash]struct{}) + for len(seen) < len(insert) { + switch protocol { + case 63, 64: + select { + case <-anns: + t.Errorf("tx announce received on pre eth/65") + case txs := <-bcasts: + for _, tx := range txs { + if _, ok := seen[tx.Hash()]; ok { + t.Errorf("duplicate transaction announced: %x", tx.Hash()) + } + seen[tx.Hash()] = struct{}{} + } + } + case 65: + select { + case hashes := <-anns: + for _, hash := range hashes { + if _, ok := seen[hash]; ok { + t.Errorf("duplicate transaction announced: %x", hash) + } + seen[hash] = struct{}{} + } + case <-bcasts: + t.Errorf("initial tx broadcast received on post eth/65") + } + + default: + panic("unsupported protocol, please extend test") + } + } + for _, tx := range insert { + if _, ok := seen[tx.Hash()]; !ok { + t.Errorf("missing transaction: %x", tx.Hash()) + } + } +} + +// Tests that transactions get propagated to all attached peers, either via direct +// broadcasts or via announcements/retrievals. +func TestTransactionPropagation64(t *testing.T) { testTransactionPropagation(t, 64) } +func TestTransactionPropagation65(t *testing.T) { testTransactionPropagation(t, 65) } + +func testTransactionPropagation(t *testing.T, protocol uint) { + t.Parallel() + + // Create a source handler to send transactions from and a number of sinks + // to receive them. We need multiple sinks since a one-to-one peering would + // broadcast all transactions without announcement. + source := newTestHandler() + defer source.close() + + sinks := make([]*testHandler, 10) + for i := 0; i < len(sinks); i++ { + sinks[i] = newTestHandler() + defer sinks[i].close() + + sinks[i].handler.acceptTxs = 1 // mark synced to accept transactions + } + // Interconnect all the sink handlers with the source handler + for i, sink := range sinks { + sink := sink // Closure for gorotuine below + + sourcePipe, sinkPipe := p2p.MsgPipe() + defer sourcePipe.Close() + defer sinkPipe.Close() + + sourcePeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, source.txpool) + sinkPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, sink.txpool) + defer sourcePeer.Close() + defer sinkPeer.Close() + + go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(source.handler), peer) + }) + go sink.handler.runEthPeer(sinkPeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(sink.handler), peer) + }) + } + // Subscribe to all the transaction pools + txChs := make([]chan core.NewTxsEvent, len(sinks)) + for i := 0; i < len(sinks); i++ { + txChs[i] = make(chan core.NewTxsEvent, 1024) + + sub := sinks[i].txpool.SubscribeNewTxsEvent(txChs[i]) + defer sub.Unsubscribe() + } + // Fill the source pool with transactions and wait for them at the sinks + txs := make([]*types.Transaction, 1024) + for nonce := range txs { + tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + + txs[nonce] = tx + } + source.txpool.AddRemotes(txs) + + // Iterate through all the sinks and ensure they all got the transactions + for i := range sinks { + for arrived := 0; arrived < len(txs); { + select { + case event := <-txChs[i]: + arrived += len(event.Txs) + case <-time.NewTimer(time.Second).C: + t.Errorf("sink %d: transaction propagation timed out: have %d, want %d", i, arrived, len(txs)) + } + } + } +} + +// Tests that post eth protocol handshake, clients perform a mutual checkpoint +// challenge to validate each other's chains. Hash mismatches, or missing ones +// during a fast sync should lead to the peer getting dropped. +func TestCheckpointChallenge(t *testing.T) { + tests := []struct { + syncmode downloader.SyncMode + checkpoint bool + timeout bool + empty bool + match bool + drop bool + }{ + // If checkpointing is not enabled locally, don't challenge and don't drop + {downloader.FullSync, false, false, false, false, false}, + {downloader.FastSync, false, false, false, false, false}, + + // If checkpointing is enabled locally and remote response is empty, only drop during fast sync + {downloader.FullSync, true, false, true, false, false}, + {downloader.FastSync, true, false, true, false, true}, // Special case, fast sync, unsynced peer + + // If checkpointing is enabled locally and remote response mismatches, always drop + {downloader.FullSync, true, false, false, false, true}, + {downloader.FastSync, true, false, false, false, true}, + + // If checkpointing is enabled locally and remote response matches, never drop + {downloader.FullSync, true, false, false, true, false}, + {downloader.FastSync, true, false, false, true, false}, + + // If checkpointing is enabled locally and remote times out, always drop + {downloader.FullSync, true, true, false, true, true}, + {downloader.FastSync, true, true, false, true, true}, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("sync %v checkpoint %v timeout %v empty %v match %v", tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match), func(t *testing.T) { + testCheckpointChallenge(t, tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match, tt.drop) + }) + } +} + +func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) { + // Reduce the checkpoint handshake challenge timeout + defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout) + syncChallengeTimeout = 250 * time.Millisecond + + // Create a test handler and inject a CHT into it. The injection is a bit + // ugly, but it beats creating everything manually just to avoid reaching + // into the internals a bit. + handler := newTestHandler() + defer handler.close() + + if syncmode == downloader.FastSync { + atomic.StoreUint32(&handler.handler.fastSync, 1) + } else { + atomic.StoreUint32(&handler.handler.fastSync, 0) + } + var response *types.Header + if checkpoint { + number := (uint64(rand.Intn(500))+1)*params.CHTFrequency - 1 + response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")} + + handler.handler.checkpointNumber = number + handler.handler.checkpointHash = response.Hash() + } + // Create a challenger peer and a challenged one + p2pLocal, p2pRemote := p2p.MsgPipe() + defer p2pLocal.Close() + defer p2pRemote.Close() + + local := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{1}, "", nil), p2pLocal, handler.txpool) + remote := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{2}, "", nil), p2pRemote, handler.txpool) + defer local.Close() + defer remote.Close() + + go handler.handler.runEthPeer(local, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(handler.handler), peer) + }) + // Run the handshake locally to avoid spinning up a remote handler + var ( + genesis = handler.chain.Genesis() + head = handler.chain.CurrentBlock() + td = handler.chain.GetTd(head.Hash(), head.NumberU64()) + ) + if err := remote.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + // Connect a new peer and check that we receive the checkpoint challenge + if checkpoint { + if err := remote.ExpectRequestHeadersByNumber(response.Number.Uint64(), 1, 0, false); err != nil { + t.Fatalf("challenge mismatch: %v", err) + } + // Create a block to reply to the challenge if no timeout is simulated + if !timeout { + if empty { + if err := remote.SendBlockHeaders([]*types.Header{}); err != nil { + t.Fatalf("failed to answer challenge: %v", err) + } + } else if match { + if err := remote.SendBlockHeaders([]*types.Header{response}); err != nil { + t.Fatalf("failed to answer challenge: %v", err) + } + } else { + if err := remote.SendBlockHeaders([]*types.Header{{Number: response.Number}}); err != nil { + t.Fatalf("failed to answer challenge: %v", err) + } + } + } + } + // Wait until the test timeout passes to ensure proper cleanup + time.Sleep(syncChallengeTimeout + 300*time.Millisecond) + + // Verify that the remote peer is maintained or dropped + if drop { + if peers := handler.handler.peers.Len(); peers != 0 { + t.Fatalf("peer count mismatch: have %d, want %d", peers, 0) + } + } else { + if peers := handler.handler.peers.Len(); peers != 1 { + t.Fatalf("peer count mismatch: have %d, want %d", peers, 1) + } + } +} + +// Tests that blocks are broadcast to a sqrt number of peers only. +func TestBroadcastBlock1Peer(t *testing.T) { testBroadcastBlock(t, 1, 1) } +func TestBroadcastBlock2Peers(t *testing.T) { testBroadcastBlock(t, 2, 1) } +func TestBroadcastBlock3Peers(t *testing.T) { testBroadcastBlock(t, 3, 1) } +func TestBroadcastBlock4Peers(t *testing.T) { testBroadcastBlock(t, 4, 2) } +func TestBroadcastBlock5Peers(t *testing.T) { testBroadcastBlock(t, 5, 2) } +func TestBroadcastBlock8Peers(t *testing.T) { testBroadcastBlock(t, 9, 3) } +func TestBroadcastBlock12Peers(t *testing.T) { testBroadcastBlock(t, 12, 3) } +func TestBroadcastBlock16Peers(t *testing.T) { testBroadcastBlock(t, 16, 4) } +func TestBroadcastBloc26Peers(t *testing.T) { testBroadcastBlock(t, 26, 5) } +func TestBroadcastBlock100Peers(t *testing.T) { testBroadcastBlock(t, 100, 10) } + +func testBroadcastBlock(t *testing.T, peers, bcasts int) { + t.Parallel() + + // Create a source handler to broadcast blocks from and a number of sinks + // to receive them. + source := newTestHandlerWithBlocks(1) + defer source.close() + + sinks := make([]*testEthHandler, peers) + for i := 0; i < len(sinks); i++ { + sinks[i] = new(testEthHandler) + } + // Interconnect all the sink handlers with the source handler + var ( + genesis = source.chain.Genesis() + td = source.chain.GetTd(genesis.Hash(), genesis.NumberU64()) + ) + for i, sink := range sinks { + sink := sink // Closure for gorotuine below + + sourcePipe, sinkPipe := p2p.MsgPipe() + defer sourcePipe.Close() + defer sinkPipe.Close() + + sourcePeer := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, nil) + sinkPeer := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, nil) + defer sourcePeer.Close() + defer sinkPeer.Close() + + go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(source.handler), peer) + }) + if err := sinkPeer.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + go eth.Handle(sink, sinkPeer) + } + // Subscribe to all the transaction pools + blockChs := make([]chan *types.Block, len(sinks)) + for i := 0; i < len(sinks); i++ { + blockChs[i] = make(chan *types.Block, 1) + defer close(blockChs[i]) + + sub := sinks[i].blockBroadcasts.Subscribe(blockChs[i]) + defer sub.Unsubscribe() + } + // Initiate a block propagation across the peers + time.Sleep(100 * time.Millisecond) + source.handler.BroadcastBlock(source.chain.CurrentBlock(), true) + + // Iterate through all the sinks and ensure the correct number got the block + done := make(chan struct{}, peers) + for _, ch := range blockChs { + ch := ch + go func() { + <-ch + done <- struct{}{} + }() + } + var received int + for { + select { + case <-done: + received++ + + case <-time.After(100 * time.Millisecond): + if received != bcasts { + t.Errorf("broadcast count mismatch: have %d, want %d", received, bcasts) + } + return + } + } +} + +// Tests that a propagated malformed block (uncles or transactions don't match +// with the hashes in the header) gets discarded and not broadcast forward. +func TestBroadcastMalformedBlock64(t *testing.T) { testBroadcastMalformedBlock(t, 64) } +func TestBroadcastMalformedBlock65(t *testing.T) { testBroadcastMalformedBlock(t, 65) } + +func testBroadcastMalformedBlock(t *testing.T, protocol uint) { + t.Parallel() + + // Create a source handler to broadcast blocks from and a number of sinks + // to receive them. + source := newTestHandlerWithBlocks(1) + defer source.close() + + // Create a source handler to send messages through and a sink peer to receive them + p2pSrc, p2pSink := p2p.MsgPipe() + defer p2pSrc.Close() + defer p2pSink.Close() + + src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, source.txpool) + sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, source.txpool) + defer src.Close() + defer sink.Close() + + go source.handler.runEthPeer(src, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(source.handler), peer) + }) + // Run the handshake locally to avoid spinning up a sink handler + var ( + genesis = source.chain.Genesis() + td = source.chain.GetTd(genesis.Hash(), genesis.NumberU64()) + ) + if err := sink.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + // After the handshake completes, the source handler should stream the sink + // the blocks, subscribe to inbound network events + backend := new(testEthHandler) + + blocks := make(chan *types.Block, 1) + sub := backend.blockBroadcasts.Subscribe(blocks) + defer sub.Unsubscribe() + + go eth.Handle(backend, sink) + + // Create various combinations of malformed blocks + head := source.chain.CurrentBlock() + + malformedUncles := head.Header() + malformedUncles.UncleHash[0]++ + malformedTransactions := head.Header() + malformedTransactions.TxHash[0]++ + malformedEverything := head.Header() + malformedEverything.UncleHash[0]++ + malformedEverything.TxHash[0]++ + + // Try to broadcast all malformations and ensure they all get discarded + for _, header := range []*types.Header{malformedUncles, malformedTransactions, malformedEverything} { + block := types.NewBlockWithHeader(header).WithBody(head.Transactions(), head.Uncles()) + if err := src.SendNewBlock(block, big.NewInt(131136)); err != nil { + t.Fatalf("failed to broadcast block: %v", err) + } + select { + case <-blocks: + t.Fatalf("malformed block forwarded") + case <-time.After(100 * time.Millisecond): + } + } +} diff --git a/eth/handler_snap.go b/eth/handler_snap.go new file mode 100644 index 0000000000..25975bf60b --- /dev/null +++ b/eth/handler_snap.go @@ -0,0 +1,48 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/protocols/snap" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// snapHandler implements the snap.Backend interface to handle the various network +// packets that are sent as replies or broadcasts. +type snapHandler handler + +func (h *snapHandler) Chain() *core.BlockChain { return h.chain } + +// RunPeer is invoked when a peer joins on the `snap` protocol. +func (h *snapHandler) RunPeer(peer *snap.Peer, hand snap.Handler) error { + return (*handler)(h).runSnapPeer(peer, hand) +} + +// PeerInfo retrieves all known `snap` information about a peer. +func (h *snapHandler) PeerInfo(id enode.ID) interface{} { + if p := h.peers.snapPeer(id.String()); p != nil { + return p.info() + } + return nil +} + +// Handle is invoked from a peer's message handler when it receives a new remote +// message that the handler couldn't consume and serve itself. +func (h *snapHandler) Handle(peer *snap.Peer, packet snap.Packet) error { + return h.downloader.DeliverSnapPacket(peer, packet) +} diff --git a/eth/handler_test.go b/eth/handler_test.go index fc6c6f2745..a90ef5c348 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -17,678 +17,154 @@ package eth import ( - "fmt" - "math" "math/big" - "math/rand" - "testing" - "time" + "sort" + "sync" "github.com/ethereum/go-ethereum/common" "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/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" ) -// Tests that block headers can be retrieved from a remote chain based on user queries. -func TestGetBlockHeaders63(t *testing.T) { testGetBlockHeaders(t, 63) } -func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) } +var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") -func testGetBlockHeaders(t *testing.T, protocol int) { - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxHashFetch+15, nil, nil) - peer, _ := newTestPeer("peer", protocol, pm, true) - defer peer.close() + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) - // Create a "random" unknown hash for testing - var unknown common.Hash - for i := range unknown { - unknown[i] = byte(i) - } - // Create a batch of tests for various scenarios - limit := uint64(downloader.MaxHeaderFetch) - tests := []struct { - query *getBlockHeadersData // The query to execute for header retrieval - expect []common.Hash // The hashes of the block whose headers are expected - }{ - // A single random block should be retrievable by hash and number too - { - &getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, - []common.Hash{pm.blockchain.GetBlockByNumber(limit / 2).Hash()}, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1}, - []common.Hash{pm.blockchain.GetBlockByNumber(limit / 2).Hash()}, - }, - // Multiple headers should be retrievable in both directions - { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(limit / 2).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 + 1).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 + 2).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(limit / 2).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 - 1).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 - 2).Hash(), - }, - }, - // Multiple headers with skip lists should be retrievable - { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(limit / 2).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 + 4).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 + 8).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(limit / 2).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 - 4).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 - 8).Hash(), - }, - }, - // The chain endpoints should be retrievable - { - &getBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1}, - []common.Hash{pm.blockchain.GetBlockByNumber(0).Hash()}, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64()}, Amount: 1}, - []common.Hash{pm.blockchain.CurrentBlock().Hash()}, - }, - // Ensure protocol limits are honored - { - &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true}, - pm.blockchain.GetBlockHashesFromHash(pm.blockchain.CurrentBlock().Hash(), limit), - }, - // Check that requesting more than available is handled gracefully - { - &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 4).Hash(), - pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64()).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(4).Hash(), - pm.blockchain.GetBlockByNumber(0).Hash(), - }, - }, - // Check that requesting more than available is handled gracefully, even if mid skip - { - &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 4).Hash(), - pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 1).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(4).Hash(), - pm.blockchain.GetBlockByNumber(1).Hash(), - }, - }, - // Check a corner case where requesting more can iterate past the endpoints - { - &getBlockHeadersData{Origin: hashOrNumber{Number: 2}, Amount: 5, Reverse: true}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(2).Hash(), - pm.blockchain.GetBlockByNumber(1).Hash(), - pm.blockchain.GetBlockByNumber(0).Hash(), - }, - }, - // Check a corner case where skipping overflow loops back into the chain start - { - &getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(3).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64 - 1}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(3).Hash(), - }, - }, - // Check a corner case where skipping overflow loops back to the same header - { - &getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(1).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(1).Hash(), - }, - }, - // Check that non existing headers aren't returned - { - &getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1}, - []common.Hash{}, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() + 1}, Amount: 1}, - []common.Hash{}, - }, - } - // Run each of the tests and verify the results against the chain - for i, tt := range tests { - // Collect the headers to expect in the response - headers := []*types.Header{} - for _, hash := range tt.expect { - headers = append(headers, pm.blockchain.GetBlockByHash(hash).Header()) - } - // Send the hash request and verify the response - p2p.Send(peer.app, 0x03, tt.query) - if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { - t.Errorf("test %d: headers mismatch: %v", i, err) - } - // If the test used number origins, repeat with hashes as the too - if tt.query.Origin.Hash == (common.Hash{}) { - if origin := pm.blockchain.GetBlockByNumber(tt.query.Origin.Number); origin != nil { - tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0 +// testTxPool is a mock transaction pool that blindly accepts all transactions. +// Its goal is to get around setting up a valid statedb for the balance and nonce +// checks. +type testTxPool struct { + pool map[common.Hash]*types.Transaction // Hash map of collected transactions - p2p.Send(peer.app, 0x03, tt.query) - if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { - t.Errorf("test %d: headers mismatch: %v", i, err) - } - } - } - } + txFeed event.Feed // Notification feed to allow waiting for inclusion + lock sync.RWMutex // Protects the transaction pool } -// Tests that block contents can be retrieved from a remote chain based on their hashes. -func TestGetBlockBodies63(t *testing.T) { testGetBlockBodies(t, 63) } -func TestGetBlockBodies64(t *testing.T) { testGetBlockBodies(t, 64) } - -func testGetBlockBodies(t *testing.T, protocol int) { - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxBlockFetch+15, nil, nil) - peer, _ := newTestPeer("peer", protocol, pm, true) - defer peer.close() - - // Create a batch of tests for various scenarios - limit := downloader.MaxBlockFetch - tests := []struct { - random int // Number of blocks to fetch randomly from the chain - explicit []common.Hash // Explicitly requested blocks - available []bool // Availability of explicitly requested blocks - expected int // Total number of existing blocks to expect - }{ - {1, nil, nil, 1}, // A single random block should be retrievable - {10, nil, nil, 10}, // Multiple random blocks should be retrievable - {limit, nil, nil, limit}, // The maximum possible blocks should be retrievable - {limit + 1, nil, nil, limit}, // No more than the possible block count should be returned - {0, []common.Hash{pm.blockchain.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable - {0, []common.Hash{pm.blockchain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable - {0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned - - // Existing and non-existing blocks interleaved should not cause problems - {0, []common.Hash{ - {}, - pm.blockchain.GetBlockByNumber(1).Hash(), - {}, - pm.blockchain.GetBlockByNumber(10).Hash(), - {}, - pm.blockchain.GetBlockByNumber(100).Hash(), - {}, - }, []bool{false, true, false, true, false, true, false}, 3}, - } - // Run each of the tests and verify the results against the chain - for i, tt := range tests { - // Collect the hashes to request, and the response to expect - hashes, seen := []common.Hash{}, make(map[int64]bool) - bodies := []*blockBody{} - - for j := 0; j < tt.random; j++ { - for { - num := rand.Int63n(int64(pm.blockchain.CurrentBlock().NumberU64())) - if !seen[num] { - seen[num] = true - - block := pm.blockchain.GetBlockByNumber(uint64(num)) - hashes = append(hashes, block.Hash()) - if len(bodies) < tt.expected { - bodies = append(bodies, &blockBody{Transactions: block.Transactions(), Uncles: block.Uncles()}) - } - break - } - } - } - for j, hash := range tt.explicit { - hashes = append(hashes, hash) - if tt.available[j] && len(bodies) < tt.expected { - block := pm.blockchain.GetBlockByHash(hash) - bodies = append(bodies, &blockBody{Transactions: block.Transactions(), Uncles: block.Uncles()}) - } - } - // Send the hash request and verify the response - p2p.Send(peer.app, 0x05, hashes) - if err := p2p.ExpectMsg(peer.app, 0x06, bodies); err != nil { - t.Errorf("test %d: bodies mismatch: %v", i, err) - } +// newTestTxPool creates a mock transaction pool. +func newTestTxPool() *testTxPool { + return &testTxPool{ + pool: make(map[common.Hash]*types.Transaction), } } -// Tests that the node state database can be retrieved based on hashes. -func TestGetNodeData63(t *testing.T) { testGetNodeData(t, 63) } -func TestGetNodeData64(t *testing.T) { testGetNodeData(t, 64) } +// Has returns an indicator whether txpool has a transaction +// cached with the given hash. +func (p *testTxPool) Has(hash common.Hash) bool { + p.lock.Lock() + defer p.lock.Unlock() -func testGetNodeData(t *testing.T, protocol int) { - // Define three accounts to simulate transactions with - acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) - acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) - - signer := types.HomesteadSigner{} - // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test) - generator := func(i int, block *core.BlockGen) { - switch i { - case 0: - // In block 1, the test bank sends account #1 some ether. - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey) - block.AddTx(tx) - case 1: - // In block 2, the test bank sends some more ether to account #1. - // acc1Addr passes it on to account #2. - tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey) - tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key) - block.AddTx(tx1) - block.AddTx(tx2) - case 2: - // Block 3 is empty but was mined by account #2. - block.SetCoinbase(acc2Addr) - block.SetExtra([]byte("yeehaw")) - case 3: - // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). - b2 := block.PrevBlock(1).Header() - b2.Extra = []byte("foo") - block.AddUncle(b2) - b3 := block.PrevBlock(2).Header() - b3.Extra = []byte("foo") - block.AddUncle(b3) - } - } - // Assemble the test environment - pm, db := newTestProtocolManagerMust(t, downloader.FullSync, 4, generator, nil) - peer, _ := newTestPeer("peer", protocol, pm, true) - defer peer.close() - - // Fetch for now the entire chain db - hashes := []common.Hash{} - - it := db.NewIterator(nil, nil) - for it.Next() { - if key := it.Key(); len(key) == common.HashLength { - hashes = append(hashes, common.BytesToHash(key)) - } - } - it.Release() - - p2p.Send(peer.app, 0x0d, hashes) - msg, err := peer.app.ReadMsg() - if err != nil { - t.Fatalf("failed to read node data response: %v", err) - } - if msg.Code != 0x0e { - t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, 0x0c) - } - var data [][]byte - if err := msg.Decode(&data); err != nil { - t.Fatalf("failed to decode response node data: %v", err) - } - // Verify that all hashes correspond to the requested data, and reconstruct a state tree - for i, want := range hashes { - if hash := crypto.Keccak256Hash(data[i]); hash != want { - t.Errorf("data hash mismatch: have %x, want %x", hash, want) - } - } - statedb := rawdb.NewMemoryDatabase() - for i := 0; i < len(data); i++ { - statedb.Put(hashes[i].Bytes(), data[i]) - } - accounts := []common.Address{testBank, acc1Addr, acc2Addr} - for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ { - trie, _ := state.New(pm.blockchain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb), nil) - - for j, acc := range accounts { - state, _ := pm.blockchain.State() - bw := state.GetBalance(acc) - bh := trie.GetBalance(acc) - - if (bw != nil && bh == nil) || (bw == nil && bh != nil) { - t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) - } - if bw != nil && bh != nil && bw.Cmp(bw) != 0 { - t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) - } - } - } + return p.pool[hash] != nil } -// Tests that the transaction receipts can be retrieved based on hashes. -func TestGetReceipt63(t *testing.T) { testGetReceipt(t, 63) } -func TestGetReceipt64(t *testing.T) { testGetReceipt(t, 64) } +// Get retrieves the transaction from local txpool with given +// tx hash. +func (p *testTxPool) Get(hash common.Hash) *types.Transaction { + p.lock.Lock() + defer p.lock.Unlock() -func testGetReceipt(t *testing.T, protocol int) { - // Define three accounts to simulate transactions with - acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) - acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) - - signer := types.HomesteadSigner{} - // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test) - generator := func(i int, block *core.BlockGen) { - switch i { - case 0: - // In block 1, the test bank sends account #1 some ether. - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey) - block.AddTx(tx) - case 1: - // In block 2, the test bank sends some more ether to account #1. - // acc1Addr passes it on to account #2. - tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey) - tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key) - block.AddTx(tx1) - block.AddTx(tx2) - case 2: - // Block 3 is empty but was mined by account #2. - block.SetCoinbase(acc2Addr) - block.SetExtra([]byte("yeehaw")) - case 3: - // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). - b2 := block.PrevBlock(1).Header() - b2.Extra = []byte("foo") - block.AddUncle(b2) - b3 := block.PrevBlock(2).Header() - b3.Extra = []byte("foo") - block.AddUncle(b3) - } - } - // Assemble the test environment - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 4, generator, nil) - peer, _ := newTestPeer("peer", protocol, pm, true) - defer peer.close() - - // Collect the hashes to request, and the response to expect - hashes, receipts := []common.Hash{}, []types.Receipts{} - for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ { - block := pm.blockchain.GetBlockByNumber(i) - - hashes = append(hashes, block.Hash()) - receipts = append(receipts, pm.blockchain.GetReceiptsByHash(block.Hash())) - } - // Send the hash request and verify the response - p2p.Send(peer.app, 0x0f, hashes) - if err := p2p.ExpectMsg(peer.app, 0x10, receipts); err != nil { - t.Errorf("receipts mismatch: %v", err) - } + return p.pool[hash] } -// Tests that post eth protocol handshake, clients perform a mutual checkpoint -// challenge to validate each other's chains. Hash mismatches, or missing ones -// during a fast sync should lead to the peer getting dropped. -func TestCheckpointChallenge(t *testing.T) { - tests := []struct { - syncmode downloader.SyncMode - checkpoint bool - timeout bool - empty bool - match bool - drop bool - }{ - // If checkpointing is not enabled locally, don't challenge and don't drop - {downloader.FullSync, false, false, false, false, false}, - {downloader.FastSync, false, false, false, false, false}, - - // If checkpointing is enabled locally and remote response is empty, only drop during fast sync - {downloader.FullSync, true, false, true, false, false}, - {downloader.FastSync, true, false, true, false, true}, // Special case, fast sync, unsynced peer - - // If checkpointing is enabled locally and remote response mismatches, always drop - {downloader.FullSync, true, false, false, false, true}, - {downloader.FastSync, true, false, false, false, true}, - - // If checkpointing is enabled locally and remote response matches, never drop - {downloader.FullSync, true, false, false, true, false}, - {downloader.FastSync, true, false, false, true, false}, +// AddRemotes appends a batch of transactions to the pool, and notifies any +// listeners if the addition channel is non nil +func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error { + p.lock.Lock() + defer p.lock.Unlock() - // If checkpointing is enabled locally and remote times out, always drop - {downloader.FullSync, true, true, false, true, true}, - {downloader.FastSync, true, true, false, true, true}, - } - for _, tt := range tests { - t.Run(fmt.Sprintf("sync %v checkpoint %v timeout %v empty %v match %v", tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match), func(t *testing.T) { - testCheckpointChallenge(t, tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match, tt.drop) - }) + for _, tx := range txs { + p.pool[tx.Hash()] = tx } + p.txFeed.Send(core.NewTxsEvent{Txs: txs}) + return make([]error, len(txs)) } -func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) { - // Reduce the checkpoint handshake challenge timeout - defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout) - syncChallengeTimeout = 250 * time.Millisecond - - // Initialize a chain and generate a fake CHT if checkpointing is enabled - var ( - db = rawdb.NewMemoryDatabase() - config = new(params.ChainConfig) - ) - (&core.Genesis{Config: config}).MustCommit(db) // Commit genesis block - // If checkpointing is enabled, create and inject a fake CHT and the corresponding - // chllenge response. - var response *types.Header - var cht *params.TrustedCheckpoint - if checkpoint { - index := uint64(rand.Intn(500)) - number := (index+1)*params.CHTFrequency - 1 - response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")} +// Pending returns all the transactions known to the pool +func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) { + p.lock.RLock() + defer p.lock.RUnlock() - cht = ¶ms.TrustedCheckpoint{ - SectionIndex: index, - SectionHead: response.Hash(), - } + batches := make(map[common.Address]types.Transactions) + for _, tx := range p.pool { + from, _ := types.Sender(types.HomesteadSigner{}, tx) + batches[from] = append(batches[from], tx) } - // Create a checkpoint aware protocol manager - blockchain, err := core.NewBlockChain(db, nil, config, ethash.NewFaker(), vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("failed to create new blockchain: %v", err) - } - pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, ethash.NewFaker(), blockchain, db, 1, nil) - if err != nil { - t.Fatalf("failed to start test protocol manager: %v", err) - } - pm.Start(1000) - defer pm.Stop() - - // Connect a new peer and check that we receive the checkpoint challenge - peer, _ := newTestPeer("peer", eth63, pm, true) - defer peer.close() - - if checkpoint { - challenge := &getBlockHeadersData{ - Origin: hashOrNumber{Number: response.Number.Uint64()}, - Amount: 1, - Skip: 0, - Reverse: false, - } - if err := p2p.ExpectMsg(peer.app, GetBlockHeadersMsg, challenge); err != nil { - t.Fatalf("challenge mismatch: %v", err) - } - // Create a block to reply to the challenge if no timeout is simulated - if !timeout { - if empty { - if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{}); err != nil { - t.Fatalf("failed to answer challenge: %v", err) - } - } else if match { - if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{response}); err != nil { - t.Fatalf("failed to answer challenge: %v", err) - } - } else { - if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{{Number: response.Number}}); err != nil { - t.Fatalf("failed to answer challenge: %v", err) - } - } - } - } - // Wait until the test timeout passes to ensure proper cleanup - time.Sleep(syncChallengeTimeout + 300*time.Millisecond) - - // Verify that the remote peer is maintained or dropped - if drop { - if peers := pm.peers.Len(); peers != 0 { - t.Fatalf("peer count mismatch: have %d, want %d", peers, 0) - } - } else { - if peers := pm.peers.Len(); peers != 1 { - t.Fatalf("peer count mismatch: have %d, want %d", peers, 1) - } + for _, batch := range batches { + sort.Sort(types.TxByNonce(batch)) } + return batches, nil } -func TestBroadcastBlock(t *testing.T) { - var tests = []struct { - totalPeers int - broadcastExpected int - }{ - {1, 1}, - {2, 1}, - {3, 1}, - {4, 2}, - {5, 2}, - {9, 3}, - {12, 3}, - {16, 4}, - {26, 5}, - {100, 10}, - } - for _, test := range tests { - testBroadcastBlock(t, test.totalPeers, test.broadcastExpected) - } +// SubscribeNewTxsEvent should return an event subscription of NewTxsEvent and +// send events to the given channel. +func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { + return p.txFeed.Subscribe(ch) } -func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) { - var ( - evmux = new(event.TypeMux) - pow = ethash.NewFaker() - db = rawdb.NewMemoryDatabase() - config = ¶ms.ChainConfig{} - gspec = &core.Genesis{Config: config} - genesis = gspec.MustCommit(db) - ) - blockchain, err := core.NewBlockChain(db, nil, config, pow, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("failed to create new blockchain: %v", err) - } - pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, pow, blockchain, db, 1, nil) - if err != nil { - t.Fatalf("failed to start test protocol manager: %v", err) - } - pm.Start(1000) - defer pm.Stop() - var peers []*testPeer - for i := 0; i < totalPeers; i++ { - peer, _ := newTestPeer(fmt.Sprintf("peer %d", i), eth63, pm, true) - defer peer.close() - - peers = append(peers, peer) - } - chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {}) - pm.BroadcastBlock(chain[0], true /*propagate*/) - - errCh := make(chan error, totalPeers) - doneCh := make(chan struct{}, totalPeers) - for _, peer := range peers { - go func(p *testPeer) { - if err := p2p.ExpectMsg(p.app, NewBlockMsg, &newBlockData{Block: chain[0], TD: big.NewInt(131136)}); err != nil { - errCh <- err - } else { - doneCh <- struct{}{} - } - }(peer) - } - var received int - for { - select { - case <-doneCh: - received++ - if received > broadcastExpected { - // We can bail early here - t.Errorf("broadcast count mismatch: have %d > want %d", received, broadcastExpected) - return - } - case <-time.After(2 * time.Second): - if received != broadcastExpected { - t.Errorf("broadcast count mismatch: have %d, want %d", received, broadcastExpected) - } - return - case err = <-errCh: - t.Fatalf("broadcast failed: %v", err) - } - } +// testHandler is a live implementation of the Ethereum protocol handler, just +// preinitialized with some sane testing defaults and the transaction pool mocked +// out. +type testHandler struct { + db ethdb.Database + chain *core.BlockChain + txpool *testTxPool + handler *handler +} +// newTestHandler creates a new handler for testing purposes with no blocks. +func newTestHandler() *testHandler { + return newTestHandlerWithBlocks(0) } -// Tests that a propagated malformed block (uncles or transactions don't match -// with the hashes in the header) gets discarded and not broadcast forward. -func TestBroadcastMalformedBlock(t *testing.T) { - // Create a live node to test propagation with - var ( - engine = ethash.NewFaker() - db = rawdb.NewMemoryDatabase() - config = ¶ms.ChainConfig{} - gspec = &core.Genesis{Config: config} - genesis = gspec.MustCommit(db) - ) - blockchain, err := core.NewBlockChain(db, nil, config, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("failed to create new blockchain: %v", err) +// newTestHandlerWithBlocks creates a new handler for testing purposes, with a +// given number of initial blocks. +func newTestHandlerWithBlocks(blocks int) *testHandler { + // Create a database pre-initialize with a genesis block + db := rawdb.NewMemoryDatabase() + (&core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}}, + }).MustCommit(db) + + chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + + bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, nil) + if _, err := chain.InsertChain(bs); err != nil { + panic(err) + } + txpool := newTestTxPool() + + handler, _ := newHandler(&handlerConfig{ + Database: db, + Chain: chain, + TxPool: txpool, + Network: 1, + Sync: downloader.FastSync, + BloomCache: 1, + }) + handler.Start(1000) + + return &testHandler{ + db: db, + chain: chain, + txpool: txpool, + handler: handler, } - pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), engine, blockchain, db, 1, nil) - if err != nil { - t.Fatalf("failed to start test protocol manager: %v", err) - } - pm.Start(2) - defer pm.Stop() - - // Create two peers, one to send the malformed block with and one to check - // propagation - source, _ := newTestPeer("source", eth63, pm, true) - defer source.close() - - sink, _ := newTestPeer("sink", eth63, pm, true) - defer sink.close() - - // Create various combinations of malformed blocks - chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {}) - - malformedUncles := chain[0].Header() - malformedUncles.UncleHash[0]++ - malformedTransactions := chain[0].Header() - malformedTransactions.TxHash[0]++ - malformedEverything := chain[0].Header() - malformedEverything.UncleHash[0]++ - malformedEverything.TxHash[0]++ +} - // Keep listening to broadcasts and notify if any arrives - notify := make(chan struct{}, 1) - go func() { - if _, err := sink.app.ReadMsg(); err == nil { - notify <- struct{}{} - } - }() - // Try to broadcast all malformations and ensure they all get discarded - for _, header := range []*types.Header{malformedUncles, malformedTransactions, malformedEverything} { - block := types.NewBlockWithHeader(header).WithBody(chain[0].Transactions(), chain[0].Uncles()) - if err := p2p.Send(source.app, NewBlockMsg, []interface{}{block, big.NewInt(131136)}); err != nil { - t.Fatalf("failed to broadcast block: %v", err) - } - select { - case <-notify: - t.Fatalf("malformed block forwarded") - case <-time.After(100 * time.Millisecond): - } - } +// close tears down the handler and all its internal constructs. +func (b *testHandler) close() { + b.handler.Stop() + b.chain.Stop() } diff --git a/eth/helper_test.go b/eth/helper_test.go deleted file mode 100644 index c0bda181ea..0000000000 --- a/eth/helper_test.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// This file contains some shares testing functionality, common to multiple -// different files and modules being tested. - -package eth - -import ( - "crypto/ecdsa" - "crypto/rand" - "fmt" - "math/big" - "sort" - "sync" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/params" -) - -var ( - testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testBank = crypto.PubkeyToAddress(testBankKey.PublicKey) -) - -// newTestProtocolManager creates a new protocol manager for testing purposes, -// with the given number of blocks already known, and potential notification -// channels for different events. -func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func(int, *core.BlockGen), newtx chan<- []*types.Transaction) (*ProtocolManager, ethdb.Database, error) { - var ( - evmux = new(event.TypeMux) - engine = ethash.NewFaker() - db = rawdb.NewMemoryDatabase() - gspec = &core.Genesis{ - Config: params.TestChainConfig, - Alloc: core.GenesisAlloc{testBank: {Balance: big.NewInt(1000000)}}, - } - genesis = gspec.MustCommit(db) - blockchain, _ = core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil) - ) - chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, blocks, generator) - if _, err := blockchain.InsertChain(chain); err != nil { - panic(err) - } - pm, err := NewProtocolManager(gspec.Config, nil, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx, pool: make(map[common.Hash]*types.Transaction)}, engine, blockchain, db, 1, nil) - if err != nil { - return nil, nil, err - } - pm.Start(1000) - return pm, db, nil -} - -// newTestProtocolManagerMust creates a new protocol manager for testing purposes, -// with the given number of blocks already known, and potential notification -// channels for different events. In case of an error, the constructor force- -// fails the test. -func newTestProtocolManagerMust(t *testing.T, mode downloader.SyncMode, blocks int, generator func(int, *core.BlockGen), newtx chan<- []*types.Transaction) (*ProtocolManager, ethdb.Database) { - pm, db, err := newTestProtocolManager(mode, blocks, generator, newtx) - if err != nil { - t.Fatalf("Failed to create protocol manager: %v", err) - } - return pm, db -} - -// testTxPool is a fake, helper transaction pool for testing purposes -type testTxPool struct { - txFeed event.Feed - pool map[common.Hash]*types.Transaction // Hash map of collected transactions - added chan<- []*types.Transaction // Notification channel for new transactions - - lock sync.RWMutex // Protects the transaction pool -} - -// Has returns an indicator whether txpool has a transaction -// cached with the given hash. -func (p *testTxPool) Has(hash common.Hash) bool { - p.lock.Lock() - defer p.lock.Unlock() - - return p.pool[hash] != nil -} - -// Get retrieves the transaction from local txpool with given -// tx hash. -func (p *testTxPool) Get(hash common.Hash) *types.Transaction { - p.lock.Lock() - defer p.lock.Unlock() - - return p.pool[hash] -} - -// AddRemotes appends a batch of transactions to the pool, and notifies any -// listeners if the addition channel is non nil -func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error { - p.lock.Lock() - defer p.lock.Unlock() - - for _, tx := range txs { - p.pool[tx.Hash()] = tx - } - if p.added != nil { - p.added <- txs - } - p.txFeed.Send(core.NewTxsEvent{Txs: txs}) - return make([]error, len(txs)) -} - -// Pending returns all the transactions known to the pool -func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) { - p.lock.RLock() - defer p.lock.RUnlock() - - batches := make(map[common.Address]types.Transactions) - for _, tx := range p.pool { - from, _ := types.Sender(types.HomesteadSigner{}, tx) - batches[from] = append(batches[from], tx) - } - for _, batch := range batches { - sort.Sort(types.TxByNonce(batch)) - } - return batches, nil -} - -func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { - return p.txFeed.Subscribe(ch) -} - -// newTestTransaction create a new dummy transaction. -func newTestTransaction(from *ecdsa.PrivateKey, nonce uint64, datasize int) *types.Transaction { - tx := types.NewTransaction(nonce, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, datasize)) - tx, _ = types.SignTx(tx, types.HomesteadSigner{}, from) - return tx -} - -// testPeer is a simulated peer to allow testing direct network calls. -type testPeer struct { - net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging - app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side - *peer -} - -// newTestPeer creates a new peer registered at the given protocol manager. -func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*testPeer, <-chan error) { - // Create a message pipe to communicate through - app, net := p2p.MsgPipe() - - // Start the peer on a new thread - var id enode.ID - rand.Read(id[:]) - peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net, pm.txpool.Get) - errc := make(chan error, 1) - go func() { errc <- pm.runPeer(peer) }() - tp := &testPeer{app: app, net: net, peer: peer} - - // Execute any implicitly requested handshakes and return - if shake { - var ( - genesis = pm.blockchain.Genesis() - head = pm.blockchain.CurrentHeader() - td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) - ) - forkID := forkid.NewID(pm.blockchain.Config(), pm.blockchain.Genesis().Hash(), pm.blockchain.CurrentHeader().Number.Uint64()) - tp.handshake(nil, td, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(pm.blockchain)) - } - return tp, errc -} - -// handshake simulates a trivial handshake that expects the same state from the -// remote side as we are simulating locally. -func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) { - var msg interface{} - switch { - case p.version == eth63: - msg = &statusData63{ - ProtocolVersion: uint32(p.version), - NetworkId: DefaultConfig.NetworkId, - TD: td, - CurrentBlock: head, - GenesisBlock: genesis, - } - case p.version >= eth64: - msg = &statusData{ - ProtocolVersion: uint32(p.version), - NetworkID: DefaultConfig.NetworkId, - TD: td, - Head: head, - Genesis: genesis, - ForkID: forkID, - } - default: - panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version)) - } - if err := p2p.ExpectMsg(p.app, StatusMsg, msg); err != nil { - t.Fatalf("status recv: %v", err) - } - if err := p2p.Send(p.app, StatusMsg, msg); err != nil { - t.Fatalf("status send: %v", err) - } -} - -// close terminates the local side of the peer, notifying the remote protocol -// manager of termination. -func (p *testPeer) close() { - p.app.Close() -} diff --git a/eth/peer.go b/eth/peer.go index 21b82a19c5..6970c8afd3 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -17,806 +17,58 @@ package eth import ( - "errors" - "fmt" "math/big" "sync" "time" - mapset "github.com/deckarep/golang-set" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" ) -var ( - errClosed = errors.New("peer set is closed") - errAlreadyRegistered = errors.New("peer is already registered") - errNotRegistered = errors.New("peer is not registered") -) - -const ( - maxKnownTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS) - maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS) - - // maxQueuedTxs is the maximum number of transactions to queue up before dropping - // older broadcasts. - maxQueuedTxs = 4096 - - // maxQueuedTxAnns is the maximum number of transaction announcements to queue up - // before dropping older announcements. - maxQueuedTxAnns = 4096 - - // maxQueuedBlocks is the maximum number of block propagations to queue up before - // dropping broadcasts. There's not much point in queueing stale blocks, so a few - // that might cover uncles should be enough. - maxQueuedBlocks = 4 - - // maxQueuedBlockAnns is the maximum number of block announcements to queue up before - // dropping broadcasts. Similarly to block propagations, there's no point to queue - // above some healthy uncle limit, so use that. - maxQueuedBlockAnns = 4 - - handshakeTimeout = 5 * time.Second -) - -// max is a helper function which returns the larger of the two given integers. -func max(a, b int) int { - if a > b { - return a - } - return b -} - -// PeerInfo represents a short summary of the Ethereum sub-protocol metadata known +// ethPeerInfo represents a short summary of the `eth` sub-protocol metadata known // about a connected peer. -type PeerInfo struct { - Version int `json:"version"` // Ethereum protocol version negotiated +type ethPeerInfo struct { + Version uint `json:"version"` // Ethereum protocol version negotiated Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain - Head string `json:"head"` // SHA3 hash of the peer's best owned block -} - -// propEvent is a block propagation, waiting for its turn in the broadcast queue. -type propEvent struct { - block *types.Block - td *big.Int -} - -type peer struct { - id string - - *p2p.Peer - rw p2p.MsgReadWriter - - version int // Protocol version negotiated - syncDrop *time.Timer // Timed connection dropper if sync progress isn't validated in time - - head common.Hash - td *big.Int - lock sync.RWMutex - - knownBlocks mapset.Set // Set of block hashes known to be known by this peer - queuedBlocks chan *propEvent // Queue of blocks to broadcast to the peer - queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer - - knownTxs mapset.Set // Set of transaction hashes known to be known by this peer - txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests - txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests - getPooledTx func(common.Hash) *types.Transaction // Callback used to retrieve transaction from txpool - - term chan struct{} // Termination channel to stop the broadcaster + Head string `json:"head"` // Hex hash of the peer's best owned block } -func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer { - return &peer{ - Peer: p, - rw: rw, - version: version, - id: fmt.Sprintf("%x", p.ID().Bytes()[:8]), - knownTxs: mapset.NewSet(), - knownBlocks: mapset.NewSet(), - queuedBlocks: make(chan *propEvent, maxQueuedBlocks), - queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns), - txBroadcast: make(chan []common.Hash), - txAnnounce: make(chan []common.Hash), - getPooledTx: getPooledTx, - term: make(chan struct{}), - } -} - -// broadcastBlocks is a write loop that multiplexes blocks and block accouncements -// to the remote peer. The goal is to have an async writer that does not lock up -// node internals and at the same time rate limits queued data. -func (p *peer) broadcastBlocks(removePeer func(string)) { - for { - select { - case prop := <-p.queuedBlocks: - if err := p.SendNewBlock(prop.block, prop.td); err != nil { - removePeer(p.id) - return - } - p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td) - - case block := <-p.queuedBlockAnns: - if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil { - removePeer(p.id) - return - } - p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash()) - - case <-p.term: - return - } - } -} - -// broadcastTransactions is a write loop that schedules transaction broadcasts -// to the remote peer. The goal is to have an async writer that does not lock up -// node internals and at the same time rate limits queued data. -func (p *peer) broadcastTransactions(removePeer func(string)) { - var ( - queue []common.Hash // Queue of hashes to broadcast as full transactions - done chan struct{} // Non-nil if background broadcaster is running - fail = make(chan error, 1) // Channel used to receive network error - ) - for { - // If there's no in-flight broadcast running, check if a new one is needed - if done == nil && len(queue) > 0 { - // Pile transaction until we reach our allowed network limit - var ( - hashes []common.Hash - txs []*types.Transaction - size common.StorageSize - ) - for i := 0; i < len(queue) && size < txsyncPackSize; i++ { - if tx := p.getPooledTx(queue[i]); tx != nil { - txs = append(txs, tx) - size += tx.Size() - } - hashes = append(hashes, queue[i]) - } - queue = queue[:copy(queue, queue[len(hashes):])] - - // If there's anything available to transfer, fire up an async writer - if len(txs) > 0 { - done = make(chan struct{}) - go func() { - if err := p.sendTransactions(txs); err != nil { - fail <- err - return - } - close(done) - p.Log().Trace("Sent transactions", "count", len(txs)) - }() - } - } - // Transfer goroutine may or may not have been started, listen for events - select { - case hashes := <-p.txBroadcast: - // New batch of transactions to be broadcast, queue them (with cap) - queue = append(queue, hashes...) - if len(queue) > maxQueuedTxs { - // Fancy copy and resize to ensure buffer doesn't grow indefinitely - queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])] - } - - case <-done: - done = nil - - case <-fail: - removePeer(p.id) - return +// ethPeer is a wrapper around eth.Peer to maintain a few extra metadata. +type ethPeer struct { + *eth.Peer - case <-p.term: - return - } - } + syncDrop *time.Timer // Connection dropper if `eth` sync progress isn't validated in time + lock sync.RWMutex // Mutex protecting the internal fields } -// announceTransactions is a write loop that schedules transaction broadcasts -// to the remote peer. The goal is to have an async writer that does not lock up -// node internals and at the same time rate limits queued data. -func (p *peer) announceTransactions(removePeer func(string)) { - var ( - queue []common.Hash // Queue of hashes to announce as transaction stubs - done chan struct{} // Non-nil if background announcer is running - fail = make(chan error, 1) // Channel used to receive network error - ) - for { - // If there's no in-flight announce running, check if a new one is needed - if done == nil && len(queue) > 0 { - // Pile transaction hashes until we reach our allowed network limit - var ( - hashes []common.Hash - pending []common.Hash - size common.StorageSize - ) - for i := 0; i < len(queue) && size < txsyncPackSize; i++ { - if p.getPooledTx(queue[i]) != nil { - pending = append(pending, queue[i]) - size += common.HashLength - } - hashes = append(hashes, queue[i]) - } - queue = queue[:copy(queue, queue[len(hashes):])] - - // If there's anything available to transfer, fire up an async writer - if len(pending) > 0 { - done = make(chan struct{}) - go func() { - if err := p.sendPooledTransactionHashes(pending); err != nil { - fail <- err - return - } - close(done) - p.Log().Trace("Sent transaction announcements", "count", len(pending)) - }() - } - } - // Transfer goroutine may or may not have been started, listen for events - select { - case hashes := <-p.txAnnounce: - // New batch of transactions to be broadcast, queue them (with cap) - queue = append(queue, hashes...) - if len(queue) > maxQueuedTxAnns { - // Fancy copy and resize to ensure buffer doesn't grow indefinitely - queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxAnns:])] - } - - case <-done: - done = nil - - case <-fail: - removePeer(p.id) - return - - case <-p.term: - return - } - } -} - -// close signals the broadcast goroutine to terminate. -func (p *peer) close() { - close(p.term) -} - -// Info gathers and returns a collection of metadata known about a peer. -func (p *peer) Info() *PeerInfo { +// info gathers and returns some `eth` protocol metadata known about a peer. +func (p *ethPeer) info() *ethPeerInfo { hash, td := p.Head() - return &PeerInfo{ - Version: p.version, + return ðPeerInfo{ + Version: p.Version(), Difficulty: td, Head: hash.Hex(), } } -// Head retrieves a copy of the current head hash and total difficulty of the -// peer. -func (p *peer) Head() (hash common.Hash, td *big.Int) { - p.lock.RLock() - defer p.lock.RUnlock() - - copy(hash[:], p.head[:]) - return hash, new(big.Int).Set(p.td) -} - -// SetHead updates the head hash and total difficulty of the peer. -func (p *peer) SetHead(hash common.Hash, td *big.Int) { - p.lock.Lock() - defer p.lock.Unlock() - - copy(p.head[:], hash[:]) - p.td.Set(td) -} - -// MarkBlock marks a block as known for the peer, ensuring that the block will -// never be propagated to this particular peer. -func (p *peer) MarkBlock(hash common.Hash) { - // If we reached the memory allowance, drop a previously known block hash - for p.knownBlocks.Cardinality() >= maxKnownBlocks { - p.knownBlocks.Pop() - } - p.knownBlocks.Add(hash) -} - -// MarkTransaction marks a transaction as known for the peer, ensuring that it -// will never be propagated to this particular peer. -func (p *peer) MarkTransaction(hash common.Hash) { - // If we reached the memory allowance, drop a previously known transaction hash - for p.knownTxs.Cardinality() >= maxKnownTxs { - p.knownTxs.Pop() - } - p.knownTxs.Add(hash) -} - -// SendTransactions64 sends transactions to the peer and includes the hashes -// in its transaction hash set for future reference. -// -// This method is legacy support for initial transaction exchange in eth/64 and -// prior. For eth/65 and higher use SendPooledTransactionHashes. -func (p *peer) SendTransactions64(txs types.Transactions) error { - return p.sendTransactions(txs) -} - -// sendTransactions sends transactions to the peer and includes the hashes -// in its transaction hash set for future reference. -// -// This method is a helper used by the async transaction sender. Don't call it -// directly as the queueing (memory) and transmission (bandwidth) costs should -// not be managed directly. -func (p *peer) sendTransactions(txs types.Transactions) error { - // Mark all the transactions as known, but ensure we don't overflow our limits - for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(txs)) { - p.knownTxs.Pop() - } - for _, tx := range txs { - p.knownTxs.Add(tx.Hash()) - } - return p2p.Send(p.rw, TransactionMsg, txs) -} - -// AsyncSendTransactions queues a list of transactions (by hash) to eventually -// propagate to a remote peer. The number of pending sends are capped (new ones -// will force old sends to be dropped) -func (p *peer) AsyncSendTransactions(hashes []common.Hash) { - select { - case p.txBroadcast <- hashes: - // Mark all the transactions as known, but ensure we don't overflow our limits - for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { - p.knownTxs.Pop() - } - for _, hash := range hashes { - p.knownTxs.Add(hash) - } - case <-p.term: - p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) - } -} - -// sendPooledTransactionHashes sends transaction hashes to the peer and includes -// them in its transaction hash set for future reference. -// -// This method is a helper used by the async transaction announcer. Don't call it -// directly as the queueing (memory) and transmission (bandwidth) costs should -// not be managed directly. -func (p *peer) sendPooledTransactionHashes(hashes []common.Hash) error { - // Mark all the transactions as known, but ensure we don't overflow our limits - for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { - p.knownTxs.Pop() - } - for _, hash := range hashes { - p.knownTxs.Add(hash) - } - return p2p.Send(p.rw, NewPooledTransactionHashesMsg, hashes) -} - -// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually -// announce to a remote peer. The number of pending sends are capped (new ones -// will force old sends to be dropped) -func (p *peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) { - select { - case p.txAnnounce <- hashes: - // Mark all the transactions as known, but ensure we don't overflow our limits - for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { - p.knownTxs.Pop() - } - for _, hash := range hashes { - p.knownTxs.Add(hash) - } - case <-p.term: - p.Log().Debug("Dropping transaction announcement", "count", len(hashes)) - } -} - -// SendPooledTransactionsRLP sends requested transactions to the peer and adds the -// hashes in its transaction hash set for future reference. -// -// Note, the method assumes the hashes are correct and correspond to the list of -// transactions being sent. -func (p *peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValue) error { - // Mark all the transactions as known, but ensure we don't overflow our limits - for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { - p.knownTxs.Pop() - } - for _, hash := range hashes { - p.knownTxs.Add(hash) - } - return p2p.Send(p.rw, PooledTransactionsMsg, txs) -} - -// SendNewBlockHashes announces the availability of a number of blocks through -// a hash notification. -func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error { - // Mark all the block hashes as known, but ensure we don't overflow our limits - for p.knownBlocks.Cardinality() > max(0, maxKnownBlocks-len(hashes)) { - p.knownBlocks.Pop() - } - for _, hash := range hashes { - p.knownBlocks.Add(hash) - } - request := make(newBlockHashesData, len(hashes)) - for i := 0; i < len(hashes); i++ { - request[i].Hash = hashes[i] - request[i].Number = numbers[i] - } - return p2p.Send(p.rw, NewBlockHashesMsg, request) -} - -// AsyncSendNewBlockHash queues the availability of a block for propagation to a -// remote peer. If the peer's broadcast queue is full, the event is silently -// dropped. -func (p *peer) AsyncSendNewBlockHash(block *types.Block) { - select { - case p.queuedBlockAnns <- block: - // Mark all the block hash as known, but ensure we don't overflow our limits - for p.knownBlocks.Cardinality() >= maxKnownBlocks { - p.knownBlocks.Pop() - } - p.knownBlocks.Add(block.Hash()) - default: - p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash()) - } -} - -// SendNewBlock propagates an entire block to a remote peer. -func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error { - // Mark all the block hash as known, but ensure we don't overflow our limits - for p.knownBlocks.Cardinality() >= maxKnownBlocks { - p.knownBlocks.Pop() - } - p.knownBlocks.Add(block.Hash()) - return p2p.Send(p.rw, NewBlockMsg, []interface{}{block, td}) -} - -// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If -// the peer's broadcast queue is full, the event is silently dropped. -func (p *peer) AsyncSendNewBlock(block *types.Block, td *big.Int) { - select { - case p.queuedBlocks <- &propEvent{block: block, td: td}: - // Mark all the block hash as known, but ensure we don't overflow our limits - for p.knownBlocks.Cardinality() >= maxKnownBlocks { - p.knownBlocks.Pop() - } - p.knownBlocks.Add(block.Hash()) - default: - p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash()) - } -} - -// SendBlockHeaders sends a batch of block headers to the remote peer. -func (p *peer) SendBlockHeaders(headers []*types.Header) error { - return p2p.Send(p.rw, BlockHeadersMsg, headers) -} - -// SendBlockBodies sends a batch of block contents to the remote peer. -func (p *peer) SendBlockBodies(bodies []*blockBody) error { - return p2p.Send(p.rw, BlockBodiesMsg, blockBodiesData(bodies)) -} - -// SendBlockBodiesRLP sends a batch of block contents to the remote peer from -// an already RLP encoded format. -func (p *peer) SendBlockBodiesRLP(bodies []rlp.RawValue) error { - return p2p.Send(p.rw, BlockBodiesMsg, bodies) -} - -// SendNodeDataRLP sends a batch of arbitrary internal data, corresponding to the -// hashes requested. -func (p *peer) SendNodeData(data [][]byte) error { - return p2p.Send(p.rw, NodeDataMsg, data) -} - -// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the -// ones requested from an already RLP encoded format. -func (p *peer) SendReceiptsRLP(receipts []rlp.RawValue) error { - return p2p.Send(p.rw, ReceiptsMsg, receipts) -} - -// RequestOneHeader is a wrapper around the header query functions to fetch a -// single header. It is used solely by the fetcher. -func (p *peer) RequestOneHeader(hash common.Hash) error { - p.Log().Debug("Fetching single header", "hash", hash) - return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}, Amount: uint64(1), Skip: uint64(0), Reverse: false}) -} - -// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the -// specified header query, based on the hash of an origin block. -func (p *peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { - p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) - return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}) -} - -// RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the -// specified header query, based on the number of an origin block. -func (p *peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { - p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) - return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}) -} - -// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes -// specified. -func (p *peer) RequestBodies(hashes []common.Hash) error { - p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) - return p2p.Send(p.rw, GetBlockBodiesMsg, hashes) -} - -// RequestNodeData fetches a batch of arbitrary data from a node's known state -// data, corresponding to the specified hashes. -func (p *peer) RequestNodeData(hashes []common.Hash) error { - p.Log().Debug("Fetching batch of state data", "count", len(hashes)) - return p2p.Send(p.rw, GetNodeDataMsg, hashes) -} - -// RequestReceipts fetches a batch of transaction receipts from a remote node. -func (p *peer) RequestReceipts(hashes []common.Hash) error { - p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) - return p2p.Send(p.rw, GetReceiptsMsg, hashes) -} - -// RequestTxs fetches a batch of transactions from a remote node. -func (p *peer) RequestTxs(hashes []common.Hash) error { - p.Log().Debug("Fetching batch of transactions", "count", len(hashes)) - return p2p.Send(p.rw, GetPooledTransactionsMsg, hashes) -} - -// Handshake executes the eth protocol handshake, negotiating version number, -// network IDs, difficulties, head and genesis blocks. -func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error { - // Send out own handshake in a new thread - errc := make(chan error, 2) - - var ( - status63 statusData63 // safe to read after two values have been received from errc - status statusData // safe to read after two values have been received from errc - ) - go func() { - switch { - case p.version == eth63: - errc <- p2p.Send(p.rw, StatusMsg, &statusData63{ - ProtocolVersion: uint32(p.version), - NetworkId: network, - TD: td, - CurrentBlock: head, - GenesisBlock: genesis, - }) - case p.version >= eth64: - errc <- p2p.Send(p.rw, StatusMsg, &statusData{ - ProtocolVersion: uint32(p.version), - NetworkID: network, - TD: td, - Head: head, - Genesis: genesis, - ForkID: forkID, - }) - default: - panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version)) - } - }() - go func() { - switch { - case p.version == eth63: - errc <- p.readStatusLegacy(network, &status63, genesis) - case p.version >= eth64: - errc <- p.readStatus(network, &status, genesis, forkFilter) - default: - panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version)) - } - }() - timeout := time.NewTimer(handshakeTimeout) - defer timeout.Stop() - for i := 0; i < 2; i++ { - select { - case err := <-errc: - if err != nil { - return err - } - case <-timeout.C: - return p2p.DiscReadTimeout - } - } - switch { - case p.version == eth63: - p.td, p.head = status63.TD, status63.CurrentBlock - case p.version >= eth64: - p.td, p.head = status.TD, status.Head - default: - panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version)) - } - return nil -} - -func (p *peer) readStatusLegacy(network uint64, status *statusData63, genesis common.Hash) error { - msg, err := p.rw.ReadMsg() - if err != nil { - return err - } - if msg.Code != StatusMsg { - return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) - } - if msg.Size > protocolMaxMsgSize { - return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize) - } - // Decode the handshake and make sure everything matches - if err := msg.Decode(&status); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - if status.GenesisBlock != genesis { - return errResp(ErrGenesisMismatch, "%x (!= %x)", status.GenesisBlock[:8], genesis[:8]) - } - if status.NetworkId != network { - return errResp(ErrNetworkIDMismatch, "%d (!= %d)", status.NetworkId, network) - } - if int(status.ProtocolVersion) != p.version { - return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version) - } - return nil -} - -func (p *peer) readStatus(network uint64, status *statusData, genesis common.Hash, forkFilter forkid.Filter) error { - msg, err := p.rw.ReadMsg() - if err != nil { - return err - } - if msg.Code != StatusMsg { - return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) - } - if msg.Size > protocolMaxMsgSize { - return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize) - } - // Decode the handshake and make sure everything matches - if err := msg.Decode(&status); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - if status.NetworkID != network { - return errResp(ErrNetworkIDMismatch, "%d (!= %d)", status.NetworkID, network) - } - if int(status.ProtocolVersion) != p.version { - return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version) - } - if status.Genesis != genesis { - return errResp(ErrGenesisMismatch, "%x (!= %x)", status.Genesis, genesis) - } - if err := forkFilter(status.ForkID); err != nil { - return errResp(ErrForkIDRejected, "%v", err) - } - return nil -} - -// String implements fmt.Stringer. -func (p *peer) String() string { - return fmt.Sprintf("Peer %s [%s]", p.id, - fmt.Sprintf("eth/%2d", p.version), - ) -} - -// peerSet represents the collection of active peers currently participating in -// the Ethereum sub-protocol. -type peerSet struct { - peers map[string]*peer - lock sync.RWMutex - closed bool -} - -// newPeerSet creates a new peer set to track the active participants. -func newPeerSet() *peerSet { - return &peerSet{ - peers: make(map[string]*peer), - } -} - -// Register injects a new peer into the working set, or returns an error if the -// peer is already known. If a new peer it registered, its broadcast loop is also -// started. -func (ps *peerSet) Register(p *peer, removePeer func(string)) error { - ps.lock.Lock() - defer ps.lock.Unlock() - - if ps.closed { - return errClosed - } - if _, ok := ps.peers[p.id]; ok { - return errAlreadyRegistered - } - ps.peers[p.id] = p - - go p.broadcastBlocks(removePeer) - go p.broadcastTransactions(removePeer) - if p.version >= eth65 { - go p.announceTransactions(removePeer) - } - return nil -} - -// Unregister removes a remote peer from the active set, disabling any further -// actions to/from that particular entity. -func (ps *peerSet) Unregister(id string) error { - ps.lock.Lock() - defer ps.lock.Unlock() - - p, ok := ps.peers[id] - if !ok { - return errNotRegistered - } - delete(ps.peers, id) - p.close() - - return nil -} - -// Peer retrieves the registered peer with the given id. -func (ps *peerSet) Peer(id string) *peer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return ps.peers[id] -} - -// Len returns if the current number of peers in the set. -func (ps *peerSet) Len() int { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return len(ps.peers) -} - -// PeersWithoutBlock retrieves a list of peers that do not have a given block in -// their set of known hashes. -func (ps *peerSet) PeersWithoutBlock(hash common.Hash) []*peer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - list := make([]*peer, 0, len(ps.peers)) - for _, p := range ps.peers { - if !p.knownBlocks.Contains(hash) { - list = append(list, p) - } - } - return list -} - -// PeersWithoutTx retrieves a list of peers that do not have a given transaction -// in their set of known hashes. -func (ps *peerSet) PeersWithoutTx(hash common.Hash) []*peer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - list := make([]*peer, 0, len(ps.peers)) - for _, p := range ps.peers { - if !p.knownTxs.Contains(hash) { - list = append(list, p) - } - } - return list +// snapPeerInfo represents a short summary of the `snap` sub-protocol metadata known +// about a connected peer. +type snapPeerInfo struct { + Version uint `json:"version"` // Snapshot protocol version negotiated } -// BestPeer retrieves the known peer with the currently highest total difficulty. -func (ps *peerSet) BestPeer() *peer { - ps.lock.RLock() - defer ps.lock.RUnlock() +// snapPeer is a wrapper around snap.Peer to maintain a few extra metadata. +type snapPeer struct { + *snap.Peer - var ( - bestPeer *peer - bestTd *big.Int - ) - for _, p := range ps.peers { - if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 { - bestPeer, bestTd = p, td - } - } - return bestPeer + ethDrop *time.Timer // Connection dropper if `eth` doesn't connect in time + lock sync.RWMutex // Mutex protecting the internal fields } -// Close disconnects all peers. -// No new peers can be registered after Close has returned. -func (ps *peerSet) Close() { - ps.lock.Lock() - defer ps.lock.Unlock() - - for _, p := range ps.peers { - p.Disconnect(p2p.DiscQuitting) +// info gathers and returns some `snap` protocol metadata known about a peer. +func (p *snapPeer) info() *snapPeerInfo { + return &snapPeerInfo{ + Version: p.Version(), } - ps.closed = true } diff --git a/eth/peerset.go b/eth/peerset.go new file mode 100644 index 0000000000..9b584ec320 --- /dev/null +++ b/eth/peerset.go @@ -0,0 +1,301 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/p2p" +) + +var ( + // errPeerSetClosed is returned if a peer is attempted to be added or removed + // from the peer set after it has been terminated. + errPeerSetClosed = errors.New("peerset closed") + + // errPeerAlreadyRegistered is returned if a peer is attempted to be added + // to the peer set, but one with the same id already exists. + errPeerAlreadyRegistered = errors.New("peer already registered") + + // errPeerNotRegistered is returned if a peer is attempted to be removed from + // a peer set, but no peer with the given id exists. + errPeerNotRegistered = errors.New("peer not registered") + + // ethConnectTimeout is the `snap` timeout for `eth` to connect too. + ethConnectTimeout = 3 * time.Second +) + +// peerSet represents the collection of active peers currently participating in +// the `eth` or `snap` protocols. +type peerSet struct { + ethPeers map[string]*ethPeer // Peers connected on the `eth` protocol + snapPeers map[string]*snapPeer // Peers connected on the `snap` protocol + + ethJoinFeed event.Feed // Events when an `eth` peer successfully joins + ethDropFeed event.Feed // Events when an `eth` peer gets dropped + snapJoinFeed event.Feed // Events when a `snap` peer joins on both `eth` and `snap` + snapDropFeed event.Feed // Events when a `snap` peer gets dropped (only if fully joined) + + scope event.SubscriptionScope // Subscription group to unsubscribe everyone at once + + lock sync.RWMutex + closed bool +} + +// newPeerSet creates a new peer set to track the active participants. +func newPeerSet() *peerSet { + return &peerSet{ + ethPeers: make(map[string]*ethPeer), + snapPeers: make(map[string]*snapPeer), + } +} + +// subscribeEthJoin registers a subscription for peers joining (and completing +// the handshake) on the `eth` protocol. +func (ps *peerSet) subscribeEthJoin(ch chan<- *eth.Peer) event.Subscription { + return ps.scope.Track(ps.ethJoinFeed.Subscribe(ch)) +} + +// subscribeEthDrop registers a subscription for peers being dropped from the +// `eth` protocol. +func (ps *peerSet) subscribeEthDrop(ch chan<- *eth.Peer) event.Subscription { + return ps.scope.Track(ps.ethDropFeed.Subscribe(ch)) +} + +// subscribeSnapJoin registers a subscription for peers joining (and completing +// the `eth` join) on the `snap` protocol. +func (ps *peerSet) subscribeSnapJoin(ch chan<- *snap.Peer) event.Subscription { + return ps.scope.Track(ps.snapJoinFeed.Subscribe(ch)) +} + +// subscribeSnapDrop registers a subscription for peers being dropped from the +// `snap` protocol. +func (ps *peerSet) subscribeSnapDrop(ch chan<- *snap.Peer) event.Subscription { + return ps.scope.Track(ps.snapDropFeed.Subscribe(ch)) +} + +// registerEthPeer injects a new `eth` peer into the working set, or returns an +// error if the peer is already known. The peer is announced on the `eth` join +// feed and if it completes a pending `snap` peer, also on that feed. +func (ps *peerSet) registerEthPeer(peer *eth.Peer) error { + ps.lock.Lock() + if ps.closed { + ps.lock.Unlock() + return errPeerSetClosed + } + id := peer.ID() + if _, ok := ps.ethPeers[id]; ok { + ps.lock.Unlock() + return errPeerAlreadyRegistered + } + ps.ethPeers[id] = ðPeer{Peer: peer} + + snap, ok := ps.snapPeers[id] + ps.lock.Unlock() + + if ok { + // Previously dangling `snap` peer, stop it's timer since `eth` connected + snap.lock.Lock() + if snap.ethDrop != nil { + snap.ethDrop.Stop() + snap.ethDrop = nil + } + snap.lock.Unlock() + } + ps.ethJoinFeed.Send(peer) + if ok { + ps.snapJoinFeed.Send(snap.Peer) + } + return nil +} + +// unregisterEthPeer removes a remote peer from the active set, disabling any further +// actions to/from that particular entity. The drop is announced on the `eth` drop +// feed and also on the `snap` feed if the eth/snap duality was broken just now. +func (ps *peerSet) unregisterEthPeer(id string) error { + ps.lock.Lock() + eth, ok := ps.ethPeers[id] + if !ok { + ps.lock.Unlock() + return errPeerNotRegistered + } + delete(ps.ethPeers, id) + + snap, ok := ps.snapPeers[id] + ps.lock.Unlock() + + ps.ethDropFeed.Send(eth) + if ok { + ps.snapDropFeed.Send(snap) + } + return nil +} + +// registerSnapPeer injects a new `snap` peer into the working set, or returns +// an error if the peer is already known. The peer is announced on the `snap` +// join feed if it completes an existing `eth` peer. +// +// If the peer isn't yet connected on `eth` and fails to do so within a given +// amount of time, it is dropped. This enforces that `snap` is an extension to +// `eth`, not a standalone leeching protocol. +func (ps *peerSet) registerSnapPeer(peer *snap.Peer) error { + ps.lock.Lock() + if ps.closed { + ps.lock.Unlock() + return errPeerSetClosed + } + id := peer.ID() + if _, ok := ps.snapPeers[id]; ok { + ps.lock.Unlock() + return errPeerAlreadyRegistered + } + ps.snapPeers[id] = &snapPeer{Peer: peer} + + _, ok := ps.ethPeers[id] + if !ok { + // Dangling `snap` peer, start a timer to drop if `eth` doesn't connect + ps.snapPeers[id].ethDrop = time.AfterFunc(ethConnectTimeout, func() { + peer.Log().Warn("Snapshot peer missing eth, dropping", "addr", peer.RemoteAddr(), "type", peer.Name()) + peer.Disconnect(p2p.DiscUselessPeer) + }) + } + ps.lock.Unlock() + + if ok { + ps.snapJoinFeed.Send(peer) + } + return nil +} + +// unregisterSnapPeer removes a remote peer from the active set, disabling any +// further actions to/from that particular entity. The drop is announced on the +// `snap` drop feed. +func (ps *peerSet) unregisterSnapPeer(id string) error { + ps.lock.Lock() + peer, ok := ps.snapPeers[id] + if !ok { + ps.lock.Unlock() + return errPeerNotRegistered + } + delete(ps.snapPeers, id) + ps.lock.Unlock() + + peer.lock.Lock() + if peer.ethDrop != nil { + peer.ethDrop.Stop() + peer.ethDrop = nil + } + peer.lock.Unlock() + + ps.snapDropFeed.Send(peer) + return nil +} + +// ethPeer retrieves the registered `eth` peer with the given id. +func (ps *peerSet) ethPeer(id string) *ethPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return ps.ethPeers[id] +} + +// snapPeer retrieves the registered `snap` peer with the given id. +func (ps *peerSet) snapPeer(id string) *snapPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return ps.snapPeers[id] +} + +// ethPeersWithoutBlock retrieves a list of `eth` peers that do not have a given +// block in their set of known hashes so it might be propagated to them. +func (ps *peerSet) ethPeersWithoutBlock(hash common.Hash) []*ethPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + list := make([]*ethPeer, 0, len(ps.ethPeers)) + for _, p := range ps.ethPeers { + if !p.KnownBlock(hash) { + list = append(list, p) + } + } + return list +} + +// ethPeersWithoutTransacion retrieves a list of `eth` peers that do not have a +// given transaction in their set of known hashes. +func (ps *peerSet) ethPeersWithoutTransacion(hash common.Hash) []*ethPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + list := make([]*ethPeer, 0, len(ps.ethPeers)) + for _, p := range ps.ethPeers { + if !p.KnownTransaction(hash) { + list = append(list, p) + } + } + return list +} + +// Len returns if the current number of `eth` peers in the set. Since the `snap` +// peers are tied to the existnce of an `eth` connection, that will always be a +// subset of `eth`. +func (ps *peerSet) Len() int { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return len(ps.ethPeers) +} + +// ethPeerWithHighestTD retrieves the known peer with the currently highest total +// difficulty. +func (ps *peerSet) ethPeerWithHighestTD() *eth.Peer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + var ( + bestPeer *eth.Peer + bestTd *big.Int + ) + for _, p := range ps.ethPeers { + if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 { + bestPeer, bestTd = p.Peer, td + } + } + return bestPeer +} + +// close disconnects all peers. +func (ps *peerSet) close() { + ps.lock.Lock() + defer ps.lock.Unlock() + + for _, p := range ps.ethPeers { + p.Disconnect(p2p.DiscQuitting) + } + for _, p := range ps.snapPeers { + p.Disconnect(p2p.DiscQuitting) + } + ps.closed = true +} diff --git a/eth/protocol.go b/eth/protocol.go deleted file mode 100644 index dc75d6b31a..0000000000 --- a/eth/protocol.go +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package eth - -import ( - "fmt" - "io" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/rlp" -) - -// Constants to match up protocol versions and messages -const ( - eth63 = 63 - eth64 = 64 - eth65 = 65 -) - -// protocolName is the official short name of the protocol used during capability negotiation. -const protocolName = "eth" - -// ProtocolVersions are the supported versions of the eth protocol (first is primary). -var ProtocolVersions = []uint{eth65, eth64, eth63} - -// protocolLengths are the number of implemented message corresponding to different protocol versions. -var protocolLengths = map[uint]uint64{eth65: 17, eth64: 17, eth63: 17} - -const protocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message - -// eth protocol message codes -const ( - StatusMsg = 0x00 - NewBlockHashesMsg = 0x01 - TransactionMsg = 0x02 - GetBlockHeadersMsg = 0x03 - BlockHeadersMsg = 0x04 - GetBlockBodiesMsg = 0x05 - BlockBodiesMsg = 0x06 - NewBlockMsg = 0x07 - GetNodeDataMsg = 0x0d - NodeDataMsg = 0x0e - GetReceiptsMsg = 0x0f - ReceiptsMsg = 0x10 - - // New protocol message codes introduced in eth65 - // - // Previously these message ids were used by some legacy and unsupported - // eth protocols, reown them here. - NewPooledTransactionHashesMsg = 0x08 - GetPooledTransactionsMsg = 0x09 - PooledTransactionsMsg = 0x0a -) - -type errCode int - -const ( - ErrMsgTooLarge = iota - ErrDecode - ErrInvalidMsgCode - ErrProtocolVersionMismatch - ErrNetworkIDMismatch - ErrGenesisMismatch - ErrForkIDRejected - ErrNoStatusMsg - ErrExtraStatusMsg -) - -func (e errCode) String() string { - return errorToString[int(e)] -} - -// XXX change once legacy code is out -var errorToString = map[int]string{ - ErrMsgTooLarge: "Message too long", - ErrDecode: "Invalid message", - ErrInvalidMsgCode: "Invalid message code", - ErrProtocolVersionMismatch: "Protocol version mismatch", - ErrNetworkIDMismatch: "Network ID mismatch", - ErrGenesisMismatch: "Genesis mismatch", - ErrForkIDRejected: "Fork ID rejected", - ErrNoStatusMsg: "No status message", - ErrExtraStatusMsg: "Extra status message", -} - -type txPool interface { - // Has returns an indicator whether txpool has a transaction - // cached with the given hash. - Has(hash common.Hash) bool - - // Get retrieves the transaction from local txpool with given - // tx hash. - Get(hash common.Hash) *types.Transaction - - // AddRemotes should add the given transactions to the pool. - AddRemotes([]*types.Transaction) []error - - // Pending should return pending transactions. - // The slice should be modifiable by the caller. - Pending() (map[common.Address]types.Transactions, error) - - // SubscribeNewTxsEvent should return an event subscription of - // NewTxsEvent and send events to the given channel. - SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription -} - -// statusData63 is the network packet for the status message for eth/63. -type statusData63 struct { - ProtocolVersion uint32 - NetworkId uint64 - TD *big.Int - CurrentBlock common.Hash - GenesisBlock common.Hash -} - -// statusData is the network packet for the status message for eth/64 and later. -type statusData struct { - ProtocolVersion uint32 - NetworkID uint64 - TD *big.Int - Head common.Hash - Genesis common.Hash - ForkID forkid.ID -} - -// newBlockHashesData is the network packet for the block announcements. -type newBlockHashesData []struct { - Hash common.Hash // Hash of one particular block being announced - Number uint64 // Number of one particular block being announced -} - -// getBlockHeadersData represents a block header query. -type getBlockHeadersData struct { - Origin hashOrNumber // Block from which to retrieve headers - Amount uint64 // Maximum number of headers to retrieve - Skip uint64 // Blocks to skip between consecutive headers - Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) -} - -// hashOrNumber is a combined field for specifying an origin block. -type hashOrNumber struct { - Hash common.Hash // Block hash from which to retrieve headers (excludes Number) - Number uint64 // Block hash from which to retrieve headers (excludes Hash) -} - -// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the -// two contained union fields. -func (hn *hashOrNumber) EncodeRLP(w io.Writer) error { - if hn.Hash == (common.Hash{}) { - return rlp.Encode(w, hn.Number) - } - if hn.Number != 0 { - return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number) - } - return rlp.Encode(w, hn.Hash) -} - -// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents -// into either a block hash or a block number. -func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error { - _, size, _ := s.Kind() - origin, err := s.Raw() - if err == nil { - switch { - case size == 32: - err = rlp.DecodeBytes(origin, &hn.Hash) - case size <= 8: - err = rlp.DecodeBytes(origin, &hn.Number) - default: - err = fmt.Errorf("invalid input size %d for origin", size) - } - } - return err -} - -// newBlockData is the network packet for the block propagation message. -type newBlockData struct { - Block *types.Block - TD *big.Int -} - -// sanityCheck verifies that the values are reasonable, as a DoS protection -func (request *newBlockData) sanityCheck() error { - if err := request.Block.SanityCheck(); err != nil { - return err - } - //TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times - // larger, it will still fit within 100 bits - if tdlen := request.TD.BitLen(); tdlen > 100 { - return fmt.Errorf("too large block TD: bitlen %d", tdlen) - } - return nil -} - -// blockBody represents the data content of a single block. -type blockBody struct { - Transactions []*types.Transaction // Transactions contained within a block - Uncles []*types.Header // Uncles contained within a block -} - -// blockBodiesData is the network packet for block content distribution. -type blockBodiesData []*blockBody diff --git a/eth/protocol_test.go b/eth/protocol_test.go deleted file mode 100644 index 331dd05ce1..0000000000 --- a/eth/protocol_test.go +++ /dev/null @@ -1,459 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package eth - -import ( - "fmt" - "math/big" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" -) - -func init() { - // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) -} - -var testAccount, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - -// Tests that handshake failures are detected and reported correctly. -func TestStatusMsgErrors63(t *testing.T) { - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil) - var ( - genesis = pm.blockchain.Genesis() - head = pm.blockchain.CurrentHeader() - td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) - ) - defer pm.Stop() - - tests := []struct { - code uint64 - data interface{} - wantError error - }{ - { - code: TransactionMsg, data: []interface{}{}, - wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"), - }, - { - code: StatusMsg, data: statusData63{10, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash()}, - wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", 63), - }, - { - code: StatusMsg, data: statusData63{63, 999, td, head.Hash(), genesis.Hash()}, - wantError: errResp(ErrNetworkIDMismatch, "999 (!= %d)", DefaultConfig.NetworkId), - }, - { - code: StatusMsg, data: statusData63{63, DefaultConfig.NetworkId, td, head.Hash(), common.Hash{3}}, - wantError: errResp(ErrGenesisMismatch, "0300000000000000 (!= %x)", genesis.Hash().Bytes()[:8]), - }, - } - for i, test := range tests { - p, errc := newTestPeer("peer", 63, pm, false) - // The send call might hang until reset because - // the protocol might not read the payload. - go p2p.Send(p.app, test.code, test.data) - - select { - case err := <-errc: - if err == nil { - t.Errorf("test %d: protocol returned nil error, want %q", i, test.wantError) - } else if err.Error() != test.wantError.Error() { - t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.wantError) - } - case <-time.After(2 * time.Second): - t.Errorf("protocol did not shut down within 2 seconds") - } - p.close() - } -} - -func TestStatusMsgErrors64(t *testing.T) { - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil) - var ( - genesis = pm.blockchain.Genesis() - head = pm.blockchain.CurrentHeader() - td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) - forkID = forkid.NewID(pm.blockchain.Config(), pm.blockchain.Genesis().Hash(), pm.blockchain.CurrentHeader().Number.Uint64()) - ) - defer pm.Stop() - - tests := []struct { - code uint64 - data interface{} - wantError error - }{ - { - code: TransactionMsg, data: []interface{}{}, - wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"), - }, - { - code: StatusMsg, data: statusData{10, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash(), forkID}, - wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", 64), - }, - { - code: StatusMsg, data: statusData{64, 999, td, head.Hash(), genesis.Hash(), forkID}, - wantError: errResp(ErrNetworkIDMismatch, "999 (!= %d)", DefaultConfig.NetworkId), - }, - { - code: StatusMsg, data: statusData{64, DefaultConfig.NetworkId, td, head.Hash(), common.Hash{3}, forkID}, - wantError: errResp(ErrGenesisMismatch, "0300000000000000000000000000000000000000000000000000000000000000 (!= %x)", genesis.Hash()), - }, - { - code: StatusMsg, data: statusData{64, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}}, - wantError: errResp(ErrForkIDRejected, forkid.ErrLocalIncompatibleOrStale.Error()), - }, - } - for i, test := range tests { - p, errc := newTestPeer("peer", 64, pm, false) - // The send call might hang until reset because - // the protocol might not read the payload. - go p2p.Send(p.app, test.code, test.data) - - select { - case err := <-errc: - if err == nil { - t.Errorf("test %d: protocol returned nil error, want %q", i, test.wantError) - } else if err.Error() != test.wantError.Error() { - t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.wantError) - } - case <-time.After(2 * time.Second): - t.Errorf("protocol did not shut down within 2 seconds") - } - p.close() - } -} - -func TestForkIDSplit(t *testing.T) { - var ( - engine = ethash.NewFaker() - - configNoFork = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)} - configProFork = ¶ms.ChainConfig{ - HomesteadBlock: big.NewInt(1), - EIP150Block: big.NewInt(2), - EIP155Block: big.NewInt(2), - EIP158Block: big.NewInt(2), - ByzantiumBlock: big.NewInt(3), - } - dbNoFork = rawdb.NewMemoryDatabase() - dbProFork = rawdb.NewMemoryDatabase() - - gspecNoFork = &core.Genesis{Config: configNoFork} - gspecProFork = &core.Genesis{Config: configProFork} - - genesisNoFork = gspecNoFork.MustCommit(dbNoFork) - genesisProFork = gspecProFork.MustCommit(dbProFork) - - chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, configNoFork, engine, vm.Config{}, nil, nil) - chainProFork, _ = core.NewBlockChain(dbProFork, nil, configProFork, engine, vm.Config{}, nil, nil) - - blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil) - blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil) - - ethNoFork, _ = NewProtocolManager(configNoFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainNoFork, dbNoFork, 1, nil) - ethProFork, _ = NewProtocolManager(configProFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainProFork, dbProFork, 1, nil) - ) - ethNoFork.Start(1000) - ethProFork.Start(1000) - - // Both nodes should allow the other to connect (same genesis, next fork is the same) - p2pNoFork, p2pProFork := p2p.MsgPipe() - peerNoFork := newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) - peerProFork := newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) - - errc := make(chan error, 2) - go func() { errc <- ethNoFork.handle(peerProFork) }() - go func() { errc <- ethProFork.handle(peerNoFork) }() - - select { - case err := <-errc: - t.Fatalf("frontier nofork <-> profork failed: %v", err) - case <-time.After(250 * time.Millisecond): - p2pNoFork.Close() - p2pProFork.Close() - } - // Progress into Homestead. Fork's match, so we don't care what the future holds - chainNoFork.InsertChain(blocksNoFork[:1]) - chainProFork.InsertChain(blocksProFork[:1]) - - p2pNoFork, p2pProFork = p2p.MsgPipe() - peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) - peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) - - errc = make(chan error, 2) - go func() { errc <- ethNoFork.handle(peerProFork) }() - go func() { errc <- ethProFork.handle(peerNoFork) }() - - select { - case err := <-errc: - t.Fatalf("homestead nofork <-> profork failed: %v", err) - case <-time.After(250 * time.Millisecond): - p2pNoFork.Close() - p2pProFork.Close() - } - // Progress into Spurious. Forks mismatch, signalling differing chains, reject - chainNoFork.InsertChain(blocksNoFork[1:2]) - chainProFork.InsertChain(blocksProFork[1:2]) - - p2pNoFork, p2pProFork = p2p.MsgPipe() - peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) - peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) - - errc = make(chan error, 2) - go func() { errc <- ethNoFork.handle(peerProFork) }() - go func() { errc <- ethProFork.handle(peerNoFork) }() - - select { - case err := <-errc: - if want := errResp(ErrForkIDRejected, forkid.ErrLocalIncompatibleOrStale.Error()); err.Error() != want.Error() { - t.Fatalf("fork ID rejection error mismatch: have %v, want %v", err, want) - } - case <-time.After(250 * time.Millisecond): - t.Fatalf("split peers not rejected") - } -} - -// This test checks that received transactions are added to the local pool. -func TestRecvTransactions63(t *testing.T) { testRecvTransactions(t, 63) } -func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) } -func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, 65) } - -func testRecvTransactions(t *testing.T, protocol int) { - txAdded := make(chan []*types.Transaction) - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, txAdded) - pm.acceptTxs = 1 // mark synced to accept transactions - p, _ := newTestPeer("peer", protocol, pm, true) - defer pm.Stop() - defer p.close() - - tx := newTestTransaction(testAccount, 0, 0) - if err := p2p.Send(p.app, TransactionMsg, []interface{}{tx}); err != nil { - t.Fatalf("send error: %v", err) - } - select { - case added := <-txAdded: - if len(added) != 1 { - t.Errorf("wrong number of added transactions: got %d, want 1", len(added)) - } else if added[0].Hash() != tx.Hash() { - t.Errorf("added wrong tx hash: got %v, want %v", added[0].Hash(), tx.Hash()) - } - case <-time.After(2 * time.Second): - t.Errorf("no NewTxsEvent received within 2 seconds") - } -} - -// This test checks that pending transactions are sent. -func TestSendTransactions63(t *testing.T) { testSendTransactions(t, 63) } -func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) } -func TestSendTransactions65(t *testing.T) { testSendTransactions(t, 65) } - -func testSendTransactions(t *testing.T, protocol int) { - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil) - defer pm.Stop() - - // Fill the pool with big transactions (use a subscription to wait until all - // the transactions are announced to avoid spurious events causing extra - // broadcasts). - const txsize = txsyncPackSize / 10 - alltxs := make([]*types.Transaction, 100) - for nonce := range alltxs { - alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), txsize) - } - pm.txpool.AddRemotes(alltxs) - time.Sleep(100 * time.Millisecond) // Wait until new tx even gets out of the system (lame) - - // Connect several peers. They should all receive the pending transactions. - var wg sync.WaitGroup - checktxs := func(p *testPeer) { - defer wg.Done() - defer p.close() - seen := make(map[common.Hash]bool) - for _, tx := range alltxs { - seen[tx.Hash()] = false - } - for n := 0; n < len(alltxs) && !t.Failed(); { - var forAllHashes func(callback func(hash common.Hash)) - switch protocol { - case 63: - fallthrough - case 64: - msg, err := p.app.ReadMsg() - if err != nil { - t.Errorf("%v: read error: %v", p.Peer, err) - continue - } else if msg.Code != TransactionMsg { - t.Errorf("%v: got code %d, want TxMsg", p.Peer, msg.Code) - continue - } - var txs []*types.Transaction - if err := msg.Decode(&txs); err != nil { - t.Errorf("%v: %v", p.Peer, err) - continue - } - forAllHashes = func(callback func(hash common.Hash)) { - for _, tx := range txs { - callback(tx.Hash()) - } - } - case 65: - msg, err := p.app.ReadMsg() - if err != nil { - t.Errorf("%v: read error: %v", p.Peer, err) - continue - } else if msg.Code != NewPooledTransactionHashesMsg { - t.Errorf("%v: got code %d, want NewPooledTransactionHashesMsg", p.Peer, msg.Code) - continue - } - var hashes []common.Hash - if err := msg.Decode(&hashes); err != nil { - t.Errorf("%v: %v", p.Peer, err) - continue - } - forAllHashes = func(callback func(hash common.Hash)) { - for _, h := range hashes { - callback(h) - } - } - } - forAllHashes(func(hash common.Hash) { - seentx, want := seen[hash] - if seentx { - t.Errorf("%v: got tx more than once: %x", p.Peer, hash) - } - if !want { - t.Errorf("%v: got unexpected tx: %x", p.Peer, hash) - } - seen[hash] = true - n++ - }) - } - } - for i := 0; i < 3; i++ { - p, _ := newTestPeer(fmt.Sprintf("peer #%d", i), protocol, pm, true) - wg.Add(1) - go checktxs(p) - } - wg.Wait() -} - -func TestTransactionPropagation(t *testing.T) { testSyncTransaction(t, true) } -func TestTransactionAnnouncement(t *testing.T) { testSyncTransaction(t, false) } - -func testSyncTransaction(t *testing.T, propagtion bool) { - // Create a protocol manager for transaction fetcher and sender - pmFetcher, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil) - defer pmFetcher.Stop() - pmSender, _ := newTestProtocolManagerMust(t, downloader.FastSync, 1024, nil, nil) - pmSender.broadcastTxAnnouncesOnly = !propagtion - defer pmSender.Stop() - - // Sync up the two peers - io1, io2 := p2p.MsgPipe() - - go pmSender.handle(pmSender.newPeer(65, p2p.NewPeer(enode.ID{}, "sender", nil), io2, pmSender.txpool.Get)) - go pmFetcher.handle(pmFetcher.newPeer(65, p2p.NewPeer(enode.ID{}, "fetcher", nil), io1, pmFetcher.txpool.Get)) - - time.Sleep(250 * time.Millisecond) - pmFetcher.doSync(peerToSyncOp(downloader.FullSync, pmFetcher.peers.BestPeer())) - atomic.StoreUint32(&pmFetcher.acceptTxs, 1) - - newTxs := make(chan core.NewTxsEvent, 1024) - sub := pmFetcher.txpool.SubscribeNewTxsEvent(newTxs) - defer sub.Unsubscribe() - - // Fill the pool with new transactions - alltxs := make([]*types.Transaction, 1024) - for nonce := range alltxs { - alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), 0) - } - pmSender.txpool.AddRemotes(alltxs) - - var got int -loop: - for { - select { - case ev := <-newTxs: - got += len(ev.Txs) - if got == 1024 { - break loop - } - case <-time.NewTimer(time.Second).C: - t.Fatal("Failed to retrieve all transaction") - } - } -} - -// Tests that the custom union field encoder and decoder works correctly. -func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { - // Create a "random" hash for testing - var hash common.Hash - for i := range hash { - hash[i] = byte(i) - } - // Assemble some table driven tests - tests := []struct { - packet *getBlockHeadersData - fail bool - }{ - // Providing the origin as either a hash or a number should both work - {fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Number: 314}}}, - {fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}}}, - - // Providing arbitrary query field should also work - {fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}}, - {fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}}, - - // Providing both the origin hash and origin number must fail - {fail: true, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash, Number: 314}}}, - } - // Iterate over each of the tests and try to encode and then decode - for i, tt := range tests { - bytes, err := rlp.EncodeToBytes(tt.packet) - if err != nil && !tt.fail { - t.Fatalf("test %d: failed to encode packet: %v", i, err) - } else if err == nil && tt.fail { - t.Fatalf("test %d: encode should have failed", i) - } - if !tt.fail { - packet := new(getBlockHeadersData) - if err := rlp.DecodeBytes(bytes, packet); err != nil { - t.Fatalf("test %d: failed to decode packet: %v", i, err) - } - if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount || - packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse { - t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet) - } - } - } -} diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go new file mode 100644 index 0000000000..2349398fae --- /dev/null +++ b/eth/protocols/eth/broadcast.go @@ -0,0 +1,195 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +const ( + // This is the target size for the packs of transactions or announcements. A + // pack can get larger than this if a single transactions exceeds this size. + maxTxPacketSize = 100 * 1024 +) + +// blockPropagation is a block propagation event, waiting for its turn in the +// broadcast queue. +type blockPropagation struct { + block *types.Block + td *big.Int +} + +// broadcastBlocks is a write loop that multiplexes blocks and block accouncements +// to the remote peer. The goal is to have an async writer that does not lock up +// node internals and at the same time rate limits queued data. +func (p *Peer) broadcastBlocks() { + for { + select { + case prop := <-p.queuedBlocks: + if err := p.SendNewBlock(prop.block, prop.td); err != nil { + return + } + p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td) + + case block := <-p.queuedBlockAnns: + if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil { + return + } + p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash()) + + case <-p.term: + return + } + } +} + +// broadcastTransactions is a write loop that schedules transaction broadcasts +// to the remote peer. The goal is to have an async writer that does not lock up +// node internals and at the same time rate limits queued data. +func (p *Peer) broadcastTransactions() { + var ( + queue []common.Hash // Queue of hashes to broadcast as full transactions + done chan struct{} // Non-nil if background broadcaster is running + fail = make(chan error, 1) // Channel used to receive network error + failed bool // Flag whether a send failed, discard everything onward + ) + for { + // If there's no in-flight broadcast running, check if a new one is needed + if done == nil && len(queue) > 0 { + // Pile transaction until we reach our allowed network limit + var ( + hashes []common.Hash + txs []*types.Transaction + size common.StorageSize + ) + for i := 0; i < len(queue) && size < maxTxPacketSize; i++ { + if tx := p.txpool.Get(queue[i]); tx != nil { + txs = append(txs, tx) + size += tx.Size() + } + hashes = append(hashes, queue[i]) + } + queue = queue[:copy(queue, queue[len(hashes):])] + + // If there's anything available to transfer, fire up an async writer + if len(txs) > 0 { + done = make(chan struct{}) + go func() { + if err := p.SendTransactions(txs); err != nil { + fail <- err + return + } + close(done) + p.Log().Trace("Sent transactions", "count", len(txs)) + }() + } + } + // Transfer goroutine may or may not have been started, listen for events + select { + case hashes := <-p.txBroadcast: + // If the connection failed, discard all transaction events + if failed { + continue + } + // New batch of transactions to be broadcast, queue them (with cap) + queue = append(queue, hashes...) + if len(queue) > maxQueuedTxs { + // Fancy copy and resize to ensure buffer doesn't grow indefinitely + queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])] + } + + case <-done: + done = nil + + case <-fail: + failed = true + + case <-p.term: + return + } + } +} + +// announceTransactions is a write loop that schedules transaction broadcasts +// to the remote peer. The goal is to have an async writer that does not lock up +// node internals and at the same time rate limits queued data. +func (p *Peer) announceTransactions() { + var ( + queue []common.Hash // Queue of hashes to announce as transaction stubs + done chan struct{} // Non-nil if background announcer is running + fail = make(chan error, 1) // Channel used to receive network error + failed bool // Flag whether a send failed, discard everything onward + ) + for { + // If there's no in-flight announce running, check if a new one is needed + if done == nil && len(queue) > 0 { + // Pile transaction hashes until we reach our allowed network limit + var ( + hashes []common.Hash + pending []common.Hash + size common.StorageSize + ) + for i := 0; i < len(queue) && size < maxTxPacketSize; i++ { + if p.txpool.Get(queue[i]) != nil { + pending = append(pending, queue[i]) + size += common.HashLength + } + hashes = append(hashes, queue[i]) + } + queue = queue[:copy(queue, queue[len(hashes):])] + + // If there's anything available to transfer, fire up an async writer + if len(pending) > 0 { + done = make(chan struct{}) + go func() { + if err := p.sendPooledTransactionHashes(pending); err != nil { + fail <- err + return + } + close(done) + p.Log().Trace("Sent transaction announcements", "count", len(pending)) + }() + } + } + // Transfer goroutine may or may not have been started, listen for events + select { + case hashes := <-p.txAnnounce: + // If the connection failed, discard all transaction events + if failed { + continue + } + // New batch of transactions to be broadcast, queue them (with cap) + queue = append(queue, hashes...) + if len(queue) > maxQueuedTxAnns { + // Fancy copy and resize to ensure buffer doesn't grow indefinitely + queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])] + } + + case <-done: + done = nil + + case <-fail: + failed = true + + case <-p.term: + return + } + } +} diff --git a/eth/protocols/eth/discovery.go b/eth/protocols/eth/discovery.go new file mode 100644 index 0000000000..025479b423 --- /dev/null +++ b/eth/protocols/eth/discovery.go @@ -0,0 +1,65 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" +) + +// enrEntry is the ENR entry which advertises `eth` protocol on the discovery. +type enrEntry struct { + ForkID forkid.ID // Fork identifier per EIP-2124 + + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` +} + +// ENRKey implements enr.Entry. +func (e enrEntry) ENRKey() string { + return "eth" +} + +// StartENRUpdater starts the `eth` ENR updater loop, which listens for chain +// head events and updates the requested node record whenever a fork is passed. +func StartENRUpdater(chain *core.BlockChain, ln *enode.LocalNode) { + var newHead = make(chan core.ChainHeadEvent, 10) + sub := chain.SubscribeChainHeadEvent(newHead) + + go func() { + defer sub.Unsubscribe() + for { + select { + case <-newHead: + ln.Set(currentENREntry(chain)) + case <-sub.Err(): + // Would be nice to sync with Stop, but there is no + // good way to do that. + return + } + } + }() +} + +// currentENREntry constructs an `eth` ENR entry based on the current state of the chain. +func currentENREntry(chain *core.BlockChain) *enrEntry { + return &enrEntry{ + ForkID: forkid.NewID(chain.Config(), chain.Genesis().Hash(), chain.CurrentHeader().Number.Uint64()), + } +} diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go new file mode 100644 index 0000000000..25ddcd93ec --- /dev/null +++ b/eth/protocols/eth/handler.go @@ -0,0 +1,512 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "encoding/json" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +const ( + // softResponseLimit is the target maximum size of replies to data retrievals. + softResponseLimit = 2 * 1024 * 1024 + + // estHeaderSize is the approximate size of an RLP encoded block header. + estHeaderSize = 500 + + // maxHeadersServe is the maximum number of block headers to serve. This number + // is there to limit the number of disk lookups. + maxHeadersServe = 1024 + + // maxBodiesServe is the maximum number of block bodies to serve. This number + // is mostly there to limit the number of disk lookups. With 24KB block sizes + // nowadays, the practical limit will always be softResponseLimit. + maxBodiesServe = 1024 + + // maxNodeDataServe is the maximum number of state trie nodes to serve. This + // number is there to limit the number of disk lookups. + maxNodeDataServe = 1024 + + // maxReceiptsServe is the maximum number of block receipts to serve. This + // number is mostly there to limit the number of disk lookups. With block + // containing 200+ transactions nowadays, the practical limit will always + // be softResponseLimit. + maxReceiptsServe = 1024 +) + +// Handler is a callback to invoke from an outside runner after the boilerplate +// exchanges have passed. +type Handler func(peer *Peer) error + +// Backend defines the data retrieval methods to serve remote requests and the +// callback methods to invoke on remote deliveries. +type Backend interface { + // Chain retrieves the blockchain object to serve data. + Chain() *core.BlockChain + + // StateBloom retrieves the bloom filter - if any - for state trie nodes. + StateBloom() *trie.SyncBloom + + // TxPool retrieves the transaction pool object to serve data. + TxPool() TxPool + + // AcceptTxs retrieves whether transaction processing is enabled on the node + // or if inbound transactions should simply be dropped. + AcceptTxs() bool + + // RunPeer is invoked when a peer joins on the `eth` protocol. The handler + // should do any peer maintenance work, handshakes and validations. If all + // is passed, control should be given back to the `handler` to process the + // inbound messages going forward. + RunPeer(peer *Peer, handler Handler) error + + // PeerInfo retrieves all known `eth` information about a peer. + PeerInfo(id enode.ID) interface{} + + // Handle is a callback to be invoked when a data packet is received from + // the remote peer. Only packets not consumed by the protocol handler will + // be forwarded to the backend. + Handle(peer *Peer, packet Packet) error +} + +// TxPool defines the methods needed by the protocol handler to serve transactions. +type TxPool interface { + // Get retrieves the the transaction from the local txpool with the given hash. + Get(hash common.Hash) *types.Transaction +} + +// MakeProtocols constructs the P2P protocol definitions for `eth`. +func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol { + protocols := make([]p2p.Protocol, len(protocolVersions)) + for i, version := range protocolVersions { + version := version // Closure + + protocols[i] = p2p.Protocol{ + Name: protocolName, + Version: version, + Length: protocolLengths[version], + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + peer := NewPeer(version, p, rw, backend.TxPool()) + defer peer.Close() + + return backend.RunPeer(peer, func(peer *Peer) error { + return Handle(backend, peer) + }) + }, + NodeInfo: func() interface{} { + return nodeInfo(backend.Chain(), network) + }, + PeerInfo: func(id enode.ID) interface{} { + return backend.PeerInfo(id) + }, + Attributes: []enr.Entry{currentENREntry(backend.Chain())}, + DialCandidates: dnsdisc, + } + } + return protocols +} + +// NodeInfo represents a short summary of the `eth` sub-protocol metadata +// known about the host peer. +type NodeInfo struct { + Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4) + Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain + Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block + Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules + Head common.Hash `json:"head"` // Hex hash of the host's best owned block +} + +// nodeInfo retrieves some `eth` protocol metadata about the running host node. +func nodeInfo(chain *core.BlockChain, network uint64) *NodeInfo { + head := chain.CurrentBlock() + return &NodeInfo{ + Network: network, + Difficulty: chain.GetTd(head.Hash(), head.NumberU64()), + Genesis: chain.Genesis().Hash(), + Config: chain.Config(), + Head: head.Hash(), + } +} + +// Handle is invoked whenever an `eth` connection is made that successfully passes +// the protocol handshake. This method will keep processing messages until the +// connection is torn down. +func Handle(backend Backend, peer *Peer) error { + for { + if err := handleMessage(backend, peer); err != nil { + peer.Log().Debug("Message handling failed in `eth`", "err", err) + return err + } + } +} + +// handleMessage is invoked whenever an inbound message is received from a remote +// peer. The remote connection is torn down upon returning any error. +func handleMessage(backend Backend, peer *Peer) error { + // Read the next message from the remote peer, and ensure it's fully consumed + msg, err := peer.rw.ReadMsg() + if err != nil { + return err + } + if msg.Size > maxMessageSize { + return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) + } + defer msg.Discard() + + // Handle the message depending on its contents + switch { + case msg.Code == StatusMsg: + // Status messages should never arrive after the handshake + return fmt.Errorf("%w: uncontrolled status message", errExtraStatusMsg) + + // Block header query, collect the requested headers and reply + case msg.Code == GetBlockHeadersMsg: + // Decode the complex header query + var query GetBlockHeadersPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + hashMode := query.Origin.Hash != (common.Hash{}) + first := true + maxNonCanonical := uint64(100) + + // Gather headers until the fetch or network limits is reached + var ( + bytes common.StorageSize + headers []*types.Header + unknown bool + lookups int + ) + for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && + len(headers) < maxHeadersServe && lookups < 2*maxHeadersServe { + lookups++ + // Retrieve the next header satisfying the query + var origin *types.Header + if hashMode { + if first { + first = false + origin = backend.Chain().GetHeaderByHash(query.Origin.Hash) + if origin != nil { + query.Origin.Number = origin.Number.Uint64() + } + } else { + origin = backend.Chain().GetHeader(query.Origin.Hash, query.Origin.Number) + } + } else { + origin = backend.Chain().GetHeaderByNumber(query.Origin.Number) + } + if origin == nil { + break + } + headers = append(headers, origin) + bytes += estHeaderSize + + // Advance to the next header of the query + switch { + case hashMode && query.Reverse: + // Hash based traversal towards the genesis block + ancestor := query.Skip + 1 + if ancestor == 0 { + unknown = true + } else { + query.Origin.Hash, query.Origin.Number = backend.Chain().GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) + unknown = (query.Origin.Hash == common.Hash{}) + } + case hashMode && !query.Reverse: + // Hash based traversal towards the leaf block + var ( + current = origin.Number.Uint64() + next = current + query.Skip + 1 + ) + if next <= current { + infos, _ := json.MarshalIndent(peer.Peer.Info(), "", " ") + peer.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) + unknown = true + } else { + if header := backend.Chain().GetHeaderByNumber(next); header != nil { + nextHash := header.Hash() + expOldHash, _ := backend.Chain().GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) + if expOldHash == query.Origin.Hash { + query.Origin.Hash, query.Origin.Number = nextHash, next + } else { + unknown = true + } + } else { + unknown = true + } + } + case query.Reverse: + // Number based traversal towards the genesis block + if query.Origin.Number >= query.Skip+1 { + query.Origin.Number -= query.Skip + 1 + } else { + unknown = true + } + + case !query.Reverse: + // Number based traversal towards the leaf block + query.Origin.Number += query.Skip + 1 + } + } + return peer.SendBlockHeaders(headers) + + case msg.Code == BlockHeadersMsg: + // A batch of headers arrived to one of our previous requests + res := new(BlockHeadersPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + case msg.Code == GetBlockBodiesMsg: + // Decode the block body retrieval message + var query GetBlockBodiesPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Gather blocks until the fetch or network limits is reached + var ( + bytes int + bodies []rlp.RawValue + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(bodies) >= maxBodiesServe || + lookups >= 2*maxBodiesServe { + break + } + if data := backend.Chain().GetBodyRLP(hash); len(data) != 0 { + bodies = append(bodies, data) + bytes += len(data) + } + } + return peer.SendBlockBodiesRLP(bodies) + + case msg.Code == BlockBodiesMsg: + // A batch of block bodies arrived to one of our previous requests + res := new(BlockBodiesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + case msg.Code == GetNodeDataMsg: + // Decode the trie node data retrieval message + var query GetNodeDataPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Gather state data until the fetch or network limits is reached + var ( + bytes int + nodes [][]byte + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(nodes) >= maxNodeDataServe || + lookups >= 2*maxNodeDataServe { + break + } + // Retrieve the requested state entry + if bloom := backend.StateBloom(); bloom != nil && !bloom.Contains(hash[:]) { + // Only lookup the trie node if there's chance that we actually have it + continue + } + entry, err := backend.Chain().TrieNode(hash) + if len(entry) == 0 || err != nil { + // Read the contract code with prefix only to save unnecessary lookups. + entry, err = backend.Chain().ContractCodeWithPrefix(hash) + } + if err == nil && len(entry) > 0 { + nodes = append(nodes, entry) + bytes += len(entry) + } + } + return peer.SendNodeData(nodes) + + case msg.Code == NodeDataMsg: + // A batch of node state data arrived to one of our previous requests + res := new(NodeDataPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + case msg.Code == GetReceiptsMsg: + // Decode the block receipts retrieval message + var query GetReceiptsPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Gather state data until the fetch or network limits is reached + var ( + bytes int + receipts []rlp.RawValue + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe || + lookups >= 2*maxReceiptsServe { + break + } + // Retrieve the requested block's receipts + results := backend.Chain().GetReceiptsByHash(hash) + if results == nil { + if header := backend.Chain().GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { + continue + } + } + // If known, encode and queue for response packet + if encoded, err := rlp.EncodeToBytes(results); err != nil { + log.Error("Failed to encode receipt", "err", err) + } else { + receipts = append(receipts, encoded) + bytes += len(encoded) + } + } + return peer.SendReceiptsRLP(receipts) + + case msg.Code == ReceiptsMsg: + // A batch of receipts arrived to one of our previous requests + res := new(ReceiptsPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + case msg.Code == NewBlockHashesMsg: + // A batch of new block announcements just arrived + ann := new(NewBlockHashesPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Mark the hashes as present at the remote node + for _, block := range *ann { + peer.markBlock(block.Hash) + } + // Deliver them all to the backend for queuing + return backend.Handle(peer, ann) + + case msg.Code == NewBlockMsg: + // Retrieve and decode the propagated block + ann := new(NewBlockPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if hash := types.CalcUncleHash(ann.Block.Uncles()); hash != ann.Block.UncleHash() { + log.Warn("Propagated block has invalid uncles", "have", hash, "exp", ann.Block.UncleHash()) + break // TODO(karalabe): return error eventually, but wait a few releases + } + if hash := types.DeriveSha(ann.Block.Transactions(), trie.NewStackTrie(nil)); hash != ann.Block.TxHash() { + log.Warn("Propagated block has invalid body", "have", hash, "exp", ann.Block.TxHash()) + break // TODO(karalabe): return error eventually, but wait a few releases + } + if err := ann.sanityCheck(); err != nil { + return err + } + ann.Block.ReceivedAt = msg.ReceivedAt + ann.Block.ReceivedFrom = peer + + // Mark the peer as owning the block + peer.markBlock(ann.Block.Hash()) + + return backend.Handle(peer, ann) + + case msg.Code == NewPooledTransactionHashesMsg && peer.version >= ETH65: + // New transaction announcement arrived, make sure we have + // a valid and fresh chain to handle them + if !backend.AcceptTxs() { + break + } + ann := new(NewPooledTransactionHashesPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Schedule all the unknown hashes for retrieval + for _, hash := range *ann { + peer.markTransaction(hash) + } + return backend.Handle(peer, ann) + + case msg.Code == GetPooledTransactionsMsg && peer.version >= ETH65: + // Decode the pooled transactions retrieval message + var query GetPooledTransactionsPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Gather transactions until the fetch or network limits is reached + var ( + bytes int + hashes []common.Hash + txs []rlp.RawValue + ) + for _, hash := range query { + if bytes >= softResponseLimit { + break + } + // Retrieve the requested transaction, skipping if unknown to us + tx := backend.TxPool().Get(hash) + if tx == nil { + continue + } + // If known, encode and queue for response packet + if encoded, err := rlp.EncodeToBytes(tx); err != nil { + log.Error("Failed to encode transaction", "err", err) + } else { + hashes = append(hashes, hash) + txs = append(txs, encoded) + bytes += len(encoded) + } + } + return peer.SendPooledTransactionsRLP(hashes, txs) + + case msg.Code == TransactionsMsg || (msg.Code == PooledTransactionsMsg && peer.version >= ETH65): + // Transactions arrived, make sure we have a valid and fresh chain to handle them + if !backend.AcceptTxs() { + break + } + // Transactions can be processed, parse all of them and deliver to the pool + var txs []*types.Transaction + if err := msg.Decode(&txs); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + for i, tx := range txs { + // Validate and mark the remote transaction + if tx == nil { + return fmt.Errorf("%w: transaction %d is nil", errDecode, i) + } + peer.markTransaction(tx.Hash()) + } + if msg.Code == PooledTransactionsMsg { + return backend.Handle(peer, (*PooledTransactionsPacket)(&txs)) + } + return backend.Handle(peer, (*TransactionsPacket)(&txs)) + + default: + return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) + } + return nil +} diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go new file mode 100644 index 0000000000..65c4a10b0a --- /dev/null +++ b/eth/protocols/eth/handler_test.go @@ -0,0 +1,519 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "math" + "math/big" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "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/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) + +// testBackend is a mock implementation of the live Ethereum message handler. Its +// purpose is to allow testing the request/reply workflows and wire serialization +// in the `eth` protocol without actually doing any data processing. +type testBackend struct { + db ethdb.Database + chain *core.BlockChain + txpool *core.TxPool +} + +// newTestBackend creates an empty chain and wraps it into a mock backend. +func newTestBackend(blocks int) *testBackend { + return newTestBackendWithGenerator(blocks, nil) +} + +// newTestBackend creates a chain with a number of explicitly defined blocks and +// wraps it into a mock backend. +func newTestBackendWithGenerator(blocks int, generator func(int, *core.BlockGen)) *testBackend { + // Create a database pre-initialize with a genesis block + db := rawdb.NewMemoryDatabase() + (&core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}}, + }).MustCommit(db) + + chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + + bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, generator) + if _, err := chain.InsertChain(bs); err != nil { + panic(err) + } + txconfig := core.DefaultTxPoolConfig + txconfig.Journal = "" // Don't litter the disk with test journals + + return &testBackend{ + db: db, + chain: chain, + txpool: core.NewTxPool(txconfig, params.TestChainConfig, chain), + } +} + +// close tears down the transaction pool and chain behind the mock backend. +func (b *testBackend) close() { + b.txpool.Stop() + b.chain.Stop() +} + +func (b *testBackend) Chain() *core.BlockChain { return b.chain } +func (b *testBackend) StateBloom() *trie.SyncBloom { return nil } +func (b *testBackend) TxPool() TxPool { return b.txpool } + +func (b *testBackend) RunPeer(peer *Peer, handler Handler) error { + // Normally the backend would do peer mainentance and handshakes. All that + // is omitted and we will just give control back to the handler. + return handler(peer) +} +func (b *testBackend) PeerInfo(enode.ID) interface{} { panic("not implemented") } + +func (b *testBackend) AcceptTxs() bool { + panic("data processing tests should be done in the handler package") +} +func (b *testBackend) Handle(*Peer, Packet) error { + panic("data processing tests should be done in the handler package") +} + +// Tests that block headers can be retrieved from a remote chain based on user queries. +func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) } +func TestGetBlockHeaders65(t *testing.T) { testGetBlockHeaders(t, 65) } + +func testGetBlockHeaders(t *testing.T, protocol uint) { + t.Parallel() + + backend := newTestBackend(maxHeadersServe + 15) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + // Create a "random" unknown hash for testing + var unknown common.Hash + for i := range unknown { + unknown[i] = byte(i) + } + // Create a batch of tests for various scenarios + limit := uint64(maxHeadersServe) + tests := []struct { + query *GetBlockHeadersPacket // The query to execute for header retrieval + expect []common.Hash // The hashes of the block whose headers are expected + }{ + // A single random block should be retrievable by hash and number too + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, + []common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()}, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 1}, + []common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()}, + }, + // Multiple headers should be retrievable in both directions + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 3}, + []common.Hash{ + backend.chain.GetBlockByNumber(limit / 2).Hash(), + backend.chain.GetBlockByNumber(limit/2 + 1).Hash(), + backend.chain.GetBlockByNumber(limit/2 + 2).Hash(), + }, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(limit / 2).Hash(), + backend.chain.GetBlockByNumber(limit/2 - 1).Hash(), + backend.chain.GetBlockByNumber(limit/2 - 2).Hash(), + }, + }, + // Multiple headers with skip lists should be retrievable + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, + []common.Hash{ + backend.chain.GetBlockByNumber(limit / 2).Hash(), + backend.chain.GetBlockByNumber(limit/2 + 4).Hash(), + backend.chain.GetBlockByNumber(limit/2 + 8).Hash(), + }, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(limit / 2).Hash(), + backend.chain.GetBlockByNumber(limit/2 - 4).Hash(), + backend.chain.GetBlockByNumber(limit/2 - 8).Hash(), + }, + }, + // The chain endpoints should be retrievable + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 0}, Amount: 1}, + []common.Hash{backend.chain.GetBlockByNumber(0).Hash()}, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64()}, Amount: 1}, + []common.Hash{backend.chain.CurrentBlock().Hash()}, + }, + // Ensure protocol limits are honored + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true}, + backend.chain.GetBlockHashesFromHash(backend.chain.CurrentBlock().Hash(), limit), + }, + // Check that requesting more than available is handled gracefully + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3}, + []common.Hash{ + backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 4).Hash(), + backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64()).Hash(), + }, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(4).Hash(), + backend.chain.GetBlockByNumber(0).Hash(), + }, + }, + // Check that requesting more than available is handled gracefully, even if mid skip + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3}, + []common.Hash{ + backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 4).Hash(), + backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 1).Hash(), + }, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(4).Hash(), + backend.chain.GetBlockByNumber(1).Hash(), + }, + }, + // Check a corner case where requesting more can iterate past the endpoints + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 2}, Amount: 5, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(2).Hash(), + backend.chain.GetBlockByNumber(1).Hash(), + backend.chain.GetBlockByNumber(0).Hash(), + }, + }, + // Check a corner case where skipping overflow loops back into the chain start + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(3).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64 - 1}, + []common.Hash{ + backend.chain.GetBlockByNumber(3).Hash(), + }, + }, + // Check a corner case where skipping overflow loops back to the same header + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(1).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64}, + []common.Hash{ + backend.chain.GetBlockByNumber(1).Hash(), + }, + }, + // Check that non existing headers aren't returned + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: unknown}, Amount: 1}, + []common.Hash{}, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() + 1}, Amount: 1}, + []common.Hash{}, + }, + } + // Run each of the tests and verify the results against the chain + for i, tt := range tests { + // Collect the headers to expect in the response + var headers []*types.Header + for _, hash := range tt.expect { + headers = append(headers, backend.chain.GetBlockByHash(hash).Header()) + } + // Send the hash request and verify the response + p2p.Send(peer.app, 0x03, tt.query) + if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { + t.Errorf("test %d: headers mismatch: %v", i, err) + } + // If the test used number origins, repeat with hashes as the too + if tt.query.Origin.Hash == (common.Hash{}) { + if origin := backend.chain.GetBlockByNumber(tt.query.Origin.Number); origin != nil { + tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0 + + p2p.Send(peer.app, 0x03, tt.query) + if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { + t.Errorf("test %d: headers mismatch: %v", i, err) + } + } + } + } +} + +// Tests that block contents can be retrieved from a remote chain based on their hashes. +func TestGetBlockBodies64(t *testing.T) { testGetBlockBodies(t, 64) } +func TestGetBlockBodies65(t *testing.T) { testGetBlockBodies(t, 65) } + +func testGetBlockBodies(t *testing.T, protocol uint) { + t.Parallel() + + backend := newTestBackend(maxBodiesServe + 15) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + // Create a batch of tests for various scenarios + limit := maxBodiesServe + tests := []struct { + random int // Number of blocks to fetch randomly from the chain + explicit []common.Hash // Explicitly requested blocks + available []bool // Availability of explicitly requested blocks + expected int // Total number of existing blocks to expect + }{ + {1, nil, nil, 1}, // A single random block should be retrievable + {10, nil, nil, 10}, // Multiple random blocks should be retrievable + {limit, nil, nil, limit}, // The maximum possible blocks should be retrievable + {limit + 1, nil, nil, limit}, // No more than the possible block count should be returned + {0, []common.Hash{backend.chain.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable + {0, []common.Hash{backend.chain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable + {0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned + + // Existing and non-existing blocks interleaved should not cause problems + {0, []common.Hash{ + {}, + backend.chain.GetBlockByNumber(1).Hash(), + {}, + backend.chain.GetBlockByNumber(10).Hash(), + {}, + backend.chain.GetBlockByNumber(100).Hash(), + {}, + }, []bool{false, true, false, true, false, true, false}, 3}, + } + // Run each of the tests and verify the results against the chain + for i, tt := range tests { + // Collect the hashes to request, and the response to expectva + var ( + hashes []common.Hash + bodies []*BlockBody + seen = make(map[int64]bool) + ) + for j := 0; j < tt.random; j++ { + for { + num := rand.Int63n(int64(backend.chain.CurrentBlock().NumberU64())) + if !seen[num] { + seen[num] = true + + block := backend.chain.GetBlockByNumber(uint64(num)) + hashes = append(hashes, block.Hash()) + if len(bodies) < tt.expected { + bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles()}) + } + break + } + } + } + for j, hash := range tt.explicit { + hashes = append(hashes, hash) + if tt.available[j] && len(bodies) < tt.expected { + block := backend.chain.GetBlockByHash(hash) + bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles()}) + } + } + // Send the hash request and verify the response + p2p.Send(peer.app, 0x05, hashes) + if err := p2p.ExpectMsg(peer.app, 0x06, bodies); err != nil { + t.Errorf("test %d: bodies mismatch: %v", i, err) + } + } +} + +// Tests that the state trie nodes can be retrieved based on hashes. +func TestGetNodeData64(t *testing.T) { testGetNodeData(t, 64) } +func TestGetNodeData65(t *testing.T) { testGetNodeData(t, 65) } + +func testGetNodeData(t *testing.T, protocol uint) { + t.Parallel() + + // Define three accounts to simulate transactions with + acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) + + signer := types.HomesteadSigner{} + // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test) + generator := func(i int, block *core.BlockGen) { + switch i { + case 0: + // In block 1, the test bank sends account #1 some ether. + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testKey) + block.AddTx(tx) + case 1: + // In block 2, the test bank sends some more ether to account #1. + // acc1Addr passes it on to account #2. + tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey) + tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key) + block.AddTx(tx1) + block.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by account #2. + block.SetCoinbase(acc2Addr) + block.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := block.PrevBlock(1).Header() + b2.Extra = []byte("foo") + block.AddUncle(b2) + b3 := block.PrevBlock(2).Header() + b3.Extra = []byte("foo") + block.AddUncle(b3) + } + } + // Assemble the test environment + backend := newTestBackendWithGenerator(4, generator) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + // Fetch for now the entire chain db + var hashes []common.Hash + + it := backend.db.NewIterator(nil, nil) + for it.Next() { + if key := it.Key(); len(key) == common.HashLength { + hashes = append(hashes, common.BytesToHash(key)) + } + } + it.Release() + + p2p.Send(peer.app, 0x0d, hashes) + msg, err := peer.app.ReadMsg() + if err != nil { + t.Fatalf("failed to read node data response: %v", err) + } + if msg.Code != 0x0e { + t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, 0x0c) + } + var data [][]byte + if err := msg.Decode(&data); err != nil { + t.Fatalf("failed to decode response node data: %v", err) + } + // Verify that all hashes correspond to the requested data, and reconstruct a state tree + for i, want := range hashes { + if hash := crypto.Keccak256Hash(data[i]); hash != want { + t.Errorf("data hash mismatch: have %x, want %x", hash, want) + } + } + statedb := rawdb.NewMemoryDatabase() + for i := 0; i < len(data); i++ { + statedb.Put(hashes[i].Bytes(), data[i]) + } + accounts := []common.Address{testAddr, acc1Addr, acc2Addr} + for i := uint64(0); i <= backend.chain.CurrentBlock().NumberU64(); i++ { + trie, _ := state.New(backend.chain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb), nil) + + for j, acc := range accounts { + state, _ := backend.chain.State() + bw := state.GetBalance(acc) + bh := trie.GetBalance(acc) + + if (bw != nil && bh == nil) || (bw == nil && bh != nil) { + t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) + } + if bw != nil && bh != nil && bw.Cmp(bw) != 0 { + t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) + } + } + } +} + +// Tests that the transaction receipts can be retrieved based on hashes. +func TestGetBlockReceipts64(t *testing.T) { testGetBlockReceipts(t, 64) } +func TestGetBlockReceipts65(t *testing.T) { testGetBlockReceipts(t, 65) } + +func testGetBlockReceipts(t *testing.T, protocol uint) { + t.Parallel() + + // Define three accounts to simulate transactions with + acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) + + signer := types.HomesteadSigner{} + // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test) + generator := func(i int, block *core.BlockGen) { + switch i { + case 0: + // In block 1, the test bank sends account #1 some ether. + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testKey) + block.AddTx(tx) + case 1: + // In block 2, the test bank sends some more ether to account #1. + // acc1Addr passes it on to account #2. + tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey) + tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key) + block.AddTx(tx1) + block.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by account #2. + block.SetCoinbase(acc2Addr) + block.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := block.PrevBlock(1).Header() + b2.Extra = []byte("foo") + block.AddUncle(b2) + b3 := block.PrevBlock(2).Header() + b3.Extra = []byte("foo") + block.AddUncle(b3) + } + } + // Assemble the test environment + backend := newTestBackendWithGenerator(4, generator) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + // Collect the hashes to request, and the response to expect + var ( + hashes []common.Hash + receipts []types.Receipts + ) + for i := uint64(0); i <= backend.chain.CurrentBlock().NumberU64(); i++ { + block := backend.chain.GetBlockByNumber(i) + + hashes = append(hashes, block.Hash()) + receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash())) + } + // Send the hash request and verify the response + p2p.Send(peer.app, 0x0f, hashes) + if err := p2p.ExpectMsg(peer.app, 0x10, receipts); err != nil { + t.Errorf("receipts mismatch: %v", err) + } +} diff --git a/eth/protocols/eth/handshake.go b/eth/protocols/eth/handshake.go new file mode 100644 index 0000000000..57a4e0bc34 --- /dev/null +++ b/eth/protocols/eth/handshake.go @@ -0,0 +1,107 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/p2p" +) + +const ( + // handshakeTimeout is the maximum allowed time for the `eth` handshake to + // complete before dropping the connection.= as malicious. + handshakeTimeout = 5 * time.Second +) + +// Handshake executes the eth protocol handshake, negotiating version number, +// network IDs, difficulties, head and genesis blocks. +func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error { + // Send out own handshake in a new thread + errc := make(chan error, 2) + + var status StatusPacket // safe to read after two values have been received from errc + + go func() { + errc <- p2p.Send(p.rw, StatusMsg, &StatusPacket{ + ProtocolVersion: uint32(p.version), + NetworkID: network, + TD: td, + Head: head, + Genesis: genesis, + ForkID: forkID, + }) + }() + go func() { + errc <- p.readStatus(network, &status, genesis, forkFilter) + }() + timeout := time.NewTimer(handshakeTimeout) + defer timeout.Stop() + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err != nil { + return err + } + case <-timeout.C: + return p2p.DiscReadTimeout + } + } + p.td, p.head = status.TD, status.Head + + // TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times + // larger, it will still fit within 100 bits + if tdlen := p.td.BitLen(); tdlen > 100 { + return fmt.Errorf("too large total difficulty: bitlen %d", tdlen) + } + return nil +} + +// readStatus reads the remote handshake message. +func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.Hash, forkFilter forkid.Filter) error { + msg, err := p.rw.ReadMsg() + if err != nil { + return err + } + if msg.Code != StatusMsg { + return fmt.Errorf("%w: first msg has code %x (!= %x)", errNoStatusMsg, msg.Code, StatusMsg) + } + if msg.Size > maxMessageSize { + return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) + } + // Decode the handshake and make sure everything matches + if err := msg.Decode(&status); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if status.NetworkID != network { + return fmt.Errorf("%w: %d (!= %d)", errNetworkIDMismatch, status.NetworkID, network) + } + if uint(status.ProtocolVersion) != p.version { + return fmt.Errorf("%w: %d (!= %d)", errProtocolVersionMismatch, status.ProtocolVersion, p.version) + } + if status.Genesis != genesis { + return fmt.Errorf("%w: %x (!= %x)", errGenesisMismatch, status.Genesis, genesis) + } + if err := forkFilter(status.ForkID); err != nil { + return fmt.Errorf("%w: %v", errForkIDRejected, err) + } + return nil +} diff --git a/eth/protocols/eth/handshake_test.go b/eth/protocols/eth/handshake_test.go new file mode 100644 index 0000000000..65f9a00064 --- /dev/null +++ b/eth/protocols/eth/handshake_test.go @@ -0,0 +1,91 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// Tests that handshake failures are detected and reported correctly. +func TestHandshake64(t *testing.T) { testHandshake(t, 64) } +func TestHandshake65(t *testing.T) { testHandshake(t, 65) } + +func testHandshake(t *testing.T, protocol uint) { + t.Parallel() + + // Create a test backend only to have some valid genesis chain + backend := newTestBackend(3) + defer backend.close() + + var ( + genesis = backend.chain.Genesis() + head = backend.chain.CurrentBlock() + td = backend.chain.GetTd(head.Hash(), head.NumberU64()) + forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis().Hash(), backend.chain.CurrentHeader().Number.Uint64()) + ) + tests := []struct { + code uint64 + data interface{} + want error + }{ + { + code: TransactionsMsg, data: []interface{}{}, + want: errNoStatusMsg, + }, + { + code: StatusMsg, data: StatusPacket{10, 1, td, head.Hash(), genesis.Hash(), forkID}, + want: errProtocolVersionMismatch, + }, + { + code: StatusMsg, data: StatusPacket{uint32(protocol), 999, td, head.Hash(), genesis.Hash(), forkID}, + want: errNetworkIDMismatch, + }, + { + code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), common.Hash{3}, forkID}, + want: errGenesisMismatch, + }, + { + code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}}, + want: errForkIDRejected, + }, + } + for i, test := range tests { + // Create the two peers to shake with each other + app, net := p2p.MsgPipe() + defer app.Close() + defer net.Close() + + peer := NewPeer(protocol, p2p.NewPeer(enode.ID{}, "peer", nil), net, nil) + defer peer.Close() + + // Send the junk test with one peer, check the handshake failure + go p2p.Send(app, test.code, test.data) + + err := peer.Handshake(1, td, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(backend.chain)) + if err == nil { + t.Errorf("test %d: protocol returned nil error, want %q", i, test.want) + } else if !errors.Is(err, test.want) { + t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.want) + } + } +} diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go new file mode 100644 index 0000000000..735ef78ce7 --- /dev/null +++ b/eth/protocols/eth/peer.go @@ -0,0 +1,429 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "math/big" + "sync" + + mapset "github.com/deckarep/golang-set" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" +) + +const ( + // maxKnownTxs is the maximum transactions hashes to keep in the known list + // before starting to randomly evict them. + maxKnownTxs = 32768 + + // maxKnownBlocks is the maximum block hashes to keep in the known list + // before starting to randomly evict them. + maxKnownBlocks = 1024 + + // maxQueuedTxs is the maximum number of transactions to queue up before dropping + // older broadcasts. + maxQueuedTxs = 4096 + + // maxQueuedTxAnns is the maximum number of transaction announcements to queue up + // before dropping older announcements. + maxQueuedTxAnns = 4096 + + // maxQueuedBlocks is the maximum number of block propagations to queue up before + // dropping broadcasts. There's not much point in queueing stale blocks, so a few + // that might cover uncles should be enough. + maxQueuedBlocks = 4 + + // maxQueuedBlockAnns is the maximum number of block announcements to queue up before + // dropping broadcasts. Similarly to block propagations, there's no point to queue + // above some healthy uncle limit, so use that. + maxQueuedBlockAnns = 4 +) + +// max is a helper function which returns the larger of the two given integers. +func max(a, b int) int { + if a > b { + return a + } + return b +} + +// Peer is a collection of relevant information we have about a `eth` peer. +type Peer struct { + id string // Unique ID for the peer, cached + + *p2p.Peer // The embedded P2P package peer + rw p2p.MsgReadWriter // Input/output streams for snap + version uint // Protocol version negotiated + + head common.Hash // Latest advertised head block hash + td *big.Int // Latest advertised head block total difficulty + + knownBlocks mapset.Set // Set of block hashes known to be known by this peer + queuedBlocks chan *blockPropagation // Queue of blocks to broadcast to the peer + queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer + + txpool TxPool // Transaction pool used by the broadcasters for liveness checks + knownTxs mapset.Set // Set of transaction hashes known to be known by this peer + txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests + txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests + + term chan struct{} // Termination channel to stop the broadcasters + lock sync.RWMutex // Mutex protecting the internal fields +} + +// NewPeer create a wrapper for a network connection and negotiated protocol +// version. +func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool) *Peer { + peer := &Peer{ + id: p.ID().String(), + Peer: p, + rw: rw, + version: version, + knownTxs: mapset.NewSet(), + knownBlocks: mapset.NewSet(), + queuedBlocks: make(chan *blockPropagation, maxQueuedBlocks), + queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns), + txBroadcast: make(chan []common.Hash), + txAnnounce: make(chan []common.Hash), + txpool: txpool, + term: make(chan struct{}), + } + // Start up all the broadcasters + go peer.broadcastBlocks() + go peer.broadcastTransactions() + if version >= ETH65 { + go peer.announceTransactions() + } + return peer +} + +// Close signals the broadcast goroutine to terminate. Only ever call this if +// you created the peer yourself via NewPeer. Otherwise let whoever created it +// clean it up! +func (p *Peer) Close() { + close(p.term) +} + +// ID retrieves the peer's unique identifier. +func (p *Peer) ID() string { + return p.id +} + +// Version retrieves the peer's negoatiated `eth` protocol version. +func (p *Peer) Version() uint { + return p.version +} + +// Head retrieves the current head hash and total difficulty of the peer. +func (p *Peer) Head() (hash common.Hash, td *big.Int) { + p.lock.RLock() + defer p.lock.RUnlock() + + copy(hash[:], p.head[:]) + return hash, new(big.Int).Set(p.td) +} + +// SetHead updates the head hash and total difficulty of the peer. +func (p *Peer) SetHead(hash common.Hash, td *big.Int) { + p.lock.Lock() + defer p.lock.Unlock() + + copy(p.head[:], hash[:]) + p.td.Set(td) +} + +// KnownBlock returns whether peer is known to already have a block. +func (p *Peer) KnownBlock(hash common.Hash) bool { + return p.knownBlocks.Contains(hash) +} + +// KnownTransaction returns whether peer is known to already have a transaction. +func (p *Peer) KnownTransaction(hash common.Hash) bool { + return p.knownTxs.Contains(hash) +} + +// markBlock marks a block as known for the peer, ensuring that the block will +// never be propagated to this particular peer. +func (p *Peer) markBlock(hash common.Hash) { + // If we reached the memory allowance, drop a previously known block hash + for p.knownBlocks.Cardinality() >= maxKnownBlocks { + p.knownBlocks.Pop() + } + p.knownBlocks.Add(hash) +} + +// markTransaction marks a transaction as known for the peer, ensuring that it +// will never be propagated to this particular peer. +func (p *Peer) markTransaction(hash common.Hash) { + // If we reached the memory allowance, drop a previously known transaction hash + for p.knownTxs.Cardinality() >= maxKnownTxs { + p.knownTxs.Pop() + } + p.knownTxs.Add(hash) +} + +// SendTransactions sends transactions to the peer and includes the hashes +// in its transaction hash set for future reference. +// +// This method is a helper used by the async transaction sender. Don't call it +// directly as the queueing (memory) and transmission (bandwidth) costs should +// not be managed directly. +// +// The reasons this is public is to allow packages using this protocol to write +// tests that directly send messages without having to do the asyn queueing. +func (p *Peer) SendTransactions(txs types.Transactions) error { + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(txs)) { + p.knownTxs.Pop() + } + for _, tx := range txs { + p.knownTxs.Add(tx.Hash()) + } + return p2p.Send(p.rw, TransactionsMsg, txs) +} + +// AsyncSendTransactions queues a list of transactions (by hash) to eventually +// propagate to a remote peer. The number of pending sends are capped (new ones +// will force old sends to be dropped) +func (p *Peer) AsyncSendTransactions(hashes []common.Hash) { + select { + case p.txBroadcast <- hashes: + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { + p.knownTxs.Pop() + } + for _, hash := range hashes { + p.knownTxs.Add(hash) + } + case <-p.term: + p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) + } +} + +// sendPooledTransactionHashes sends transaction hashes to the peer and includes +// them in its transaction hash set for future reference. +// +// This method is a helper used by the async transaction announcer. Don't call it +// directly as the queueing (memory) and transmission (bandwidth) costs should +// not be managed directly. +func (p *Peer) sendPooledTransactionHashes(hashes []common.Hash) error { + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { + p.knownTxs.Pop() + } + for _, hash := range hashes { + p.knownTxs.Add(hash) + } + return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket(hashes)) +} + +// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually +// announce to a remote peer. The number of pending sends are capped (new ones +// will force old sends to be dropped) +func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) { + select { + case p.txAnnounce <- hashes: + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { + p.knownTxs.Pop() + } + for _, hash := range hashes { + p.knownTxs.Add(hash) + } + case <-p.term: + p.Log().Debug("Dropping transaction announcement", "count", len(hashes)) + } +} + +// SendPooledTransactionsRLP sends requested transactions to the peer and adds the +// hashes in its transaction hash set for future reference. +// +// Note, the method assumes the hashes are correct and correspond to the list of +// transactions being sent. +func (p *Peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValue) error { + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { + p.knownTxs.Pop() + } + for _, hash := range hashes { + p.knownTxs.Add(hash) + } + return p2p.Send(p.rw, PooledTransactionsMsg, txs) // Not packed into PooledTransactionsPacket to avoid RLP decoding +} + +// SendNewBlockHashes announces the availability of a number of blocks through +// a hash notification. +func (p *Peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error { + // Mark all the block hashes as known, but ensure we don't overflow our limits + for p.knownBlocks.Cardinality() > max(0, maxKnownBlocks-len(hashes)) { + p.knownBlocks.Pop() + } + for _, hash := range hashes { + p.knownBlocks.Add(hash) + } + request := make(NewBlockHashesPacket, len(hashes)) + for i := 0; i < len(hashes); i++ { + request[i].Hash = hashes[i] + request[i].Number = numbers[i] + } + return p2p.Send(p.rw, NewBlockHashesMsg, request) +} + +// AsyncSendNewBlockHash queues the availability of a block for propagation to a +// remote peer. If the peer's broadcast queue is full, the event is silently +// dropped. +func (p *Peer) AsyncSendNewBlockHash(block *types.Block) { + select { + case p.queuedBlockAnns <- block: + // Mark all the block hash as known, but ensure we don't overflow our limits + for p.knownBlocks.Cardinality() >= maxKnownBlocks { + p.knownBlocks.Pop() + } + p.knownBlocks.Add(block.Hash()) + default: + p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash()) + } +} + +// SendNewBlock propagates an entire block to a remote peer. +func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error { + // Mark all the block hash as known, but ensure we don't overflow our limits + for p.knownBlocks.Cardinality() >= maxKnownBlocks { + p.knownBlocks.Pop() + } + p.knownBlocks.Add(block.Hash()) + return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{block, td}) +} + +// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If +// the peer's broadcast queue is full, the event is silently dropped. +func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) { + select { + case p.queuedBlocks <- &blockPropagation{block: block, td: td}: + // Mark all the block hash as known, but ensure we don't overflow our limits + for p.knownBlocks.Cardinality() >= maxKnownBlocks { + p.knownBlocks.Pop() + } + p.knownBlocks.Add(block.Hash()) + default: + p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash()) + } +} + +// SendBlockHeaders sends a batch of block headers to the remote peer. +func (p *Peer) SendBlockHeaders(headers []*types.Header) error { + return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket(headers)) +} + +// SendBlockBodies sends a batch of block contents to the remote peer. +func (p *Peer) SendBlockBodies(bodies []*BlockBody) error { + return p2p.Send(p.rw, BlockBodiesMsg, BlockBodiesPacket(bodies)) +} + +// SendBlockBodiesRLP sends a batch of block contents to the remote peer from +// an already RLP encoded format. +func (p *Peer) SendBlockBodiesRLP(bodies []rlp.RawValue) error { + return p2p.Send(p.rw, BlockBodiesMsg, bodies) // Not packed into BlockBodiesPacket to avoid RLP decoding +} + +// SendNodeDataRLP sends a batch of arbitrary internal data, corresponding to the +// hashes requested. +func (p *Peer) SendNodeData(data [][]byte) error { + return p2p.Send(p.rw, NodeDataMsg, NodeDataPacket(data)) +} + +// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the +// ones requested from an already RLP encoded format. +func (p *Peer) SendReceiptsRLP(receipts []rlp.RawValue) error { + return p2p.Send(p.rw, ReceiptsMsg, receipts) // Not packed into ReceiptsPacket to avoid RLP decoding +} + +// RequestOneHeader is a wrapper around the header query functions to fetch a +// single header. It is used solely by the fetcher. +func (p *Peer) RequestOneHeader(hash common.Hash) error { + p.Log().Debug("Fetching single header", "hash", hash) + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + Origin: HashOrNumber{Hash: hash}, + Amount: uint64(1), + Skip: uint64(0), + Reverse: false, + }) +} + +// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the +// specified header query, based on the hash of an origin block. +func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { + p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + Origin: HashOrNumber{Hash: origin}, + Amount: uint64(amount), + Skip: uint64(skip), + Reverse: reverse, + }) +} + +// RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the +// specified header query, based on the number of an origin block. +func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { + p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + Origin: HashOrNumber{Number: origin}, + Amount: uint64(amount), + Skip: uint64(skip), + Reverse: reverse, + }) +} + +// ExpectRequestHeadersByNumber is a testing method to mirror the recipient side +// of the RequestHeadersByNumber operation. +func (p *Peer) ExpectRequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { + req := &GetBlockHeadersPacket{ + Origin: HashOrNumber{Number: origin}, + Amount: uint64(amount), + Skip: uint64(skip), + Reverse: reverse, + } + return p2p.ExpectMsg(p.rw, GetBlockHeadersMsg, req) +} + +// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes +// specified. +func (p *Peer) RequestBodies(hashes []common.Hash) error { + p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) + return p2p.Send(p.rw, GetBlockBodiesMsg, GetBlockBodiesPacket(hashes)) +} + +// RequestNodeData fetches a batch of arbitrary data from a node's known state +// data, corresponding to the specified hashes. +func (p *Peer) RequestNodeData(hashes []common.Hash) error { + p.Log().Debug("Fetching batch of state data", "count", len(hashes)) + return p2p.Send(p.rw, GetNodeDataMsg, GetNodeDataPacket(hashes)) +} + +// RequestReceipts fetches a batch of transaction receipts from a remote node. +func (p *Peer) RequestReceipts(hashes []common.Hash) error { + p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) + return p2p.Send(p.rw, GetReceiptsMsg, GetReceiptsPacket(hashes)) +} + +// RequestTxs fetches a batch of transactions from a remote node. +func (p *Peer) RequestTxs(hashes []common.Hash) error { + p.Log().Debug("Fetching batch of transactions", "count", len(hashes)) + return p2p.Send(p.rw, GetPooledTransactionsMsg, GetPooledTransactionsPacket(hashes)) +} diff --git a/eth/protocols/eth/peer_test.go b/eth/protocols/eth/peer_test.go new file mode 100644 index 0000000000..70e9959f82 --- /dev/null +++ b/eth/protocols/eth/peer_test.go @@ -0,0 +1,61 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// This file contains some shares testing functionality, common to multiple +// different files and modules being tested. + +package eth + +import ( + "crypto/rand" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// testPeer is a simulated peer to allow testing direct network calls. +type testPeer struct { + *Peer + + net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging + app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side +} + +// newTestPeer creates a new peer registered at the given data backend. +func newTestPeer(name string, version uint, backend Backend) (*testPeer, <-chan error) { + // Create a message pipe to communicate through + app, net := p2p.MsgPipe() + + // Start the peer on a new thread + var id enode.ID + rand.Read(id[:]) + + peer := NewPeer(version, p2p.NewPeer(id, name, nil), net, backend.TxPool()) + errc := make(chan error, 1) + go func() { + errc <- backend.RunPeer(peer, func(peer *Peer) error { + return Handle(backend, peer) + }) + }() + return &testPeer{app: app, net: net, Peer: peer}, errc +} + +// close terminates the local side of the peer, notifying the remote protocol +// manager of termination. +func (p *testPeer) close() { + p.Peer.Close() + p.app.Close() +} diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go new file mode 100644 index 0000000000..63d3494ec4 --- /dev/null +++ b/eth/protocols/eth/protocol.go @@ -0,0 +1,279 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// Constants to match up protocol versions and messages +const ( + ETH64 = 64 + ETH65 = 65 +) + +// protocolName is the official short name of the `eth` protocol used during +// devp2p capability negotiation. +const protocolName = "eth" + +// protocolVersions are the supported versions of the `eth` protocol (first +// is primary). +var protocolVersions = []uint{ETH65, ETH64} + +// protocolLengths are the number of implemented message corresponding to +// different protocol versions. +var protocolLengths = map[uint]uint64{ETH65: 17, ETH64: 17} + +// maxMessageSize is the maximum cap on the size of a protocol message. +const maxMessageSize = 10 * 1024 * 1024 + +const ( + // Protocol messages in eth/64 + StatusMsg = 0x00 + NewBlockHashesMsg = 0x01 + TransactionsMsg = 0x02 + GetBlockHeadersMsg = 0x03 + BlockHeadersMsg = 0x04 + GetBlockBodiesMsg = 0x05 + BlockBodiesMsg = 0x06 + NewBlockMsg = 0x07 + GetNodeDataMsg = 0x0d + NodeDataMsg = 0x0e + GetReceiptsMsg = 0x0f + ReceiptsMsg = 0x10 + + // Protocol messages overloaded in eth/65 + NewPooledTransactionHashesMsg = 0x08 + GetPooledTransactionsMsg = 0x09 + PooledTransactionsMsg = 0x0a +) + +var ( + errNoStatusMsg = errors.New("no status message") + errMsgTooLarge = errors.New("message too long") + errDecode = errors.New("invalid message") + errInvalidMsgCode = errors.New("invalid message code") + errProtocolVersionMismatch = errors.New("protocol version mismatch") + errNetworkIDMismatch = errors.New("network ID mismatch") + errGenesisMismatch = errors.New("genesis mismatch") + errForkIDRejected = errors.New("fork ID rejected") + errExtraStatusMsg = errors.New("extra status message") +) + +// Packet represents a p2p message in the `eth` protocol. +type Packet interface { + Name() string // Name returns a string corresponding to the message type. + Kind() byte // Kind returns the message type. +} + +// StatusPacket is the network packet for the status message for eth/64 and later. +type StatusPacket struct { + ProtocolVersion uint32 + NetworkID uint64 + TD *big.Int + Head common.Hash + Genesis common.Hash + ForkID forkid.ID +} + +// NewBlockHashesPacket is the network packet for the block announcements. +type NewBlockHashesPacket []struct { + Hash common.Hash // Hash of one particular block being announced + Number uint64 // Number of one particular block being announced +} + +// Unpack retrieves the block hashes and numbers from the announcement packet +// and returns them in a split flat format that's more consistent with the +// internal data structures. +func (p *NewBlockHashesPacket) Unpack() ([]common.Hash, []uint64) { + var ( + hashes = make([]common.Hash, len(*p)) + numbers = make([]uint64, len(*p)) + ) + for i, body := range *p { + hashes[i], numbers[i] = body.Hash, body.Number + } + return hashes, numbers +} + +// TransactionsPacket is the network packet for broadcasting new transactions. +type TransactionsPacket []*types.Transaction + +// GetBlockHeadersPacket represents a block header query. +type GetBlockHeadersPacket struct { + Origin HashOrNumber // Block from which to retrieve headers + Amount uint64 // Maximum number of headers to retrieve + Skip uint64 // Blocks to skip between consecutive headers + Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) +} + +// HashOrNumber is a combined field for specifying an origin block. +type HashOrNumber struct { + Hash common.Hash // Block hash from which to retrieve headers (excludes Number) + Number uint64 // Block hash from which to retrieve headers (excludes Hash) +} + +// EncodeRLP is a specialized encoder for HashOrNumber to encode only one of the +// two contained union fields. +func (hn *HashOrNumber) EncodeRLP(w io.Writer) error { + if hn.Hash == (common.Hash{}) { + return rlp.Encode(w, hn.Number) + } + if hn.Number != 0 { + return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number) + } + return rlp.Encode(w, hn.Hash) +} + +// DecodeRLP is a specialized decoder for HashOrNumber to decode the contents +// into either a block hash or a block number. +func (hn *HashOrNumber) DecodeRLP(s *rlp.Stream) error { + _, size, _ := s.Kind() + origin, err := s.Raw() + if err == nil { + switch { + case size == 32: + err = rlp.DecodeBytes(origin, &hn.Hash) + case size <= 8: + err = rlp.DecodeBytes(origin, &hn.Number) + default: + err = fmt.Errorf("invalid input size %d for origin", size) + } + } + return err +} + +// BlockHeadersPacket represents a block header response. +type BlockHeadersPacket []*types.Header + +// NewBlockPacket is the network packet for the block propagation message. +type NewBlockPacket struct { + Block *types.Block + TD *big.Int +} + +// sanityCheck verifies that the values are reasonable, as a DoS protection +func (request *NewBlockPacket) sanityCheck() error { + if err := request.Block.SanityCheck(); err != nil { + return err + } + //TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times + // larger, it will still fit within 100 bits + if tdlen := request.TD.BitLen(); tdlen > 100 { + return fmt.Errorf("too large block TD: bitlen %d", tdlen) + } + return nil +} + +// GetBlockBodiesPacket represents a block body query. +type GetBlockBodiesPacket []common.Hash + +// BlockBodiesPacket is the network packet for block content distribution. +type BlockBodiesPacket []*BlockBody + +// BlockBody represents the data content of a single block. +type BlockBody struct { + Transactions []*types.Transaction // Transactions contained within a block + Uncles []*types.Header // Uncles contained within a block +} + +// Unpack retrieves the transactions and uncles from the range packet and returns +// them in a split flat format that's more consistent with the internal data structures. +func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header) { + var ( + txset = make([][]*types.Transaction, len(*p)) + uncleset = make([][]*types.Header, len(*p)) + ) + for i, body := range *p { + txset[i], uncleset[i] = body.Transactions, body.Uncles + } + return txset, uncleset +} + +// GetNodeDataPacket represents a trie node data query. +type GetNodeDataPacket []common.Hash + +// NodeDataPacket is the network packet for trie node data distribution. +type NodeDataPacket [][]byte + +// GetReceiptsPacket represents a block receipts query. +type GetReceiptsPacket []common.Hash + +// ReceiptsPacket is the network packet for block receipts distribution. +type ReceiptsPacket [][]*types.Receipt + +// NewPooledTransactionHashesPacket represents a transaction announcement packet. +type NewPooledTransactionHashesPacket []common.Hash + +// GetPooledTransactionsPacket represents a transaction query. +type GetPooledTransactionsPacket []common.Hash + +// PooledTransactionsPacket is the network packet for transaction distribution. +type PooledTransactionsPacket []*types.Transaction + +func (*StatusPacket) Name() string { return "Status" } +func (*StatusPacket) Kind() byte { return StatusMsg } + +func (*NewBlockHashesPacket) Name() string { return "NewBlockHashes" } +func (*NewBlockHashesPacket) Kind() byte { return NewBlockHashesMsg } + +func (*TransactionsPacket) Name() string { return "Transactions" } +func (*TransactionsPacket) Kind() byte { return TransactionsMsg } + +func (*GetBlockHeadersPacket) Name() string { return "GetBlockHeaders" } +func (*GetBlockHeadersPacket) Kind() byte { return GetBlockHeadersMsg } + +func (*BlockHeadersPacket) Name() string { return "BlockHeaders" } +func (*BlockHeadersPacket) Kind() byte { return BlockHeadersMsg } + +func (*GetBlockBodiesPacket) Name() string { return "GetBlockBodies" } +func (*GetBlockBodiesPacket) Kind() byte { return GetBlockBodiesMsg } + +func (*BlockBodiesPacket) Name() string { return "BlockBodies" } +func (*BlockBodiesPacket) Kind() byte { return BlockBodiesMsg } + +func (*NewBlockPacket) Name() string { return "NewBlock" } +func (*NewBlockPacket) Kind() byte { return NewBlockMsg } + +func (*GetNodeDataPacket) Name() string { return "GetNodeData" } +func (*GetNodeDataPacket) Kind() byte { return GetNodeDataMsg } + +func (*NodeDataPacket) Name() string { return "NodeData" } +func (*NodeDataPacket) Kind() byte { return NodeDataMsg } + +func (*GetReceiptsPacket) Name() string { return "GetReceipts" } +func (*GetReceiptsPacket) Kind() byte { return GetReceiptsMsg } + +func (*ReceiptsPacket) Name() string { return "Receipts" } +func (*ReceiptsPacket) Kind() byte { return ReceiptsMsg } + +func (*NewPooledTransactionHashesPacket) Name() string { return "NewPooledTransactionHashes" } +func (*NewPooledTransactionHashesPacket) Kind() byte { return NewPooledTransactionHashesMsg } + +func (*GetPooledTransactionsPacket) Name() string { return "GetPooledTransactions" } +func (*GetPooledTransactionsPacket) Kind() byte { return GetPooledTransactionsMsg } + +func (*PooledTransactionsPacket) Name() string { return "PooledTransactions" } +func (*PooledTransactionsPacket) Kind() byte { return PooledTransactionsMsg } diff --git a/eth/protocols/eth/protocol_test.go b/eth/protocols/eth/protocol_test.go new file mode 100644 index 0000000000..056ea56480 --- /dev/null +++ b/eth/protocols/eth/protocol_test.go @@ -0,0 +1,68 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +// Tests that the custom union field encoder and decoder works correctly. +func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { + // Create a "random" hash for testing + var hash common.Hash + for i := range hash { + hash[i] = byte(i) + } + // Assemble some table driven tests + tests := []struct { + packet *GetBlockHeadersPacket + fail bool + }{ + // Providing the origin as either a hash or a number should both work + {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}}}, + {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}}}, + + // Providing arbitrary query field should also work + {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}}, + {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}}, + + // Providing both the origin hash and origin number must fail + {fail: true, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash, Number: 314}}}, + } + // Iterate over each of the tests and try to encode and then decode + for i, tt := range tests { + bytes, err := rlp.EncodeToBytes(tt.packet) + if err != nil && !tt.fail { + t.Fatalf("test %d: failed to encode packet: %v", i, err) + } else if err == nil && tt.fail { + t.Fatalf("test %d: encode should have failed", i) + } + if !tt.fail { + packet := new(GetBlockHeadersPacket) + if err := rlp.DecodeBytes(bytes, packet); err != nil { + t.Fatalf("test %d: failed to decode packet: %v", i, err) + } + if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount || + packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse { + t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet) + } + } + } +} diff --git a/eth/protocols/snap/discovery.go b/eth/protocols/snap/discovery.go new file mode 100644 index 0000000000..684ec7e632 --- /dev/null +++ b/eth/protocols/snap/discovery.go @@ -0,0 +1,32 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "github.com/ethereum/go-ethereum/rlp" +) + +// enrEntry is the ENR entry which advertises `snap` protocol on the discovery. +type enrEntry struct { + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` +} + +// ENRKey implements enr.Entry. +func (e enrEntry) ENRKey() string { + return "snap" +} diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go new file mode 100644 index 0000000000..36322e648b --- /dev/null +++ b/eth/protocols/snap/handler.go @@ -0,0 +1,490 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "bytes" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +const ( + // softResponseLimit is the target maximum size of replies to data retrievals. + softResponseLimit = 2 * 1024 * 1024 + + // maxCodeLookups is the maximum number of bytecodes to serve. This number is + // there to limit the number of disk lookups. + maxCodeLookups = 1024 + + // stateLookupSlack defines the ratio by how much a state response can exceed + // the requested limit in order to try and avoid breaking up contracts into + // multiple packages and proving them. + stateLookupSlack = 0.1 + + // maxTrieNodeLookups is the maximum number of state trie nodes to serve. This + // number is there to limit the number of disk lookups. + maxTrieNodeLookups = 1024 +) + +// Handler is a callback to invoke from an outside runner after the boilerplate +// exchanges have passed. +type Handler func(peer *Peer) error + +// Backend defines the data retrieval methods to serve remote requests and the +// callback methods to invoke on remote deliveries. +type Backend interface { + // Chain retrieves the blockchain object to serve data. + Chain() *core.BlockChain + + // RunPeer is invoked when a peer joins on the `eth` protocol. The handler + // should do any peer maintenance work, handshakes and validations. If all + // is passed, control should be given back to the `handler` to process the + // inbound messages going forward. + RunPeer(peer *Peer, handler Handler) error + + // PeerInfo retrieves all known `snap` information about a peer. + PeerInfo(id enode.ID) interface{} + + // Handle is a callback to be invoked when a data packet is received from + // the remote peer. Only packets not consumed by the protocol handler will + // be forwarded to the backend. + Handle(peer *Peer, packet Packet) error +} + +// MakeProtocols constructs the P2P protocol definitions for `snap`. +func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { + protocols := make([]p2p.Protocol, len(protocolVersions)) + for i, version := range protocolVersions { + version := version // Closure + + protocols[i] = p2p.Protocol{ + Name: protocolName, + Version: version, + Length: protocolLengths[version], + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + return backend.RunPeer(newPeer(version, p, rw), func(peer *Peer) error { + return handle(backend, peer) + }) + }, + NodeInfo: func() interface{} { + return nodeInfo(backend.Chain()) + }, + PeerInfo: func(id enode.ID) interface{} { + return backend.PeerInfo(id) + }, + Attributes: []enr.Entry{&enrEntry{}}, + DialCandidates: dnsdisc, + } + } + return protocols +} + +// handle is the callback invoked to manage the life cycle of a `snap` peer. +// When this function terminates, the peer is disconnected. +func handle(backend Backend, peer *Peer) error { + for { + if err := handleMessage(backend, peer); err != nil { + peer.Log().Debug("Message handling failed in `snap`", "err", err) + return err + } + } +} + +// handleMessage is invoked whenever an inbound message is received from a +// remote peer on the `spap` protocol. The remote connection is torn down upon +// returning any error. +func handleMessage(backend Backend, peer *Peer) error { + // Read the next message from the remote peer, and ensure it's fully consumed + msg, err := peer.rw.ReadMsg() + if err != nil { + return err + } + if msg.Size > maxMessageSize { + return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) + } + defer msg.Discard() + + // Handle the message depending on its contents + switch { + case msg.Code == GetAccountRangeMsg: + // Decode the account retrieval request + var req GetAccountRangePacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + // Retrieve the requested state and bail out if non existent + tr, err := trie.New(req.Root, backend.Chain().StateCache().TrieDB()) + if err != nil { + return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID}) + } + it, err := backend.Chain().Snapshots().AccountIterator(req.Root, req.Origin) + if err != nil { + return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID}) + } + // Iterate over the requested range and pile accounts up + var ( + accounts []*AccountData + size uint64 + last common.Hash + ) + for it.Next() && size < req.Bytes { + hash, account := it.Hash(), common.CopyBytes(it.Account()) + + // Track the returned interval for the Merkle proofs + last = hash + + // Assemble the reply item + size += uint64(common.HashLength + len(account)) + accounts = append(accounts, &AccountData{ + Hash: hash, + Body: account, + }) + // If we've exceeded the request threshold, abort + if bytes.Compare(hash[:], req.Limit[:]) >= 0 { + break + } + } + it.Release() + + // Generate the Merkle proofs for the first and last account + proof := light.NewNodeSet() + if err := tr.Prove(req.Origin[:], 0, proof); err != nil { + log.Warn("Failed to prove account range", "origin", req.Origin, "err", err) + return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID}) + } + if last != (common.Hash{}) { + if err := tr.Prove(last[:], 0, proof); err != nil { + log.Warn("Failed to prove account range", "last", last, "err", err) + return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID}) + } + } + var proofs [][]byte + for _, blob := range proof.NodeList() { + proofs = append(proofs, blob) + } + // Send back anything accumulated + return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ + ID: req.ID, + Accounts: accounts, + Proof: proofs, + }) + + case msg.Code == AccountRangeMsg: + // A range of accounts arrived to one of our previous requests + res := new(AccountRangePacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Ensure the range is monotonically increasing + for i := 1; i < len(res.Accounts); i++ { + if bytes.Compare(res.Accounts[i-1].Hash[:], res.Accounts[i].Hash[:]) >= 0 { + return fmt.Errorf("accounts not monotonically increasing: #%d [%x] vs #%d [%x]", i-1, res.Accounts[i-1].Hash[:], i, res.Accounts[i].Hash[:]) + } + } + return backend.Handle(peer, res) + + case msg.Code == GetStorageRangesMsg: + // Decode the storage retrieval request + var req GetStorageRangesPacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + // TODO(karalabe): Do we want to enforce > 0 accounts and 1 account if origin is set? + // TODO(karalabe): - Logging locally is not ideal as remote faulst annoy the local user + // TODO(karalabe): - Dropping the remote peer is less flexible wrt client bugs (slow is better than non-functional) + + // Calculate the hard limit at which to abort, even if mid storage trie + hardLimit := uint64(float64(req.Bytes) * (1 + stateLookupSlack)) + + // Retrieve storage ranges until the packet limit is reached + var ( + slots [][]*StorageData + proofs [][]byte + size uint64 + ) + for _, account := range req.Accounts { + // If we've exceeded the requested data limit, abort without opening + // a new storage range (that we'd need to prove due to exceeded size) + if size >= req.Bytes { + break + } + // The first account might start from a different origin and end sooner + var origin common.Hash + if len(req.Origin) > 0 { + origin, req.Origin = common.BytesToHash(req.Origin), nil + } + var limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + if len(req.Limit) > 0 { + limit, req.Limit = common.BytesToHash(req.Limit), nil + } + // Retrieve the requested state and bail out if non existent + it, err := backend.Chain().Snapshots().StorageIterator(req.Root, account, origin) + if err != nil { + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + // Iterate over the requested range and pile slots up + var ( + storage []*StorageData + last common.Hash + ) + for it.Next() && size < hardLimit { + hash, slot := it.Hash(), common.CopyBytes(it.Slot()) + + // Track the returned interval for the Merkle proofs + last = hash + + // Assemble the reply item + size += uint64(common.HashLength + len(slot)) + storage = append(storage, &StorageData{ + Hash: hash, + Body: slot, + }) + // If we've exceeded the request threshold, abort + if bytes.Compare(hash[:], limit[:]) >= 0 { + break + } + } + slots = append(slots, storage) + it.Release() + + // Generate the Merkle proofs for the first and last storage slot, but + // only if the response was capped. If the entire storage trie included + // in the response, no need for any proofs. + if origin != (common.Hash{}) || size >= hardLimit { + // Request started at a non-zero hash or was capped prematurely, add + // the endpoint Merkle proofs + accTrie, err := trie.New(req.Root, backend.Chain().StateCache().TrieDB()) + if err != nil { + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + var acc state.Account + if err := rlp.DecodeBytes(accTrie.Get(account[:]), &acc); err != nil { + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + stTrie, err := trie.New(acc.Root, backend.Chain().StateCache().TrieDB()) + if err != nil { + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + proof := light.NewNodeSet() + if err := stTrie.Prove(origin[:], 0, proof); err != nil { + log.Warn("Failed to prove storage range", "origin", req.Origin, "err", err) + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + if last != (common.Hash{}) { + if err := stTrie.Prove(last[:], 0, proof); err != nil { + log.Warn("Failed to prove storage range", "last", last, "err", err) + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + } + for _, blob := range proof.NodeList() { + proofs = append(proofs, blob) + } + // Proof terminates the reply as proofs are only added if a node + // refuses to serve more data (exception when a contract fetch is + // finishing, but that's that). + break + } + } + // Send back anything accumulated + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ + ID: req.ID, + Slots: slots, + Proof: proofs, + }) + + case msg.Code == StorageRangesMsg: + // A range of storage slots arrived to one of our previous requests + res := new(StorageRangesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Ensure the ranges ae monotonically increasing + for i, slots := range res.Slots { + for j := 1; j < len(slots); j++ { + if bytes.Compare(slots[j-1].Hash[:], slots[j].Hash[:]) >= 0 { + return fmt.Errorf("storage slots not monotonically increasing for account #%d: #%d [%x] vs #%d [%x]", i, j-1, slots[j-1].Hash[:], j, slots[j].Hash[:]) + } + } + } + return backend.Handle(peer, res) + + case msg.Code == GetByteCodesMsg: + // Decode bytecode retrieval request + var req GetByteCodesPacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + if len(req.Hashes) > maxCodeLookups { + req.Hashes = req.Hashes[:maxCodeLookups] + } + // Retrieve bytecodes until the packet size limit is reached + var ( + codes [][]byte + bytes uint64 + ) + for _, hash := range req.Hashes { + if hash == emptyCode { + // Peers should not request the empty code, but if they do, at + // least sent them back a correct response without db lookups + codes = append(codes, []byte{}) + } else if blob, err := backend.Chain().ContractCode(hash); err == nil { + codes = append(codes, blob) + bytes += uint64(len(blob)) + } + if bytes > req.Bytes { + break + } + } + // Send back anything accumulated + return p2p.Send(peer.rw, ByteCodesMsg, &ByteCodesPacket{ + ID: req.ID, + Codes: codes, + }) + + case msg.Code == ByteCodesMsg: + // A batch of byte codes arrived to one of our previous requests + res := new(ByteCodesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + case msg.Code == GetTrieNodesMsg: + // Decode trie node retrieval request + var req GetTrieNodesPacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + // Make sure we have the state associated with the request + triedb := backend.Chain().StateCache().TrieDB() + + accTrie, err := trie.NewSecure(req.Root, triedb) + if err != nil { + // We don't have the requested state available, bail out + return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ID: req.ID}) + } + snap := backend.Chain().Snapshots().Snapshot(req.Root) + if snap == nil { + // We don't have the requested state snapshotted yet, bail out. + // In reality we could still serve using the account and storage + // tries only, but let's protect the node a bit while it's doing + // snapshot generation. + return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ID: req.ID}) + } + // Retrieve trie nodes until the packet size limit is reached + var ( + nodes [][]byte + bytes uint64 + loads int // Trie hash expansions to cound database reads + ) + for _, pathset := range req.Paths { + switch len(pathset) { + case 0: + // Ensure we penalize invalid requests + return fmt.Errorf("%w: zero-item pathset requested", errBadRequest) + + case 1: + // If we're only retrieving an account trie node, fetch it directly + blob, resolved, err := accTrie.TryGetNode(pathset[0]) + loads += resolved // always account database reads, even for failures + if err != nil { + break + } + nodes = append(nodes, blob) + bytes += uint64(len(blob)) + + default: + // Storage slots requested, open the storage trie and retrieve from there + account, err := snap.Account(common.BytesToHash(pathset[0])) + loads++ // always account database reads, even for failures + if err != nil { + break + } + stTrie, err := trie.NewSecure(common.BytesToHash(account.Root), triedb) + loads++ // always account database reads, even for failures + if err != nil { + break + } + for _, path := range pathset[1:] { + blob, resolved, err := stTrie.TryGetNode(path) + loads += resolved // always account database reads, even for failures + if err != nil { + break + } + nodes = append(nodes, blob) + bytes += uint64(len(blob)) + + // Sanity check limits to avoid DoS on the store trie loads + if bytes > req.Bytes || loads > maxTrieNodeLookups { + break + } + } + } + // Abort request processing if we've exceeded our limits + if bytes > req.Bytes || loads > maxTrieNodeLookups { + break + } + } + // Send back anything accumulated + return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ + ID: req.ID, + Nodes: nodes, + }) + + case msg.Code == TrieNodesMsg: + // A batch of trie nodes arrived to one of our previous requests + res := new(TrieNodesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + default: + return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) + } +} + +// NodeInfo represents a short summary of the `snap` sub-protocol metadata +// known about the host peer. +type NodeInfo struct{} + +// nodeInfo retrieves some `snap` protocol metadata about the running host node. +func nodeInfo(chain *core.BlockChain) *NodeInfo { + return &NodeInfo{} +} diff --git a/eth/protocols/snap/peer.go b/eth/protocols/snap/peer.go new file mode 100644 index 0000000000..73eaaadd09 --- /dev/null +++ b/eth/protocols/snap/peer.go @@ -0,0 +1,111 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" +) + +// Peer is a collection of relevant information we have about a `snap` peer. +type Peer struct { + id string // Unique ID for the peer, cached + + *p2p.Peer // The embedded P2P package peer + rw p2p.MsgReadWriter // Input/output streams for snap + version uint // Protocol version negotiated + + logger log.Logger // Contextual logger with the peer id injected +} + +// newPeer create a wrapper for a network connection and negotiated protocol +// version. +func newPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer { + id := p.ID().String() + return &Peer{ + id: id, + Peer: p, + rw: rw, + version: version, + logger: log.New("peer", id[:8]), + } +} + +// ID retrieves the peer's unique identifier. +func (p *Peer) ID() string { + return p.id +} + +// Version retrieves the peer's negoatiated `snap` protocol version. +func (p *Peer) Version() uint { + return p.version +} + +// RequestAccountRange fetches a batch of accounts rooted in a specific account +// trie, starting with the origin. +func (p *Peer) RequestAccountRange(id uint64, root common.Hash, origin, limit common.Hash, bytes uint64) error { + p.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes)) + return p2p.Send(p.rw, GetAccountRangeMsg, &GetAccountRangePacket{ + ID: id, + Root: root, + Origin: origin, + Limit: limit, + Bytes: bytes, + }) +} + +// RequestStorageRange fetches a batch of storage slots belonging to one or more +// accounts. If slots from only one accout is requested, an origin marker may also +// be used to retrieve from there. +func (p *Peer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error { + if len(accounts) == 1 && origin != nil { + p.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes)) + } else { + p.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes)) + } + return p2p.Send(p.rw, GetStorageRangesMsg, &GetStorageRangesPacket{ + ID: id, + Root: root, + Accounts: accounts, + Origin: origin, + Limit: limit, + Bytes: bytes, + }) +} + +// RequestByteCodes fetches a batch of bytecodes by hash. +func (p *Peer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error { + p.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes)) + return p2p.Send(p.rw, GetByteCodesMsg, &GetByteCodesPacket{ + ID: id, + Hashes: hashes, + Bytes: bytes, + }) +} + +// RequestTrieNodes fetches a batch of account or storage trie nodes rooted in +// a specificstate trie. +func (p *Peer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error { + p.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes)) + return p2p.Send(p.rw, GetTrieNodesMsg, &GetTrieNodesPacket{ + ID: id, + Root: root, + Paths: paths, + Bytes: bytes, + }) +} diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go new file mode 100644 index 0000000000..a1e4349691 --- /dev/null +++ b/eth/protocols/snap/protocol.go @@ -0,0 +1,218 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/rlp" +) + +// Constants to match up protocol versions and messages +const ( + snap1 = 1 +) + +// protocolName is the official short name of the `snap` protocol used during +// devp2p capability negotiation. +const protocolName = "snap" + +// protocolVersions are the supported versions of the `snap` protocol (first +// is primary). +var protocolVersions = []uint{snap1} + +// protocolLengths are the number of implemented message corresponding to +// different protocol versions. +var protocolLengths = map[uint]uint64{snap1: 8} + +// maxMessageSize is the maximum cap on the size of a protocol message. +const maxMessageSize = 10 * 1024 * 1024 + +const ( + GetAccountRangeMsg = 0x00 + AccountRangeMsg = 0x01 + GetStorageRangesMsg = 0x02 + StorageRangesMsg = 0x03 + GetByteCodesMsg = 0x04 + ByteCodesMsg = 0x05 + GetTrieNodesMsg = 0x06 + TrieNodesMsg = 0x07 +) + +var ( + errMsgTooLarge = errors.New("message too long") + errDecode = errors.New("invalid message") + errInvalidMsgCode = errors.New("invalid message code") + errBadRequest = errors.New("bad request") +) + +// Packet represents a p2p message in the `snap` protocol. +type Packet interface { + Name() string // Name returns a string corresponding to the message type. + Kind() byte // Kind returns the message type. +} + +// GetAccountRangePacket represents an account query. +type GetAccountRangePacket struct { + ID uint64 // Request ID to match up responses with + Root common.Hash // Root hash of the account trie to serve + Origin common.Hash // Hash of the first account to retrieve + Limit common.Hash // Hash of the last account to retrieve + Bytes uint64 // Soft limit at which to stop returning data +} + +// AccountRangePacket represents an account query response. +type AccountRangePacket struct { + ID uint64 // ID of the request this is a response for + Accounts []*AccountData // List of consecutive accounts from the trie + Proof [][]byte // List of trie nodes proving the account range +} + +// AccountData represents a single account in a query response. +type AccountData struct { + Hash common.Hash // Hash of the account + Body rlp.RawValue // Account body in slim format +} + +// Unpack retrieves the accounts from the range packet and converts from slim +// wire representation to consensus format. The returned data is RLP encoded +// since it's expected to be serialized to disk without further interpretation. +// +// Note, this method does a round of RLP decoding and reencoding, so only use it +// once and cache the results if need be. Ideally discard the packet afterwards +// to not double the memory use. +func (p *AccountRangePacket) Unpack() ([]common.Hash, [][]byte, error) { + var ( + hashes = make([]common.Hash, len(p.Accounts)) + accounts = make([][]byte, len(p.Accounts)) + ) + for i, acc := range p.Accounts { + val, err := snapshot.FullAccountRLP(acc.Body) + if err != nil { + return nil, nil, fmt.Errorf("invalid account %x: %v", acc.Body, err) + } + hashes[i], accounts[i] = acc.Hash, val + } + return hashes, accounts, nil +} + +// GetStorageRangesPacket represents an storage slot query. +type GetStorageRangesPacket struct { + ID uint64 // Request ID to match up responses with + Root common.Hash // Root hash of the account trie to serve + Accounts []common.Hash // Account hashes of the storage tries to serve + Origin []byte // Hash of the first storage slot to retrieve (large contract mode) + Limit []byte // Hash of the last storage slot to retrieve (large contract mode) + Bytes uint64 // Soft limit at which to stop returning data +} + +// StorageRangesPacket represents a storage slot query response. +type StorageRangesPacket struct { + ID uint64 // ID of the request this is a response for + Slots [][]*StorageData // Lists of consecutive storage slots for the requested accounts + Proof [][]byte // Merkle proofs for the *last* slot range, if it's incomplete +} + +// StorageData represents a single storage slot in a query response. +type StorageData struct { + Hash common.Hash // Hash of the storage slot + Body []byte // Data content of the slot +} + +// Unpack retrieves the storage slots from the range packet and returns them in +// a split flat format that's more consistent with the internal data structures. +func (p *StorageRangesPacket) Unpack() ([][]common.Hash, [][][]byte) { + var ( + hashset = make([][]common.Hash, len(p.Slots)) + slotset = make([][][]byte, len(p.Slots)) + ) + for i, slots := range p.Slots { + hashset[i] = make([]common.Hash, len(slots)) + slotset[i] = make([][]byte, len(slots)) + for j, slot := range slots { + hashset[i][j] = slot.Hash + slotset[i][j] = slot.Body + } + } + return hashset, slotset +} + +// GetByteCodesPacket represents a contract bytecode query. +type GetByteCodesPacket struct { + ID uint64 // Request ID to match up responses with + Hashes []common.Hash // Code hashes to retrieve the code for + Bytes uint64 // Soft limit at which to stop returning data +} + +// ByteCodesPacket represents a contract bytecode query response. +type ByteCodesPacket struct { + ID uint64 // ID of the request this is a response for + Codes [][]byte // Requested contract bytecodes +} + +// GetTrieNodesPacket represents a state trie node query. +type GetTrieNodesPacket struct { + ID uint64 // Request ID to match up responses with + Root common.Hash // Root hash of the account trie to serve + Paths []TrieNodePathSet // Trie node hashes to retrieve the nodes for + Bytes uint64 // Soft limit at which to stop returning data +} + +// TrieNodePathSet is a list of trie node paths to retrieve. A naive way to +// represent trie nodes would be a simple list of `account || storage` path +// segments concatenated, but that would be very wasteful on the network. +// +// Instead, this array special cases the first element as the path in the +// account trie and the remaining elements as paths in the storage trie. To +// address an account node, the slice should have a length of 1 consisting +// of only the account path. There's no need to be able to address both an +// account node and a storage node in the same request as it cannot happen +// that a slot is accessed before the account path is fully expanded. +type TrieNodePathSet [][]byte + +// TrieNodesPacket represents a state trie node query response. +type TrieNodesPacket struct { + ID uint64 // ID of the request this is a response for + Nodes [][]byte // Requested state trie nodes +} + +func (*GetAccountRangePacket) Name() string { return "GetAccountRange" } +func (*GetAccountRangePacket) Kind() byte { return GetAccountRangeMsg } + +func (*AccountRangePacket) Name() string { return "AccountRange" } +func (*AccountRangePacket) Kind() byte { return AccountRangeMsg } + +func (*GetStorageRangesPacket) Name() string { return "GetStorageRanges" } +func (*GetStorageRangesPacket) Kind() byte { return GetStorageRangesMsg } + +func (*StorageRangesPacket) Name() string { return "StorageRanges" } +func (*StorageRangesPacket) Kind() byte { return StorageRangesMsg } + +func (*GetByteCodesPacket) Name() string { return "GetByteCodes" } +func (*GetByteCodesPacket) Kind() byte { return GetByteCodesMsg } + +func (*ByteCodesPacket) Name() string { return "ByteCodes" } +func (*ByteCodesPacket) Kind() byte { return ByteCodesMsg } + +func (*GetTrieNodesPacket) Name() string { return "GetTrieNodes" } +func (*GetTrieNodesPacket) Kind() byte { return GetTrieNodesMsg } + +func (*TrieNodesPacket) Name() string { return "TrieNodes" } +func (*TrieNodesPacket) Kind() byte { return TrieNodesMsg } diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go new file mode 100644 index 0000000000..679b328283 --- /dev/null +++ b/eth/protocols/snap/sync.go @@ -0,0 +1,2481 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "math/big" + "math/rand" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "golang.org/x/crypto/sha3" +) + +var ( + // emptyRoot is the known root hash of an empty trie. + emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // emptyCode is the known hash of the empty EVM bytecode. + emptyCode = crypto.Keccak256Hash(nil) +) + +const ( + // maxRequestSize is the maximum number of bytes to request from a remote peer. + maxRequestSize = 512 * 1024 + + // maxStorageSetRequestCountis th maximum number of contracts to request the + // storage of in a single query. If this number is too low, we're not filling + // responses fully and waste round trip times. If it's too high, we're capping + // responses and waste bandwidth. + maxStorageSetRequestCount = maxRequestSize / 1024 + + // maxCodeRequestCount is the maximum number of bytecode blobs to request in a + // single query. If this number is too low, we're not filling responses fully + // and waste round trip times. If it's too high, we're capping responses and + // waste bandwidth. + // + // Depoyed bytecodes are currently capped at 24KB, so the minimum request + // size should be maxRequestSize / 24K. Assuming that most contracts do not + // come close to that, requesting 4x should be a good approximation. + maxCodeRequestCount = maxRequestSize / (24 * 1024) * 4 + + // maxTrieRequestCount is the maximum number of trie node blobs to request in + // a single query. If this number is too low, we're not filling responses fully + // and waste round trip times. If it's too high, we're capping responses and + // waste bandwidth. + maxTrieRequestCount = 512 + + // requestTimeout is the maximum time a peer is allowed to spend on serving + // a single network request. + requestTimeout = 10 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? + + // accountConcurrency is the number of chunks to split the account trie into + // to allow concurrent retrievals. + accountConcurrency = 16 + + // storageConcurrency is the number of chunks to split the a large contract + // storage trie into to allow concurrent retrievals. + storageConcurrency = 16 +) + +// accountRequest tracks a pending account range request to ensure responses are +// to actual requests and to validate any security constraints. +// +// Concurrency note: account requests and responses are handled concurrently from +// the main runloop to allow Merkle proof verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type accountRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + origin common.Hash // First account requested to allow continuation checks + limit common.Hash // Last account requested to allow non-overlapping chunking + + task *accountTask // Task which this request is filling (only access fields through the runloop!!) +} + +// accountResponse is an already Merkle-verified remote response to an account +// range request. It contains the subtrie for the requested account range and +// the database that's going to be filled with the internal nodes on commit. +type accountResponse struct { + task *accountTask // Task which this request is filling + + hashes []common.Hash // Account hashes in the returned range + accounts []*state.Account // Expanded accounts in the returned range + + nodes ethdb.KeyValueStore // Database containing the reconstructed trie nodes + trie *trie.Trie // Reconstructed trie to reject incomplete account paths + + bounds map[common.Hash]struct{} // Boundary nodes to avoid persisting incomplete accounts + overflow *light.NodeSet // Overflow nodes to avoid persisting across chunk boundaries + + cont bool // Whether the account range has a continuation +} + +// bytecodeRequest tracks a pending bytecode request to ensure responses are to +// actual requests and to validate any security constraints. +// +// Concurrency note: bytecode requests and responses are handled concurrently from +// the main runloop to allow Keccak256 hash verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type bytecodeRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + hashes []common.Hash // Bytecode hashes to validate responses + task *accountTask // Task which this request is filling (only access fields through the runloop!!) +} + +// bytecodeResponse is an already verified remote response to a bytecode request. +type bytecodeResponse struct { + task *accountTask // Task which this request is filling + + hashes []common.Hash // Hashes of the bytecode to avoid double hashing + codes [][]byte // Actual bytecodes to store into the database (nil = missing) +} + +// storageRequest tracks a pending storage ranges request to ensure responses are +// to actual requests and to validate any security constraints. +// +// Concurrency note: storage requests and responses are handled concurrently from +// the main runloop to allow Merkel proof verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. tasks). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type storageRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + accounts []common.Hash // Account hashes to validate responses + roots []common.Hash // Storage roots to validate responses + + origin common.Hash // First storage slot requested to allow continuation checks + limit common.Hash // Last storage slot requested to allow non-overlapping chunking + + mainTask *accountTask // Task which this response belongs to (only access fields through the runloop!!) + subTask *storageTask // Task which this response is filling (only access fields through the runloop!!) +} + +// storageResponse is an already Merkle-verified remote response to a storage +// range request. It contains the subtries for the requested storage ranges and +// the databases that's going to be filled with the internal nodes on commit. +type storageResponse struct { + mainTask *accountTask // Task which this response belongs to + subTask *storageTask // Task which this response is filling + + accounts []common.Hash // Account hashes requested, may be only partially filled + roots []common.Hash // Storage roots requested, may be only partially filled + + hashes [][]common.Hash // Storage slot hashes in the returned range + slots [][][]byte // Storage slot values in the returned range + nodes []ethdb.KeyValueStore // Database containing the reconstructed trie nodes + tries []*trie.Trie // Reconstructed tries to reject overflown slots + + // Fields relevant for the last account only + bounds map[common.Hash]struct{} // Boundary nodes to avoid persisting (incomplete) + overflow *light.NodeSet // Overflow nodes to avoid persisting across chunk boundaries + cont bool // Whether the last storage range has a continuation +} + +// trienodeHealRequest tracks a pending state trie request to ensure responses +// are to actual requests and to validate any security constraints. +// +// Concurrency note: trie node requests and responses are handled concurrently from +// the main runloop to allow Keccak256 hash verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type trienodeHealRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + hashes []common.Hash // Trie node hashes to validate responses + paths []trie.SyncPath // Trie node paths requested for rescheduling + + task *healTask // Task which this request is filling (only access fields through the runloop!!) +} + +// trienodeHealResponse is an already verified remote response to a trie node request. +type trienodeHealResponse struct { + task *healTask // Task which this request is filling + + hashes []common.Hash // Hashes of the trie nodes to avoid double hashing + paths []trie.SyncPath // Trie node paths requested for rescheduling missing ones + nodes [][]byte // Actual trie nodes to store into the database (nil = missing) +} + +// bytecodeHealRequest tracks a pending bytecode request to ensure responses are to +// actual requests and to validate any security constraints. +// +// Concurrency note: bytecode requests and responses are handled concurrently from +// the main runloop to allow Keccak256 hash verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type bytecodeHealRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + hashes []common.Hash // Bytecode hashes to validate responses + task *healTask // Task which this request is filling (only access fields through the runloop!!) +} + +// bytecodeHealResponse is an already verified remote response to a bytecode request. +type bytecodeHealResponse struct { + task *healTask // Task which this request is filling + + hashes []common.Hash // Hashes of the bytecode to avoid double hashing + codes [][]byte // Actual bytecodes to store into the database (nil = missing) +} + +// accountTask represents the sync task for a chunk of the account snapshot. +type accountTask struct { + // These fields get serialized to leveldb on shutdown + Next common.Hash // Next account to sync in this interval + Last common.Hash // Last account to sync in this interval + SubTasks map[common.Hash][]*storageTask // Storage intervals needing fetching for large contracts + + // These fields are internals used during runtime + req *accountRequest // Pending request to fill this task + res *accountResponse // Validate response filling this task + pend int // Number of pending subtasks for this round + + needCode []bool // Flags whether the filling accounts need code retrieval + needState []bool // Flags whether the filling accounts need storage retrieval + needHeal []bool // Flags whether the filling accounts's state was chunked and need healing + + codeTasks map[common.Hash]struct{} // Code hashes that need retrieval + stateTasks map[common.Hash]common.Hash // Account hashes->roots that need full state retrieval + + done bool // Flag whether the task can be removed +} + +// storageTask represents the sync task for a chunk of the storage snapshot. +type storageTask struct { + Next common.Hash // Next account to sync in this interval + Last common.Hash // Last account to sync in this interval + + // These fields are internals used during runtime + root common.Hash // Storage root hash for this instance + req *storageRequest // Pending request to fill this task + done bool // Flag whether the task can be removed +} + +// healTask represents the sync task for healing the snap-synced chunk boundaries. +type healTask struct { + scheduler *trie.Sync // State trie sync scheduler defining the tasks + + trieTasks map[common.Hash]trie.SyncPath // Set of trie node tasks currently queued for retrieval + codeTasks map[common.Hash]struct{} // Set of byte code tasks currently queued for retrieval +} + +// syncProgress is a database entry to allow suspending and resuming a snapshot state +// sync. Opposed to full and fast sync, there is no way to restart a suspended +// snap sync without prior knowledge of the suspension point. +type syncProgress struct { + Tasks []*accountTask // The suspended account tasks (contract tasks within) + + // Status report during syncing phase + AccountSynced uint64 // Number of accounts downloaded + AccountBytes common.StorageSize // Number of account trie bytes persisted to disk + BytecodeSynced uint64 // Number of bytecodes downloaded + BytecodeBytes common.StorageSize // Number of bytecode bytes downloaded + StorageSynced uint64 // Number of storage slots downloaded + StorageBytes common.StorageSize // Number of storage trie bytes persisted to disk + + // Status report during healing phase + TrienodeHealSynced uint64 // Number of state trie nodes downloaded + TrienodeHealBytes common.StorageSize // Number of state trie bytes persisted to disk + TrienodeHealDups uint64 // Number of state trie nodes already processed + TrienodeHealNops uint64 // Number of state trie nodes not requested + BytecodeHealSynced uint64 // Number of bytecodes downloaded + BytecodeHealBytes common.StorageSize // Number of bytecodes persisted to disk + BytecodeHealDups uint64 // Number of bytecodes already processed + BytecodeHealNops uint64 // Number of bytecodes not requested +} + +// Syncer is an Ethereum account and storage trie syncer based on snapshots and +// the snap protocol. It's purpose is to download all the accounts and storage +// slots from remote peers and reassemble chunks of the state trie, on top of +// which a state sync can be run to fix any gaps / overlaps. +// +// Every network request has a variety of failure events: +// - The peer disconnects after task assignment, failing to send the request +// - The peer disconnects after sending the request, before delivering on it +// - The peer remains connected, but does not deliver a response in time +// - The peer delivers a stale response after a previous timeout +// - The peer delivers a refusal to serve the requested state +type Syncer struct { + db ethdb.KeyValueStore // Database to store the trie nodes into (and dedup) + bloom *trie.SyncBloom // Bloom filter to deduplicate nodes for state fixup + + root common.Hash // Current state trie root being synced + tasks []*accountTask // Current account task set being synced + healer *healTask // Current state healing task being executed + update chan struct{} // Notification channel for possible sync progression + + peers map[string]*Peer // Currently active peers to download from + peerJoin *event.Feed // Event feed to react to peers joining + peerDrop *event.Feed // Event feed to react to peers dropping + + // Request tracking during syncing phase + statelessPeers map[string]struct{} // Peers that failed to deliver state data + accountIdlers map[string]struct{} // Peers that aren't serving account requests + bytecodeIdlers map[string]struct{} // Peers that aren't serving bytecode requests + storageIdlers map[string]struct{} // Peers that aren't serving storage requests + + accountReqs map[uint64]*accountRequest // Account requests currently running + bytecodeReqs map[uint64]*bytecodeRequest // Bytecode requests currently running + storageReqs map[uint64]*storageRequest // Storage requests currently running + + accountReqFails chan *accountRequest // Failed account range requests to revert + bytecodeReqFails chan *bytecodeRequest // Failed bytecode requests to revert + storageReqFails chan *storageRequest // Failed storage requests to revert + + accountResps chan *accountResponse // Account sub-tries to integrate into the database + bytecodeResps chan *bytecodeResponse // Bytecodes to integrate into the database + storageResps chan *storageResponse // Storage sub-tries to integrate into the database + + accountSynced uint64 // Number of accounts downloaded + accountBytes common.StorageSize // Number of account trie bytes persisted to disk + bytecodeSynced uint64 // Number of bytecodes downloaded + bytecodeBytes common.StorageSize // Number of bytecode bytes downloaded + storageSynced uint64 // Number of storage slots downloaded + storageBytes common.StorageSize // Number of storage trie bytes persisted to disk + + // Request tracking during healing phase + trienodeHealIdlers map[string]struct{} // Peers that aren't serving trie node requests + bytecodeHealIdlers map[string]struct{} // Peers that aren't serving bytecode requests + + trienodeHealReqs map[uint64]*trienodeHealRequest // Trie node requests currently running + bytecodeHealReqs map[uint64]*bytecodeHealRequest // Bytecode requests currently running + + trienodeHealReqFails chan *trienodeHealRequest // Failed trienode requests to revert + bytecodeHealReqFails chan *bytecodeHealRequest // Failed bytecode requests to revert + + trienodeHealResps chan *trienodeHealResponse // Trie nodes to integrate into the database + bytecodeHealResps chan *bytecodeHealResponse // Bytecodes to integrate into the database + + trienodeHealSynced uint64 // Number of state trie nodes downloaded + trienodeHealBytes common.StorageSize // Number of state trie bytes persisted to disk + trienodeHealDups uint64 // Number of state trie nodes already processed + trienodeHealNops uint64 // Number of state trie nodes not requested + bytecodeHealSynced uint64 // Number of bytecodes downloaded + bytecodeHealBytes common.StorageSize // Number of bytecodes persisted to disk + bytecodeHealDups uint64 // Number of bytecodes already processed + bytecodeHealNops uint64 // Number of bytecodes not requested + + startTime time.Time // Time instance when snapshot sync started + startAcc common.Hash // Account hash where sync started from + logTime time.Time // Time instance when status was last reported + + pend sync.WaitGroup // Tracks network request goroutines for graceful shutdown + lock sync.RWMutex // Protects fields that can change outside of sync (peers, reqs, root) +} + +func NewSyncer(db ethdb.KeyValueStore, bloom *trie.SyncBloom) *Syncer { + return &Syncer{ + db: db, + bloom: bloom, + + peers: make(map[string]*Peer), + peerJoin: new(event.Feed), + peerDrop: new(event.Feed), + update: make(chan struct{}, 1), + + accountIdlers: make(map[string]struct{}), + storageIdlers: make(map[string]struct{}), + bytecodeIdlers: make(map[string]struct{}), + + accountReqs: make(map[uint64]*accountRequest), + storageReqs: make(map[uint64]*storageRequest), + bytecodeReqs: make(map[uint64]*bytecodeRequest), + accountReqFails: make(chan *accountRequest), + storageReqFails: make(chan *storageRequest), + bytecodeReqFails: make(chan *bytecodeRequest), + accountResps: make(chan *accountResponse), + storageResps: make(chan *storageResponse), + bytecodeResps: make(chan *bytecodeResponse), + + trienodeHealIdlers: make(map[string]struct{}), + bytecodeHealIdlers: make(map[string]struct{}), + + trienodeHealReqs: make(map[uint64]*trienodeHealRequest), + bytecodeHealReqs: make(map[uint64]*bytecodeHealRequest), + trienodeHealReqFails: make(chan *trienodeHealRequest), + bytecodeHealReqFails: make(chan *bytecodeHealRequest), + trienodeHealResps: make(chan *trienodeHealResponse), + bytecodeHealResps: make(chan *bytecodeHealResponse), + } +} + +// Register injects a new data source into the syncer's peerset. +func (s *Syncer) Register(peer *Peer) error { + // Make sure the peer is not registered yet + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + log.Error("Snap peer already registered", "id", peer.id) + + s.lock.Unlock() + return errors.New("already registered") + } + s.peers[peer.id] = peer + + // Mark the peer as idle, even if no sync is running + s.accountIdlers[peer.id] = struct{}{} + s.storageIdlers[peer.id] = struct{}{} + s.bytecodeIdlers[peer.id] = struct{}{} + s.trienodeHealIdlers[peer.id] = struct{}{} + s.bytecodeHealIdlers[peer.id] = struct{}{} + s.lock.Unlock() + + // Notify any active syncs that a new peer can be assigned data + s.peerJoin.Send(peer.id) + return nil +} + +// Unregister injects a new data source into the syncer's peerset. +func (s *Syncer) Unregister(id string) error { + // Remove all traces of the peer from the registry + s.lock.Lock() + if _, ok := s.peers[id]; !ok { + log.Error("Snap peer not registered", "id", id) + + s.lock.Unlock() + return errors.New("not registered") + } + delete(s.peers, id) + + // Remove status markers, even if no sync is running + delete(s.statelessPeers, id) + + delete(s.accountIdlers, id) + delete(s.storageIdlers, id) + delete(s.bytecodeIdlers, id) + delete(s.trienodeHealIdlers, id) + delete(s.bytecodeHealIdlers, id) + s.lock.Unlock() + + // Notify any active syncs that pending requests need to be reverted + s.peerDrop.Send(id) + return nil +} + +// Sync starts (or resumes a previous) sync cycle to iterate over an state trie +// with the given root and reconstruct the nodes based on the snapshot leaves. +// Previously downloaded segments will not be redownloaded of fixed, rather any +// errors will be healed after the leaves are fully accumulated. +func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { + // Move the trie root from any previous value, revert stateless markers for + // any peers and initialize the syncer if it was not yet run + s.lock.Lock() + s.root = root + s.healer = &healTask{ + scheduler: state.NewStateSync(root, s.db, s.bloom), + trieTasks: make(map[common.Hash]trie.SyncPath), + codeTasks: make(map[common.Hash]struct{}), + } + s.statelessPeers = make(map[string]struct{}) + s.lock.Unlock() + + if s.startTime == (time.Time{}) { + s.startTime = time.Now() + } + // Retrieve the previous sync status from LevelDB and abort if already synced + s.loadSyncStatus() + if len(s.tasks) == 0 && s.healer.scheduler.Pending() == 0 { + log.Debug("Snapshot sync already completed") + return nil + } + defer func() { // Persist any progress, independent of failure + for _, task := range s.tasks { + s.forwardAccountTask(task) + } + s.cleanAccountTasks() + s.saveSyncStatus() + }() + + log.Debug("Starting snapshot sync cycle", "root", root) + defer s.report(true) + + // Whether sync completed or not, disregard any future packets + defer func() { + log.Debug("Terminating snapshot sync cycle", "root", root) + s.lock.Lock() + s.accountReqs = make(map[uint64]*accountRequest) + s.storageReqs = make(map[uint64]*storageRequest) + s.bytecodeReqs = make(map[uint64]*bytecodeRequest) + s.trienodeHealReqs = make(map[uint64]*trienodeHealRequest) + s.bytecodeHealReqs = make(map[uint64]*bytecodeHealRequest) + s.lock.Unlock() + }() + // Keep scheduling sync tasks + peerJoin := make(chan string, 16) + peerJoinSub := s.peerJoin.Subscribe(peerJoin) + defer peerJoinSub.Unsubscribe() + + peerDrop := make(chan string, 16) + peerDropSub := s.peerDrop.Subscribe(peerDrop) + defer peerDropSub.Unsubscribe() + + for { + // Remove all completed tasks and terminate sync if everything's done + s.cleanStorageTasks() + s.cleanAccountTasks() + if len(s.tasks) == 0 && s.healer.scheduler.Pending() == 0 { + return nil + } + // Assign all the data retrieval tasks to any free peers + s.assignAccountTasks(cancel) + s.assignBytecodeTasks(cancel) + s.assignStorageTasks(cancel) + if len(s.tasks) == 0 { + // Sync phase done, run heal phase + s.assignTrienodeHealTasks(cancel) + s.assignBytecodeHealTasks(cancel) + } + // Wait for something to happen + select { + case <-s.update: + // Something happened (new peer, delivery, timeout), recheck tasks + case <-peerJoin: + // A new peer joined, try to schedule it new tasks + case id := <-peerDrop: + s.revertRequests(id) + case <-cancel: + return nil + + case req := <-s.accountReqFails: + s.revertAccountRequest(req) + case req := <-s.bytecodeReqFails: + s.revertBytecodeRequest(req) + case req := <-s.storageReqFails: + s.revertStorageRequest(req) + case req := <-s.trienodeHealReqFails: + s.revertTrienodeHealRequest(req) + case req := <-s.bytecodeHealReqFails: + s.revertBytecodeHealRequest(req) + + case res := <-s.accountResps: + s.processAccountResponse(res) + case res := <-s.bytecodeResps: + s.processBytecodeResponse(res) + case res := <-s.storageResps: + s.processStorageResponse(res) + case res := <-s.trienodeHealResps: + s.processTrienodeHealResponse(res) + case res := <-s.bytecodeHealResps: + s.processBytecodeHealResponse(res) + } + // Report stats if something meaningful happened + s.report(false) + } +} + +// loadSyncStatus retrieves a previously aborted sync status from the database, +// or generates a fresh one if none is available. +func (s *Syncer) loadSyncStatus() { + var progress syncProgress + + if status := rawdb.ReadSanpshotSyncStatus(s.db); status != nil { + if err := json.Unmarshal(status, &progress); err != nil { + log.Error("Failed to decode snap sync status", "err", err) + } else { + for _, task := range progress.Tasks { + log.Debug("Scheduled account sync task", "from", task.Next, "last", task.Last) + } + s.tasks = progress.Tasks + + s.accountSynced = progress.AccountSynced + s.accountBytes = progress.AccountBytes + s.bytecodeSynced = progress.BytecodeSynced + s.bytecodeBytes = progress.BytecodeBytes + s.storageSynced = progress.StorageSynced + s.storageBytes = progress.StorageBytes + + s.trienodeHealSynced = progress.TrienodeHealSynced + s.trienodeHealBytes = progress.TrienodeHealBytes + s.bytecodeHealSynced = progress.BytecodeHealSynced + s.bytecodeHealBytes = progress.BytecodeHealBytes + return + } + } + // Either we've failed to decode the previus state, or there was none. + // Start a fresh sync by chunking up the account range and scheduling + // them for retrieval. + s.tasks = nil + s.accountSynced, s.accountBytes = 0, 0 + s.bytecodeSynced, s.bytecodeBytes = 0, 0 + s.storageSynced, s.storageBytes = 0, 0 + s.trienodeHealSynced, s.trienodeHealBytes = 0, 0 + s.bytecodeHealSynced, s.bytecodeHealBytes = 0, 0 + + var next common.Hash + step := new(big.Int).Sub( + new(big.Int).Div( + new(big.Int).Exp(common.Big2, common.Big256, nil), + big.NewInt(accountConcurrency), + ), common.Big1, + ) + for i := 0; i < accountConcurrency; i++ { + last := common.BigToHash(new(big.Int).Add(next.Big(), step)) + if i == accountConcurrency-1 { + // Make sure we don't overflow if the step is not a proper divisor + last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + } + s.tasks = append(s.tasks, &accountTask{ + Next: next, + Last: last, + SubTasks: make(map[common.Hash][]*storageTask), + }) + log.Debug("Created account sync task", "from", next, "last", last) + next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + } +} + +// saveSyncStatus marshals the remaining sync tasks into leveldb. +func (s *Syncer) saveSyncStatus() { + progress := &syncProgress{ + Tasks: s.tasks, + AccountSynced: s.accountSynced, + AccountBytes: s.accountBytes, + BytecodeSynced: s.bytecodeSynced, + BytecodeBytes: s.bytecodeBytes, + StorageSynced: s.storageSynced, + StorageBytes: s.storageBytes, + TrienodeHealSynced: s.trienodeHealSynced, + TrienodeHealBytes: s.trienodeHealBytes, + BytecodeHealSynced: s.bytecodeHealSynced, + BytecodeHealBytes: s.bytecodeHealBytes, + } + status, err := json.Marshal(progress) + if err != nil { + panic(err) // This can only fail during implementation + } + rawdb.WriteSnapshotSyncStatus(s.db, status) +} + +// cleanAccountTasks removes account range retrieval tasks that have already been +// completed. +func (s *Syncer) cleanAccountTasks() { + for i := 0; i < len(s.tasks); i++ { + if s.tasks[i].done { + s.tasks = append(s.tasks[:i], s.tasks[i+1:]...) + i-- + } + } +} + +// cleanStorageTasks iterates over all the account tasks and storage sub-tasks +// within, cleaning any that have been completed. +func (s *Syncer) cleanStorageTasks() { + for _, task := range s.tasks { + for account, subtasks := range task.SubTasks { + // Remove storage range retrieval tasks that completed + for j := 0; j < len(subtasks); j++ { + if subtasks[j].done { + subtasks = append(subtasks[:j], subtasks[j+1:]...) + j-- + } + } + if len(subtasks) > 0 { + task.SubTasks[account] = subtasks + continue + } + // If all storage chunks are done, mark the account as done too + for j, hash := range task.res.hashes { + if hash == account { + task.needState[j] = false + } + } + delete(task.SubTasks, account) + task.pend-- + + // If this was the last pending task, forward the account task + if task.pend == 0 { + s.forwardAccountTask(task) + } + } + } +} + +// assignAccountTasks attempts to match idle peers to pending account range +// retrievals. +func (s *Syncer) assignAccountTasks(cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // If there are no idle peers, short circuit assignment + if len(s.accountIdlers) == 0 { + return + } + // Iterate over all the tasks and try to find a pending one + for _, task := range s.tasks { + // Skip any tasks already filling + if task.req != nil || task.res != nil { + continue + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + var idle string + for id := range s.accountIdlers { + // If the peer rejected a query in this sync cycle, don't bother asking + // again for anything, it's either out of sync or already pruned + if _, ok := s.statelessPeers[id]; ok { + continue + } + idle = id + break + } + if idle == "" { + return + } + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.accountReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + req := &accountRequest{ + peer: idle, + id: reqid, + cancel: cancel, + stale: make(chan struct{}), + origin: task.Next, + limit: task.Last, + task: task, + } + req.timeout = time.AfterFunc(requestTimeout, func() { + log.Debug("Account range request timed out") + select { + case s.accountReqFails <- req: + default: + } + }) + s.accountReqs[reqid] = req + delete(s.accountIdlers, idle) + + s.pend.Add(1) + go func(peer *Peer, root common.Hash) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if err := peer.RequestAccountRange(reqid, root, req.origin, req.limit, maxRequestSize); err != nil { + peer.Log().Debug("Failed to request account range", "err", err) + select { + case s.accountReqFails <- req: + default: + } + } + // Request successfully sent, start a + }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists + + // Inject the request into the task to block further assignments + task.req = req + } +} + +// assignBytecodeTasks attempts to match idle peers to pending code retrievals. +func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // If there are no idle peers, short circuit assignment + if len(s.bytecodeIdlers) == 0 { + return + } + // Iterate over all the tasks and try to find a pending one + for _, task := range s.tasks { + // Skip any tasks not in the bytecode retrieval phase + if task.res == nil { + continue + } + // Skip tasks that are already retrieving (or done with) all codes + if len(task.codeTasks) == 0 { + continue + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + var idle string + for id := range s.bytecodeIdlers { + // If the peer rejected a query in this sync cycle, don't bother asking + // again for anything, it's either out of sync or already pruned + if _, ok := s.statelessPeers[id]; ok { + continue + } + idle = id + break + } + if idle == "" { + return + } + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.bytecodeReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + hashes := make([]common.Hash, 0, maxCodeRequestCount) + for hash := range task.codeTasks { + delete(task.codeTasks, hash) + hashes = append(hashes, hash) + if len(hashes) >= maxCodeRequestCount { + break + } + } + req := &bytecodeRequest{ + peer: idle, + id: reqid, + cancel: cancel, + stale: make(chan struct{}), + hashes: hashes, + task: task, + } + req.timeout = time.AfterFunc(requestTimeout, func() { + log.Debug("Bytecode request timed out") + select { + case s.bytecodeReqFails <- req: + default: + } + }) + s.bytecodeReqs[reqid] = req + delete(s.bytecodeIdlers, idle) + + s.pend.Add(1) + go func(peer *Peer) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil { + log.Debug("Failed to request bytecodes", "err", err) + select { + case s.bytecodeReqFails <- req: + default: + } + } + // Request successfully sent, start a + }(s.peers[idle]) // We're in the lock, peers[id] surely exists + } +} + +// assignStorageTasks attempts to match idle peers to pending storage range +// retrievals. +func (s *Syncer) assignStorageTasks(cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // If there are no idle peers, short circuit assignment + if len(s.storageIdlers) == 0 { + return + } + // Iterate over all the tasks and try to find a pending one + for _, task := range s.tasks { + // Skip any tasks not in the storage retrieval phase + if task.res == nil { + continue + } + // Skip tasks that are already retrieving (or done with) all small states + if len(task.SubTasks) == 0 && len(task.stateTasks) == 0 { + continue + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + var idle string + for id := range s.storageIdlers { + // If the peer rejected a query in this sync cycle, don't bother asking + // again for anything, it's either out of sync or already pruned + if _, ok := s.statelessPeers[id]; ok { + continue + } + idle = id + break + } + if idle == "" { + return + } + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.storageReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer. If there are + // large contract tasks pending, complete those before diving into + // even more new contracts. + var ( + accounts = make([]common.Hash, 0, maxStorageSetRequestCount) + roots = make([]common.Hash, 0, maxStorageSetRequestCount) + subtask *storageTask + ) + for account, subtasks := range task.SubTasks { + for _, st := range subtasks { + // Skip any subtasks already filling + if st.req != nil { + continue + } + // Found an incomplete storage chunk, schedule it + accounts = append(accounts, account) + roots = append(roots, st.root) + + subtask = st + break // Large contract chunks are downloaded individually + } + if subtask != nil { + break // Large contract chunks are downloaded individually + } + } + if subtask == nil { + // No large contract required retrieval, but small ones available + for acccount, root := range task.stateTasks { + delete(task.stateTasks, acccount) + + accounts = append(accounts, acccount) + roots = append(roots, root) + + if len(accounts) >= maxStorageSetRequestCount { + break + } + } + } + // If nothing was found, it means this task is actually already fully + // retrieving, but large contracts are hard to detect. Skip to the next. + if len(accounts) == 0 { + continue + } + req := &storageRequest{ + peer: idle, + id: reqid, + cancel: cancel, + stale: make(chan struct{}), + accounts: accounts, + roots: roots, + mainTask: task, + subTask: subtask, + } + if subtask != nil { + req.origin = subtask.Next + req.limit = subtask.Last + } + req.timeout = time.AfterFunc(requestTimeout, func() { + log.Debug("Storage request timed out") + select { + case s.storageReqFails <- req: + default: + } + }) + s.storageReqs[reqid] = req + delete(s.storageIdlers, idle) + + s.pend.Add(1) + go func(peer *Peer, root common.Hash) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + var origin, limit []byte + if subtask != nil { + origin, limit = req.origin[:], req.limit[:] + } + if err := peer.RequestStorageRanges(reqid, root, accounts, origin, limit, maxRequestSize); err != nil { + log.Debug("Failed to request storage", "err", err) + select { + case s.storageReqFails <- req: + default: + } + } + // Request successfully sent, start a + }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists + + // Inject the request into the subtask to block further assignments + if subtask != nil { + subtask.req = req + } + } +} + +// assignTrienodeHealTasks attempts to match idle peers to trie node requests to +// heal any trie errors caused by the snap sync's chunked retrieval model. +func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // If there are no idle peers, short circuit assignment + if len(s.trienodeHealIdlers) == 0 { + return + } + // Iterate over pending tasks and try to find a peer to retrieve with + for len(s.healer.trieTasks) > 0 || s.healer.scheduler.Pending() > 0 { + // If there are not enough trie tasks queued to fully assign, fill the + // queue from the state sync scheduler. The trie synced schedules these + // together with bytecodes, so we need to queue them combined. + var ( + have = len(s.healer.trieTasks) + len(s.healer.codeTasks) + want = maxTrieRequestCount + maxCodeRequestCount + ) + if have < want { + nodes, paths, codes := s.healer.scheduler.Missing(want - have) + for i, hash := range nodes { + s.healer.trieTasks[hash] = paths[i] + } + for _, hash := range codes { + s.healer.codeTasks[hash] = struct{}{} + } + } + // If all the heal tasks are bytecodes or already downloading, bail + if len(s.healer.trieTasks) == 0 { + return + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + var idle string + for id := range s.trienodeHealIdlers { + // If the peer rejected a query in this sync cycle, don't bother asking + // again for anything, it's either out of sync or already pruned + if _, ok := s.statelessPeers[id]; ok { + continue + } + idle = id + break + } + if idle == "" { + return + } + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.trienodeHealReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + var ( + hashes = make([]common.Hash, 0, maxTrieRequestCount) + paths = make([]trie.SyncPath, 0, maxTrieRequestCount) + pathsets = make([]TrieNodePathSet, 0, maxTrieRequestCount) + ) + for hash, pathset := range s.healer.trieTasks { + delete(s.healer.trieTasks, hash) + + hashes = append(hashes, hash) + paths = append(paths, pathset) + pathsets = append(pathsets, [][]byte(pathset)) // TODO(karalabe): group requests by account hash + + if len(hashes) >= maxTrieRequestCount { + break + } + } + req := &trienodeHealRequest{ + peer: idle, + id: reqid, + cancel: cancel, + stale: make(chan struct{}), + hashes: hashes, + paths: paths, + task: s.healer, + } + req.timeout = time.AfterFunc(requestTimeout, func() { + log.Debug("Trienode heal request timed out") + select { + case s.trienodeHealReqFails <- req: + default: + } + }) + s.trienodeHealReqs[reqid] = req + delete(s.trienodeHealIdlers, idle) + + s.pend.Add(1) + go func(peer *Peer, root common.Hash) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if err := peer.RequestTrieNodes(reqid, root, pathsets, maxRequestSize); err != nil { + log.Debug("Failed to request trienode healers", "err", err) + select { + case s.trienodeHealReqFails <- req: + default: + } + } + // Request successfully sent, start a + }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists + } +} + +// assignBytecodeHealTasks attempts to match idle peers to bytecode requests to +// heal any trie errors caused by the snap sync's chunked retrieval model. +func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // If there are no idle peers, short circuit assignment + if len(s.bytecodeHealIdlers) == 0 { + return + } + // Iterate over pending tasks and try to find a peer to retrieve with + for len(s.healer.codeTasks) > 0 || s.healer.scheduler.Pending() > 0 { + // If there are not enough trie tasks queued to fully assign, fill the + // queue from the state sync scheduler. The trie synced schedules these + // together with trie nodes, so we need to queue them combined. + var ( + have = len(s.healer.trieTasks) + len(s.healer.codeTasks) + want = maxTrieRequestCount + maxCodeRequestCount + ) + if have < want { + nodes, paths, codes := s.healer.scheduler.Missing(want - have) + for i, hash := range nodes { + s.healer.trieTasks[hash] = paths[i] + } + for _, hash := range codes { + s.healer.codeTasks[hash] = struct{}{} + } + } + // If all the heal tasks are trienodes or already downloading, bail + if len(s.healer.codeTasks) == 0 { + return + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + var idle string + for id := range s.bytecodeHealIdlers { + // If the peer rejected a query in this sync cycle, don't bother asking + // again for anything, it's either out of sync or already pruned + if _, ok := s.statelessPeers[id]; ok { + continue + } + idle = id + break + } + if idle == "" { + return + } + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.bytecodeHealReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + hashes := make([]common.Hash, 0, maxCodeRequestCount) + for hash := range s.healer.codeTasks { + delete(s.healer.codeTasks, hash) + + hashes = append(hashes, hash) + if len(hashes) >= maxCodeRequestCount { + break + } + } + req := &bytecodeHealRequest{ + peer: idle, + id: reqid, + cancel: cancel, + stale: make(chan struct{}), + hashes: hashes, + task: s.healer, + } + req.timeout = time.AfterFunc(requestTimeout, func() { + log.Debug("Bytecode heal request timed out") + select { + case s.bytecodeHealReqFails <- req: + default: + } + }) + s.bytecodeHealReqs[reqid] = req + delete(s.bytecodeHealIdlers, idle) + + s.pend.Add(1) + go func(peer *Peer) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil { + log.Debug("Failed to request bytecode healers", "err", err) + select { + case s.bytecodeHealReqFails <- req: + default: + } + } + // Request successfully sent, start a + }(s.peers[idle]) // We're in the lock, peers[id] surely exists + } +} + +// revertRequests locates all the currently pending reuqests from a particular +// peer and reverts them, rescheduling for others to fulfill. +func (s *Syncer) revertRequests(peer string) { + // Gather the requests first, revertals need the lock too + s.lock.Lock() + var accountReqs []*accountRequest + for _, req := range s.accountReqs { + if req.peer == peer { + accountReqs = append(accountReqs, req) + } + } + var bytecodeReqs []*bytecodeRequest + for _, req := range s.bytecodeReqs { + if req.peer == peer { + bytecodeReqs = append(bytecodeReqs, req) + } + } + var storageReqs []*storageRequest + for _, req := range s.storageReqs { + if req.peer == peer { + storageReqs = append(storageReqs, req) + } + } + var trienodeHealReqs []*trienodeHealRequest + for _, req := range s.trienodeHealReqs { + if req.peer == peer { + trienodeHealReqs = append(trienodeHealReqs, req) + } + } + var bytecodeHealReqs []*bytecodeHealRequest + for _, req := range s.bytecodeHealReqs { + if req.peer == peer { + bytecodeHealReqs = append(bytecodeHealReqs, req) + } + } + s.lock.Unlock() + + // Revert all the requests matching the peer + for _, req := range accountReqs { + s.revertAccountRequest(req) + } + for _, req := range bytecodeReqs { + s.revertBytecodeRequest(req) + } + for _, req := range storageReqs { + s.revertStorageRequest(req) + } + for _, req := range trienodeHealReqs { + s.revertTrienodeHealRequest(req) + } + for _, req := range bytecodeHealReqs { + s.revertBytecodeHealRequest(req) + } +} + +// revertAccountRequest cleans up an account range request and returns all failed +// retrieval tasks to the scheduler for reassignment. +func (s *Syncer) revertAccountRequest(req *accountRequest) { + log.Trace("Reverting account request", "peer", req.peer, "reqid", req.id) + select { + case <-req.stale: + log.Trace("Account request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.accountReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the account + // task as not-pending, ready for resheduling + req.timeout.Stop() + if req.task.req == req { + req.task.req = nil + } +} + +// revertBytecodeRequest cleans up an bytecode request and returns all failed +// retrieval tasks to the scheduler for reassignment. +func (s *Syncer) revertBytecodeRequest(req *bytecodeRequest) { + log.Trace("Reverting bytecode request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Bytecode request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.bytecodeReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the code + // retrievals as not-pending, ready for resheduling + req.timeout.Stop() + for _, hash := range req.hashes { + req.task.codeTasks[hash] = struct{}{} + } +} + +// revertStorageRequest cleans up a storage range request and returns all failed +// retrieval tasks to the scheduler for reassignment. +func (s *Syncer) revertStorageRequest(req *storageRequest) { + log.Trace("Reverting storage request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Storage request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.storageReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the storage + // task as not-pending, ready for resheduling + req.timeout.Stop() + if req.subTask != nil { + req.subTask.req = nil + } else { + for i, account := range req.accounts { + req.mainTask.stateTasks[account] = req.roots[i] + } + } +} + +// revertTrienodeHealRequest cleans up an trienode heal request and returns all +// failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) { + log.Trace("Reverting trienode heal request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Trienode heal request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.trienodeHealReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the trie node + // retrievals as not-pending, ready for resheduling + req.timeout.Stop() + for i, hash := range req.hashes { + req.task.trieTasks[hash] = [][]byte(req.paths[i]) + } +} + +// revertBytecodeHealRequest cleans up an bytecode request and returns all failed +// retrieval tasks to the scheduler for reassignment. +func (s *Syncer) revertBytecodeHealRequest(req *bytecodeHealRequest) { + log.Trace("Reverting bytecode heal request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Bytecode heal request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.bytecodeHealReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the code + // retrievals as not-pending, ready for resheduling + req.timeout.Stop() + for _, hash := range req.hashes { + req.task.codeTasks[hash] = struct{}{} + } +} + +// processAccountResponse integrates an already validated account range response +// into the account tasks. +func (s *Syncer) processAccountResponse(res *accountResponse) { + // Switch the task from pending to filling + res.task.req = nil + res.task.res = res + + // Ensure that the response doesn't overflow into the subsequent task + last := res.task.Last.Big() + for i, hash := range res.hashes { + if hash.Big().Cmp(last) > 0 { + // Chunk overflown, cut off excess, but also update the boundary nodes + for j := i; j < len(res.hashes); j++ { + if err := res.trie.Prove(res.hashes[j][:], 0, res.overflow); err != nil { + panic(err) // Account range was already proven, what happened + } + } + res.hashes = res.hashes[:i] + res.accounts = res.accounts[:i] + res.cont = false // Mark range completed + break + } + } + // Itereate over all the accounts and assemble which ones need further sub- + // filling before the entire account range can be persisted. + res.task.needCode = make([]bool, len(res.accounts)) + res.task.needState = make([]bool, len(res.accounts)) + res.task.needHeal = make([]bool, len(res.accounts)) + + res.task.codeTasks = make(map[common.Hash]struct{}) + res.task.stateTasks = make(map[common.Hash]common.Hash) + + resumed := make(map[common.Hash]struct{}) + + res.task.pend = 0 + for i, account := range res.accounts { + // Check if the account is a contract with an unknown code + if !bytes.Equal(account.CodeHash, emptyCode[:]) { + if code := rawdb.ReadCodeWithPrefix(s.db, common.BytesToHash(account.CodeHash)); code == nil { + res.task.codeTasks[common.BytesToHash(account.CodeHash)] = struct{}{} + res.task.needCode[i] = true + res.task.pend++ + } + } + // Check if the account is a contract with an unknown storage trie + if account.Root != emptyRoot { + if node, err := s.db.Get(account.Root[:]); err != nil || node == nil { + // If there was a previous large state retrieval in progress, + // don't restart it from scratch. This happens if a sync cycle + // is interrupted and resumed later. However, *do* update the + // previous root hash. + if subtasks, ok := res.task.SubTasks[res.hashes[i]]; ok { + log.Error("Resuming large storage retrieval", "account", res.hashes[i], "root", account.Root) + for _, subtask := range subtasks { + subtask.root = account.Root + } + res.task.needHeal[i] = true + resumed[res.hashes[i]] = struct{}{} + } else { + res.task.stateTasks[res.hashes[i]] = account.Root + } + res.task.needState[i] = true + res.task.pend++ + } + } + } + // Delete any subtasks that have been aborted but not resumed. This may undo + // some progress if a newpeer gives us less accounts than an old one, but for + // now we have to live with that. + for hash := range res.task.SubTasks { + if _, ok := resumed[hash]; !ok { + log.Error("Aborting suspended storage retrieval", "account", hash) + delete(res.task.SubTasks, hash) + } + } + // If the account range contained no contracts, or all have been fully filled + // beforehand, short circuit storage filling and forward to the next task + if res.task.pend == 0 { + s.forwardAccountTask(res.task) + return + } + // Some accounts are incomplete, leave as is for the storage and contract + // task assigners to pick up and fill. +} + +// processBytecodeResponse integrates an already validated bytecode response +// into the account tasks. +func (s *Syncer) processBytecodeResponse(res *bytecodeResponse) { + batch := s.db.NewBatch() + + var ( + codes uint64 + bytes common.StorageSize + ) + for i, hash := range res.hashes { + code := res.codes[i] + + // If the bytecode was not delivered, reschedule it + if code == nil { + res.task.codeTasks[hash] = struct{}{} + continue + } + // Code was delivered, mark it not needed any more + for j, account := range res.task.res.accounts { + if res.task.needCode[j] && hash == common.BytesToHash(account.CodeHash) { + res.task.needCode[j] = false + res.task.pend-- + } + } + // Push the bytecode into a database batch + s.bytecodeSynced++ + s.bytecodeBytes += common.StorageSize(len(code)) + + codes++ + bytes += common.StorageSize(len(code)) + + rawdb.WriteCode(batch, hash, code) + s.bloom.Add(hash[:]) + } + if err := batch.Write(); err != nil { + log.Crit("Failed to persist bytecodes", "err", err) + } + log.Debug("Persisted set of bytecodes", "count", codes, "bytes", bytes) + + // If this delivery completed the last pending task, forward the account task + // to the next chunk + if res.task.pend == 0 { + s.forwardAccountTask(res.task) + return + } + // Some accounts are still incomplete, leave as is for the storage and contract + // task assigners to pick up and fill. +} + +// processStorageResponse integrates an already validated storage response +// into the account tasks. +func (s *Syncer) processStorageResponse(res *storageResponse) { + // Switch the suntask from pending to idle + if res.subTask != nil { + res.subTask.req = nil + } + batch := s.db.NewBatch() + + var ( + slots int + nodes int + skipped int + bytes common.StorageSize + ) + // Iterate over all the accounts and reconstruct their storage tries from the + // delivered slots + delivered := make(map[common.Hash]bool) + for i := 0; i < len(res.hashes); i++ { + delivered[res.roots[i]] = true + } + for i, account := range res.accounts { + // If the account was not delivered, reschedule it + if i >= len(res.hashes) { + if !delivered[res.roots[i]] { + res.mainTask.stateTasks[account] = res.roots[i] + } + continue + } + // State was delivered, if complete mark as not needed any more, otherwise + // mark the account as needing healing + for j, acc := range res.mainTask.res.accounts { + if res.roots[i] == acc.Root { + // If the packet contains multiple contract storage slots, all + // but the last are surely complete. The last contract may be + // chunked, so check it's continuation flag. + if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) { + res.mainTask.needState[j] = false + res.mainTask.pend-- + } + // If the last contract was chunked, mark it as needing healing + // to avoid writing it out to disk prematurely. + if res.subTask == nil && !res.mainTask.needHeal[j] && i == len(res.hashes)-1 && res.cont { + res.mainTask.needHeal[j] = true + } + // If the last contract was chunked, we need to switch to large + // contract handling mode + if res.subTask == nil && i == len(res.hashes)-1 && res.cont { + // If we haven't yet started a large-contract retrieval, create + // the subtasks for it within the main account task + if tasks, ok := res.mainTask.SubTasks[account]; !ok { + var ( + next common.Hash + ) + step := new(big.Int).Sub( + new(big.Int).Div( + new(big.Int).Exp(common.Big2, common.Big256, nil), + big.NewInt(storageConcurrency), + ), common.Big1, + ) + for k := 0; k < storageConcurrency; k++ { + last := common.BigToHash(new(big.Int).Add(next.Big(), step)) + if k == storageConcurrency-1 { + // Make sure we don't overflow if the step is not a proper divisor + last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + } + tasks = append(tasks, &storageTask{ + Next: next, + Last: last, + root: acc.Root, + }) + log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", next, "last", last) + next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + } + res.mainTask.SubTasks[account] = tasks + + // Since we've just created the sub-tasks, this response + // is surely for the first one (zero origin) + res.subTask = tasks[0] + } + } + // If we're in large contract delivery mode, forward the subtask + if res.subTask != nil { + // Ensure the response doesn't overflow into the subsequent task + last := res.subTask.Last.Big() + for k, hash := range res.hashes[i] { + if hash.Big().Cmp(last) > 0 { + // Chunk overflown, cut off excess, but also update the boundary + for l := k; l < len(res.hashes[i]); l++ { + if err := res.tries[i].Prove(res.hashes[i][l][:], 0, res.overflow); err != nil { + panic(err) // Account range was already proven, what happened + } + } + res.hashes[i] = res.hashes[i][:k] + res.slots[i] = res.slots[i][:k] + res.cont = false // Mark range completed + break + } + } + // Forward the relevant storage chunk (even if created just now) + if res.cont { + res.subTask.Next = common.BigToHash(new(big.Int).Add(res.hashes[i][len(res.hashes[i])-1].Big(), big.NewInt(1))) + } else { + res.subTask.done = true + } + } + } + } + // Iterate over all the reconstructed trie nodes and push them to disk + slots += len(res.hashes[i]) + + it := res.nodes[i].NewIterator(nil, nil) + for it.Next() { + // Boundary nodes are not written for the last result, since they are incomplete + if i == len(res.hashes)-1 { + if _, ok := res.bounds[common.BytesToHash(it.Key())]; ok { + skipped++ + continue + } + } + // Node is not a boundary, persist to disk + batch.Put(it.Key(), it.Value()) + s.bloom.Add(it.Key()) + + bytes += common.StorageSize(common.HashLength + len(it.Value())) + nodes++ + } + it.Release() + } + if err := batch.Write(); err != nil { + log.Crit("Failed to persist storage slots", "err", err) + } + s.storageSynced += uint64(slots) + s.storageBytes += bytes + + log.Debug("Persisted set of storage slots", "accounts", len(res.hashes), "slots", slots, "nodes", nodes, "skipped", skipped, "bytes", bytes) + + // If this delivery completed the last pending task, forward the account task + // to the next chunk + if res.mainTask.pend == 0 { + s.forwardAccountTask(res.mainTask) + return + } + // Some accounts are still incomplete, leave as is for the storage and contract + // task assigners to pick up and fill. +} + +// processTrienodeHealResponse integrates an already validated trienode response +// into the healer tasks. +func (s *Syncer) processTrienodeHealResponse(res *trienodeHealResponse) { + for i, hash := range res.hashes { + node := res.nodes[i] + + // If the trie node was not delivered, reschedule it + if node == nil { + res.task.trieTasks[hash] = res.paths[i] + continue + } + // Push the trie node into the state syncer + s.trienodeHealSynced++ + s.trienodeHealBytes += common.StorageSize(len(node)) + + err := s.healer.scheduler.Process(trie.SyncResult{Hash: hash, Data: node}) + switch err { + case nil: + case trie.ErrAlreadyProcessed: + s.trienodeHealDups++ + case trie.ErrNotRequested: + s.trienodeHealNops++ + default: + log.Error("Invalid trienode processed", "hash", hash, "err", err) + } + } + batch := s.db.NewBatch() + if err := s.healer.scheduler.Commit(batch); err != nil { + log.Error("Failed to commit healing data", "err", err) + } + if err := batch.Write(); err != nil { + log.Crit("Failed to persist healing data", "err", err) + } + log.Debug("Persisted set of healing data", "bytes", common.StorageSize(batch.ValueSize())) +} + +// processBytecodeHealResponse integrates an already validated bytecode response +// into the healer tasks. +func (s *Syncer) processBytecodeHealResponse(res *bytecodeHealResponse) { + for i, hash := range res.hashes { + node := res.codes[i] + + // If the trie node was not delivered, reschedule it + if node == nil { + res.task.codeTasks[hash] = struct{}{} + continue + } + // Push the trie node into the state syncer + s.bytecodeHealSynced++ + s.bytecodeHealBytes += common.StorageSize(len(node)) + + err := s.healer.scheduler.Process(trie.SyncResult{Hash: hash, Data: node}) + switch err { + case nil: + case trie.ErrAlreadyProcessed: + s.bytecodeHealDups++ + case trie.ErrNotRequested: + s.bytecodeHealNops++ + default: + log.Error("Invalid bytecode processed", "hash", hash, "err", err) + } + } + batch := s.db.NewBatch() + if err := s.healer.scheduler.Commit(batch); err != nil { + log.Error("Failed to commit healing data", "err", err) + } + if err := batch.Write(); err != nil { + log.Crit("Failed to persist healing data", "err", err) + } + log.Debug("Persisted set of healing data", "bytes", common.StorageSize(batch.ValueSize())) +} + +// forwardAccountTask takes a filled account task and persists anything available +// into the database, after which it forwards the next account marker so that the +// task's next chunk may be filled. +func (s *Syncer) forwardAccountTask(task *accountTask) { + // Remove any pending delivery + res := task.res + if res == nil { + return // nothing to forward + } + task.res = nil + + // Iterate over all the accounts and gather all the incomplete trie nodes. A + // node is incomplete if we haven't yet filled it (sync was interrupted), or + // if we filled it in multiple chunks (storage trie), in which case the few + // nodes on the chunk boundaries are missing. + incompletes := light.NewNodeSet() + for i := range res.accounts { + // If the filling was interrupted, mark everything after as incomplete + if task.needCode[i] || task.needState[i] { + for j := i; j < len(res.accounts); j++ { + if err := res.trie.Prove(res.hashes[j][:], 0, incompletes); err != nil { + panic(err) // Account range was already proven, what happened + } + } + break + } + // Filling not interrupted until this point, mark incomplete if needs healing + if task.needHeal[i] { + if err := res.trie.Prove(res.hashes[i][:], 0, incompletes); err != nil { + panic(err) // Account range was already proven, what happened + } + } + } + // Persist every finalized trie node that's not on the boundary + batch := s.db.NewBatch() + + var ( + nodes int + skipped int + bytes common.StorageSize + ) + it := res.nodes.NewIterator(nil, nil) + for it.Next() { + // Boundary nodes are not written, since they are incomplete + if _, ok := res.bounds[common.BytesToHash(it.Key())]; ok { + skipped++ + continue + } + // Overflow nodes are not written, since they mess with another task + if _, err := res.overflow.Get(it.Key()); err == nil { + skipped++ + continue + } + // Accounts with split storage requests are incomplete + if _, err := incompletes.Get(it.Key()); err == nil { + skipped++ + continue + } + // Node is neither a boundary, not an incomplete account, persist to disk + batch.Put(it.Key(), it.Value()) + s.bloom.Add(it.Key()) + + bytes += common.StorageSize(common.HashLength + len(it.Value())) + nodes++ + } + it.Release() + + if err := batch.Write(); err != nil { + log.Crit("Failed to persist accounts", "err", err) + } + s.accountBytes += bytes + s.accountSynced += uint64(len(res.accounts)) + + log.Debug("Persisted range of accounts", "accounts", len(res.accounts), "nodes", nodes, "skipped", skipped, "bytes", bytes) + + // Task filling persisted, push it the chunk marker forward to the first + // account still missing data. + for i, hash := range res.hashes { + if task.needCode[i] || task.needState[i] { + return + } + task.Next = common.BigToHash(new(big.Int).Add(hash.Big(), big.NewInt(1))) + } + // All accounts marked as complete, track if the entire task is done + task.done = !res.cont +} + +// OnAccounts is a callback method to invoke when a range of accounts are +// received from a remote peer. +func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, accounts [][]byte, proof [][]byte) error { + size := common.StorageSize(len(hashes) * common.HashLength) + for _, account := range accounts { + size += common.StorageSize(len(account)) + } + for _, node := range proof { + size += common.StorageSize(len(node)) + } + logger := peer.logger.New("reqid", id) + logger.Trace("Delivering range of accounts", "hashes", len(hashes), "accounts", len(accounts), "proofs", len(proof), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + s.accountIdlers[peer.id] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + // Ensure the response is for a valid request + req, ok := s.accountReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected account range packet") + s.lock.Unlock() + return nil + } + delete(s.accountReqs, id) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + req.timeout.Stop() + + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For account range queries that means the state being + // retrieved was either already pruned remotely, or the peer is not yet + // synced to our head. + if len(hashes) == 0 && len(accounts) == 0 && len(proof) == 0 { + logger.Debug("Peer rejected account range request", "root", s.root) + s.statelessPeers[peer.id] = struct{}{} + s.lock.Unlock() + return nil + } + root := s.root + s.lock.Unlock() + + // Reconstruct a partial trie from the response and verify it + keys := make([][]byte, len(hashes)) + for i, key := range hashes { + keys[i] = common.CopyBytes(key[:]) + } + nodes := make(light.NodeList, len(proof)) + for i, node := range proof { + nodes[i] = node + } + proofdb := nodes.NodeSet() + + var end []byte + if len(keys) > 0 { + end = keys[len(keys)-1] + } + db, tr, notary, cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb) + if err != nil { + logger.Warn("Account range failed proof", "err", err) + return err + } + // Partial trie reconstructed, send it to the scheduler for storage filling + bounds := make(map[common.Hash]struct{}) + + it := notary.Accessed().NewIterator(nil, nil) + for it.Next() { + bounds[common.BytesToHash(it.Key())] = struct{}{} + } + it.Release() + + accs := make([]*state.Account, len(accounts)) + for i, account := range accounts { + acc := new(state.Account) + if err := rlp.DecodeBytes(account, acc); err != nil { + panic(err) // We created these blobs, we must be able to decode them + } + accs[i] = acc + } + response := &accountResponse{ + task: req.task, + hashes: hashes, + accounts: accs, + nodes: db, + trie: tr, + bounds: bounds, + overflow: light.NewNodeSet(), + cont: cont, + } + select { + case s.accountResps <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// OnByteCodes is a callback method to invoke when a batch of contract +// bytes codes are received from a remote peer. +func (s *Syncer) OnByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { + s.lock.RLock() + syncing := len(s.tasks) > 0 + s.lock.RUnlock() + + if syncing { + return s.onByteCodes(peer, id, bytecodes) + } + return s.onHealByteCodes(peer, id, bytecodes) +} + +// onByteCodes is a callback method to invoke when a batch of contract +// bytes codes are received from a remote peer in the syncing phase. +func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { + var size common.StorageSize + for _, code := range bytecodes { + size += common.StorageSize(len(code)) + } + logger := peer.logger.New("reqid", id) + logger.Trace("Delivering set of bytecodes", "bytecodes", len(bytecodes), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + s.bytecodeIdlers[peer.id] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + // Ensure the response is for a valid request + req, ok := s.bytecodeReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected bytecode packet") + s.lock.Unlock() + return nil + } + delete(s.bytecodeReqs, id) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + req.timeout.Stop() + + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For bytecode range queries that means the peer is not + // yet synced. + if len(bytecodes) == 0 { + logger.Debug("Peer rejected bytecode request") + s.statelessPeers[peer.id] = struct{}{} + s.lock.Unlock() + return nil + } + s.lock.Unlock() + + // Cross reference the requested bytecodes with the response to find gaps + // that the serving node is missing + hasher := sha3.NewLegacyKeccak256() + + codes := make([][]byte, len(req.hashes)) + for i, j := 0, 0; i < len(bytecodes); i++ { + // Find the next hash that we've been served, leaving misses with nils + hasher.Reset() + hasher.Write(bytecodes[i]) + hash := hasher.Sum(nil) + + for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { + j++ + } + if j < len(req.hashes) { + codes[j] = bytecodes[i] + j++ + continue + } + // We've either ran out of hashes, or got unrequested data + logger.Warn("Unexpected bytecodes", "count", len(bytecodes)-i) + return errors.New("unexpected bytecode") + } + // Response validated, send it to the scheduler for filling + response := &bytecodeResponse{ + task: req.task, + hashes: req.hashes, + codes: codes, + } + select { + case s.bytecodeResps <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// OnStorage is a callback method to invoke when ranges of storage slots +// are received from a remote peer. +func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots [][][]byte, proof [][]byte) error { + // Gather some trace stats to aid in debugging issues + var ( + hashCount int + slotCount int + size common.StorageSize + ) + for _, hashset := range hashes { + size += common.StorageSize(common.HashLength * len(hashset)) + hashCount += len(hashset) + } + for _, slotset := range slots { + for _, slot := range slotset { + size += common.StorageSize(len(slot)) + } + slotCount += len(slotset) + } + for _, node := range proof { + size += common.StorageSize(len(node)) + } + logger := peer.logger.New("reqid", id) + logger.Trace("Delivering ranges of storage slots", "accounts", len(hashes), "hashes", hashCount, "slots", slotCount, "proofs", len(proof), "size", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + s.storageIdlers[peer.id] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + // Ensure the response is for a valid request + req, ok := s.storageReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected storage ranges packet") + s.lock.Unlock() + return nil + } + delete(s.storageReqs, id) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + req.timeout.Stop() + + // Reject the response if the hash sets and slot sets don't match, or if the + // peer sent more data than requested. + if len(hashes) != len(slots) { + s.lock.Unlock() + logger.Warn("Hash and slot set size mismatch", "hashset", len(hashes), "slotset", len(slots)) + return errors.New("hash and slot set size mismatch") + } + if len(hashes) > len(req.accounts) { + s.lock.Unlock() + logger.Warn("Hash set larger than requested", "hashset", len(hashes), "requested", len(req.accounts)) + return errors.New("hash set larger than requested") + } + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For storage range queries that means the state being + // retrieved was either already pruned remotely, or the peer is not yet + // synced to our head. + if len(hashes) == 0 { + logger.Debug("Peer rejected storage request") + s.statelessPeers[peer.id] = struct{}{} + s.lock.Unlock() + return nil + } + s.lock.Unlock() + + // Reconstruct the partial tries from the response and verify them + var ( + dbs = make([]ethdb.KeyValueStore, len(hashes)) + tries = make([]*trie.Trie, len(hashes)) + notary *trie.KeyValueNotary + cont bool + ) + for i := 0; i < len(hashes); i++ { + // Convert the keys and proofs into an internal format + keys := make([][]byte, len(hashes[i])) + for j, key := range hashes[i] { + keys[j] = common.CopyBytes(key[:]) + } + nodes := make(light.NodeList, 0, len(proof)) + if i == len(hashes)-1 { + for _, node := range proof { + nodes = append(nodes, node) + } + } + var err error + if len(nodes) == 0 { + // No proof has been attached, the response must cover the entire key + // space and hash to the origin root. + dbs[i], tries[i], _, _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) + if err != nil { + logger.Warn("Storage slots failed proof", "err", err) + return err + } + } else { + // A proof was attached, the response is only partial, check that the + // returned data is indeed part of the storage trie + proofdb := nodes.NodeSet() + + var end []byte + if len(keys) > 0 { + end = keys[len(keys)-1] + } + dbs[i], tries[i], notary, cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb) + if err != nil { + logger.Warn("Storage range failed proof", "err", err) + return err + } + } + } + // Partial tries reconstructed, send them to the scheduler for storage filling + bounds := make(map[common.Hash]struct{}) + + if notary != nil { // if all contract storages are delivered in full, no notary will be created + it := notary.Accessed().NewIterator(nil, nil) + for it.Next() { + bounds[common.BytesToHash(it.Key())] = struct{}{} + } + it.Release() + } + response := &storageResponse{ + mainTask: req.mainTask, + subTask: req.subTask, + accounts: req.accounts, + roots: req.roots, + hashes: hashes, + slots: slots, + nodes: dbs, + tries: tries, + bounds: bounds, + overflow: light.NewNodeSet(), + cont: cont, + } + select { + case s.storageResps <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// OnTrieNodes is a callback method to invoke when a batch of trie nodes +// are received from a remote peer. +func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { + var size common.StorageSize + for _, node := range trienodes { + size += common.StorageSize(len(node)) + } + logger := peer.logger.New("reqid", id) + logger.Trace("Delivering set of healing trienodes", "trienodes", len(trienodes), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + s.trienodeHealIdlers[peer.id] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + // Ensure the response is for a valid request + req, ok := s.trienodeHealReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected trienode heal packet") + s.lock.Unlock() + return nil + } + delete(s.trienodeHealReqs, id) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + req.timeout.Stop() + + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For bytecode range queries that means the peer is not + // yet synced. + if len(trienodes) == 0 { + logger.Debug("Peer rejected trienode heal request") + s.statelessPeers[peer.id] = struct{}{} + s.lock.Unlock() + return nil + } + s.lock.Unlock() + + // Cross reference the requested trienodes with the response to find gaps + // that the serving node is missing + hasher := sha3.NewLegacyKeccak256() + + nodes := make([][]byte, len(req.hashes)) + for i, j := 0, 0; i < len(trienodes); i++ { + // Find the next hash that we've been served, leaving misses with nils + hasher.Reset() + hasher.Write(trienodes[i]) + hash := hasher.Sum(nil) + + for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { + j++ + } + if j < len(req.hashes) { + nodes[j] = trienodes[i] + j++ + continue + } + // We've either ran out of hashes, or got unrequested data + logger.Warn("Unexpected healing trienodes", "count", len(trienodes)-i) + return errors.New("unexpected healing trienode") + } + // Response validated, send it to the scheduler for filling + response := &trienodeHealResponse{ + task: req.task, + hashes: req.hashes, + paths: req.paths, + nodes: nodes, + } + select { + case s.trienodeHealResps <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// onHealByteCodes is a callback method to invoke when a batch of contract +// bytes codes are received from a remote peer in the healing phase. +func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { + var size common.StorageSize + for _, code := range bytecodes { + size += common.StorageSize(len(code)) + } + logger := peer.logger.New("reqid", id) + logger.Trace("Delivering set of healing bytecodes", "bytecodes", len(bytecodes), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + s.bytecodeHealIdlers[peer.id] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + // Ensure the response is for a valid request + req, ok := s.bytecodeHealReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected bytecode heal packet") + s.lock.Unlock() + return nil + } + delete(s.bytecodeHealReqs, id) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + req.timeout.Stop() + + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For bytecode range queries that means the peer is not + // yet synced. + if len(bytecodes) == 0 { + logger.Debug("Peer rejected bytecode heal request") + s.statelessPeers[peer.id] = struct{}{} + s.lock.Unlock() + return nil + } + s.lock.Unlock() + + // Cross reference the requested bytecodes with the response to find gaps + // that the serving node is missing + hasher := sha3.NewLegacyKeccak256() + + codes := make([][]byte, len(req.hashes)) + for i, j := 0, 0; i < len(bytecodes); i++ { + // Find the next hash that we've been served, leaving misses with nils + hasher.Reset() + hasher.Write(bytecodes[i]) + hash := hasher.Sum(nil) + + for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { + j++ + } + if j < len(req.hashes) { + codes[j] = bytecodes[i] + j++ + continue + } + // We've either ran out of hashes, or got unrequested data + logger.Warn("Unexpected healing bytecodes", "count", len(bytecodes)-i) + return errors.New("unexpected healing bytecode") + } + // Response validated, send it to the scheduler for filling + response := &bytecodeHealResponse{ + task: req.task, + hashes: req.hashes, + codes: codes, + } + select { + case s.bytecodeHealResps <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// hashSpace is the total size of the 256 bit hash space for accounts. +var hashSpace = new(big.Int).Exp(common.Big2, common.Big256, nil) + +// report calculates various status reports and provides it to the user. +func (s *Syncer) report(force bool) { + if len(s.tasks) > 0 { + s.reportSyncProgress(force) + return + } + s.reportHealProgress(force) +} + +// reportSyncProgress calculates various status reports and provides it to the user. +func (s *Syncer) reportSyncProgress(force bool) { + // Don't report all the events, just occasionally + if !force && time.Since(s.logTime) < 3*time.Second { + return + } + // Don't report anything until we have a meaningful progress + synced := s.accountBytes + s.bytecodeBytes + s.storageBytes + if synced == 0 { + return + } + accountGaps := new(big.Int) + for _, task := range s.tasks { + accountGaps.Add(accountGaps, new(big.Int).Sub(task.Last.Big(), task.Next.Big())) + } + accountFills := new(big.Int).Sub(hashSpace, accountGaps) + if accountFills.BitLen() == 0 { + return + } + s.logTime = time.Now() + estBytes := float64(new(big.Int).Div( + new(big.Int).Mul(new(big.Int).SetUint64(uint64(synced)), hashSpace), + accountFills, + ).Uint64()) + + elapsed := time.Since(s.startTime) + estTime := elapsed / time.Duration(synced) * time.Duration(estBytes) + + // Create a mega progress report + var ( + progress = fmt.Sprintf("%.2f%%", float64(synced)*100/estBytes) + accounts = fmt.Sprintf("%d@%v", s.accountSynced, s.accountBytes.TerminalString()) + storage = fmt.Sprintf("%d@%v", s.storageSynced, s.storageBytes.TerminalString()) + bytecode = fmt.Sprintf("%d@%v", s.bytecodeSynced, s.bytecodeBytes.TerminalString()) + ) + log.Info("State sync in progress", "synced", progress, "state", synced, + "accounts", accounts, "slots", storage, "codes", bytecode, "eta", common.PrettyDuration(estTime-elapsed)) +} + +// reportHealProgress calculates various status reports and provides it to the user. +func (s *Syncer) reportHealProgress(force bool) { + // Don't report all the events, just occasionally + if !force && time.Since(s.logTime) < 3*time.Second { + return + } + s.logTime = time.Now() + + // Create a mega progress report + var ( + trienode = fmt.Sprintf("%d@%v", s.trienodeHealSynced, s.trienodeHealBytes.TerminalString()) + bytecode = fmt.Sprintf("%d@%v", s.bytecodeHealSynced, s.bytecodeHealBytes.TerminalString()) + ) + log.Info("State heal in progress", "nodes", trienode, "codes", bytecode, + "pending", s.healer.scheduler.Pending()) +} diff --git a/eth/sync.go b/eth/sync.go index 26badd1e21..03a5165245 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" ) @@ -40,12 +41,12 @@ const ( ) type txsync struct { - p *peer + p *eth.Peer txs []*types.Transaction } // syncTransactions starts sending all currently pending transactions to the given peer. -func (pm *ProtocolManager) syncTransactions(p *peer) { +func (h *handler) syncTransactions(p *eth.Peer) { // Assemble the set of transaction to broadcast or announce to the remote // peer. Fun fact, this is quite an expensive operation as it needs to sort // the transactions if the sorting is not cached yet. However, with a random @@ -53,7 +54,7 @@ func (pm *ProtocolManager) syncTransactions(p *peer) { // // TODO(karalabe): Figure out if we could get away with random order somehow var txs types.Transactions - pending, _ := pm.txpool.Pending() + pending, _ := h.txpool.Pending() for _, batch := range pending { txs = append(txs, batch...) } @@ -63,7 +64,7 @@ func (pm *ProtocolManager) syncTransactions(p *peer) { // The eth/65 protocol introduces proper transaction announcements, so instead // of dripping transactions across multiple peers, just send the entire list as // an announcement and let the remote side decide what they need (likely nothing). - if p.version >= eth65 { + if p.Version() >= eth.ETH65 { hashes := make([]common.Hash, len(txs)) for i, tx := range txs { hashes[i] = tx.Hash() @@ -73,8 +74,8 @@ func (pm *ProtocolManager) syncTransactions(p *peer) { } // Out of luck, peer is running legacy protocols, drop the txs over select { - case pm.txsyncCh <- &txsync{p: p, txs: txs}: - case <-pm.quitSync: + case h.txsyncCh <- &txsync{p: p, txs: txs}: + case <-h.quitSync: } } @@ -82,8 +83,8 @@ func (pm *ProtocolManager) syncTransactions(p *peer) { // connection. When a new peer appears, we relay all currently pending // transactions. In order to minimise egress bandwidth usage, we send // the transactions in small packs to one peer at a time. -func (pm *ProtocolManager) txsyncLoop64() { - defer pm.wg.Done() +func (h *handler) txsyncLoop64() { + defer h.wg.Done() var ( pending = make(map[enode.ID]*txsync) @@ -94,7 +95,7 @@ func (pm *ProtocolManager) txsyncLoop64() { // send starts a sending a pack of transactions from the sync. send := func(s *txsync) { - if s.p.version >= eth65 { + if s.p.Version() >= eth.ETH65 { panic("initial transaction syncer running on eth/65+") } // Fill pack with transactions up to the target size. @@ -108,14 +109,13 @@ func (pm *ProtocolManager) txsyncLoop64() { // Remove the transactions that will be sent. s.txs = s.txs[:copy(s.txs, s.txs[len(pack.txs):])] if len(s.txs) == 0 { - delete(pending, s.p.ID()) + delete(pending, s.p.Peer.ID()) } // Send the pack in the background. s.p.Log().Trace("Sending batch of transactions", "count", len(pack.txs), "bytes", size) sending = true - go func() { done <- pack.p.SendTransactions64(pack.txs) }() + go func() { done <- pack.p.SendTransactions(pack.txs) }() } - // pick chooses the next pending sync. pick := func() *txsync { if len(pending) == 0 { @@ -132,8 +132,8 @@ func (pm *ProtocolManager) txsyncLoop64() { for { select { - case s := <-pm.txsyncCh: - pending[s.p.ID()] = s + case s := <-h.txsyncCh: + pending[s.p.Peer.ID()] = s if !sending { send(s) } @@ -142,13 +142,13 @@ func (pm *ProtocolManager) txsyncLoop64() { // Stop tracking peers that cause send failures. if err != nil { pack.p.Log().Debug("Transaction send failed", "err", err) - delete(pending, pack.p.ID()) + delete(pending, pack.p.Peer.ID()) } // Schedule the next send. if s := pick(); s != nil { send(s) } - case <-pm.quitSync: + case <-h.quitSync: return } } @@ -156,7 +156,7 @@ func (pm *ProtocolManager) txsyncLoop64() { // chainSyncer coordinates blockchain sync components. type chainSyncer struct { - pm *ProtocolManager + handler *handler force *time.Timer forced bool // true when force timer fired peerEventCh chan struct{} @@ -166,15 +166,15 @@ type chainSyncer struct { // chainSyncOp is a scheduled sync operation. type chainSyncOp struct { mode downloader.SyncMode - peer *peer + peer *eth.Peer td *big.Int head common.Hash } // newChainSyncer creates a chainSyncer. -func newChainSyncer(pm *ProtocolManager) *chainSyncer { +func newChainSyncer(handler *handler) *chainSyncer { return &chainSyncer{ - pm: pm, + handler: handler, peerEventCh: make(chan struct{}), } } @@ -182,23 +182,24 @@ func newChainSyncer(pm *ProtocolManager) *chainSyncer { // handlePeerEvent notifies the syncer about a change in the peer set. // This is called for new peers and every time a peer announces a new // chain head. -func (cs *chainSyncer) handlePeerEvent(p *peer) bool { +func (cs *chainSyncer) handlePeerEvent(peer *eth.Peer) bool { select { case cs.peerEventCh <- struct{}{}: return true - case <-cs.pm.quitSync: + case <-cs.handler.quitSync: return false } } // loop runs in its own goroutine and launches the sync when necessary. func (cs *chainSyncer) loop() { - defer cs.pm.wg.Done() + defer cs.handler.wg.Done() - cs.pm.blockFetcher.Start() - cs.pm.txFetcher.Start() - defer cs.pm.blockFetcher.Stop() - defer cs.pm.txFetcher.Stop() + cs.handler.blockFetcher.Start() + cs.handler.txFetcher.Start() + defer cs.handler.blockFetcher.Stop() + defer cs.handler.txFetcher.Stop() + defer cs.handler.downloader.Terminate() // The force timer lowers the peer count threshold down to one when it fires. // This ensures we'll always start sync even if there aren't enough peers. @@ -209,7 +210,6 @@ func (cs *chainSyncer) loop() { if op := cs.nextSyncOp(); op != nil { cs.startSync(op) } - select { case <-cs.peerEventCh: // Peer information changed, recheck. @@ -220,14 +220,13 @@ func (cs *chainSyncer) loop() { case <-cs.force.C: cs.forced = true - case <-cs.pm.quitSync: + case <-cs.handler.quitSync: // Disable all insertion on the blockchain. This needs to happen before // terminating the downloader because the downloader waits for blockchain // inserts, and these can take a long time to finish. - cs.pm.blockchain.StopInsert() - cs.pm.downloader.Terminate() + cs.handler.chain.StopInsert() + cs.handler.downloader.Terminate() if cs.doneCh != nil { - // Wait for the current sync to end. <-cs.doneCh } return @@ -245,19 +244,22 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { minPeers := defaultMinSyncPeers if cs.forced { minPeers = 1 - } else if minPeers > cs.pm.maxPeers { - minPeers = cs.pm.maxPeers + } else if minPeers > cs.handler.maxPeers { + minPeers = cs.handler.maxPeers } - if cs.pm.peers.Len() < minPeers { + if cs.handler.peers.Len() < minPeers { return nil } - - // We have enough peers, check TD. - peer := cs.pm.peers.BestPeer() + // We have enough peers, check TD + peer := cs.handler.peers.ethPeerWithHighestTD() if peer == nil { return nil } mode, ourTD := cs.modeAndLocalHead() + if mode == downloader.FastSync && atomic.LoadUint32(&cs.handler.snapSync) == 1 { + // Fast sync via the snap protocol + mode = downloader.SnapSync + } op := peerToSyncOp(mode, peer) if op.td.Cmp(ourTD) <= 0 { return nil // We're in sync. @@ -265,42 +267,42 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { return op } -func peerToSyncOp(mode downloader.SyncMode, p *peer) *chainSyncOp { +func peerToSyncOp(mode downloader.SyncMode, p *eth.Peer) *chainSyncOp { peerHead, peerTD := p.Head() return &chainSyncOp{mode: mode, peer: p, td: peerTD, head: peerHead} } func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) { // If we're in fast sync mode, return that directly - if atomic.LoadUint32(&cs.pm.fastSync) == 1 { - block := cs.pm.blockchain.CurrentFastBlock() - td := cs.pm.blockchain.GetTdByHash(block.Hash()) + if atomic.LoadUint32(&cs.handler.fastSync) == 1 { + block := cs.handler.chain.CurrentFastBlock() + td := cs.handler.chain.GetTdByHash(block.Hash()) return downloader.FastSync, td } // We are probably in full sync, but we might have rewound to before the // fast sync pivot, check if we should reenable - if pivot := rawdb.ReadLastPivotNumber(cs.pm.chaindb); pivot != nil { - if head := cs.pm.blockchain.CurrentBlock(); head.NumberU64() < *pivot { - block := cs.pm.blockchain.CurrentFastBlock() - td := cs.pm.blockchain.GetTdByHash(block.Hash()) + if pivot := rawdb.ReadLastPivotNumber(cs.handler.database); pivot != nil { + if head := cs.handler.chain.CurrentBlock(); head.NumberU64() < *pivot { + block := cs.handler.chain.CurrentFastBlock() + td := cs.handler.chain.GetTdByHash(block.Hash()) return downloader.FastSync, td } } // Nope, we're really full syncing - head := cs.pm.blockchain.CurrentHeader() - td := cs.pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) + head := cs.handler.chain.CurrentHeader() + td := cs.handler.chain.GetTd(head.Hash(), head.Number.Uint64()) return downloader.FullSync, td } // startSync launches doSync in a new goroutine. func (cs *chainSyncer) startSync(op *chainSyncOp) { cs.doneCh = make(chan error, 1) - go func() { cs.doneCh <- cs.pm.doSync(op) }() + go func() { cs.doneCh <- cs.handler.doSync(op) }() } // doSync synchronizes the local blockchain with a remote peer. -func (pm *ProtocolManager) doSync(op *chainSyncOp) error { - if op.mode == downloader.FastSync { +func (h *handler) doSync(op *chainSyncOp) error { + if op.mode == downloader.FastSync || op.mode == downloader.SnapSync { // Before launch the fast sync, we have to ensure user uses the same // txlookup limit. // The main concern here is: during the fast sync Geth won't index the @@ -310,35 +312,33 @@ func (pm *ProtocolManager) doSync(op *chainSyncOp) error { // has been indexed. So here for the user-experience wise, it's non-optimal // that user can't change limit during the fast sync. If changed, Geth // will just blindly use the original one. - limit := pm.blockchain.TxLookupLimit() - if stored := rawdb.ReadFastTxLookupLimit(pm.chaindb); stored == nil { - rawdb.WriteFastTxLookupLimit(pm.chaindb, limit) + limit := h.chain.TxLookupLimit() + if stored := rawdb.ReadFastTxLookupLimit(h.database); stored == nil { + rawdb.WriteFastTxLookupLimit(h.database, limit) } else if *stored != limit { - pm.blockchain.SetTxLookupLimit(*stored) + h.chain.SetTxLookupLimit(*stored) log.Warn("Update txLookup limit", "provided", limit, "updated", *stored) } } // Run the sync cycle, and disable fast sync if we're past the pivot block - err := pm.downloader.Synchronise(op.peer.id, op.head, op.td, op.mode) + err := h.downloader.Synchronise(op.peer.ID(), op.head, op.td, op.mode) if err != nil { return err } - if atomic.LoadUint32(&pm.fastSync) == 1 { + if atomic.LoadUint32(&h.fastSync) == 1 { log.Info("Fast sync complete, auto disabling") - atomic.StoreUint32(&pm.fastSync, 0) + atomic.StoreUint32(&h.fastSync, 0) } - // If we've successfully finished a sync cycle and passed any required checkpoint, // enable accepting transactions from the network. - head := pm.blockchain.CurrentBlock() - if head.NumberU64() >= pm.checkpointNumber { + head := h.chain.CurrentBlock() + if head.NumberU64() >= h.checkpointNumber { // Checkpoint passed, sanity check the timestamp to have a fallback mechanism // for non-checkpointed (number = 0) private networks. if head.Time() >= uint64(time.Now().AddDate(0, -1, 0).Unix()) { - atomic.StoreUint32(&pm.acceptTxs, 1) + atomic.StoreUint32(&h.acceptTxs, 1) } } - if head.NumberU64() > 0 { // We've completed a sync cycle, notify all peers of new state. This path is // essential in star-topology networks where a gateway node needs to notify @@ -346,8 +346,7 @@ func (pm *ProtocolManager) doSync(op *chainSyncOp) error { // scenario will most often crop up in private and hackathon networks with // degenerate connectivity, but it should be healthy for the mainnet too to // more reliably update peers or the local TD state. - pm.BroadcastBlock(head, false) + h.BroadcastBlock(head, false) } - return nil } diff --git a/eth/sync_test.go b/eth/sync_test.go index ac1e5fad1b..473e19518f 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -22,43 +22,59 @@ import ( "time" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" ) -func TestFastSyncDisabling63(t *testing.T) { testFastSyncDisabling(t, 63) } +// Tests that fast sync is disabled after a successful sync cycle. func TestFastSyncDisabling64(t *testing.T) { testFastSyncDisabling(t, 64) } func TestFastSyncDisabling65(t *testing.T) { testFastSyncDisabling(t, 65) } // Tests that fast sync gets disabled as soon as a real block is successfully // imported into the blockchain. -func testFastSyncDisabling(t *testing.T, protocol int) { +func testFastSyncDisabling(t *testing.T, protocol uint) { t.Parallel() - // Create a pristine protocol manager, check that fast sync is left enabled - pmEmpty, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil) - if atomic.LoadUint32(&pmEmpty.fastSync) == 0 { + // Create an empty handler and ensure it's in fast sync mode + empty := newTestHandler() + if atomic.LoadUint32(&empty.handler.fastSync) == 0 { t.Fatalf("fast sync disabled on pristine blockchain") } - // Create a full protocol manager, check that fast sync gets disabled - pmFull, _ := newTestProtocolManagerMust(t, downloader.FastSync, 1024, nil, nil) - if atomic.LoadUint32(&pmFull.fastSync) == 1 { + defer empty.close() + + // Create a full handler and ensure fast sync ends up disabled + full := newTestHandlerWithBlocks(1024) + if atomic.LoadUint32(&full.handler.fastSync) == 1 { t.Fatalf("fast sync not disabled on non-empty blockchain") } + defer full.close() + + // Sync up the two handlers + emptyPipe, fullPipe := p2p.MsgPipe() + defer emptyPipe.Close() + defer fullPipe.Close() - // Sync up the two peers - io1, io2 := p2p.MsgPipe() - go pmFull.handle(pmFull.newPeer(protocol, p2p.NewPeer(enode.ID{}, "empty", nil), io2, pmFull.txpool.Get)) - go pmEmpty.handle(pmEmpty.newPeer(protocol, p2p.NewPeer(enode.ID{}, "full", nil), io1, pmEmpty.txpool.Get)) + emptyPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), emptyPipe, empty.txpool) + fullPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), fullPipe, full.txpool) + defer emptyPeer.Close() + defer fullPeer.Close() + go empty.handler.runEthPeer(emptyPeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(empty.handler), peer) + }) + go full.handler.runEthPeer(fullPeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(full.handler), peer) + }) + // Wait a bit for the above handlers to start time.Sleep(250 * time.Millisecond) - op := peerToSyncOp(downloader.FastSync, pmEmpty.peers.BestPeer()) - if err := pmEmpty.doSync(op); err != nil { - t.Fatal("sync failed:", err) - } // Check that fast sync was disabled - if atomic.LoadUint32(&pmEmpty.fastSync) == 1 { + op := peerToSyncOp(downloader.FastSync, empty.handler.peers.ethPeerWithHighestTD()) + if err := empty.handler.doSync(op); err != nil { + t.Fatal("sync failed:", err) + } + if atomic.LoadUint32(&empty.handler.fastSync) == 1 { t.Fatalf("fast sync not disabled after successful synchronisation") } } diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 1828ad70fb..e0f4f95ff3 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -36,8 +36,8 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + ethproto "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" @@ -444,13 +444,15 @@ func (s *Service) login(conn *connWrapper) error { // Construct and send the login authentication infos := s.server.NodeInfo() - var network, protocol string + var protocols []string + for _, proto := range s.server.Protocols { + protocols = append(protocols, fmt.Sprintf("%s/%d", proto.Name, proto.Version)) + } + var network string if info := infos.Protocols["eth"]; info != nil { - network = fmt.Sprintf("%d", info.(*eth.NodeInfo).Network) - protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0]) + network = fmt.Sprintf("%d", info.(*ethproto.NodeInfo).Network) } else { network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network) - protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0]) } auth := &authMsg{ ID: s.node, @@ -459,7 +461,7 @@ func (s *Service) login(conn *connWrapper) error { Node: infos.Name, Port: infos.Ports.Listener, Network: network, - Protocol: protocol, + Protocol: strings.Join(protocols, ", "), API: "No", Os: runtime.GOOS, OsVer: runtime.GOARCH, diff --git a/graphql/graphql.go b/graphql/graphql.go index 16a74b4037..22cfcf6637 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -1040,10 +1040,6 @@ func (r *Resolver) GasPrice(ctx context.Context) (hexutil.Big, error) { return hexutil.Big(*price), err } -func (r *Resolver) ProtocolVersion(ctx context.Context) (int32, error) { - return int32(r.backend.ProtocolVersion()), nil -} - func (r *Resolver) ChainID(ctx context.Context) (hexutil.Big, error) { return hexutil.Big(*r.backend.ChainConfig().ChainID), nil } diff --git a/graphql/schema.go b/graphql/schema.go index d7b253f227..1fdc370040 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -310,8 +310,6 @@ const schema string = ` # GasPrice returns the node's estimate of a gas price sufficient to # ensure a transaction is mined in a timely fashion. gasPrice: BigInt! - # ProtocolVersion returns the current wire protocol version number. - protocolVersion: Int! # Syncing returns information on the current synchronisation state. syncing: SyncState # ChainID returns the current chain ID for transaction replay protection. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 030bdb37a4..9ff1781e4a 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -64,11 +64,6 @@ func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) return (*hexutil.Big)(price), err } -// ProtocolVersion returns the current Ethereum protocol version this node supports -func (s *PublicEthereumAPI) ProtocolVersion() hexutil.Uint { - return hexutil.Uint(s.b.ProtocolVersion()) -} - // Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not // yet received the latest block headers from its pears. In case it is synchronizing: // - startingBlock: block number this node started to synchronise from @@ -1905,13 +1900,12 @@ func (api *PrivateDebugAPI) SetHead(number hexutil.Uint64) { // PublicNetAPI offers network related RPC methods type PublicNetAPI struct { - net *p2p.Server - networkVersion uint64 + net *p2p.Server } // NewPublicNetAPI creates a new net API instance. -func NewPublicNetAPI(net *p2p.Server, networkVersion uint64) *PublicNetAPI { - return &PublicNetAPI{net, networkVersion} +func NewPublicNetAPI(net *p2p.Server) *PublicNetAPI { + return &PublicNetAPI{net} } // Listening returns an indication if the node is listening for network connections. @@ -1924,11 +1918,6 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint { return hexutil.Uint(s.net.PeerCount()) } -// Version returns the current ethereum protocol version. -func (s *PublicNetAPI) Version() string { - return fmt.Sprintf("%d", s.networkVersion) -} - // checkTxFee is an internal function used to check whether the fee of // the given transaction is _reasonable_(under the cap). func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 10e716bf20..f0a4c0493c 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -41,7 +41,6 @@ import ( type Backend interface { // General Ethereum API Downloader() *downloader.Downloader - ProtocolVersion() int SuggestPrice(ctx context.Context) (*big.Int, error) ChainDb() ethdb.Database AccountManager() *accounts.Manager diff --git a/les/client.go b/les/client.go index 47997a098b..198255dc54 100644 --- a/les/client.go +++ b/les/client.go @@ -171,7 +171,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { leth.blockchain.DisableCheckFreq() } - leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer, leth.config.NetworkId) + leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer) // Register the backend on the node stack.RegisterAPIs(leth.APIs()) diff --git a/les/enr_entry.go b/les/enr_entry.go index 8f0169bee1..a357f689df 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -35,9 +35,9 @@ func (e lesEntry) ENRKey() string { // setupDiscovery creates the node discovery source for the eth protocol. func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) { - if len(eth.config.DiscoveryURLs) == 0 { + if len(eth.config.EthDiscoveryURLs) == 0 { return nil, nil } client := dnsdisc.NewClient(dnsdisc.Config{}) - return client.NewIterator(eth.config.DiscoveryURLs...) + return client.NewIterator(eth.config.EthDiscoveryURLs...) } diff --git a/les/handler_test.go b/les/handler_test.go index 1612caf427..04277f661b 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -51,7 +51,7 @@ func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) } func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) } func testGetBlockHeaders(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, downloader.MaxHashFetch+15, protocol, nil, false, true, 0) + server, tearDown := newServerEnv(t, downloader.MaxHeaderFetch+15, protocol, nil, false, true, 0) defer tearDown() bc := server.handler.blockchain diff --git a/les/peer.go b/les/peer.go index 31ee8f7f04..6004af03f5 100644 --- a/les/peer.go +++ b/les/peer.go @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les/flowcontrol" lpc "github.com/ethereum/go-ethereum/les/lespay/client" lps "github.com/ethereum/go-ethereum/les/lespay/server" @@ -162,9 +161,17 @@ func (p *peerCommons) String() string { return fmt.Sprintf("Peer %s [%s]", p.id, fmt.Sprintf("les/%d", p.version)) } +// PeerInfo represents a short summary of the `eth` sub-protocol metadata known +// about a connected peer. +type PeerInfo struct { + Version int `json:"version"` // Ethereum protocol version negotiated + Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain + Head string `json:"head"` // SHA3 hash of the peer's best owned block +} + // Info gathers and returns a collection of metadata known about a peer. -func (p *peerCommons) Info() *eth.PeerInfo { - return ð.PeerInfo{ +func (p *peerCommons) Info() *PeerInfo { + return &PeerInfo{ Version: p.version, Difficulty: p.Td(), Head: fmt.Sprintf("%x", p.Head()), diff --git a/les/server_handler.go b/les/server_handler.go index f965e3fc64..2316c9c5a4 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -47,7 +47,7 @@ import ( const ( softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data. estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header - ethVersion = 63 // equivalent eth version for the downloader + ethVersion = 64 // equivalent eth version for the downloader MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request MaxBodyFetch = 32 // Amount of block bodies to be fetched per retrieval request diff --git a/tests/block_test_util.go b/tests/block_test_util.go index be9cdb70cd..c043f0b3eb 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -147,7 +147,7 @@ func (t *BlockTest) Run(snapshotter bool) error { } // Cross-check the snapshot-to-hash against the trie hash if snapshotter { - if err := snapshot.VerifyState(chain.Snapshot(), chain.CurrentBlock().Root()); err != nil { + if err := snapshot.VerifyState(chain.Snapshots(), chain.CurrentBlock().Root()); err != nil { return err } } diff --git a/tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1 b/tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1 new file mode 100644 index 0000000000000000000000000000000000000000..31c08bafaff6f7268e7babe1eacae4239d397cf8 GIT binary patch literal 16005 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2 b/tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2 new file mode 100644 index 0000000000000000000000000000000000000000..7bce13ef80e5a67258a89e3a0e4da1c7610e20bb GIT binary patch literal 15965 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 LNurw1vw$0>u6V$y literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 b/tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 new file mode 100644 index 0000000000000000000000000000000000000000..613e76a020f5870f2e45687b8bdb97a1b570381a GIT binary patch literal 15976 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

    Pa>BrM`Ck<4mCkN=Y`#_E!Cw7L5Ojm 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 W%DL(vR^B^@dJtVnqMFaMfE%W*cEP3q literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1 b/tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1 new file mode 100644 index 0000000000000000000000000000000000000000..805ad8df77be7ed7802ef8666f0cb5e180b2caaf GIT binary patch literal 14980 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

    Pa>BrM`Ck<4mCkN=Y`#_E!Cw7L5Ojm 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 OdJtVnqMFaMfE%U+^5S^_ literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4 b/tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4 new file mode 100644 index 0000000000000000000000000000000000000000..605acf81c1a9814ac7c3950c4cc47081b4d29f4e GIT binary patch literal 15977 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 X_R6{HA6DKwhk6iQNurw1vw$0>5y--x literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 b/tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 new file mode 100644 index 0000000000000000000000000000000000000000..8f32dd775ada5351365dbe3035668944b112046e GIT binary patch literal 16000 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

    tqh-JF4rtl^ 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2 b/tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2 new file mode 100644 index 0000000000000000000000000000000000000000..af96210f204e19513ebde336e6db31430d771a7c GIT binary patch literal 1748 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

    tqh-JF4rtl^ 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/debug/main.go b/tests/fuzzers/rangeproof/debug/main.go new file mode 100644 index 0000000000..a81c69fea5 --- /dev/null +++ b/tests/fuzzers/rangeproof/debug/main.go @@ -0,0 +1,41 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/tests/fuzzers/rangeproof" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: debug \n") + fmt.Fprintf(os.Stderr, "Example\n") + fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") + os.Exit(1) + } + crasher := os.Args[1] + data, err := ioutil.ReadFile(crasher) + if err != nil { + fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) + os.Exit(1) + } + rangeproof.Fuzz(data) +} diff --git a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go new file mode 100644 index 0000000000..b82a380723 --- /dev/null +++ b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go @@ -0,0 +1,218 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rangeproof + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/trie" +) + +type kv struct { + k, v []byte + t bool +} + +type entrySlice []*kv + +func (p entrySlice) Len() int { return len(p) } +func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 } +func (p entrySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +type fuzzer struct { + input io.Reader + exhausted bool +} + +func (f *fuzzer) randBytes(n int) []byte { + r := make([]byte, n) + if _, err := f.input.Read(r); err != nil { + f.exhausted = true + } + return r +} + +func (f *fuzzer) readInt() uint64 { + var x uint64 + if err := binary.Read(f.input, binary.LittleEndian, &x); err != nil { + f.exhausted = true + } + return x +} + +func (f *fuzzer) randomTrie(n int) (*trie.Trie, map[string]*kv) { + + trie := new(trie.Trie) + vals := make(map[string]*kv) + size := f.readInt() + // Fill it with some fluff + for i := byte(0); i < byte(size); i++ { + value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} + value2 := &kv{common.LeftPadBytes([]byte{i + 10}, 32), []byte{i}, false} + trie.Update(value.k, value.v) + trie.Update(value2.k, value2.v) + vals[string(value.k)] = value + vals[string(value2.k)] = value2 + } + if f.exhausted { + return nil, nil + } + // And now fill with some random + for i := 0; i < n; i++ { + k := f.randBytes(32) + v := f.randBytes(20) + value := &kv{k, v, false} + trie.Update(k, v) + vals[string(k)] = value + if f.exhausted { + return nil, nil + } + } + return trie, vals +} + +func (f *fuzzer) fuzz() int { + maxSize := 200 + tr, vals := f.randomTrie(1 + int(f.readInt())%maxSize) + if f.exhausted { + return 0 // input too short + } + var entries entrySlice + for _, kv := range vals { + entries = append(entries, kv) + } + if len(entries) <= 1 { + return 0 + } + sort.Sort(entries) + + var ok = 0 + for { + start := int(f.readInt() % uint64(len(entries))) + end := 1 + int(f.readInt()%uint64(len(entries)-1)) + testcase := int(f.readInt() % uint64(6)) + index := int(f.readInt() & 0xFFFFFFFF) + index2 := int(f.readInt() & 0xFFFFFFFF) + if f.exhausted { + break + } + proof := memorydb.New() + if err := tr.Prove(entries[start].k, 0, proof); err != nil { + panic(fmt.Sprintf("Failed to prove the first node %v", err)) + } + if err := tr.Prove(entries[end-1].k, 0, proof); err != nil { + panic(fmt.Sprintf("Failed to prove the last node %v", err)) + } + var keys [][]byte + var vals [][]byte + for i := start; i < end; i++ { + keys = append(keys, entries[i].k) + vals = append(vals, entries[i].v) + } + if len(keys) == 0 { + return 0 + } + var first, last = keys[0], keys[len(keys)-1] + testcase %= 6 + switch testcase { + case 0: + // Modified key + keys[index%len(keys)] = f.randBytes(32) // In theory it can't be same + case 1: + // Modified val + vals[index%len(vals)] = f.randBytes(20) // In theory it can't be same + case 2: + // Gapped entry slice + index = index % len(keys) + keys = append(keys[:index], keys[index+1:]...) + vals = append(vals[:index], vals[index+1:]...) + case 3: + // Out of order + index1 := index % len(keys) + index2 := index2 % len(keys) + keys[index1], keys[index2] = keys[index2], keys[index1] + vals[index1], vals[index2] = vals[index2], vals[index1] + case 4: + // Set random key to nil, do nothing + keys[index%len(keys)] = nil + case 5: + // Set random value to nil, deletion + vals[index%len(vals)] = nil + + // Other cases: + // Modify something in the proof db + // add stuff to proof db + // drop stuff from proof db + + } + if f.exhausted { + break + } + ok = 1 + //nodes, subtrie + nodes, subtrie, notary, hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof) + if err != nil { + if nodes != nil { + panic("err != nil && nodes != nil") + } + if subtrie != nil { + panic("err != nil && subtrie != nil") + } + if notary != nil { + panic("err != nil && notary != nil") + } + if hasMore { + panic("err != nil && hasMore == true") + } + } else { + if nodes == nil { + panic("err == nil && nodes == nil") + } + if subtrie == nil { + panic("err == nil && subtrie == nil") + } + if notary == nil { + panic("err == nil && subtrie == nil") + } + } + } + return ok +} + +// The function must return +// 1 if the fuzzer should increase priority of the +// given input during subsequent fuzzing (for example, the input is lexically +// correct and was parsed successfully); +// -1 if the input must not be added to corpus even if gives new coverage; and +// 0 otherwise; other values are reserved for future use. +func Fuzz(input []byte) int { + if len(input) < 100 { + return 0 + } + r := bytes.NewReader(input) + f := fuzzer{ + input: r, + exhausted: false, + } + return f.fuzz() +} diff --git a/trie/notary.go b/trie/notary.go new file mode 100644 index 0000000000..5a64727aa7 --- /dev/null +++ b/trie/notary.go @@ -0,0 +1,57 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/memorydb" +) + +// KeyValueNotary tracks which keys have been accessed through a key-value reader +// with te scope of verifying if certain proof datasets are maliciously bloated. +type KeyValueNotary struct { + ethdb.KeyValueReader + reads map[string]struct{} +} + +// NewKeyValueNotary wraps a key-value database with an access notary to track +// which items have bene accessed. +func NewKeyValueNotary(db ethdb.KeyValueReader) *KeyValueNotary { + return &KeyValueNotary{ + KeyValueReader: db, + reads: make(map[string]struct{}), + } +} + +// Get retrieves an item from the underlying database, but also tracks it as an +// accessed slot for bloat checks. +func (k *KeyValueNotary) Get(key []byte) ([]byte, error) { + k.reads[string(key)] = struct{}{} + return k.KeyValueReader.Get(key) +} + +// Accessed returns s snapshot of the original key-value store containing only the +// data accessed through the notary. +func (k *KeyValueNotary) Accessed() ethdb.KeyValueStore { + db := memorydb.New() + for keystr := range k.reads { + key := []byte(keystr) + val, _ := k.KeyValueReader.Get(key) + db.Put(key, val) + } + return db +} diff --git a/trie/proof.go b/trie/proof.go index 2f52438f98..e7102f12b7 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -426,7 +426,7 @@ func hasRightElement(node node, key []byte) bool { // VerifyRangeProof checks whether the given leaf nodes and edge proof // can prove the given trie leaves range is matched with the specific root. -// Besides, the range should be consecutive(no gap inside) and monotonic +// Besides, the range should be consecutive (no gap inside) and monotonic // increasing. // // Note the given proof actually contains two edge proofs. Both of them can @@ -454,96 +454,136 @@ func hasRightElement(node node, key []byte) bool { // // Except returning the error to indicate the proof is valid or not, the function will // also return a flag to indicate whether there exists more accounts/slots in the trie. -func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (error, bool) { +func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (ethdb.KeyValueStore, *Trie, *KeyValueNotary, bool, error) { if len(keys) != len(values) { - return fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)), false + return nil, nil, nil, false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)) } // Ensure the received batch is monotonic increasing. for i := 0; i < len(keys)-1; i++ { if bytes.Compare(keys[i], keys[i+1]) >= 0 { - return errors.New("range is not monotonically increasing"), false + return nil, nil, nil, false, errors.New("range is not monotonically increasing") } } + // Create a key-value notary to track which items from the given proof the + // range prover actually needed to verify the data + notary := NewKeyValueNotary(proof) + // Special case, there is no edge proof at all. The given range is expected // to be the whole leaf-set in the trie. if proof == nil { - emptytrie, err := New(common.Hash{}, NewDatabase(memorydb.New())) + var ( + diskdb = memorydb.New() + triedb = NewDatabase(diskdb) + ) + tr, err := New(common.Hash{}, triedb) if err != nil { - return err, false + return nil, nil, nil, false, err } for index, key := range keys { - emptytrie.TryUpdate(key, values[index]) + tr.TryUpdate(key, values[index]) + } + if tr.Hash() != rootHash { + return nil, nil, nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) + } + // Proof seems valid, serialize all the nodes into the database + if _, err := tr.Commit(nil); err != nil { + return nil, nil, nil, false, err } - if emptytrie.Hash() != rootHash { - return fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, emptytrie.Hash()), false + if err := triedb.Commit(rootHash, false, nil); err != nil { + return nil, nil, nil, false, err } - return nil, false // no more element. + return diskdb, tr, notary, false, nil // No more elements } // Special case, there is a provided edge proof but zero key/value // pairs, ensure there are no more accounts / slots in the trie. if len(keys) == 0 { - root, val, err := proofToPath(rootHash, nil, firstKey, proof, true) + root, val, err := proofToPath(rootHash, nil, firstKey, notary, true) if err != nil { - return err, false + return nil, nil, nil, false, err } if val != nil || hasRightElement(root, firstKey) { - return errors.New("more entries available"), false + return nil, nil, nil, false, errors.New("more entries available") } - return nil, false + // Since the entire proof is a single path, we can construct a trie and a + // node database directly out of the inputs, no need to generate them + diskdb := notary.Accessed() + tr := &Trie{ + db: NewDatabase(diskdb), + root: root, + } + return diskdb, tr, notary, hasRightElement(root, firstKey), nil } // Special case, there is only one element and two edge keys are same. // In this case, we can't construct two edge paths. So handle it here. if len(keys) == 1 && bytes.Equal(firstKey, lastKey) { - root, val, err := proofToPath(rootHash, nil, firstKey, proof, false) + root, val, err := proofToPath(rootHash, nil, firstKey, notary, false) if err != nil { - return err, false + return nil, nil, nil, false, err } if !bytes.Equal(firstKey, keys[0]) { - return errors.New("correct proof but invalid key"), false + return nil, nil, nil, false, errors.New("correct proof but invalid key") } if !bytes.Equal(val, values[0]) { - return errors.New("correct proof but invalid data"), false + return nil, nil, nil, false, errors.New("correct proof but invalid data") + } + // Since the entire proof is a single path, we can construct a trie and a + // node database directly out of the inputs, no need to generate them + diskdb := notary.Accessed() + tr := &Trie{ + db: NewDatabase(diskdb), + root: root, } - return nil, hasRightElement(root, firstKey) + return diskdb, tr, notary, hasRightElement(root, firstKey), nil } // Ok, in all other cases, we require two edge paths available. // First check the validity of edge keys. if bytes.Compare(firstKey, lastKey) >= 0 { - return errors.New("invalid edge keys"), false + return nil, nil, nil, false, errors.New("invalid edge keys") } // todo(rjl493456442) different length edge keys should be supported if len(firstKey) != len(lastKey) { - return errors.New("inconsistent edge keys"), false + return nil, nil, nil, false, errors.New("inconsistent edge keys") } // Convert the edge proofs to edge trie paths. Then we can // have the same tree architecture with the original one. // For the first edge proof, non-existent proof is allowed. - root, _, err := proofToPath(rootHash, nil, firstKey, proof, true) + root, _, err := proofToPath(rootHash, nil, firstKey, notary, true) if err != nil { - return err, false + return nil, nil, nil, false, err } // Pass the root node here, the second path will be merged // with the first one. For the last edge proof, non-existent // proof is also allowed. - root, _, err = proofToPath(rootHash, root, lastKey, proof, true) + root, _, err = proofToPath(rootHash, root, lastKey, notary, true) if err != nil { - return err, false + return nil, nil, nil, false, err } // Remove all internal references. All the removed parts should // be re-filled(or re-constructed) by the given leaves range. if err := unsetInternal(root, firstKey, lastKey); err != nil { - return err, false + return nil, nil, nil, false, err } - // Rebuild the trie with the leave stream, the shape of trie + // Rebuild the trie with the leaf stream, the shape of trie // should be same with the original one. - newtrie := &Trie{root: root, db: NewDatabase(memorydb.New())} + var ( + diskdb = memorydb.New() + triedb = NewDatabase(diskdb) + ) + tr := &Trie{root: root, db: triedb} for index, key := range keys { - newtrie.TryUpdate(key, values[index]) + tr.TryUpdate(key, values[index]) + } + if tr.Hash() != rootHash { + return nil, nil, nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) + } + // Proof seems valid, serialize all the nodes into the database + if _, err := tr.Commit(nil); err != nil { + return nil, nil, nil, false, err } - if newtrie.Hash() != rootHash { - return fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, newtrie.Hash()), false + if err := triedb.Commit(rootHash, false, nil); err != nil { + return nil, nil, nil, false, err } - return nil, hasRightElement(root, keys[len(keys)-1]) + return diskdb, tr, notary, hasRightElement(root, keys[len(keys)-1]), nil } // get returns the child of the given node. Return nil if the diff --git a/trie/proof_test.go b/trie/proof_test.go index 6cdc242d9a..3ecd318886 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -19,6 +19,7 @@ package trie import ( "bytes" crand "crypto/rand" + "encoding/binary" mrand "math/rand" "sort" "testing" @@ -181,7 +182,7 @@ func TestRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -232,7 +233,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -253,7 +254,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), first, last, k, v, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, k, v, proof) if err != nil { t.Fatal("Failed to verify whole rang with non-existent edges") } @@ -288,7 +289,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") } @@ -310,7 +311,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") } @@ -334,7 +335,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - err, _ := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -349,7 +350,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -364,7 +365,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -379,7 +380,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -401,7 +402,7 @@ func TestAllElementsProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -414,7 +415,7 @@ func TestAllElementsProof(t *testing.T) { if err := trie.Prove(entries[len(entries)-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -429,7 +430,7 @@ func TestAllElementsProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), first, last, k, v, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -462,7 +463,7 @@ func TestSingleSideRangeProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -498,7 +499,7 @@ func TestReverseSingleSideRangeProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -570,7 +571,7 @@ func TestBadRangeProof(t *testing.T) { index = mrand.Intn(end - start) vals[index] = nil } - err, _ := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) if err == nil { t.Fatalf("%d Case %d index %d range: (%d->%d) expect error, got nil", i, testcase, index, start, end-1) } @@ -604,7 +605,7 @@ func TestGappedRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) if err == nil { t.Fatal("expect error, got nil") } @@ -631,7 +632,7 @@ func TestSameSideProofs(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) if err == nil { t.Fatalf("Expected error, got nil") } @@ -647,7 +648,7 @@ func TestSameSideProofs(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) if err == nil { t.Fatalf("Expected error, got nil") } @@ -715,7 +716,7 @@ func TestHasRightElement(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, hasMore := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof) + _, _, _, hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -748,13 +749,57 @@ func TestEmptyRangeProof(t *testing.T) { if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - err, _ := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof) + db, tr, not, _, err := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof) if c.err && err == nil { t.Fatalf("Expected error, got nil") } if !c.err && err != nil { t.Fatalf("Expected no error, got %v", err) } + // If no error was returned, ensure the returned trie and database contains + // the entire proof, since there's no value + if !c.err { + if err := tr.Prove(first, 0, memorydb.New()); err != nil { + t.Errorf("returned trie doesn't contain original proof: %v", err) + } + if memdb := db.(*memorydb.Database); memdb.Len() != proof.Len() { + t.Errorf("database entry count mismatch: have %d, want %d", memdb.Len(), proof.Len()) + } + if not == nil { + t.Errorf("missing notary") + } + } + } +} + +// TestBloatedProof tests a malicious proof, where the proof is more or less the +// whole trie. +func TestBloatedProof(t *testing.T) { + // Use a small trie + trie, kvs := nonRandomTrie(100) + var entries entrySlice + for _, kv := range kvs { + entries = append(entries, kv) + } + sort.Sort(entries) + var keys [][]byte + var vals [][]byte + + proof := memorydb.New() + for i, entry := range entries { + trie.Prove(entry.k, 0, proof) + if i == 50 { + keys = append(keys, entry.k) + vals = append(vals, entry.v) + } + } + want := memorydb.New() + trie.Prove(keys[0], 0, want) + trie.Prove(keys[len(keys)-1], 0, want) + + _, _, notary, _, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + if used := notary.Accessed().(*memorydb.Database); used.Len() != want.Len() { + t.Fatalf("notary proof size mismatch: have %d, want %d", used.Len(), want.Len()) } } @@ -858,7 +903,7 @@ func benchmarkVerifyRangeProof(b *testing.B, size int) { b.ResetTimer() for i := 0; i < b.N; i++ { - err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof) if err != nil { b.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -889,3 +934,20 @@ func randBytes(n int) []byte { crand.Read(r) return r } + +func nonRandomTrie(n int) (*Trie, map[string]*kv) { + trie := new(Trie) + vals := make(map[string]*kv) + max := uint64(0xffffffffffffffff) + for i := uint64(0); i < uint64(n); i++ { + value := make([]byte, 32) + key := make([]byte, 32) + binary.LittleEndian.PutUint64(key, i) + binary.LittleEndian.PutUint64(value, i-max) + //value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} + elem := &kv{key, value, false} + trie.Update(elem.k, elem.v) + vals[string(elem.k)] = elem + } + return trie, vals +} diff --git a/trie/sync_bloom.go b/trie/sync_bloom.go index 89f61d66d9..979f4748f3 100644 --- a/trie/sync_bloom.go +++ b/trie/sync_bloom.go @@ -125,14 +125,14 @@ func (b *SyncBloom) init(database ethdb.Iteratee) { it.Release() it = database.NewIterator(nil, key) - log.Info("Initializing fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) + log.Info("Initializing state bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) swap = time.Now() } } it.Release() // Mark the bloom filter inited and return - log.Info("Initialized fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) + log.Info("Initialized state bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) atomic.StoreUint32(&b.inited, 1) } @@ -162,7 +162,7 @@ func (b *SyncBloom) Close() error { b.pend.Wait() // Wipe the bloom, but mark it "uninited" just in case someone attempts an access - log.Info("Deallocated fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate()) + log.Info("Deallocated state bloom", "items", b.bloom.N(), "errorrate", b.errorRate()) atomic.StoreUint32(&b.inited, 0) b.bloom = nil diff --git a/trie/trie.go b/trie/trie.go index 6ddbbd78d3..87b72ecf17 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -19,13 +19,13 @@ package trie import ( "bytes" + "errors" "fmt" "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) var ( @@ -159,29 +159,26 @@ func (t *Trie) TryGetNode(path []byte) ([]byte, int, error) { if item == nil { return nil, resolved, nil } - enc, err := rlp.EncodeToBytes(item) - if err != nil { - log.Error("Encoding existing trie node failed", "err", err) - return nil, resolved, err - } - return enc, resolved, err + return item, resolved, err } -func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item node, newnode node, resolved int, err error) { +func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item []byte, newnode node, resolved int, err error) { // If we reached the requested path, return the current node if pos >= len(path) { - // Don't return collapsed hash nodes though - if _, ok := origNode.(hashNode); !ok { - // Short nodes have expanded keys, compact them before returning - item := origNode - if sn, ok := item.(*shortNode); ok { - item = &shortNode{ - Key: hexToCompact(sn.Key), - Val: sn.Val, - } - } - return item, origNode, 0, nil + // Although we most probably have the original node expanded, encoding + // that into consensus form can be nasty (needs to cascade down) and + // time consuming. Instead, just pull the hash up from disk directly. + var hash hashNode + if node, ok := origNode.(hashNode); ok { + hash = node + } else { + hash, _ = origNode.cache() + } + if hash == nil { + return nil, origNode, 0, errors.New("non-consensus node") } + blob, err := t.db.Node(common.BytesToHash(hash)) + return blob, origNode, 1, err } // Path still needs to be traversed, descend into children switch n := (origNode).(type) { @@ -491,7 +488,7 @@ func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) { // Hash returns the root hash of the trie. It does not write to the // database and can be used even if the trie doesn't have one. func (t *Trie) Hash() common.Hash { - hash, cached, _ := t.hashRoot(nil) + hash, cached, _ := t.hashRoot() t.root = cached return common.BytesToHash(hash.(hashNode)) } @@ -545,7 +542,7 @@ func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) { } // hashRoot calculates the root hash of the given trie -func (t *Trie) hashRoot(db *Database) (node, node, error) { +func (t *Trie) hashRoot() (node, node, error) { if t.root == nil { return hashNode(emptyRoot.Bytes()), nil, nil } From 485992979827596d92e622fec25ce68fe1bfd35b Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 14 Dec 2020 14:08:53 +0100 Subject: [PATCH 070/235] cmd/geth: fixed parallelization flaw in account import test (#22002) --- cmd/geth/accountcmd_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index e27adb6916..04f55e9e7a 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -102,6 +102,7 @@ func TestAccountImport(t *testing.T) { }, } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() importAccountWithExpect(t, test.key, test.output) From 0fe66f8ae41d2ca773f6b01080ddda10bec24377 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Dec 2020 15:31:23 +0200 Subject: [PATCH 071/235] eth/protocols/eth: remove magic numbers in test (#21999) --- eth/protocols/eth/handler_test.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 65c4a10b0a..30beae931b 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -254,8 +254,8 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { headers = append(headers, backend.chain.GetBlockByHash(hash).Header()) } // Send the hash request and verify the response - p2p.Send(peer.app, 0x03, tt.query) - if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { + p2p.Send(peer.app, GetBlockHeadersMsg, tt.query) + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil { t.Errorf("test %d: headers mismatch: %v", i, err) } // If the test used number origins, repeat with hashes as the too @@ -263,8 +263,8 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { if origin := backend.chain.GetBlockByNumber(tt.query.Origin.Number); origin != nil { tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0 - p2p.Send(peer.app, 0x03, tt.query) - if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { + p2p.Send(peer.app, GetBlockHeadersMsg, tt.query) + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil { t.Errorf("test %d: headers mismatch: %v", i, err) } } @@ -343,8 +343,8 @@ func testGetBlockBodies(t *testing.T, protocol uint) { } } // Send the hash request and verify the response - p2p.Send(peer.app, 0x05, hashes) - if err := p2p.ExpectMsg(peer.app, 0x06, bodies); err != nil { + p2p.Send(peer.app, GetBlockBodiesMsg, hashes) + if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, bodies); err != nil { t.Errorf("test %d: bodies mismatch: %v", i, err) } } @@ -410,13 +410,13 @@ func testGetNodeData(t *testing.T, protocol uint) { } it.Release() - p2p.Send(peer.app, 0x0d, hashes) + p2p.Send(peer.app, GetNodeDataMsg, hashes) msg, err := peer.app.ReadMsg() if err != nil { t.Fatalf("failed to read node data response: %v", err) } - if msg.Code != 0x0e { - t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, 0x0c) + if msg.Code != NodeDataMsg { + t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, NodeDataMsg) } var data [][]byte if err := msg.Decode(&data); err != nil { @@ -512,8 +512,8 @@ func testGetBlockReceipts(t *testing.T, protocol uint) { receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash())) } // Send the hash request and verify the response - p2p.Send(peer.app, 0x0f, hashes) - if err := p2p.ExpectMsg(peer.app, 0x10, receipts); err != nil { + p2p.Send(peer.app, GetReceiptsMsg, hashes) + if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, receipts); err != nil { t.Errorf("receipts mismatch: %v", err) } } From 8cde2966af916d85805a47a4350f3567d9e51dbe Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 15 Dec 2020 18:52:51 +0100 Subject: [PATCH 072/235] eth, core: speed up some tests (#22000) --- core/bloombits/matcher_test.go | 6 ++++++ core/bloombits/scheduler_test.go | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/core/bloombits/matcher_test.go b/core/bloombits/matcher_test.go index 91143e525e..923579221f 100644 --- a/core/bloombits/matcher_test.go +++ b/core/bloombits/matcher_test.go @@ -30,6 +30,7 @@ const testSectionSize = 4096 // Tests that wildcard filter rules (nil) can be specified and are handled well. func TestMatcherWildcards(t *testing.T) { + t.Parallel() matcher := NewMatcher(testSectionSize, [][][]byte{ {common.Address{}.Bytes(), common.Address{0x01}.Bytes()}, // Default address is not a wildcard {common.Hash{}.Bytes(), common.Hash{0x01}.Bytes()}, // Default hash is not a wildcard @@ -56,6 +57,7 @@ func TestMatcherWildcards(t *testing.T) { // Tests the matcher pipeline on a single continuous workflow without interrupts. func TestMatcherContinuous(t *testing.T) { + t.Parallel() testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, false, 75) testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, false, 81) testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 0, 10000, false, 36) @@ -64,6 +66,7 @@ func TestMatcherContinuous(t *testing.T) { // Tests the matcher pipeline on a constantly interrupted and resumed work pattern // with the aim of ensuring data items are requested only once. func TestMatcherIntermittent(t *testing.T) { + t.Parallel() testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, true, 75) testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, true, 81) testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 0, 10000, true, 36) @@ -71,6 +74,7 @@ func TestMatcherIntermittent(t *testing.T) { // Tests the matcher pipeline on random input to hopefully catch anomalies. func TestMatcherRandom(t *testing.T) { + t.Parallel() for i := 0; i < 10; i++ { testMatcherBothModes(t, makeRandomIndexes([]int{1}, 50), 0, 10000, 0) testMatcherBothModes(t, makeRandomIndexes([]int{3}, 50), 0, 10000, 0) @@ -84,6 +88,7 @@ func TestMatcherRandom(t *testing.T) { // shifter from a multiple of 8. This is needed to cover an optimisation with // bitset matching https://github.com/ethereum/go-ethereum/issues/15309. func TestMatcherShifted(t *testing.T) { + t.Parallel() // Block 0 always matches in the tests, skip ahead of first 8 blocks with the // start to get a potential zero byte in the matcher bitset. @@ -97,6 +102,7 @@ func TestMatcherShifted(t *testing.T) { // Tests that matching on everything doesn't crash (special case internally). func TestWildcardMatcher(t *testing.T) { + t.Parallel() testMatcherBothModes(t, nil, 0, 10000, 0) } diff --git a/core/bloombits/scheduler_test.go b/core/bloombits/scheduler_test.go index 70772e4ab9..707e8ea11d 100644 --- a/core/bloombits/scheduler_test.go +++ b/core/bloombits/scheduler_test.go @@ -35,6 +35,7 @@ func TestSchedulerMultiClientSingleFetcher(t *testing.T) { testScheduler(t, 10, func TestSchedulerMultiClientMultiFetcher(t *testing.T) { testScheduler(t, 10, 10, 5000) } func testScheduler(t *testing.T, clients int, fetchers int, requests int) { + t.Parallel() f := newScheduler(0) // Create a batch of handler goroutines that respond to bloom bit requests and @@ -88,10 +89,10 @@ func testScheduler(t *testing.T, clients int, fetchers int, requests int) { } close(in) }() - + b := new(big.Int) for j := 0; j < requests; j++ { bits := <-out - if want := new(big.Int).SetUint64(uint64(j)).Bytes(); !bytes.Equal(bits, want) { + if want := b.SetUint64(uint64(j)).Bytes(); !bytes.Equal(bits, want) { t.Errorf("vector %d: delivered content mismatch: have %x, want %x", j, bits, want) } } From c7f2536735a1a47ae63edb488e15ae597dbaf1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Tue, 15 Dec 2020 20:12:14 +0100 Subject: [PATCH 073/235] les: les/4 minimalistic version (#21909) * les: allow tx unindexing in les/4 light server mode * les: minor fixes * les: more small fixes * les: add meaningful constants for recentTxIndex handshake field --- cmd/utils/flags.go | 7 +++---- les/odr_requests.go | 2 +- les/peer.go | 32 +++++++++++++++++++++++++++++++- les/protocol.go | 5 +++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0b1695d0a5..c51d7916ca 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1491,10 +1491,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) - // todo(rjl493456442) make it available for les server - // Ancient tx indices pruning is not available for les server now - // since light client relies on the server for transaction status query. - CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, TxLookupLimitFlag) + if (ctx.GlobalIsSet(LegacyLightServFlag.Name) || ctx.GlobalIsSet(LightServeFlag.Name)) && ctx.GlobalIsSet(TxLookupLimitFlag.Name) { + log.Warn("LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited") + } var ks *keystore.KeyStore if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 { ks = keystores[0].(*keystore.KeyStore) diff --git a/les/odr_requests.go b/les/odr_requests.go index eb1d3602e0..a8cf8f50a9 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -488,7 +488,7 @@ func (r *TxStatusRequest) GetCost(peer *serverPeer) uint64 { // CanSend tells if a certain peer is suitable for serving the given request func (r *TxStatusRequest) CanSend(peer *serverPeer) bool { - return peer.version >= lpv2 + return peer.serveTxLookup } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) diff --git a/les/peer.go b/les/peer.go index 6004af03f5..0e2ed52c12 100644 --- a/les/peer.go +++ b/les/peer.go @@ -341,6 +341,7 @@ type serverPeer struct { onlyAnnounce bool // The flag whether the server sends announcement only. chainSince, chainRecent uint64 // The range of chain server peer can serve. stateSince, stateRecent uint64 // The range of state server peer can serve. + serveTxLookup bool // The server peer can serve tx lookups. // Advertised checkpoint fields checkpointNumber uint64 // The block height which the checkpoint is registered. @@ -628,6 +629,18 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter if recv.get("txRelay", nil) != nil { p.onlyAnnounce = true } + if p.version >= lpv4 { + var recentTx uint + if err := recv.get("recentTxLookup", &recentTx); err != nil { + return err + } + // Note: in the current version we only consider the tx index service useful + // if it is unlimited. This can be made configurable in the future. + p.serveTxLookup = recentTx == txIndexUnlimited + } else { + p.serveTxLookup = true + } + if p.onlyAnnounce && !p.trusted { return errResp(ErrUselessPeer, "peer cannot serve requests") } @@ -969,6 +982,20 @@ func (p *clientPeer) freezeClient() { // Handshake executes the les protocol handshake, negotiating version number, // network IDs, difficulties, head and genesis blocks. func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, server *LesServer) error { + recentTx := server.handler.blockchain.TxLookupLimit() + if recentTx != txIndexUnlimited { + if recentTx < blockSafetyMargin { + recentTx = txIndexDisabled + } else { + recentTx -= blockSafetyMargin - txIndexRecentOffset + } + } + if server.config.UltraLightOnlyAnnounce { + recentTx = txIndexDisabled + } + if recentTx != txIndexUnlimited && p.version < lpv4 { + return errors.New("Cannot serve old clients without a complete tx index") + } // Note: clientPeer.headInfo should contain the last head announced to the client by us. // The values announced in the handshake are dummy values for compatibility reasons and should be ignored. p.headInfo = blockInfo{Hash: head, Number: headNum, Td: td} @@ -981,13 +1008,16 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge // If local ethereum node is running in archive mode, advertise ourselves we have // all version state data. Otherwise only recent state is available. - stateRecent := uint64(core.TriesInMemory - 4) + stateRecent := uint64(core.TriesInMemory - blockSafetyMargin) if server.archiveMode { stateRecent = 0 } *lists = (*lists).add("serveRecentState", stateRecent) *lists = (*lists).add("txRelay", nil) } + if p.version >= lpv4 { + *lists = (*lists).add("recentTxLookup", recentTx) + } *lists = (*lists).add("flowControl/BL", server.defParams.BufLimit) *lists = (*lists).add("flowControl/MRR", server.defParams.MinRecharge) diff --git a/les/protocol.go b/les/protocol.go index aebe0f2c04..39d9f5152f 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -50,6 +50,11 @@ var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24, lpv4: 24} const ( NetworkId = 1 ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message + blockSafetyMargin = 4 // safety margin applied to block ranges specified relative to head block + + txIndexUnlimited = 0 // this value in the "recentTxLookup" handshake field means the entire tx index history is served + txIndexDisabled = 1 // this value means tx index is not served at all + txIndexRecentOffset = 1 // txIndexRecentOffset + N in the handshake field means then tx index of the last N blocks is supported ) // les protocol message codes From 3c46f5570bd674cf49c0113352ff79a4e026a5b8 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 17 Dec 2020 01:20:20 +0100 Subject: [PATCH 074/235] cmd/faucet: sort requests by newest first (#22018) --- cmd/faucet/faucet.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index d7927ac491..008cb14296 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -516,12 +516,12 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } continue } - f.reqs = append(f.reqs, &request{ + f.reqs = append([]*request{{ Avatar: avatar, Account: address, Time: time.Now(), Tx: signed, - }) + }}, f.reqs...) timeout := time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute grace := timeout / 288 // 24h timeout => 5m grace From c5a3ffa3638c8fbcb694881efa7e89f69717cdd3 Mon Sep 17 00:00:00 2001 From: ucwong Date: Mon, 21 Dec 2020 18:54:39 +0800 Subject: [PATCH 075/235] eth/download/statesync : optimize to avoid a copy in state sync hashing (#22035) * eth/download/statesync : state hash sum optimized * go fmt with blank in imports * keccak read arg fix --- eth/downloader/statesync.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go index 69bd13c2f7..6231588ad2 100644 --- a/eth/downloader/statesync.go +++ b/eth/downloader/statesync.go @@ -18,13 +18,13 @@ package downloader import ( "fmt" - "hash" "sync" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/trie" @@ -260,9 +260,9 @@ func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []* type stateSync struct { d *Downloader // Downloader instance to access and manage current peerset - root common.Hash // State root currently being synced - sched *trie.Sync // State trie sync scheduler defining the tasks - keccak hash.Hash // Keccak256 hasher to verify deliveries with + root common.Hash // State root currently being synced + sched *trie.Sync // State trie sync scheduler defining the tasks + keccak crypto.KeccakState // Keccak256 hasher to verify deliveries with trieTasks map[common.Hash]*trieTask // Set of trie node tasks currently queued for retrieval codeTasks map[common.Hash]*codeTask // Set of byte code tasks currently queued for retrieval @@ -299,7 +299,7 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync { d: d, root: root, sched: state.NewStateSync(root, d.stateDB, d.stateBloom), - keccak: sha3.NewLegacyKeccak256(), + keccak: sha3.NewLegacyKeccak256().(crypto.KeccakState), trieTasks: make(map[common.Hash]*trieTask), codeTasks: make(map[common.Hash]*codeTask), deliver: make(chan *stateReq), @@ -590,7 +590,7 @@ func (s *stateSync) processNodeData(blob []byte) (common.Hash, error) { res := trie.SyncResult{Data: blob} s.keccak.Reset() s.keccak.Write(blob) - s.keccak.Sum(res.Hash[:0]) + s.keccak.Read(res.Hash[:]) err := s.sched.Process(res) return res.Hash, err } From 61469cfeaf2a6d0b1598afbaf35dd2d1872604ce Mon Sep 17 00:00:00 2001 From: ucwong Date: Mon, 21 Dec 2020 22:39:58 +0800 Subject: [PATCH 076/235] eth/downloader: fix typo in comment (#22019) --- eth/downloader/modes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/downloader/modes.go b/eth/downloader/modes.go index 8ea7876a1f..3ea14d22d7 100644 --- a/eth/downloader/modes.go +++ b/eth/downloader/modes.go @@ -25,7 +25,7 @@ type SyncMode uint32 const ( FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks FastSync // Quickly download the headers, full sync only at the chain - SnapSync // Download the chain and the state via compact snashots + SnapSync // Download the chain and the state via compact snapshots LightSync // Download only the headers and terminate afterwards ) From 158f72cc0c889739f49dde42210328902073353d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 23 Dec 2020 13:43:22 +0100 Subject: [PATCH 077/235] internal/ethapi: restore net_version RPC method (#22061) During the snap and eth refactor, the net_version rpc call was falsely deprecated. This restores the net_version RPC handler as most eth2 nodes and other software depend on it. --- eth/backend.go | 2 +- internal/ethapi/api.go | 12 +++++++++--- les/client.go | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 987dee6d55..c1732d3ceb 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -230,7 +230,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { return nil, err } // Start the RPC service - eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer) + eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer, config.NetworkId) // Register the backend on the node stack.RegisterAPIs(eth.APIs()) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9ff1781e4a..b424435b50 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1900,12 +1900,13 @@ func (api *PrivateDebugAPI) SetHead(number hexutil.Uint64) { // PublicNetAPI offers network related RPC methods type PublicNetAPI struct { - net *p2p.Server + net *p2p.Server + networkVersion uint64 } // NewPublicNetAPI creates a new net API instance. -func NewPublicNetAPI(net *p2p.Server) *PublicNetAPI { - return &PublicNetAPI{net} +func NewPublicNetAPI(net *p2p.Server, networkVersion uint64) *PublicNetAPI { + return &PublicNetAPI{net, networkVersion} } // Listening returns an indication if the node is listening for network connections. @@ -1918,6 +1919,11 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint { return hexutil.Uint(s.net.PeerCount()) } +// Version returns the current ethereum protocol version. +func (s *PublicNetAPI) Version() string { + return fmt.Sprintf("%d", s.networkVersion) +} + // checkTxFee is an internal function used to check whether the fee of // the given transaction is _reasonable_(under the cap). func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { diff --git a/les/client.go b/les/client.go index 198255dc54..47997a098b 100644 --- a/les/client.go +++ b/les/client.go @@ -171,7 +171,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { leth.blockchain.DisableCheckFreq() } - leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer) + leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer, leth.config.NetworkId) // Register the backend on the node stack.RegisterAPIs(leth.APIs()) From b9012a039b8aaf3e68ccc3826bb17d27eaf0fa1c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 23 Dec 2020 17:44:45 +0100 Subject: [PATCH 078/235] common,crypto: move fuzzers out of core (#22029) * common,crypto: move fuzzers out of core * fuzzers: move vm fuzzer out from core * fuzzing: rework cover package logic * fuzzers: lint --- oss-fuzz.sh | 88 ++++++++++++++----- .../fuzzers}/bitutil/compress_fuzz.go | 14 +-- {crypto => tests/fuzzers}/bn256/bn256_fuzz.go | 6 +- .../fuzzers/runtime/runtime_fuzz.go | 14 +-- 4 files changed, 84 insertions(+), 38 deletions(-) rename {common => tests/fuzzers}/bitutil/compress_fuzz.go (84%) rename {crypto => tests/fuzzers}/bn256/bn256_fuzz.go (94%) rename core/vm/runtime/fuzz.go => tests/fuzzers/runtime/runtime_fuzz.go (82%) diff --git a/oss-fuzz.sh b/oss-fuzz.sh index e060ea88e1..5919b2077f 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -26,38 +26,80 @@ # $CFLAGS, $CXXFLAGS C and C++ compiler flags. # $LIB_FUZZING_ENGINE C++ compiler argument to link fuzz target against the prebuilt engine library (e.g. libFuzzer). +# This sets the -coverpgk for the coverage report when the corpus is executed through go test +coverpkg="github.com/ethereum/go-ethereum/..." + +function coverbuild { + path=$1 + function=$2 + fuzzer=$3 + tags="" + + if [[ $# -eq 4 ]]; then + tags="-tags $4" + fi + cd $path + fuzzed_package=`pwd | rev | cut -d'/' -f 1 | rev` + cp $GOPATH/ossfuzz_coverage_runner.go ./"${function,,}"_test.go + sed -i -e 's/FuzzFunction/'$function'/' ./"${function,,}"_test.go + sed -i -e 's/mypackagebeingfuzzed/'$fuzzed_package'/' ./"${function,,}"_test.go + sed -i -e 's/TestFuzzCorpus/Test'$function'Corpus/' ./"${function,,}"_test.go + +cat << DOG > $OUT/$fuzzer +#/bin/sh + + cd $OUT/$path + go test -run Test${function}Corpus -v $tags -coverprofile \$1 -coverpkg $coverpkg + +DOG + + chmod +x $OUT/$fuzzer + #echo "Built script $OUT/$fuzzer" + #cat $OUT/$fuzzer + cd - +} + function compile_fuzzer { - path=$SRC/go-ethereum/$1 + # Inputs: + # $1: The package to fuzz, within go-ethereum + # $2: The name of the fuzzing function + # $3: The name to give to the final fuzzing-binary + + path=$GOPATH/src/github.com/ethereum/go-ethereum/$1 func=$2 fuzzer=$3 - corpusfile="${path}/testdata/${fuzzer}_seed_corpus.zip" - echo "Building $fuzzer (expecting corpus at $corpusfile)" - (cd $path && \ + + echo "Building $fuzzer" + + # Do a coverage-build or a regular build + if [[ $SANITIZER = *coverage* ]]; then + coverbuild $path $func $fuzzer $coverpkg + else + (cd $path && \ go-fuzz -func $func -o $WORK/$fuzzer.a . && \ - echo "First stage built OK" && \ - $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $WORK/$fuzzer.a -o $OUT/$fuzzer && \ - echo "Second stage built ok" ) - - ## Check if there exists a seed corpus file - if [ -f $corpusfile ] - then - cp $corpusfile $OUT/ - echo "Found seed corpus: $corpusfile" - fi + $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $WORK/$fuzzer.a -o $OUT/$fuzzer) + fi + + ## Check if there exists a seed corpus file + corpusfile="${path}/testdata/${fuzzer}_seed_corpus.zip" + if [ -f $corpusfile ] + then + cp $corpusfile $OUT/ + echo "Found seed corpus: $corpusfile" + fi } -compile_fuzzer common/bitutil Fuzz fuzzBitutilCompress -compile_fuzzer crypto/bn256 FuzzAdd fuzzBn256Add -compile_fuzzer crypto/bn256 FuzzMul fuzzBn256Mul -compile_fuzzer crypto/bn256 FuzzPair fuzzBn256Pair -compile_fuzzer core/vm/runtime Fuzz fuzzVmRuntime -compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b +compile_fuzzer tests/fuzzers/bitutil Fuzz fuzzBitutilCompress +compile_fuzzer tests/fuzzers/bn256 FuzzAdd fuzzBn256Add +compile_fuzzer tests/fuzzers/bn256 FuzzMul fuzzBn256Mul +compile_fuzzer tests/fuzzers/bn256 FuzzPair fuzzBn256Pair +compile_fuzzer tests/fuzzers/runtime Fuzz fuzzVmRuntime compile_fuzzer tests/fuzzers/keystore Fuzz fuzzKeystore compile_fuzzer tests/fuzzers/txfetcher Fuzz fuzzTxfetcher compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie -compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty +compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul @@ -69,6 +111,10 @@ compile_fuzzer tests/fuzzers/bls12381 FuzzPairing fuzz_pairing compile_fuzzer tests/fuzzers/bls12381 FuzzMapG1 fuzz_map_g1 compile_fuzzer tests/fuzzers/bls12381 FuzzMapG2 fuzz_map_g2 +#TODO: move this to tests/fuzzers, if possible +compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b + + # This doesn't work very well @TODO #compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi diff --git a/common/bitutil/compress_fuzz.go b/tests/fuzzers/bitutil/compress_fuzz.go similarity index 84% rename from common/bitutil/compress_fuzz.go rename to tests/fuzzers/bitutil/compress_fuzz.go index 714bbcd131..0b77b2dc9e 100644 --- a/common/bitutil/compress_fuzz.go +++ b/tests/fuzzers/bitutil/compress_fuzz.go @@ -14,11 +14,13 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build gofuzz - package bitutil -import "bytes" +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common/bitutil" +) // Fuzz implements a go-fuzz fuzzer method to test various encoding method // invocations. @@ -35,7 +37,7 @@ func Fuzz(data []byte) int { // fuzzEncode implements a go-fuzz fuzzer method to test the bitset encoding and // decoding algorithm. func fuzzEncode(data []byte) int { - proc, _ := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data)) + proc, _ := bitutil.DecompressBytes(bitutil.CompressBytes(data), len(data)) if !bytes.Equal(data, proc) { panic("content mismatch") } @@ -45,11 +47,11 @@ func fuzzEncode(data []byte) int { // fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and // reencoding algorithm. func fuzzDecode(data []byte) int { - blob, err := bitsetDecodeBytes(data, 1024) + blob, err := bitutil.DecompressBytes(data, 1024) if err != nil { return 0 } - if comp := bitsetEncodeBytes(blob); !bytes.Equal(comp, data) { + if comp := bitutil.CompressBytes(blob); !bytes.Equal(comp, data) { panic("content mismatch") } return 1 diff --git a/crypto/bn256/bn256_fuzz.go b/tests/fuzzers/bn256/bn256_fuzz.go similarity index 94% rename from crypto/bn256/bn256_fuzz.go rename to tests/fuzzers/bn256/bn256_fuzz.go index b34043487f..477fe0a160 100644 --- a/crypto/bn256/bn256_fuzz.go +++ b/tests/fuzzers/bn256/bn256_fuzz.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. -// +build gofuzz - package bn256 import ( @@ -24,7 +22,7 @@ func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1) { } xg := new(google.G1) if _, err := xg.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) } return xc, xg } @@ -37,7 +35,7 @@ func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2) { } xg := new(google.G2) if _, err := xg.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) } return xc, xg } diff --git a/core/vm/runtime/fuzz.go b/tests/fuzzers/runtime/runtime_fuzz.go similarity index 82% rename from core/vm/runtime/fuzz.go rename to tests/fuzzers/runtime/runtime_fuzz.go index cb9ff08b5b..9b96045752 100644 --- a/core/vm/runtime/fuzz.go +++ b/tests/fuzzers/runtime/runtime_fuzz.go @@ -14,23 +14,23 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build gofuzz - package runtime +import ( + "github.com/ethereum/go-ethereum/core/vm/runtime" +) + // Fuzz is the basic entry point for the go-fuzz tool // // This returns 1 for valid parsable/runable code, 0 // for invalid opcode. func Fuzz(input []byte) int { - _, _, err := Execute(input, input, &Config{ - GasLimit: 3000000, + _, _, err := runtime.Execute(input, input, &runtime.Config{ + GasLimit: 12000000, }) - // invalid opcode - if err != nil && len(err.Error()) > 6 && string(err.Error()[:7]) == "invalid" { + if err != nil && len(err.Error()) > 6 && err.Error()[:7] == "invalid" { return 0 } - return 1 } From 25c0bd9b43da2c5b64e17b3847a8490d6abbe5c1 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Sun, 27 Dec 2020 17:56:50 +0000 Subject: [PATCH 079/235] README.md: update Travis badge (#22079) The legacy dot-org URL was displaying a message about the repository having migrated to the dot-com service, which now covers open-source projects as well. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ddb885dfdc..19357355a8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Official Golang implementation of the Ethereum protocol. https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667 )](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc) [![Go Report Card](https://goreportcard.com/badge/github.com/ethereum/go-ethereum)](https://goreportcard.com/report/github.com/ethereum/go-ethereum) -[![Travis](https://travis-ci.org/ethereum/go-ethereum.svg?branch=master)](https://travis-ci.org/ethereum/go-ethereum) +[![Travis](https://travis-ci.com/ethereum/go-ethereum.svg?branch=master)](https://travis-ci.com/ethereum/go-ethereum) [![Discord](https://img.shields.io/badge/discord-join%20chat-blue.svg)](https://discord.gg/nthXNEv) Automated builds are available for stable releases and the unstable master branch. Binary From 9c6b5b904a0ea050a0ffda7cf7b60678b457783d Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Sun, 27 Dec 2020 21:57:19 +0100 Subject: [PATCH 080/235] eth, eth/tracers: include intrinsic gas in calltracer, expose for all tracers (#22038) * eth/tracers: share tx gas price with js tracer * eth/tracers: use `go generate` * eth/tracers: try with another version of go-bindata * eth/tracers: export txGas * eth, eth/tracers: pass intrinsic gas to js tracers eth/tracers: include tx gas in tracers usedGas eth/tracers: fix prestate tracer's sender balance eth/tracers: rm unnecessary import eth/tracers: pass intrinsicGas separately to tracer eth/tracers: fix tests broken by lack of txdata eth, eth/tracers: minor fix * eth/tracers: regenerate assets + unexport test-struct + add testcase * eth/tracers: simplify tests + make table-driven Co-authored-by: Guillaume Ballet Co-authored-by: Martin Holst Swende --- eth/api_tracer.go | 2 +- eth/tracers/internal/tracers/assets.go | 6 +- .../internal/tracers/prestate_tracer.js | 2 +- eth/tracers/tracer.go | 21 ++- eth/tracers/tracer_test.go | 151 ++++++++---------- eth/tracers/tracers_test.go | 4 +- 6 files changed, 92 insertions(+), 94 deletions(-) diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 2497c8d95e..c68b762255 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -794,7 +794,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } } // Constuct the JavaScript tracer to execute with - if tracer, err = tracers.New(*config.Tracer); err != nil { + if tracer, err = tracers.New(*config.Tracer, txContext); err != nil { return nil, err } // Handle timeouts and RPC cancellations diff --git a/eth/tracers/internal/tracers/assets.go b/eth/tracers/internal/tracers/assets.go index 432398ebb5..8b89ad4182 100644 --- a/eth/tracers/internal/tracers/assets.go +++ b/eth/tracers/internal/tracers/assets.go @@ -6,7 +6,7 @@ // evmdis_tracer.js (4.195kB) // noop_tracer.js (1.271kB) // opcount_tracer.js (1.372kB) -// prestate_tracer.js (4.234kB) +// prestate_tracer.js (4.287kB) // trigram_tracer.js (1.788kB) // unigram_tracer.js (1.51kB) @@ -197,7 +197,7 @@ func opcount_tracerJs() (*asset, error) { return a, nil } -var _prestate_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x57\xdd\x6f\xdb\x38\x12\x7f\x96\xfe\x8a\x41\x5f\x6c\xa3\xae\xdc\x64\x81\x3d\xc0\xb9\x1c\xa0\xba\x6e\x1b\x20\x9b\x04\xb6\x7b\xb9\xdc\x62\x1f\x28\x72\x24\x73\x4d\x93\x02\x49\xd9\xf1\x15\xf9\xdf\x0f\x43\x7d\xf8\xa3\x49\xd3\xdd\x37\x9b\x1c\xfe\xe6\xfb\x37\xa3\xd1\x08\x26\xa6\xdc\x59\x59\x2c\x3d\x9c\xbf\x3f\xfb\x07\x2c\x96\x08\x85\x79\x87\x7e\x89\x16\xab\x35\xa4\x95\x5f\x1a\xeb\xe2\xd1\x08\x16\x4b\xe9\x20\x97\x0a\x41\x3a\x28\x99\xf5\x60\x72\xf0\x27\xf2\x4a\x66\x96\xd9\x5d\x12\x8f\x46\xf5\x9b\x67\xaf\x09\x21\xb7\x88\xe0\x4c\xee\xb7\xcc\xe2\x18\x76\xa6\x02\xce\x34\x58\x14\xd2\x79\x2b\xb3\xca\x23\x48\x0f\x4c\x8b\x91\xb1\xb0\x36\x42\xe6\x3b\x82\x94\x1e\x2a\x2d\xd0\x06\xd5\x1e\xed\xda\xb5\x76\x7c\xbe\xf9\x0a\xd7\xe8\x1c\x5a\xf8\x8c\x1a\x2d\x53\x70\x57\x65\x4a\x72\xb8\x96\x1c\xb5\x43\x60\x0e\x4a\x3a\x71\x4b\x14\x90\x05\x38\x7a\xf8\x89\x4c\x99\x37\xa6\xc0\x27\x53\x69\xc1\xbc\x34\x7a\x08\x28\xc9\x72\xd8\xa0\x75\xd2\x68\xf8\xa5\x55\xd5\x00\x0e\xc1\x58\x02\xe9\x33\x4f\x0e\x58\x30\x25\xbd\x1b\x00\xd3\x3b\x50\xcc\xef\x9f\xfe\x44\x40\xf6\x7e\x0b\x90\x3a\xa8\x59\x9a\x12\xc1\x2f\x99\x27\xaf\xb7\x52\x29\xc8\x10\x2a\x87\x79\xa5\x86\x84\x96\x55\x1e\xee\xaf\x16\x5f\x6e\xbf\x2e\x20\xbd\x79\x80\xfb\x74\x36\x4b\x6f\x16\x0f\x17\xb0\x95\x7e\x69\x2a\x0f\xb8\xc1\x1a\x4a\xae\x4b\x25\x51\xc0\x96\x59\xcb\xb4\xdf\x81\xc9\x09\xe1\xb7\xe9\x6c\xf2\x25\xbd\x59\xa4\x1f\xae\xae\xaf\x16\x0f\x60\x2c\x7c\xba\x5a\xdc\x4c\xe7\x73\xf8\x74\x3b\x83\x14\xee\xd2\xd9\xe2\x6a\xf2\xf5\x3a\x9d\xc1\xdd\xd7\xd9\xdd\xed\x7c\x9a\xc0\x1c\xc9\x2a\xa4\xf7\xaf\xc7\x3c\x0f\xd9\xb3\x08\x02\x3d\x93\xca\xb5\x91\x78\x30\x15\xb8\xa5\xa9\x94\x80\x25\xdb\x20\x58\xe4\x28\x37\x28\x80\x01\x37\xe5\xee\xa7\x93\x4a\x58\x4c\x19\x5d\x04\x9f\x5f\x2c\x48\xb8\xca\x41\x1b\x3f\x04\x87\x08\xff\x5c\x7a\x5f\x8e\x47\xa3\xed\x76\x9b\x14\xba\x4a\x8c\x2d\x46\xaa\x86\x73\xa3\x7f\x25\x31\x61\x96\x16\x9d\x67\x1e\x17\x96\x71\xb4\x60\x2a\x5f\x56\xde\x81\xab\xf2\x5c\x72\x89\xda\x83\xd4\xb9\xb1\xeb\x50\x29\xe0\x0d\x70\x8b\xcc\x23\x30\x50\x86\x33\x05\xf8\x88\xbc\x0a\x77\x75\xa4\x43\xb9\x5a\xa6\x1d\xe3\xe1\x34\xb7\x66\x4d\xbe\x56\xce\xd3\x0f\xe7\x70\x9d\x29\x14\x50\xa0\x46\x27\x1d\x64\xca\xf0\x55\x12\x7f\x8b\xa3\x03\x63\xa8\x4e\x82\x87\x8d\x50\xa8\x8d\x2d\xf6\x2c\x42\x56\x49\x25\xa4\x2e\x92\x38\x6a\xa5\xc7\xa0\x2b\xa5\x86\x71\x80\x50\xc6\xac\xaa\x32\xe5\xdc\x54\xc1\xf6\x3f\x91\xfb\x1a\xcc\x95\xc8\x65\x4e\xc5\xc1\xba\x5b\x6f\xc2\x55\xa7\xd7\x64\x24\x9f\xc4\xd1\x11\xcc\x18\xf2\x4a\x07\x77\xfa\x4c\x08\x3b\x04\x91\x0d\xbe\xc5\x51\xb4\x61\x96\xb0\xe0\x12\xbc\xf9\x82\x8f\xe1\x72\x70\x11\x47\x91\xcc\xa1\xef\x97\xd2\x25\x2d\xf0\xef\x8c\xf3\x3f\xe0\xf2\xf2\x32\x34\x75\x2e\x35\x8a\x01\x10\x44\xf4\x9c\x58\x7d\x13\x65\x4c\x31\xcd\x71\x0c\xbd\xf7\x8f\x3d\x78\x0b\x22\x4b\x0a\xf4\x1f\xea\xd3\x5a\x59\xe2\xcd\xdc\x5b\xa9\x8b\xfe\xd9\xaf\x83\x61\x78\xa5\x4d\x78\x03\x8d\xf8\x8d\xe9\x84\xeb\x7b\x6e\x44\xb8\x6e\x6c\xae\xa5\x26\x46\x34\x42\x8d\x94\xf3\xc6\xb2\x02\xc7\xf0\xed\x89\xfe\x3f\x91\x57\x4f\x71\xf4\x74\x14\xe5\x79\x2d\xf4\x42\x94\x1b\x08\x40\xed\x6d\x57\xe7\x85\xa4\x4e\x3d\x4c\x40\xc0\xfb\x51\x12\xe6\xad\x29\x27\x49\x58\xe1\xee\xf5\x4c\xd0\x85\x14\x8f\xdd\xc5\x0a\x77\x83\x8b\xf8\xc5\x14\x25\x8d\xd1\xbf\x4b\xf1\xf8\xb3\xf9\x3a\x79\x73\x14\xd7\x39\x49\xed\xed\x1d\x0c\x4e\xe2\x68\xd1\x55\xca\x53\xb9\x4b\xbd\x31\x2b\x22\xae\x25\xc5\x47\xa9\x10\x12\x53\x52\xb6\x5c\xcd\x1c\x19\xa2\x06\xe9\xd1\x32\xa2\x4e\xb3\x41\x4b\x53\x03\x2c\xfa\xca\x6a\xd7\x85\x31\x97\x9a\xa9\x16\xb8\x89\xba\xb7\x8c\xd7\x3d\x53\x9f\x1f\xc4\x92\xfb\xc7\x10\xc5\xe0\xdd\x68\x04\xa9\x07\x72\x11\x4a\x23\xb5\x1f\xc2\x16\x41\x23\x0a\x6a\x7c\x81\xa2\xe2\x3e\xe0\xf5\x36\x4c\x55\xd8\xab\x9b\x9b\x28\x32\x3c\x35\x15\x4d\x82\x83\xe6\x1f\x06\x03\xd7\x66\x13\x46\x5c\xc6\xf8\x0a\x9a\x86\x33\x56\x16\x52\xc7\x4d\x38\x8f\x9a\x8d\x2c\x4a\x08\x38\x98\x15\x72\x45\x49\xa4\x93\x0f\x4c\xc1\x25\x64\xb2\xb8\xd2\xfe\x24\x79\x75\xd0\xdb\xa7\x83\x3f\x92\xa6\x79\x12\x47\x84\xd7\x3f\x1f\x0c\xe1\xec\xd7\xae\x22\xbc\x21\x28\x78\x1d\xcc\x9b\x97\xa1\xe2\xd3\x62\x78\xfe\x59\x50\x43\x1d\xfc\x36\x68\x4d\x5c\x95\x51\x3a\x6a\x3f\x43\x1c\x8f\xbb\xf8\xe2\x07\xb8\xc7\xbe\xb5\xb8\x4d\x68\x12\x26\xc4\xcb\xa0\x75\x8a\x3e\x22\xb7\xb8\x26\x56\xa7\x2c\x70\xa6\x14\xda\x9e\x83\xc0\x19\xc3\xa6\x9c\x42\xbe\x70\x5d\xfa\x5d\xcb\xf5\x9e\xd9\x02\xbd\x7b\xdd\xb0\x80\xf3\xee\x5d\x4b\x81\x21\x14\xbb\x12\xe1\xf2\x12\x7a\x93\xd9\x34\x5d\x4c\x7b\x4d\x1b\x8d\x46\x70\x8f\x61\x13\xca\x94\xcc\x84\xda\x81\x40\x85\x1e\x6b\xbb\x8c\x0e\x21\xea\x28\x61\x48\x2b\x0d\x2d\x1b\xf8\x28\x9d\x97\xba\x80\x9a\x29\xb6\x34\x57\x1b\xb8\xd0\x23\x9c\x55\x8e\xaa\xf5\x64\x08\x79\x43\x1b\x85\x45\xe2\x15\xe2\xff\xd0\x6e\x4c\xc9\x6e\x03\xc9\xa5\x75\x1e\x4a\xc5\x38\x26\x84\xd7\x19\xf3\x72\x7e\x9b\x4e\x26\xd5\xb3\xd0\x82\x01\x68\x3f\xe0\x98\xa2\x01\x49\xea\x1d\xf4\x5b\x8c\x41\x1c\x45\xb6\x95\x3e\xc0\xbe\xd8\x53\x82\xf3\x58\x1e\x12\x02\x2d\x16\xb8\x41\xa2\xd0\xc0\x06\xf5\x30\x24\x5d\xff\xfe\xad\x99\xbe\xe8\x92\x38\xa2\x77\x07\x7d\xad\x4c\x71\xdc\xd7\xa2\x0e\x0b\xaf\xac\xa5\xfc\x77\x14\x9c\x53\x8f\xff\x59\x39\x4f\x31\xb5\x14\x9e\x86\x2d\x9e\x23\xc9\x40\x89\x34\x6d\x07\xdf\x93\x21\xcd\xad\x30\x27\x48\x5d\x33\xa5\xea\x6d\xae\x34\x1e\xb5\x97\x4c\xa9\x1d\xe5\x61\x6b\x69\x8d\xa1\xc5\x65\x08\x4e\x92\x54\x60\x9c\x20\x2a\x35\x57\x95\xa8\xcb\x20\xd4\x71\x83\xe7\x82\xcd\xc7\xfb\xcf\x1a\x9d\x63\x05\x26\x54\x49\xb9\x7c\x6c\x36\x48\x0d\xbd\x9a\xe4\xfa\x83\x5e\xd2\x19\x79\x4c\x31\xca\x14\x49\x5b\x64\x44\xd3\xa9\x10\x16\x9d\xeb\x0f\x1a\xce\xe9\x32\x7b\xbf\x44\x4d\xc1\x07\x8d\x5b\xe8\x56\x13\xc6\x39\xad\x6a\x62\x08\x4c\x08\xa2\xb6\x93\x35\x22\x8e\x22\xb7\x95\x9e\x2f\x21\x68\x32\xe5\xbe\x17\x07\x4d\xfd\x73\xe6\x10\xde\x4c\xff\xb3\x98\xdc\x7e\x9c\x4e\x6e\xef\x1e\xde\x8c\xe1\xe8\x6c\x7e\xf5\xdf\x69\x77\xf6\x21\xbd\x4e\x6f\x26\xd3\x37\xe3\x30\x9b\x9f\x71\xc8\x9b\xd6\x05\x52\xe8\x3c\xe3\xab\xa4\x44\x5c\xf5\xdf\x1f\xf3\xc0\xde\xc1\x28\xca\x2c\xb2\xd5\xc5\xde\x98\xba\x41\x1b\x1d\x2d\xe5\xc2\x25\xbc\x18\xac\x8b\x97\xad\x99\x34\xf2\xfd\x96\xc8\xf7\xab\x48\xa0\x8a\xd7\xed\x38\xff\xcb\x86\x84\xde\x61\x7c\x35\x06\xc7\x14\x6d\xc0\xf2\x7f\xf4\xe5\x92\xe7\x0e\xfd\x10\x50\x0b\xb3\x25\xe6\xeb\x50\xeb\x9b\x06\xf7\x20\x64\x67\x83\x9a\x41\x6f\xf3\xfe\xa0\x13\x26\xb0\xef\x45\xcf\x9f\x13\x45\x2d\xe0\xb2\x45\x7f\x1b\x5e\xbe\x1e\xa8\xf3\x26\x52\x27\x0a\x7e\x39\xd9\xf0\xc2\xfd\x1a\xd7\xc6\xee\x9a\x71\x74\xe0\xdf\x8f\xa3\x9a\x5e\x5f\x77\xf5\x44\x7f\xa8\xc8\xba\x83\x8f\xd3\xeb\xe9\xe7\x74\x31\x3d\x92\x9a\x2f\xd2\xc5\xd5\xa4\x3e\xfa\xcb\x85\x77\xf6\xd3\x85\xd7\x9b\xcf\x17\xb7\xb3\x69\x6f\xdc\xfc\xbb\xbe\x4d\x3f\xf6\xbe\x53\xd8\x6c\x81\x3f\x6a\x5d\x6f\xee\x8d\x15\x7f\xa7\x03\x0e\x36\xb2\x9c\x3d\xb7\x90\x05\x6a\xe7\xbe\x3a\xf9\xe0\x01\xa6\x5b\x56\xce\xeb\x8f\xbe\x28\xbc\x7f\x96\x87\x9f\xe2\xa7\xf8\xff\x01\x00\x00\xff\xff\xb1\x28\x85\x2a\x8a\x10\x00\x00") +var _prestate_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x57\xdd\x6f\xdb\x38\x12\x7f\xb6\xfe\x8a\x41\x5f\x6c\x5d\x5d\xb9\xcd\x02\x7b\x80\x73\x39\x40\x75\xdd\x36\x40\x36\x09\x6c\xe7\x72\xb9\xc5\x3e\x50\xe4\x48\xe6\x9a\x26\x05\x92\xb2\xe3\x2b\xf2\xbf\x1f\x86\xfa\xf0\x47\x93\xa6\x7b\x6f\x16\x39\xfc\xcd\xf7\x6f\xc6\xa3\x11\x4c\x4c\xb9\xb3\xb2\x58\x7a\x38\x7b\xff\xe1\xef\xb0\x58\x22\x14\xe6\x1d\xfa\x25\x5a\xac\xd6\x90\x56\x7e\x69\xac\x8b\x46\x23\x58\x2c\xa5\x83\x5c\x2a\x04\xe9\xa0\x64\xd6\x83\xc9\xc1\x9f\xc8\x2b\x99\x59\x66\x77\x49\x34\x1a\xd5\x6f\x9e\xbd\x26\x84\xdc\x22\x82\x33\xb9\xdf\x32\x8b\x63\xd8\x99\x0a\x38\xd3\x60\x51\x48\xe7\xad\xcc\x2a\x8f\x20\x3d\x30\x2d\x46\xc6\xc2\xda\x08\x99\xef\x08\x52\x7a\xa8\xb4\x40\x1b\x54\x7b\xb4\x6b\xd7\xda\xf1\xe5\xfa\x0e\xae\xd0\x39\xb4\xf0\x05\x35\x5a\xa6\xe0\xb6\xca\x94\xe4\x70\x25\x39\x6a\x87\xc0\x1c\x94\x74\xe2\x96\x28\x20\x0b\x70\xf4\xf0\x33\x99\x32\x6f\x4c\x81\xcf\xa6\xd2\x82\x79\x69\xf4\x10\x50\x92\xe5\xb0\x41\xeb\xa4\xd1\xf0\x4b\xab\xaa\x01\x1c\x82\xb1\x04\x32\x60\x9e\x1c\xb0\x60\x4a\x7a\x17\x03\xd3\x3b\x50\xcc\xef\x9f\xfe\x44\x40\xf6\x7e\x0b\x90\x3a\xa8\x59\x9a\x12\xc1\x2f\x99\x27\xaf\xb7\x52\x29\xc8\x10\x2a\x87\x79\xa5\x86\x84\x96\x55\x1e\xee\x2f\x17\x5f\x6f\xee\x16\x90\x5e\x3f\xc0\x7d\x3a\x9b\xa5\xd7\x8b\x87\x73\xd8\x4a\xbf\x34\x95\x07\xdc\x60\x0d\x25\xd7\xa5\x92\x28\x60\xcb\xac\x65\xda\xef\xc0\xe4\x84\xf0\xdb\x74\x36\xf9\x9a\x5e\x2f\xd2\x8f\x97\x57\x97\x8b\x07\x30\x16\x3e\x5f\x2e\xae\xa7\xf3\x39\x7c\xbe\x99\x41\x0a\xb7\xe9\x6c\x71\x39\xb9\xbb\x4a\x67\x70\x7b\x37\xbb\xbd\x99\x4f\x13\x98\x23\x59\x85\xf4\xfe\xf5\x98\xe7\x21\x7b\x16\x41\xa0\x67\x52\xb9\x36\x12\x0f\xa6\x02\xb7\x34\x95\x12\xb0\x64\x1b\x04\x8b\x1c\xe5\x06\x05\x30\xe0\xa6\xdc\xfd\x74\x52\x09\x8b\x29\xa3\x8b\xe0\xf3\x8b\x05\x09\x97\x39\x68\xe3\x87\xe0\x10\xe1\x1f\x4b\xef\xcb\xf1\x68\xb4\xdd\x6e\x93\x42\x57\x89\xb1\xc5\x48\xd5\x70\x6e\xf4\xcf\x24\x22\xcc\xd2\xa2\xf3\xcc\xe3\xc2\x32\x8e\x16\x4c\xe5\xcb\xca\x3b\x70\x55\x9e\x4b\x2e\x51\x7b\x90\x3a\x37\x76\x1d\x2a\x05\xbc\x01\x6e\x91\x79\x04\x06\xca\x70\xa6\x00\x1f\x91\x57\xe1\xae\x8e\x74\x28\x57\xcb\xb4\x63\x3c\x9c\xe6\xd6\xac\xc9\xd7\xca\x79\xfa\xe1\x1c\xae\x33\x85\x02\x0a\xd4\xe8\xa4\x83\x4c\x19\xbe\x4a\xa2\x6f\x51\xef\xc0\x18\xaa\x93\xe0\x61\x23\x14\x6a\x63\x8b\x7d\x8b\x90\x55\x52\x09\xa9\x8b\x24\xea\xb5\xd2\x63\xd0\x95\x52\xc3\x28\x40\x28\x63\x56\x55\x99\x72\x6e\xaa\x60\xfb\x9f\xc8\x7d\x0d\xe6\x4a\xe4\x32\xa7\xe2\x60\xdd\xad\x37\xe1\xaa\xd3\x6b\x32\x92\x4f\xa2\xde\x11\xcc\x18\xf2\x4a\x07\x77\x06\x4c\x08\x3b\x04\x91\xc5\xdf\xa2\x5e\x6f\xc3\x2c\x61\xc1\x05\x78\xf3\x15\x1f\xc3\x65\x7c\x1e\xf5\x7a\x32\x87\x81\x5f\x4a\x97\xb4\xc0\xbf\x33\xce\xff\x80\x8b\x8b\x8b\xd0\xd4\xb9\xd4\x28\x62\x20\x88\xde\x73\x62\xf5\x4d\x2f\x63\x8a\x69\x8e\x63\xe8\xbf\x7f\xec\xc3\x5b\x10\x59\x52\xa0\xff\x58\x9f\xd6\xca\x12\x6f\xe6\xde\x4a\x5d\x0c\x3e\xfc\x1a\x0f\xc3\x2b\x6d\xc2\x1b\x68\xc4\xaf\x4d\x27\x5c\xdf\x73\x23\xc2\x75\x63\x73\x2d\x35\x31\xa2\x11\x6a\xa4\x9c\x37\x96\x15\x38\x86\x6f\x4f\xf4\xfd\x44\x5e\x3d\x45\xbd\xa7\xa3\x28\xcf\x6b\xa1\x17\xa2\xdc\x40\x00\x6a\x6f\xbb\x3a\x2f\x24\x75\xea\x61\x02\x02\xde\x8f\x92\x30\x6f\x4d\x39\x49\xc2\x0a\x77\xaf\x67\x82\x2e\xa4\x78\xec\x2e\x56\xb8\x8b\xcf\xa3\x17\x53\x94\x34\x46\xff\x2e\xc5\xe3\xcf\xe6\xeb\xe4\xcd\x51\x5c\xe7\x24\xb5\xb7\x37\x8e\x4f\xe2\x68\xd1\x55\xca\x53\xb9\x4b\xbd\x31\x2b\x22\xae\x25\xc5\x47\xa9\x10\x12\x53\x52\xb6\x5c\xcd\x1c\x19\xa2\x06\xe9\xd1\x32\xa2\x4e\xb3\x41\x4b\x53\x03\x2c\xfa\xca\x6a\xd7\x85\x31\x97\x9a\xa9\x16\xb8\x89\xba\xb7\x8c\xd7\x3d\x53\x9f\x1f\xc4\x92\xfb\xc7\x10\xc5\xe0\xdd\x68\x04\xa9\x07\x72\x11\x4a\x23\xb5\x1f\xc2\x16\x41\x23\x0a\x6a\x7c\x81\xa2\xe2\x3e\xe0\xf5\x37\x4c\x55\xd8\xaf\x9b\x9b\x28\x32\x3c\x35\x15\x4d\x82\x83\xe6\x1f\x06\x03\xd7\x66\x13\x46\x5c\xc6\xf8\x0a\x9a\x86\x33\x56\x16\x52\x47\x4d\x38\x8f\x9a\x8d\x2c\x4a\x08\x38\x98\x15\x72\x45\x49\xa4\x93\x8f\x4c\xc1\x05\x64\xb2\xb8\xd4\xfe\x24\x79\x75\xd0\xdb\xa7\xf1\x1f\x49\xd3\x3c\x89\x23\xc2\x1b\x9c\xc5\x43\xf8\xf0\x6b\x57\x11\xde\x10\x14\xbc\x0e\xe6\xcd\xcb\x50\xd1\x69\x31\x3c\xff\x2c\xa8\xa1\x0e\x7e\x1b\xb4\x26\xae\xca\x28\x1d\xb5\x9f\x21\x8e\xc7\x5d\x7c\xfe\x03\xdc\x63\xdf\x5a\xdc\x26\x34\x09\x13\xe2\x10\x94\x3e\xc3\x77\xc1\xdc\x9d\x43\x01\x6f\x81\xbe\xa4\x26\x55\x4e\xf2\x2f\xcc\xc5\xf0\x37\x68\x24\x6e\xad\xe4\xdf\x59\x52\xe7\xf5\x13\x72\x8b\x6b\x1a\x05\x94\x3a\xce\x94\x42\xdb\x77\x10\x88\x66\xd8\xd4\x60\x48\x32\xae\x4b\xbf\x6b\x07\x84\x67\xb6\x40\xef\x5e\xf7\x26\xe0\xbc\x7b\xd7\xf2\x66\x88\xdf\xae\x44\xb8\xb8\x80\xfe\x64\x36\x4d\x17\xd3\x7e\xd3\x7b\xa3\x11\xdc\x63\x58\x9f\x32\x25\x33\xa1\x76\x20\x50\xa1\xc7\xda\x2e\xa3\x43\x5c\x3b\x1e\x19\xd2\x1e\x44\x1b\x0a\x3e\x4a\xe7\xa5\x2e\xa0\xa6\x97\x2d\x0d\xe3\x06\x2e\x34\x16\x67\x15\x85\xe7\x74\x72\x79\x43\x6b\x88\x45\x22\x23\x1a\x1a\xa1\x47\x99\x92\xdd\xda\x92\x4b\xeb\x3c\x94\x8a\x71\x4c\x08\xaf\x33\xe6\xe5\xa2\x68\xda\x9f\x54\xcf\x42\xdf\x06\xa0\xfd\x54\x64\x8a\xa6\x2a\xa9\x77\x30\x68\x31\xe2\xa8\xd7\xb3\xad\xf4\x01\xf6\xf9\x9e\x47\x9c\xc7\xf2\x90\x45\x68\x1b\xc1\x0d\x12\xef\x06\x0a\xa9\x27\x28\xe9\xfa\xd7\x6f\xcd\xc8\x46\x97\x44\x3d\x7a\x77\x40\x06\xca\x14\xc7\x64\x20\xea\xb0\xf0\xca\x5a\xca\x7f\xc7\xdb\x39\x11\xc3\x9f\x95\xf3\x14\x53\x4b\xe1\x69\x28\xe6\x39\x66\x0d\x3c\x4a\x23\x3a\xfe\x9e\x41\x69\xd8\x85\xe1\x42\xea\x9a\xd1\x56\xaf\x80\xa5\xf1\xa8\xbd\x64\x4a\xed\x28\x0f\x5b\x4b\xbb\x0f\x6d\x3b\x43\x70\x92\xa4\x02\x4d\x05\x51\xa9\xb9\xaa\x44\x5d\x06\xa1\xf8\x1b\x3c\x17\x6c\x3e\x5e\x9a\xd6\xe8\x1c\x2b\x30\xa1\x4a\xca\xe5\x63\xb3\x76\x6a\xe8\xd7\xcc\x38\x88\xfb\x49\x67\xe4\x31\x2f\x29\x53\x24\x6d\x91\x11\xb7\xa7\x42\x58\x74\x6e\x10\x37\x44\xd5\x65\xf6\x7e\x89\x9a\x82\x0f\x1a\xb7\xd0\xed\x33\x8c\x73\xda\xef\xc4\x10\x98\x10\xc4\x87\x27\xbb\x47\xd4\xeb\xb9\xad\xf4\x7c\x09\x41\x93\x29\xf7\xbd\x18\x37\xf5\xcf\x99\x43\x78\x33\xfd\xf7\x62\x72\xf3\x69\x3a\xb9\xb9\x7d\x78\x33\x86\xa3\xb3\xf9\xe5\x7f\xa6\xdd\xd9\xc7\xf4\x2a\xbd\x9e\x4c\xdf\x8c\xc3\x40\x7f\xc6\x21\x6f\x5a\x17\x48\xa1\xf3\x8c\xaf\x92\x12\x71\x35\x78\x7f\xcc\x03\x7b\x07\x7b\xbd\xcc\x22\x5b\x9d\xef\x8d\xa9\x1b\xb4\xd1\xd1\xf2\x34\x5c\xc0\x8b\xc1\x3a\x7f\xd9\x9a\x49\x23\x3f\x68\xd9\x7f\xbf\xbf\x04\xaa\x78\xdd\x8e\xb3\xbf\x6c\x48\xe8\x1d\xc6\x57\x63\x70\x4c\xd1\xda\x2c\xff\x4b\x7f\x77\xf2\xdc\xa1\x1f\x02\x6a\x61\xb6\xc4\x7c\x1d\x6a\x7d\xd3\xe0\x1e\x84\xec\x43\x5c\xd3\xee\x4d\x3e\x88\x3b\x61\x02\xfb\x5e\xf4\xec\x39\x51\xd4\x02\x2e\x5a\xf4\xb7\xe1\xe5\xeb\x81\x3a\x6b\x22\x75\xa2\xe0\x97\x93\xb5\x30\xdc\xaf\x71\x6d\xec\xae\x99\x61\x07\xfe\xfd\x38\xaa\xe9\xd5\x55\x57\x4f\xf4\x41\x45\xd6\x1d\x7c\x9a\x5e\x4d\xbf\xa4\x8b\xe9\x91\xd4\x7c\x91\x2e\x2e\x27\xf5\xd1\x5f\x2e\xbc\x0f\x3f\x5d\x78\xfd\xf9\x7c\x71\x33\x9b\xf6\xc7\xcd\xd7\xd5\x4d\xfa\xa9\xff\x9d\xc2\x66\x75\xfc\x51\xeb\x7a\x73\x6f\xac\xf8\x7f\x3a\xe0\x60\x8d\xcb\xd9\x73\x5b\x5c\xa0\x76\xee\xab\x93\x7f\x49\xc0\x74\xcb\xca\x79\xfd\x4f\xb1\x17\xde\x3f\xcb\xc3\x4f\xd1\x53\xf4\xbf\x00\x00\x00\xff\xff\x3a\xb7\x37\x41\xbf\x10\x00\x00") func prestate_tracerJsBytes() ([]byte, error) { return bindataRead( @@ -213,7 +213,7 @@ func prestate_tracerJs() (*asset, error) { } info := bindataFileInfo{name: "prestate_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe9, 0x79, 0x70, 0x4f, 0xc5, 0x78, 0x57, 0x63, 0x6f, 0x5, 0x31, 0xce, 0x3e, 0x5d, 0xbd, 0x71, 0x4, 0x46, 0x78, 0xcd, 0x1d, 0xcd, 0xb9, 0xd8, 0x10, 0xff, 0xe6, 0xc5, 0x59, 0xb9, 0x25, 0x6e}} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd4, 0x9, 0xf9, 0x44, 0x13, 0x31, 0x89, 0xf7, 0x35, 0x9a, 0xc6, 0xf0, 0x86, 0x9d, 0xb2, 0xe3, 0x57, 0xe2, 0xc0, 0xde, 0xc9, 0x3a, 0x4c, 0x4a, 0x94, 0x90, 0xa5, 0x92, 0x2f, 0xbf, 0xc0, 0xb8}} return a, nil } diff --git a/eth/tracers/internal/tracers/prestate_tracer.js b/eth/tracers/internal/tracers/prestate_tracer.js index e0a22bf157..084c04ec46 100644 --- a/eth/tracers/internal/tracers/prestate_tracer.js +++ b/eth/tracers/internal/tracers/prestate_tracer.js @@ -55,7 +55,7 @@ var toBal = bigInt(this.prestate[toHex(ctx.to)].balance.slice(2), 16); this.prestate[toHex(ctx.to)].balance = '0x'+toBal.subtract(ctx.value).toString(16); - this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).toString(16); + this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).add((ctx.gasUsed + ctx.intrinsicGas) * ctx.gasPrice).toString(16); // Decrement the caller's nonce, and remove empty create targets this.prestate[toHex(ctx.from)].nonce--; diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 5d806d9026..c9f00d7371 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -27,10 +27,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "gopkg.in/olebedev/go-duktape.v3" + duktape "gopkg.in/olebedev/go-duktape.v3" ) // bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js. @@ -316,7 +317,7 @@ type Tracer struct { // New instantiates a new tracer instance. code specifies a Javascript snippet, // which must evaluate to an expression returning an object with 'step', 'fault' // and 'result' functions. -func New(code string) (*Tracer, error) { +func New(code string, txCtx vm.TxContext) (*Tracer, error) { // Resolve any tracers by name and assemble the tracer object if tracer, ok := tracer(code); ok { code = tracer @@ -335,6 +336,8 @@ func New(code string) (*Tracer, error) { depthValue: new(uint), refundValue: new(uint), } + tracer.ctx["gasPrice"] = txCtx.GasPrice + // Set up builtins for this environment tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int { ctx.PushString(hexutil.Encode(popSlice(ctx))) @@ -546,6 +549,18 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost // Initialize the context if it wasn't done yet if !jst.inited { jst.ctx["block"] = env.Context.BlockNumber.Uint64() + // Compute intrinsic gas + isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) + isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) + var input []byte + if data, ok := jst.ctx["input"].([]byte); ok { + input = data + } + intrinsicGas, err := core.IntrinsicGas(input, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul) + if err != nil { + return err + } + jst.ctx["intrinsicGas"] = intrinsicGas jst.inited = true } // If tracing was interrupted, set the error and stop @@ -597,8 +612,8 @@ func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost // CaptureEnd is called after the call finishes to finalize the tracing. func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { jst.ctx["output"] = output - jst.ctx["gasUsed"] = gasUsed jst.ctx["time"] = t.String() + jst.ctx["gasUsed"] = gasUsed if err != nil { jst.ctx["error"] = err.Error() diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 554b2282f1..f28e14864b 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -17,7 +17,6 @@ package tracers import ( - "bytes" "encoding/json" "errors" "math/big" @@ -50,94 +49,77 @@ type dummyStatedb struct { func (*dummyStatedb) GetRefund() uint64 { return 1337 } -func runTrace(tracer *Tracer) (json.RawMessage, error) { - env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) - - contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000) - contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} - - _, err := env.Interpreter().Run(contract, []byte{}, false) - if err != nil { - return nil, err - } - return tracer.GetResult() +type vmContext struct { + blockCtx vm.BlockContext + txCtx vm.TxContext } -// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory access -func TestRegressionPanicSlice(t *testing.T) { - tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}") - if err != nil { - t.Fatal(err) - } - if _, err = runTrace(tracer); err != nil { - t.Fatal(err) - } -} - -// TestRegressionPanicSlice tests that we don't panic on bad arguments to stack peeks -func TestRegressionPanicPeek(t *testing.T) { - tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}") - if err != nil { - t.Fatal(err) - } - if _, err = runTrace(tracer); err != nil { - t.Fatal(err) - } -} - -// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory getUint -func TestRegressionPanicGetUint(t *testing.T) { - tracer, err := New("{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}") - if err != nil { - t.Fatal(err) - } - if _, err = runTrace(tracer); err != nil { - t.Fatal(err) - } +func testCtx() *vmContext { + return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} } -func TestTracing(t *testing.T) { - tracer, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}") - if err != nil { - t.Fatal(err) - } - - ret, err := runTrace(tracer) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(ret, []byte("3")) { - t.Errorf("Expected return value to be 3, got %s", string(ret)) - } -} - -func TestStack(t *testing.T) { - tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}") - if err != nil { - t.Fatal(err) - } +func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { + env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + var ( + startGas uint64 = 10000 + value = big.NewInt(0) + ) + contract := vm.NewContract(account{}, account{}, value, startGas) + contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} - ret, err := runTrace(tracer) + tracer.CaptureStart(contract.Caller(), contract.Address(), false, []byte{}, startGas, value) + ret, err := env.Interpreter().Run(contract, []byte{}, false) + tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err) if err != nil { - t.Fatal(err) - } - if !bytes.Equal(ret, []byte("[0,1,2]")) { - t.Errorf("Expected return value to be [0,1,2], got %s", string(ret)) + return nil, err } + return tracer.GetResult() } -func TestOpcodes(t *testing.T) { - tracer, err := New("{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}") - if err != nil { - t.Fatal(err) - } - - ret, err := runTrace(tracer) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(ret, []byte("[\"PUSH1\",\"PUSH1\",\"STOP\"]")) { - t.Errorf("Expected return value to be [\"PUSH1\",\"PUSH1\",\"STOP\"], got %s", string(ret)) +func TestTracer(t *testing.T) { + execTracer := func(code string) []byte { + t.Helper() + ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} + tracer, err := New(code, ctx.txCtx) + if err != nil { + t.Fatal(err) + } + ret, err := runTrace(tracer, ctx) + if err != nil { + t.Fatal(err) + } + return ret + } + for i, tt := range []struct { + code string + want string + }{ + { // tests that we don't panic on bad arguments to memory access + code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", + want: `[{},{},{}]`, + }, { // tests that we don't panic on bad arguments to stack peeks + code: "{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}", + want: `["0","0","0"]`, + }, { // tests that we don't panic on bad arguments to memory getUint + code: "{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}", + want: `["0","0","0"]`, + }, { // tests some general counting + code: "{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}", + want: `3`, + }, { // tests that depth is reported correctly + code: "{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}", + want: `[0,1,2]`, + }, { // tests to-string of opcodes + code: "{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}", + want: `["PUSH1","PUSH1","STOP"]`, + }, { // tests intrinsic gas + code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}", + want: `"100000.6.21000"`, + }, + } { + if have := execTracer(tt.code); tt.want != string(have) { + t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code) + } } } @@ -145,7 +127,8 @@ func TestHalt(t *testing.T) { t.Skip("duktape doesn't support abortion") timeout := errors.New("stahp") - tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}") + vmctx := testCtx() + tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", vmctx.txCtx) if err != nil { t.Fatal(err) } @@ -155,17 +138,17 @@ func TestHalt(t *testing.T) { tracer.Stop(timeout) }() - if _, err = runTrace(tracer); err.Error() != "stahp in server-side tracer function 'step'" { + if _, err = runTrace(tracer, vmctx); err.Error() != "stahp in server-side tracer function 'step'" { t.Errorf("Expected timeout error, got %v", err) } } func TestHaltBetweenSteps(t *testing.T) { - tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}") + vmctx := testCtx() + tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", vmctx.txCtx) if err != nil { t.Fatal(err) } - env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index b749d832b4..9dc4c69631 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -173,7 +173,7 @@ func TestPrestateTracerCreate2(t *testing.T) { _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) // Create the tracer, the EVM environment and run it - tracer, err := New("prestateTracer") + tracer, err := New("prestateTracer", txContext) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } @@ -248,7 +248,7 @@ func TestCallTracer(t *testing.T) { _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) // Create the tracer, the EVM environment and run it - tracer, err := New("callTracer") + tracer, err := New("callTracer", txContext) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } From b13e9c4e3d603955f92c1542a5e86c740f43f33e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sun, 27 Dec 2020 21:58:39 +0100 Subject: [PATCH 081/235] tests/fuzzers: fix false positive in bitutil fuzzer (#22076) --- tests/fuzzers/bitutil/compress_fuzz.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/fuzzers/bitutil/compress_fuzz.go b/tests/fuzzers/bitutil/compress_fuzz.go index 0b77b2dc9e..5903cf2f93 100644 --- a/tests/fuzzers/bitutil/compress_fuzz.go +++ b/tests/fuzzers/bitutil/compress_fuzz.go @@ -51,7 +51,19 @@ func fuzzDecode(data []byte) int { if err != nil { return 0 } - if comp := bitutil.CompressBytes(blob); !bytes.Equal(comp, data) { + // re-compress it (it's OK if the re-compressed differs from the + // original - the first input may not have been compressed at all) + comp := bitutil.CompressBytes(blob) + if len(comp) > len(blob) { + // After compression, it must be smaller or equal + panic("bad compression") + } + // But decompressing it once again should work + decomp, err := bitutil.DecompressBytes(data, 1024) + if err != nil { + panic(err) + } + if !bytes.Equal(decomp, blob) { panic("content mismatch") } return 1 From 2f8100615ad1d8335e38024221143163f09d54fe Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Sun, 27 Dec 2020 22:01:28 +0100 Subject: [PATCH 082/235] cmd/geth: replace wiki links with new doc pages (#22071) --- cmd/geth/consolecmd.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index cbecbe0a5f..7822c73b31 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -43,7 +43,7 @@ var ( Description: ` The Geth console is an interactive shell for the JavaScript runtime environment which exposes a node admin interface as well as the Ðapp JavaScript API. -See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.`, +See https://geth.ethereum.org/docs/interface/javascript-console.`, } attachCommand = cli.Command{ @@ -56,7 +56,7 @@ See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.`, Description: ` The Geth console is an interactive shell for the JavaScript runtime environment which exposes a node admin interface as well as the Ðapp JavaScript API. -See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console. +See https://geth.ethereum.org/docs/interface/javascript-console. This command allows to open a console on a running geth node.`, } @@ -69,7 +69,7 @@ This command allows to open a console on a running geth node.`, Category: "CONSOLE COMMANDS", Description: ` The JavaScript VM exposes a node admin interface as well as the Ðapp -JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console`, +JavaScript API. See https://geth.ethereum.org/docs/interface/javascript-console`, } ) From 0a09a39325814a2acbf4486c74b9aa9e6cff04d6 Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Sun, 27 Dec 2020 22:09:05 +0100 Subject: [PATCH 083/235] eth/filters: replace wiki links with new doc pages (#22070) --- eth/filters/api.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index 664f229a04..b6f974c3ba 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -101,7 +101,7 @@ func (api *PublicFilterAPI) timeoutLoop() { // It is part of the filter package because this filter can be used through the // `eth_getFilterChanges` polling method that is also used for log filters. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter +// https://eth.wiki/json-rpc/API#eth_newpendingtransactionfilter func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { var ( pendingTxs = make(chan []common.Hash) @@ -171,7 +171,7 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su // NewBlockFilter creates a filter that fetches blocks that are imported into the chain. // It is part of the filter package since polling goes with eth_getFilterChanges. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter +// https://eth.wiki/json-rpc/API#eth_newblockfilter func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { var ( headers = make(chan *types.Header) @@ -287,7 +287,7 @@ type FilterCriteria ethereum.FilterQuery // // In case "fromBlock" > "toBlock" an error is returned. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter +// https://eth.wiki/json-rpc/API#eth_newfilter func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { logs := make(chan []*types.Log) logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs) @@ -322,7 +322,7 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { // GetLogs returns logs matching the given argument that are stored within the state. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs +// https://eth.wiki/json-rpc/API#eth_getlogs func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { var filter *Filter if crit.BlockHash != nil { @@ -351,7 +351,7 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([ // UninstallFilter removes the filter with the given filter id. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter +// https://eth.wiki/json-rpc/API#eth_uninstallfilter func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { api.filtersMu.Lock() f, found := api.filters[id] @@ -369,7 +369,7 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { // GetFilterLogs returns the logs for the filter with the given id. // If the filter could not be found an empty array of logs is returned. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs +// https://eth.wiki/json-rpc/API#eth_getfilterlogs func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { api.filtersMu.Lock() f, found := api.filters[id] @@ -410,7 +410,7 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ty // For pending transaction and block filters the result is []common.Hash. // (pending)Log filters return []Log. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges +// https://eth.wiki/json-rpc/API#eth_getfilterchanges func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { api.filtersMu.Lock() defer api.filtersMu.Unlock() From ab0979f9306e87025d821b2629985e8bffe130ef Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Sun, 27 Dec 2020 22:18:57 +0100 Subject: [PATCH 084/235] signer: docs - replace wiki links with new doc pages (#22069) --- signer/fourbyte/abi_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/signer/fourbyte/abi_test.go b/signer/fourbyte/abi_test.go index 314c12735b..68c027ecea 100644 --- a/signer/fourbyte/abi_test.go +++ b/signer/fourbyte/abi_test.go @@ -68,7 +68,7 @@ func TestNewUnpacker(t *testing.T) { [10]byte{49, 50, 51, 52, 53, 54, 55, 56, 57, 48}, common.Hex2Bytes("48656c6c6f2c20776f726c6421"), }, - }, { // https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#examples + }, { // https://docs.soliditylang.org/en/develop/abi-spec.html#examples `[{"type":"function","name":"sam","inputs":[{"type":"bytes"},{"type":"bool"},{"type":"uint256[]"}]}]`, // "dave", true and [1,2,3] "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", @@ -124,7 +124,7 @@ func TestCalldataDecoding(t *testing.T) { "42958b5400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", // Too short compareAndApprove "a52c101e00ff0000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", - // From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + // From https://docs.soliditylang.org/en/develop/abi-spec.html // contains a bool with illegal values "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", } { @@ -135,7 +135,7 @@ func TestCalldataDecoding(t *testing.T) { } // Expected success for i, hexdata := range []string{ - // From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + // From https://docs.soliditylang.org/en/develop/abi-spec.html "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", "a52c101e0000000000000000000000000000000000000000000000000000000000000012", "a52c101eFFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", From 653e8b9dd9b7a1565b0e26c71ad70a83803ad529 Mon Sep 17 00:00:00 2001 From: jk-jeongkyun <45347815+jeongkyun-oh@users.noreply.github.com> Date: Mon, 28 Dec 2020 06:26:42 +0900 Subject: [PATCH 085/235] eth/downloader: remove unnecessary condition (#22052) --- eth/downloader/queue.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 2150842f8e..ac7edc2c68 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -893,9 +893,6 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, return accepted, nil } // If none of the data was good, it's a stale delivery - if errors.Is(failure, errInvalidChain) { - return accepted, failure - } if accepted > 0 { return accepted, fmt.Errorf("partial failure: %v", failure) } From c17a7733df3aa7f68d4e0ff5ce9d5c2919284faa Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Sun, 27 Dec 2020 22:28:08 +0100 Subject: [PATCH 086/235] docs: replace wiki links with new doc pages in readme.md (#22065) (#22066) --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 19357355a8..57d431db1e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ archives are published at https://geth.ethereum.org/downloads/. ## Building the source -For prerequisites and detailed build instructions please read the [Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) on the wiki. +For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/install-and-build/installing-geth). Building `geth` requires both a Go (version 1.13 or later) and a C compiler. You can install them using your favourite package manager. Once the dependencies are installed, run @@ -36,18 +36,18 @@ directory. | Command | Description | | :-----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. | -| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. | +| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI page](https://geth.ethereum.org/docs/interface/command-line-options) for command line options. | +| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://docs.soliditylang.org/en/develop/abi-spec.html) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://geth.ethereum.org/docs/dapp/native-bindings) page for details. | | `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. | | `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug run`). | -| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. | -| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | +| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://eth.wiki/json-rpc/API) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. | +| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://eth.wiki/en/fundamentals/rlp)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | | `puppeth` | a CLI wizard that aids in creating a new Ethereum network. | ## Running `geth` Going through all the possible command line flags is out of scope here (please consult our -[CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options)), +[CLI Wiki page](https://geth.ethereum.org/docs/interface/command-line-options)), but we've enumerated a few common parameter combos to get you up to speed quickly on how you can run your own `geth` instance. @@ -66,9 +66,9 @@ This command will: * Start `geth` in fast sync mode (default, can be changed with the `--syncmode` flag), causing it to download more data in exchange for avoiding processing the entire history of the Ethereum network, which is very CPU intensive. - * Start up `geth`'s built-in interactive [JavaScript console](https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console), - (via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://github.com/ethereum/wiki/wiki/JavaScript-API) - as well as `geth`'s own [management APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs). + * Start up `geth`'s built-in interactive [JavaScript console](https://geth.ethereum.org/docs/interface/javascript-console), + (via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://web3js.readthedocs.io/en/) + as well as `geth`'s own [management APIs](https://geth.ethereum.org/docs/rpc/server). This tool is optional and if you leave it out you can always attach to an already running `geth` instance with `geth attach`. @@ -170,8 +170,8 @@ accessible from the outside. As a developer, sooner rather than later you'll want to start interacting with `geth` and the Ethereum network via your own programs and not manually through the console. To aid -this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://github.com/ethereum/wiki/wiki/JSON-RPC) -and [`geth` specific APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs)). +this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://eth.wiki/json-rpc/API) +and [`geth` specific APIs](https://geth.ethereum.org/docs/rpc/server)). These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based platforms, and named pipes on Windows). @@ -277,7 +277,7 @@ $ bootnode --genkey=boot.key $ bootnode --nodekey=boot.key ``` -With the bootnode online, it will display an [`enode` URL](https://github.com/ethereum/wiki/wiki/enode-url-format) +With the bootnode online, it will display an [`enode` URL](https://eth.wiki/en/fundamentals/enode-url-format) that other nodes can use to connect to it and exchange peer information. Make sure to replace the displayed IP address information (most probably `[::]`) with your externally accessible IP to get the actual `enode` URL. @@ -344,7 +344,7 @@ Please make sure your contributions adhere to our coding guidelines: * Commit messages should be prefixed with the package(s) they modify. * E.g. "eth, rpc: make trace configs optional" -Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide) +Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/devguide) for more details on configuring your environment, managing project dependencies, and testing procedures. From a425a47ddcb6078e2ae6ab062bb73f2c0939fd1d Mon Sep 17 00:00:00 2001 From: ucwong Date: Mon, 28 Dec 2020 05:38:16 +0800 Subject: [PATCH 087/235] core/rawdb, eth/protocols : Method name typo fix (#22026) --- core/rawdb/accessors_snapshot.go | 4 ++-- eth/protocols/snap/sync.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index 0a91d9353b..c3616ba3aa 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -176,8 +176,8 @@ func DeleteSnapshotRecoveryNumber(db ethdb.KeyValueWriter) { } } -// ReadSanpshotSyncStatus retrieves the serialized sync status saved at shutdown. -func ReadSanpshotSyncStatus(db ethdb.KeyValueReader) []byte { +// ReadSnapshotSyncStatus retrieves the serialized sync status saved at shutdown. +func ReadSnapshotSyncStatus(db ethdb.KeyValueReader) []byte { data, _ := db.Get(snapshotSyncStatusKey) return data } diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 679b328283..437b0caab4 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -614,7 +614,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { func (s *Syncer) loadSyncStatus() { var progress syncProgress - if status := rawdb.ReadSanpshotSyncStatus(s.db); status != nil { + if status := rawdb.ReadSnapshotSyncStatus(s.db); status != nil { if err := json.Unmarshal(status, &progress); err != nil { log.Error("Failed to decode snap sync status", "err", err) } else { From 0a3993c558616868e35f9730e92c704ac16ee437 Mon Sep 17 00:00:00 2001 From: yumiel yoomee1313 Date: Wed, 30 Dec 2020 21:10:11 +0900 Subject: [PATCH 088/235] accounts/abi/bind: fix erroneous test (#22053) closes #22049 --- accounts/abi/bind/bind_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 4a504516bb..db87703d03 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -1640,11 +1640,7 @@ var bindTests = []struct { contract NewFallbacks { event Fallback(bytes data); fallback() external { - bytes memory data; - assembly { - calldatacopy(data, 0, calldatasize()) - } - emit Fallback(data); + emit Fallback(msg.data); } event Received(address addr, uint value); @@ -1653,7 +1649,7 @@ var bindTests = []struct { } } `, - []string{"60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040526004361061000d575b36610081575b7f88a5966d370b9919b20f3e2c13ff65706f196a4e32cc2c12bf57088f885258743334604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a15b005b34801561008e5760006000fd5b505b606036600082377f9043988963722edecc2099c75b0af0ff76af14ffca42ed6bce059a20a2a9f986816040518080602001828103825283818151815260200191508051906020019080838360005b838110156100fa5780820151818401525b6020810190506100de565b50505050905090810190601f1680156101275780820380516001836020036101000a031916815260200191505b509250505060405180910390a1505b00fea26469706673582212205643ca37f40c2b352dc541f42e9e6720de065de756324b7fcc9fb1d67eda4a7d64736f6c63430006040033"}, + []string{"6080604052348015600f57600080fd5b506101078061001f6000396000f3fe608060405236605f577f88a5966d370b9919b20f3e2c13ff65706f196a4e32cc2c12bf57088f885258743334604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1005b348015606a57600080fd5b507f9043988963722edecc2099c75b0af0ff76af14ffca42ed6bce059a20a2a9f98660003660405180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405180910390a100fea26469706673582212201f994dcfbc53bf610b19176f9a361eafa77b447fd9c796fa2c615dfd0aaf3b8b64736f6c634300060c0033"}, []string{`[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Fallback","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Received","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"stateMutability":"payable","type":"receive"}]`}, ` "bytes" @@ -1701,6 +1697,7 @@ var bindTests = []struct { } // Test fallback function + gotEvent = false opts.Value = nil calldata := []byte{0x01, 0x02, 0x03} c.Fallback(opts, calldata) From 167ff563d16a405a89ce449fdb34eb6d99631053 Mon Sep 17 00:00:00 2001 From: Melvin Junhee Woo Date: Mon, 4 Jan 2021 17:07:43 +0900 Subject: [PATCH 089/235] core/state/snapshot: gethring -> gathering typo (#22104) --- core/state/snapshot/generate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 4a2fa78d3a..17f1ca6078 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -151,7 +151,7 @@ func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorSta // generate is a background thread that iterates over the state and storage tries, // constructing the state snapshot. All the arguments are purely for statistics -// gethering and logging, since the method surfs the blocks as they arrive, often +// gathering and logging, since the method surfs the blocks as they arrive, often // being restarted. func (dl *diskLayer) generate(stats *generatorStats) { // If a database wipe is in operation, wait until it's done From f83fc302a504919f6668060110cbb8b64c26dd07 Mon Sep 17 00:00:00 2001 From: Vie Date: Mon, 4 Jan 2021 18:52:23 +0800 Subject: [PATCH 090/235] cmd/geth: update copyright year (#22099) --- cmd/geth/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index e587a5f86d..0d1b569b8b 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -218,7 +218,7 @@ func init() { // Initialize the CLI app and start Geth app.Action = geth app.HideVersion = true // we have a command to print the version - app.Copyright = "Copyright 2013-2020 The go-ethereum Authors" + app.Copyright = "Copyright 2013-2021 The go-ethereum Authors" app.Commands = []cli.Command{ // See chaincmd.go: initCommand, From 47820ef726a7b08ea2e22baff8fff64231c3046b Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Mon, 4 Jan 2021 11:58:51 +0100 Subject: [PATCH 091/235] .github: Replace wiki links with new doc pages (#22065) (#22068) --- .github/CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f87996cdcb..a08542df25 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -30,11 +30,11 @@ Please make sure your contributions adhere to our coding guidelines: Before you submit a feature request, please check and make sure that it isn't possible through some other means. The JavaScript-enabled console is a powerful feature in the right hands. Please check our -[Wiki page](https://github.com/ethereum/go-ethereum/wiki) for more info +[Geth documentation page](https://geth.ethereum.org/docs/) for more info and help. ## Configuration, dependencies, and tests -Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide) +Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/devguide) for more details on configuring your environment, managing project dependencies and testing procedures. From 5c2a7ce2ccace9f453bcd320b4ac52e1f5ce3ab2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 4 Jan 2021 12:39:25 +0100 Subject: [PATCH 092/235] node: rename startNetworking to openEndpoints (#22105) --- node/node.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/node/node.go b/node/node.go index c66ebb89d0..b58594ef1d 100644 --- a/node/node.go +++ b/node/node.go @@ -159,12 +159,13 @@ func (n *Node) Start() error { return ErrNodeStopped } n.state = runningState - err := n.startNetworking() + // open networking and RPC endpoints + err := n.openEndpoints() lifecycles := make([]Lifecycle, len(n.lifecycles)) copy(lifecycles, n.lifecycles) n.lock.Unlock() - // Check if networking startup failed. + // Check if endpoint startup failed. if err != nil { n.doClose(nil) return err @@ -247,12 +248,14 @@ func (n *Node) doClose(errs []error) error { } } -// startNetworking starts all network endpoints. -func (n *Node) startNetworking() error { +// openEndpoints starts all network and RPC endpoints. +func (n *Node) openEndpoints() error { + // start networking endpoints n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) if err := n.server.Start(); err != nil { return convertFileLockError(err) } + // start RPC endpoints err := n.startRPC() if err != nil { n.stopRPC() From 1951e20d1040627faf3b6722c88ddf0e86ecf50e Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Mon, 4 Jan 2021 12:42:47 +0100 Subject: [PATCH 093/235] SECURITY.md: link to release page (#22067) Add links to go-ethereum's GitHub release page. Co-authored-by: Felix Lange --- SECURITY.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index bc54ede42f..bdce7b8d2a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,31 +2,29 @@ ## Supported Versions -Please see Releases. We recommend to use the most recent released version. +Please see [Releases](https://github.com/ethereum/go-ethereum/releases). We recommend using the [most recently released version](https://github.com/ethereum/go-ethereum/releases/latest). ## Audit reports Audit reports are published in the `docs` folder: https://github.com/ethereum/go-ethereum/tree/master/docs/audits - | Scope | Date | Report Link | | ------- | ------- | ----------- | | `geth` | 20170425 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2017-04-25_Geth-audit_Truesec.pdf) | | `clef` | 20180914 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2018-09-14_Clef-audit_NCC.pdf) | - - ## Reporting a Vulnerability **Please do not file a public ticket** mentioning the vulnerability. -To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org. +To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org. Please read the [disclosure page](https://github.com/ethereum/go-ethereum/security/advisories?state=published) for more information about publically disclosed security vulnerabilities. + +Use the built-in `geth version-check` feature to check whether the software is affected by any known vulnerability. This command will fetch the latest [`vulnerabilities.json`](https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json) file which contains known security vulnerabilities concerning `geth`, and cross-check the data against its own version number. The following key may be used to communicate sensitive information to developers. Fingerprint: `AE96 ED96 9E47 9B00 84F3 E17F E88D 3334 FA5F 6A0A` - ``` -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 From e4571d8c12be5aaa380ee58ea35a5617823e5324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 4 Jan 2021 13:58:46 +0200 Subject: [PATCH 094/235] cmd: support v1.1 Twitter API in faucet, fix puppeth --- cmd/faucet/README.md | 50 ++++++++++++++++++ cmd/faucet/faucet.go | 100 ++++++++++++++++++++++------------- cmd/puppeth/module_faucet.go | 2 +- cmd/puppeth/wizard_faucet.go | 18 ++----- 4 files changed, 118 insertions(+), 52 deletions(-) create mode 100644 cmd/faucet/README.md diff --git a/cmd/faucet/README.md b/cmd/faucet/README.md new file mode 100644 index 0000000000..364689a782 --- /dev/null +++ b/cmd/faucet/README.md @@ -0,0 +1,50 @@ +# Faucet + +The `faucet` is a simplistic web application with the goal of distributing small amounts of Ether in private and test networks. + +Users need to post their Ethereum addresses to fund in a Twitter status update or public Facebook post and share the link to the faucet. The faucet will in turn deduplicate user requests and send the Ether. After a funding round, the faucet prevents the same user requesting again for a pre-configured amount of time, proportional to the amount of Ether requested. + +## Operation + +The `faucet` is a single binary app (everything included) with all configurations set via command line flags and a few files. + +First thing's first, the `faucet` needs to connect to an Ethereum network, for which it needs the necessary genesis and network infos. Each of the following flags must be set: + +- `--genesis` is a path to a file containin the network `genesis.json` +- `--network` is the devp2p network id used during connection +- `--bootnodes` is a list of `enode://` ids to join the network through + +The `faucet` will use the `les` protocol to join the configured Ethereum network and will store its data in `$HOME/.faucet` (currently not configurable). + +## Funding + +To be able to distribute funds, the `faucet` needs access to an already funded Ethereum account. This can be configured via: + +- `--account.json` is a path to the Ethereum account's JSON key file +- `--account.pass` is a path to a text file with the decryption passphrase + +The faucet is able to distribute various amounts of Ether in exchange for various timeouts. These can be configured via: + +- `--faucet.amount` is the number of Ethers to send by default +- `--faucet.minutes` is the time to wait before allowing a rerequest +- `--faucet.tiers` is the funding tiers to support (x3 time, x2.5 funds) + +## Sybil protection + +To prevent the same user from exhausting funds in a loop, the `faucet` ties requests to social networks and captcha resolvers. + +Captcha protection uses Google's invisible ReCaptcha, thus the `faucet` needs to run on a live domain. The domain needs to be registered in Google's systems to retrieve the captcha API token and secrets. After doing so, captcha protection may be enabled via: + +- `--captcha.token` is the API token for ReCaptcha +- `--captcha.secret` is the API secret for ReCaptcha + +Sybil protection via Twitter requires an API key as of 15th December, 2020. To obtain it, a Twitter user must be upgraded to developer status and a new Twitter App deployed with it. The app's `Bearer` token is required by the faucet to retrieve tweet data: + +- `--twitter.token` is the Bearer token for `v2` API access +- `--twitter.token.v1` is the Bearer token for `v1` API access + +Sybil protection via Facebook uses the website to directly download post data thus does not currently require an API configuration. + +## Miscellaneous + +Beside the above - mostly essential - CLI flags, there are a number that can be used to fine tune the `faucet`'s operation. Please see `faucet --help` for a full list. \ No newline at end of file diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 008cb14296..79a84fd24e 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -84,7 +84,8 @@ var ( noauthFlag = flag.Bool("noauth", false, "Enables funding requests without authentication") logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet") - twitterBearerToken = flag.String("twitter.token", "", "Twitter bearer token to authenticate with the twitter API") + twitterTokenFlag = flag.String("twitter.token", "", "Bearer token to authenticate with the v2 Twitter API") + twitterTokenV1Flag = flag.String("twitter.token.v1", "", "Bearer token to authenticate with the v1.1 Twitter API") ) var ( @@ -245,6 +246,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u cfg.NetworkId = network cfg.Genesis = genesis utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock(nil).Hash()) + lesBackend, err := les.New(stack, &cfg) if err != nil { return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) @@ -388,8 +390,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { if err = conn.ReadJSON(&msg); err != nil { return } - if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") && - !strings.HasPrefix(msg.URL, "https://plus.google.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") { + if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://twitter.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") { if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil { log.Warn("Failed to send URL error to client", "err", err) return @@ -451,21 +452,8 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { address common.Address ) switch { - case strings.HasPrefix(msg.URL, "https://gist.github.com/"): - if err = sendError(conn, errors.New("GitHub authentication discontinued at the official request of GitHub")); err != nil { - log.Warn("Failed to send GitHub deprecation to client", "err", err) - return - } - continue - case strings.HasPrefix(msg.URL, "https://plus.google.com/"): - //lint:ignore ST1005 Google is a company name and should be capitalized. - if err = sendError(conn, errors.New("Google+ authentication discontinued as the service was sunset")); err != nil { - log.Warn("Failed to send Google+ deprecation to client", "err", err) - return - } - continue case strings.HasPrefix(msg.URL, "https://twitter.com/"): - id, username, avatar, address, err = authTwitter(msg.URL, *twitterBearerToken) + id, username, avatar, address, err = authTwitter(msg.URL, *twitterTokenV1Flag, *twitterTokenFlag) case strings.HasPrefix(msg.URL, "https://www.facebook.com/"): username, avatar, address, err = authFacebook(msg.URL) id = username @@ -690,23 +678,31 @@ func sendSuccess(conn *websocket.Conn, msg string) error { // authTwitter tries to authenticate a faucet request using Twitter posts, returning // the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success. -func authTwitter(url string, token string) (string, string, string, common.Address, error) { +func authTwitter(url string, tokenV1, tokenV2 string) (string, string, string, common.Address, error) { // Ensure the user specified a meaningful URL, no fancy nonsense parts := strings.Split(url, "/") if len(parts) < 4 || parts[len(parts)-2] != "status" { //lint:ignore ST1005 This error is to be displayed in the browser return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL") } - + // Strip any query parameters from the tweet id and ensure it's numeric + tweetID := strings.Split(parts[len(parts)-1], "?")[0] + if !regexp.MustCompile("^[0-9]+$").MatchString(tweetID) { + return "", "", "", common.Address{}, errors.New("Invalid Tweet URL") + } // Twitter's API isn't really friendly with direct links. // It is restricted to 300 queries / 15 minute with an app api key. // Anything more will require read only authorization from the users and that we want to avoid. - // If twitter bearer token is provided, use the twitter api - if token != "" { - return authTwitterWithToken(parts[len(parts)-1], token) + // If Twitter bearer token is provided, use the API, selecting the version + // the user would prefer (currently there's a limit of 1 v2 app / developer + // but unlimited v1.1 apps). + switch { + case tokenV1 != "": + return authTwitterWithTokenV1(tweetID, tokenV1) + case tokenV2 != "": + return authTwitterWithTokenV2(tweetID, tokenV2) } - // Twiter API token isn't provided so we just load the public posts // and scrape it for the Ethereum address and profile URL. We need to load // the mobile page though since the main page loads tweet contents via JS. @@ -742,19 +738,49 @@ func authTwitter(url string, token string) (string, string, string, common.Addre return username + "@twitter", username, avatar, address, nil } -// authTwitterWithToken tries to authenticate a faucet request using Twitter's API, returning -// the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success. -func authTwitterWithToken(tweetID string, token string) (string, string, string, common.Address, error) { - // Strip any query parameters from the tweet id - sanitizedTweetID := strings.Split(tweetID, "?")[0] +// authTwitterWithTokenV1 tries to authenticate a faucet request using Twitter's v1 +// API, returning the user id, username, avatar URL and Ethereum address to fund on +// success. +func authTwitterWithTokenV1(tweetID string, token string) (string, string, string, common.Address, error) { + // Query the tweet details from Twitter + url := fmt.Sprintf("https://api.twitter.com/1.1/statuses/show.json?id=%s", tweetID) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", "", "", common.Address{}, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + res, err := http.DefaultClient.Do(req) + if err != nil { + return "", "", "", common.Address{}, err + } + defer res.Body.Close() - // Ensure numeric tweetID - if !regexp.MustCompile("^[0-9]+$").MatchString(sanitizedTweetID) { - return "", "", "", common.Address{}, errors.New("Invalid Tweet URL") + var result struct { + Text string `json:"text"` + User struct { + ID string `json:"id_str"` + Username string `json:"screen_name"` + Avatar string `json:"profile_image_url"` + } `json:"user"` } + err = json.NewDecoder(res.Body).Decode(&result) + if err != nil { + return "", "", "", common.Address{}, err + } + address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(result.Text)) + if address == (common.Address{}) { + //lint:ignore ST1005 This error is to be displayed in the browser + return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") + } + return result.User.ID + "@twitter", result.User.Username, result.User.Avatar, address, nil +} +// authTwitterWithTokenV2 tries to authenticate a faucet request using Twitter's v2 +// API, returning the user id, username, avatar URL and Ethereum address to fund on +// success. +func authTwitterWithTokenV2(tweetID string, token string) (string, string, string, common.Address, error) { // Query the tweet details from Twitter - url := fmt.Sprintf("https://api.twitter.com/2/tweets/%s?expansions=author_id&user.fields=profile_image_url", sanitizedTweetID) + url := fmt.Sprintf("https://api.twitter.com/2/tweets/%s?expansions=author_id&user.fields=profile_image_url", tweetID) req, err := http.NewRequest("GET", url, nil) if err != nil { return "", "", "", common.Address{}, err @@ -769,15 +795,13 @@ func authTwitterWithToken(tweetID string, token string) (string, string, string, var result struct { Data struct { AuthorID string `json:"author_id"` - ID string `json:"id"` Text string `json:"text"` } `json:"data"` Includes struct { Users []struct { - ProfileImageURL string `json:"profile_image_url"` - Username string `json:"username"` - ID string `json:"id"` - Name string `json:"name"` + ID string `json:"id"` + Username string `json:"username"` + Avatar string `json:"profile_image_url"` } `json:"users"` } `json:"includes"` } @@ -792,7 +816,7 @@ func authTwitterWithToken(tweetID string, token string) (string, string, string, //lint:ignore ST1005 This error is to be displayed in the browser return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") } - return result.Data.AuthorID + "@twitter", result.Includes.Users[0].Username, result.Includes.Users[0].ProfileImageURL, address, nil + return result.Data.AuthorID + "@twitter", result.Includes.Users[0].Username, result.Includes.Users[0].Avatar, address, nil } // authFacebook tries to authenticate a faucet request using Facebook posts, diff --git a/cmd/puppeth/module_faucet.go b/cmd/puppeth/module_faucet.go index 2527e137f2..88cb80ae4c 100644 --- a/cmd/puppeth/module_faucet.go +++ b/cmd/puppeth/module_faucet.go @@ -46,7 +46,7 @@ ENTRYPOINT [ \ "--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \ "--account.json", "/account.json", "--account.pass", "/account.pass" \ {{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}} \ - {{if .TwitterToken}}, "--twitter.token", "{{.TwitterToken}}", + {{if .TwitterToken}}, "--twitter.token.v1", "{{.TwitterToken}}"{{end}} \ ]` // faucetComposefile is the docker-compose.yml file required to deploy and maintain diff --git a/cmd/puppeth/wizard_faucet.go b/cmd/puppeth/wizard_faucet.go index 47e05cd9c1..65d4e8b8ed 100644 --- a/cmd/puppeth/wizard_faucet.go +++ b/cmd/puppeth/wizard_faucet.go @@ -102,11 +102,10 @@ func (w *wizard) deployFaucet() { infos.captchaSecret = w.readPassword() } } - - // Accessing the twitter api requires a bearer token, request it + // Accessing the Twitter API requires a bearer token, request it if infos.twitterToken != "" { fmt.Println() - fmt.Println("Reuse previous twitter API Bearer token (y/n)? (default = yes)") + fmt.Println("Reuse previous Twitter API token (y/n)? (default = yes)") if !w.readDefaultYesNo(true) { infos.twitterToken = "" } @@ -114,17 +113,10 @@ func (w *wizard) deployFaucet() { if infos.twitterToken == "" { // No previous twitter token (or old one discarded) fmt.Println() - fmt.Println("Enable twitter API (y/n)? (default = no)") - if !w.readDefaultYesNo(false) { - log.Warn("The faucet will fallback to using direct calls") - } else { - // Twitter api explicitly requested, read the bearer token - fmt.Println() - fmt.Printf("What is the twitter API Bearer token?\n") - infos.twitterToken = w.readString() - } + fmt.Println() + fmt.Printf("What is the Twitter API app Bearer token?\n") + infos.twitterToken = w.readString() } - // Figure out where the user wants to store the persistent data fmt.Println() if infos.node.datadir == "" { From 9584f56b9d2fe950b8fa70f5c7398de404f6b71c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 5 Jan 2021 10:44:33 +0100 Subject: [PATCH 095/235] miner: avoid sleeping in miner (#22108) This PR removes a logic in the miner, which was originally intended to help temporary testnets based on ethash from "running off into the future". If the difficulty was low, and a few computers started mining several blocks per second, the ethash rules (which demand 1s delay between blocks) would push the blocktimes further and further away. The solution was to make the miner sleep while this happened. Nowadays, this problem is solved instead by PoA chains, and it's recommended to let testnets and devnets be based on clique instead. The existing logic is problematic, since it can cause stalls within the miner making it difficult for remote workers to submit work if the channel is blocked on a sleep. Credits to Saar Tochner for reporting this via the bug bounty --- miner/worker.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 5f07affdc4..2c5032c656 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -861,13 +861,6 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) if parent.Time() >= uint64(timestamp) { timestamp = int64(parent.Time() + 1) } - // this will ensure we're not going off too far in the future - if now := time.Now().Unix(); timestamp > now+1 { - wait := time.Duration(timestamp-now) * time.Second - log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait)) - time.Sleep(wait) - } - num := parent.Number() header := &types.Header{ ParentHash: parent.Hash(), From 664903dc889ec295b8eea292964547dc7910a26d Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 5 Jan 2021 10:18:22 +0000 Subject: [PATCH 096/235] cmd/geth: usb is off by default (#21984) --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 3 ++- cmd/utils/flags.go | 11 +++++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0d1b569b8b..a2d5c36e76 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -68,6 +68,7 @@ var ( utils.KeyStoreDirFlag, utils.ExternalSignerFlag, utils.NoUSBFlag, + utils.USBFlag, utils.SmartCardDaemonPathFlag, utils.EthashCacheDirFlag, utils.EthashCachesInMemoryFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 0e70451ed3..73fdcacac5 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -37,7 +37,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.DataDirFlag, utils.AncientFlag, utils.KeyStoreDirFlag, - utils.NoUSBFlag, + utils.USBFlag, utils.SmartCardDaemonPathFlag, utils.NetworkIdFlag, utils.GoerliFlag, @@ -219,6 +219,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ { Name: "ALIASED (deprecated)", Flags: append([]cli.Flag{ + utils.NoUSBFlag, utils.LegacyRPCEnabledFlag, utils.LegacyRPCListenAddrFlag, utils.LegacyRPCPortFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c51d7916ca..20b1744fdd 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -119,7 +119,11 @@ var ( } NoUSBFlag = cli.BoolFlag{ Name: "nousb", - Usage: "Disables monitoring for and managing USB hardware wallets", + Usage: "Disables monitoring for and managing USB hardware wallets (deprecated)", + } + USBFlag = cli.BoolFlag{ + Name: "usb", + Usage: "Enable monitoring and management of USB hardware wallets", } SmartCardDaemonPathFlag = cli.StringFlag{ Name: "pcscdpath", @@ -1225,8 +1229,11 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LightKDFFlag.Name) { cfg.UseLightweightKDF = ctx.GlobalBool(LightKDFFlag.Name) } + if ctx.GlobalIsSet(USBFlag.Name) { + cfg.NoUSB = !ctx.GlobalBool(USBFlag.Name) + } if ctx.GlobalIsSet(NoUSBFlag.Name) { - cfg.NoUSB = ctx.GlobalBool(NoUSBFlag.Name) + log.Warn("Option nousb is deprecated and USB is deactivated by default. Use --usb to enable") } if ctx.GlobalIsSet(InsecureUnlockAllowedFlag.Name) { cfg.InsecureUnlockAllowed = ctx.GlobalBool(InsecureUnlockAllowedFlag.Name) From eb2a1dfdd21eeb89fcd6f9c06d42770e5078a6ed Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 5 Jan 2021 02:22:32 -0800 Subject: [PATCH 097/235] graphql: use a decimal representation for gas limit and gas used (#21883) This changes the JSON encoding of blocks returned by the API to have decimal instead of hexadecimal numbers. The spec wants it this way. Co-authored-by: Martin Holst Swende --- graphql/graphql.go | 56 ++++++++++--- graphql/graphql_test.go | 175 ++++++++++++++++++++++++---------------- 2 files changed, 150 insertions(+), 81 deletions(-) diff --git a/graphql/graphql.go b/graphql/graphql.go index 22cfcf6637..66c581628b 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -20,6 +20,8 @@ package graphql import ( "context" "errors" + "fmt" + "strconv" "time" "github.com/ethereum/go-ethereum" @@ -39,6 +41,37 @@ var ( errBlockInvariant = errors.New("block objects must be instantiated with at least one of num or hash") ) +type Long int64 + +// ImplementsGraphQLType returns true if Long implements the provided GraphQL type. +func (b Long) ImplementsGraphQLType(name string) bool { return name == "Long" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *Long) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + // uncomment to support hex values + //if strings.HasPrefix(input, "0x") { + // // apply leniency and support hex representations of longs. + // value, err := hexutil.DecodeUint64(input) + // *b = Long(value) + // return err + //} else { + value, err := strconv.ParseInt(input, 10, 64) + *b = Long(value) + return err + //} + case int32: + *b = Long(input) + case int64: + *b = Long(input) + default: + err = fmt.Errorf("unexpected type %T for Long", input) + } + return err +} + // Account represents an Ethereum account at a particular block. type Account struct { backend ethapi.Backend @@ -415,13 +448,13 @@ func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) { return b.receipts, nil } -func (b *Block) Number(ctx context.Context) (hexutil.Uint64, error) { +func (b *Block) Number(ctx context.Context) (Long, error) { header, err := b.resolveHeader(ctx) if err != nil { return 0, err } - return hexutil.Uint64(header.Number.Uint64()), nil + return Long(header.Number.Uint64()), nil } func (b *Block) Hash(ctx context.Context) (common.Hash, error) { @@ -435,20 +468,20 @@ func (b *Block) Hash(ctx context.Context) (common.Hash, error) { return b.hash, nil } -func (b *Block) GasLimit(ctx context.Context) (hexutil.Uint64, error) { +func (b *Block) GasLimit(ctx context.Context) (Long, error) { header, err := b.resolveHeader(ctx) if err != nil { return 0, err } - return hexutil.Uint64(header.GasLimit), nil + return Long(header.GasLimit), nil } -func (b *Block) GasUsed(ctx context.Context) (hexutil.Uint64, error) { +func (b *Block) GasUsed(ctx context.Context) (Long, error) { header, err := b.resolveHeader(ctx) if err != nil { return 0, err } - return hexutil.Uint64(header.GasUsed), nil + return Long(header.GasUsed), nil } func (b *Block) Parent(ctx context.Context) (*Block, error) { @@ -902,11 +935,14 @@ type Resolver struct { } func (r *Resolver) Block(ctx context.Context, args struct { - Number *hexutil.Uint64 + Number *Long Hash *common.Hash }) (*Block, error) { var block *Block if args.Number != nil { + if *args.Number < 0 { + return nil, nil + } number := rpc.BlockNumber(*args.Number) numberOrHash := rpc.BlockNumberOrHashWithNumber(number) block = &Block{ @@ -939,10 +975,10 @@ func (r *Resolver) Block(ctx context.Context, args struct { } func (r *Resolver) Blocks(ctx context.Context, args struct { - From hexutil.Uint64 - To *hexutil.Uint64 + From *Long + To *Long }) ([]*Block, error) { - from := rpc.BlockNumber(args.From) + from := rpc.BlockNumber(*args.From) var to rpc.BlockNumber if args.To != nil { diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 98c8622ee6..77129673c0 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -19,18 +19,17 @@ package graphql import ( "fmt" "io/ioutil" + "math/big" "net/http" "strings" "testing" "time" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" - "github.com/stretchr/testify/assert" + "github.com/ethereum/go-ethereum/params" ) func TestBuildSchema(t *testing.T) { @@ -45,29 +44,95 @@ func TestBuildSchema(t *testing.T) { } // Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint -func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) { +func TestGraphQLBlockSerialization(t *testing.T) { stack := createNode(t, true) defer stack.Close() // start node if err := stack.Start(); err != nil { t.Fatalf("could not start node: %v", err) } - // create http request - body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") - gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) - if err != nil { - t.Error("could not issue new http request ", err) - } - gqlReq.Header.Set("Content-Type", "application/json") - // read from response - resp := doHTTPRequest(t, gqlReq) - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("could not read from response body: %v", err) + + for i, tt := range []struct { + body string + want string + code int + }{ + { // Should return latest block + body: `{"query": "{block{number}}","variables": null}`, + want: `{"data":{"block":{"number":10}}}`, + code: 200, + }, + { // Should return info about latest block + body: `{"query": "{block{number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":{"number":10,"gasUsed":0,"gasLimit":11500000}}}`, + code: 200, + }, + { + body: `{"query": "{block(number:0){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":{"number":0,"gasUsed":0,"gasLimit":11500000}}}`, + code: 200, + }, + { + body: `{"query": "{block(number:-1){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:-500){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"0\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":{"number":0,"gasUsed":0,"gasLimit":11500000}}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"-33\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"1337\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"0xbad\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0xbad\": invalid syntax"}],"data":{}}`, + code: 400, + }, + { // hex strings are currently not supported. If that's added to the spec, this test will need to change + body: `{"query": "{block(number:\"0x0\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0x0\": invalid syntax"}],"data":{}}`, + code: 400, + }, + { + body: `{"query": "{block(number:\"a\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"errors":[{"message":"strconv.ParseInt: parsing \"a\": invalid syntax"}],"data":{}}`, + code: 400, + }, + { + body: `{"query": "{bleh{number}}","variables": null}"`, + want: `{"errors":[{"message":"Cannot query field \"bleh\" on type \"Query\".","locations":[{"line":1,"column":2}]}]}`, + code: 400, + }, + } { + resp, err := http.Post(fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), "application/json", strings.NewReader(tt.body)) + if err != nil { + t.Fatalf("could not post: %v", err) + } + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("could not read from response body: %v", err) + } + if have := string(bodyBytes); have != tt.want { + t.Errorf("testcase %d %s,\nhave:\n%v\nwant:\n%v", i, tt.body, have, tt.want) + } + if tt.code != resp.StatusCode { + t.Errorf("testcase %d %s,\nwrong statuscode, have: %v, want: %v", i, tt.body, resp.StatusCode, tt.code) + } } - expected := "{\"data\":{\"block\":{\"number\":\"0x0\"}}}" - assert.Equal(t, 200, resp.StatusCode) - assert.Equal(t, expected, string(bodyBytes)) } // Tests that a graphQL request is not handled successfully when graphql is not enabled on the specified endpoint @@ -77,49 +142,22 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { if err := stack.Start(); err != nil { t.Fatalf("could not start node: %v", err) } - - // create http request - body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") - gqlReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) + body := strings.NewReader(`{"query": "{block{number}}","variables": null}`) + resp, err := http.Post(fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), "application/json", body) if err != nil { - t.Error("could not issue new http request ", err) + t.Fatalf("could not post: %v", err) } - gqlReq.Header.Set("Content-Type", "application/json") - // read from response - resp := doHTTPRequest(t, gqlReq) bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("could not read from response body: %v", err) } // make sure the request is not handled successfully - assert.Equal(t, 404, resp.StatusCode) - assert.Equal(t, "404 page not found\n", string(bodyBytes)) -} - -// Tests that 400 is returned when an invalid RPC request is made. -func TestGraphQL_BadRequest(t *testing.T) { - stack := createNode(t, true) - defer stack.Close() - // start node - if err := stack.Start(); err != nil { - t.Fatalf("could not start node: %v", err) + if want, have := "404 page not found\n", string(bodyBytes); have != want { + t.Errorf("have:\n%v\nwant:\n%v", have, want) } - // create http request - body := strings.NewReader("{\"query\": \"{bleh{number}}\",\"variables\": null}") - gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) - if err != nil { - t.Error("could not issue new http request ", err) - } - gqlReq.Header.Set("Content-Type", "application/json") - // read from response - resp := doHTTPRequest(t, gqlReq) - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("could not read from response body: %v", err) + if want, have := 404, resp.StatusCode; want != have { + t.Errorf("wrong statuscode, have:\n%v\nwant:%v", have, want) } - expected := "{\"errors\":[{\"message\":\"Cannot query field \\\"bleh\\\" on type \\\"Query\\\".\",\"locations\":[{\"line\":1,\"column\":2}]}]}" - assert.Equal(t, expected, string(bodyBytes)) - assert.Equal(t, 400, resp.StatusCode) } func createNode(t *testing.T, gqlEnabled bool) *node.Node { @@ -135,21 +173,20 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { if !gqlEnabled { return stack } - createGQLService(t, stack, "127.0.0.1:9393") - return stack } func createGQLService(t *testing.T, stack *node.Node, endpoint string) { - // create backend (use a config which is light on mem consumption) + // create backend ethConf := ð.Config{ - Genesis: core.DeveloperGenesisBlock(15, common.Address{}), - Miner: miner.Config{ - Etherbase: common.HexToAddress("0xaabb"), + Genesis: &core.Genesis{ + Config: params.AllEthashProtocolChanges, + GasLimit: 11500000, + Difficulty: big.NewInt(1048576), }, Ethash: ethash.Config{ - PowMode: ethash.ModeTest, + PowMode: ethash.ModeFake, }, NetworkId: 1337, TrieCleanCache: 5, @@ -163,20 +200,16 @@ func createGQLService(t *testing.T, stack *node.Node, endpoint string) { if err != nil { t.Fatalf("could not create eth backend: %v", err) } - + // Create some blocks and import them + chain, _ := core.GenerateChain(params.AllEthashProtocolChanges, ethBackend.BlockChain().Genesis(), + ethash.NewFaker(), ethBackend.ChainDb(), 10, func(i int, gen *core.BlockGen) {}) + _, err = ethBackend.BlockChain().InsertChain(chain) + if err != nil { + t.Fatalf("could not create import blocks: %v", err) + } // create gql service err = New(stack, ethBackend.APIBackend, []string{}, []string{}) if err != nil { t.Fatalf("could not create graphql service: %v", err) } } - -func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - t.Fatal("could not issue a GET request to the given endpoint", err) - - } - return resp -} From 4714ce9430d2519e869a993d9f973a062dfc52d6 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 5 Jan 2021 14:31:23 +0100 Subject: [PATCH 098/235] cmd/geth: added --mainnet flag (#21932) * cmd/geth: added --mainnet flag * cmd/utils: set default genesis if --mainnet is specified * cmd/utils: addressed comments --- cmd/geth/chaincmd.go | 2 ++ cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 12 +++++++++++- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 6418f90957..f539322654 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -159,6 +159,7 @@ The export-preimages command export hash preimages to an RLP encoded stream`, utils.CacheFlag, utils.SyncModeFlag, utils.FakePoWFlag, + utils.MainnetFlag, utils.RopstenFlag, utils.RinkebyFlag, utils.TxLookupLimitFlag, @@ -210,6 +211,7 @@ Use "ethereum dump 0" to dump the genesis block.`, utils.DataDirFlag, utils.AncientFlag, utils.CacheFlag, + utils.MainnetFlag, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, diff --git a/cmd/geth/main.go b/cmd/geth/main.go index a2d5c36e76..7af24e6523 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -140,6 +140,7 @@ var ( utils.NodeKeyFileFlag, utils.NodeKeyHexFlag, utils.DNSDiscoveryFlag, + utils.MainnetFlag, utils.DeveloperFlag, utils.DeveloperPeriodFlag, utils.LegacyTestnetFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 73fdcacac5..78ebb807e1 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -40,6 +40,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.USBFlag, utils.SmartCardDaemonPathFlag, utils.NetworkIdFlag, + utils.MainnetFlag, utils.GoerliFlag, utils.RinkebyFlag, utils.YoloV2Flag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 20b1744fdd..764d7ad73e 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -135,6 +135,10 @@ var ( Usage: "Explicitly set network id (integer)(For testnets: use --ropsten, --rinkeby, --goerli instead)", Value: eth.DefaultConfig.NetworkId, } + MainnetFlag = cli.BoolFlag{ + Name: "mainnet", + Usage: "Ethereum mainnet", + } GoerliFlag = cli.BoolFlag{ Name: "goerli", Usage: "Görli network: pre-configured proof-of-authority test network", @@ -1494,7 +1498,7 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV2Flag) + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV2Flag) CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) @@ -1608,6 +1612,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { } // Override any default configs for hard coded networks. switch { + case ctx.GlobalBool(MainnetFlag.Name): + if !ctx.GlobalIsSet(NetworkIdFlag.Name) { + cfg.NetworkId = 1 + } + cfg.Genesis = core.DefaultGenesisBlock() + SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 3 From 9ba306d47ef3211de83bb858643abab77faf0528 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 5 Jan 2021 14:48:22 +0100 Subject: [PATCH 099/235] common/compiler: fix parsing of solc output with solidity v.0.8.0 (#22092) Solidity 0.8.0 changes the way that output is marshalled. This patch allows to parse both the legacy format used previously and the new format. See also https://docs.soliditylang.org/en/breaking/080-breaking-changes.html#interface-changes --- common/compiler/solidity.go | 50 +++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/common/compiler/solidity.go b/common/compiler/solidity.go index b689f258a3..01de3d4c65 100644 --- a/common/compiler/solidity.go +++ b/common/compiler/solidity.go @@ -44,6 +44,20 @@ type solcOutput struct { Version string } +// solidity v.0.8 changes the way ABI, Devdoc and Userdoc are serialized +type solcOutputV8 struct { + Contracts map[string]struct { + BinRuntime string `json:"bin-runtime"` + SrcMapRuntime string `json:"srcmap-runtime"` + Bin, SrcMap, Metadata string + Abi interface{} + Devdoc interface{} + Userdoc interface{} + Hashes map[string]string + } + Version string +} + func (s *Solidity) makeArgs() []string { p := []string{ "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc", @@ -125,7 +139,6 @@ func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, erro if err := cmd.Run(); err != nil { return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes()) } - return ParseCombinedJSON(stdout.Bytes(), source, s.Version, s.Version, strings.Join(s.makeArgs(), " ")) } @@ -141,7 +154,8 @@ func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, erro func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { var output solcOutput if err := json.Unmarshal(combinedJSON, &output); err != nil { - return nil, err + // Try to parse the output with the new solidity v.0.8.0 rules + return parseCombinedJSONV8(combinedJSON, source, languageVersion, compilerVersion, compilerOptions) } // Compilation succeeded, assemble and return the contracts. contracts := make(map[string]*Contract) @@ -176,3 +190,35 @@ func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion strin } return contracts, nil } + +// parseCombinedJSONV8 parses the direct output of solc --combined-output +// and parses it using the rules from solidity v.0.8.0 and later. +func parseCombinedJSONV8(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { + var output solcOutputV8 + if err := json.Unmarshal(combinedJSON, &output); err != nil { + return nil, err + } + // Compilation succeeded, assemble and return the contracts. + contracts := make(map[string]*Contract) + for name, info := range output.Contracts { + contracts[name] = &Contract{ + Code: "0x" + info.Bin, + RuntimeCode: "0x" + info.BinRuntime, + Hashes: info.Hashes, + Info: ContractInfo{ + Source: source, + Language: "Solidity", + LanguageVersion: languageVersion, + CompilerVersion: compilerVersion, + CompilerOptions: compilerOptions, + SrcMap: info.SrcMap, + SrcMapRuntime: info.SrcMapRuntime, + AbiDefinition: info.Abi, + UserDoc: info.Userdoc, + DeveloperDoc: info.Devdoc, + Metadata: info.Metadata, + }, + } + } + return contracts, nil +} From 618454214b70124646dc1c6333a59138b97d6b0a Mon Sep 17 00:00:00 2001 From: jk-jeongkyun <45347815+jeongkyun-oh@users.noreply.github.com> Date: Tue, 5 Jan 2021 22:56:01 +0900 Subject: [PATCH 100/235] eth/downloader: enhanced test cases for downloader queue (#22114) --- eth/downloader/queue_test.go | 44 ++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go index aedfba4565..f43ad67a41 100644 --- a/eth/downloader/queue_test.go +++ b/eth/downloader/queue_test.go @@ -97,6 +97,9 @@ func dummyPeer(id string) *peerConnection { } func TestBasics(t *testing.T) { + numOfBlocks := len(emptyChain.blocks) + numOfReceipts := len(emptyChain.blocks) / 2 + q := newQueue(10, 10) if !q.Idle() { t.Errorf("new queue should be idle") @@ -135,6 +138,12 @@ func TestBasics(t *testing.T) { t.Fatalf("expected header %d, got %d", exp, got) } } + if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { + t.Errorf("expected block task queue to be %d, got %d", exp, got) + } + if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got { + t.Errorf("expected receipt task queue to be %d, got %d", exp, got) + } { peer := dummyPeer("peer-2") fetchReq, _, throttle := q.ReserveBodies(peer, 50) @@ -148,8 +157,12 @@ func TestBasics(t *testing.T) { t.Fatalf("should have no fetches, got %d", len(fetchReq.Headers)) } } - //fmt.Printf("blockTaskQueue len: %d\n", q.blockTaskQueue.Size()) - //fmt.Printf("receiptTaskQueue len: %d\n", q.receiptTaskQueue.Size()) + if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { + t.Errorf("expected block task queue to be %d, got %d", exp, got) + } + if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got { + t.Errorf("expected receipt task queue to be %d, got %d", exp, got) + } { // The receipt delivering peer should not be affected // by the throttling of body deliveries @@ -168,12 +181,20 @@ func TestBasics(t *testing.T) { } } - //fmt.Printf("blockTaskQueue len: %d\n", q.blockTaskQueue.Size()) - //fmt.Printf("receiptTaskQueue len: %d\n", q.receiptTaskQueue.Size()) - //fmt.Printf("processable: %d\n", q.resultCache.countCompleted()) + if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { + t.Errorf("expected block task queue to be %d, got %d", exp, got) + } + if exp, got := q.receiptTaskQueue.Size(), numOfReceipts-5; exp != got { + t.Errorf("expected receipt task queue to be %d, got %d", exp, got) + } + if got, exp := q.resultCache.countCompleted(), 0; got != exp { + t.Errorf("wrong processable count, got %d, exp %d", got, exp) + } } func TestEmptyBlocks(t *testing.T) { + numOfBlocks := len(emptyChain.blocks) + q := newQueue(10, 10) q.Prepare(1, FastSync) @@ -208,13 +229,12 @@ func TestEmptyBlocks(t *testing.T) { } } - if q.blockTaskQueue.Size() != len(emptyChain.blocks)-10 { - t.Errorf("expected block task queue to be 0, got %d", q.blockTaskQueue.Size()) + if q.blockTaskQueue.Size() != numOfBlocks-10 { + t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size()) } if q.receiptTaskQueue.Size() != 0 { - t.Errorf("expected receipt task queue to be 0, got %d", q.receiptTaskQueue.Size()) + t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size()) } - //fmt.Printf("receiptTaskQueue len: %d\n", q.receiptTaskQueue.Size()) { peer := dummyPeer("peer-3") fetchReq, _, _ := q.ReserveReceipts(peer, 50) @@ -224,6 +244,12 @@ func TestEmptyBlocks(t *testing.T) { t.Fatal("there should be no body fetch tasks remaining") } } + if q.blockTaskQueue.Size() != numOfBlocks-10 { + t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size()) + } + if q.receiptTaskQueue.Size() != 0 { + t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size()) + } if got, exp := q.resultCache.countCompleted(), 10; got != exp { t.Errorf("wrong processable count, got %d, exp %d", got, exp) } From 83d317cff937940395fcb7ece29effc9c7779c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 6 Jan 2021 08:37:45 +0200 Subject: [PATCH 101/235] cmd/utils, eth/downloader: minor snap nitpicks --- cmd/utils/flags.go | 1 - eth/downloader/downloader.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 764d7ad73e..684e3428ba 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1568,7 +1568,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // If snap-sync is requested, this flag is also required if cfg.SyncMode == downloader.SnapSync { log.Info("Snap sync requested, enabling --snapshot") - ctx.Set(SnapshotFlag.Name, "true") } else { cfg.TrieCleanCache += cfg.SnapshotCache cfg.SnapshotCache = 0 // Disabled diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 3123598437..315354ea99 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -89,7 +89,7 @@ var ( errCancelContentProcessing = errors.New("content processing canceled (requested)") errCanceled = errors.New("syncing canceled (requested)") errNoSyncActive = errors.New("no sync active") - errTooOld = errors.New("peer doesn't speak recent enough protocol version (need version >= 64)") + errTooOld = errors.New("peer's protocol version too old") ) type Downloader struct { @@ -460,7 +460,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I } }() if p.version < 64 { - return fmt.Errorf("%w, peer version: %d", errTooOld, p.version) + return fmt.Errorf("%w: advertized %d < required %d", errTooOld, p.version, 64) } mode := d.getMode() From d667ee2d1063faad0b4347db498bda00f34a1ba6 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 6 Jan 2021 12:06:44 +0100 Subject: [PATCH 102/235] crypto: fix ineffectual assignments (#22124) * crypto/bls12381: fixed ineffectual assignment * crypto/signify: fix ineffectual assignment --- crypto/bls12381/arithmetic_fallback.go | 2 +- crypto/signify/signify_fuzz.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crypto/bls12381/arithmetic_fallback.go b/crypto/bls12381/arithmetic_fallback.go index 19fb589104..91cabf4f3d 100644 --- a/crypto/bls12381/arithmetic_fallback.go +++ b/crypto/bls12381/arithmetic_fallback.go @@ -207,7 +207,7 @@ func lsubAssign(z, x *fe) { z[2], b = bits.Sub64(z[2], x[2], b) z[3], b = bits.Sub64(z[3], x[3], b) z[4], b = bits.Sub64(z[4], x[4], b) - z[5], b = bits.Sub64(z[5], x[5], b) + z[5], _ = bits.Sub64(z[5], x[5], b) } func neg(z *fe, x *fe) { diff --git a/crypto/signify/signify_fuzz.go b/crypto/signify/signify_fuzz.go index d1bcf356a4..f9167900ad 100644 --- a/crypto/signify/signify_fuzz.go +++ b/crypto/signify/signify_fuzz.go @@ -25,7 +25,6 @@ import ( "log" "os" "os/exec" - "runtime" fuzz "github.com/google/gofuzz" "github.com/jedisct1/go-minisign" @@ -129,6 +128,9 @@ func getKey(fileS string) (string, error) { func createKeyPair() (string, string) { // Create key and put it in correct format tmpKey, err := ioutil.TempFile("", "") + if err != nil { + panic(err) + } defer os.Remove(tmpKey.Name()) defer os.Remove(tmpKey.Name() + ".pub") defer os.Remove(tmpKey.Name() + ".sec") From 072fd9625472b585dc7c0bd5fab17e289c0d91a9 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 6 Jan 2021 17:19:16 +0100 Subject: [PATCH 103/235] graphql: return decimal for `estimateGas` and `cumulativeGas` queries (#22126) * estimateGas, cumulativeGas * linted * add test for estimateGas --- graphql/graphql.go | 27 ++++++++++++++------------- graphql/graphql_test.go | 6 ++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/graphql/graphql.go b/graphql/graphql.go index 66c581628b..ea587106b4 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -301,21 +301,21 @@ func (t *Transaction) Status(ctx context.Context) (*hexutil.Uint64, error) { return &ret, nil } -func (t *Transaction) GasUsed(ctx context.Context) (*hexutil.Uint64, error) { +func (t *Transaction) GasUsed(ctx context.Context) (*Long, error) { receipt, err := t.getReceipt(ctx) if err != nil || receipt == nil { return nil, err } - ret := hexutil.Uint64(receipt.GasUsed) + ret := Long(receipt.GasUsed) return &ret, nil } -func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*hexutil.Uint64, error) { +func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*Long, error) { receipt, err := t.getReceipt(ctx) if err != nil || receipt == nil { return nil, err } - ret := hexutil.Uint64(receipt.CumulativeGasUsed) + ret := Long(receipt.CumulativeGasUsed) return &ret, nil } @@ -811,7 +811,7 @@ type CallData struct { // CallResult encapsulates the result of an invocation of the `call` accessor. type CallResult struct { data hexutil.Bytes // The return data from the call - gasUsed hexutil.Uint64 // The amount of gas used + gasUsed Long // The amount of gas used status hexutil.Uint64 // The return status of the call - 0 for failure or 1 for success. } @@ -819,7 +819,7 @@ func (c *CallResult) Data() hexutil.Bytes { return c.data } -func (c *CallResult) GasUsed() hexutil.Uint64 { +func (c *CallResult) GasUsed() Long { return c.gasUsed } @@ -847,22 +847,22 @@ func (b *Block) Call(ctx context.Context, args struct { return &CallResult{ data: result.ReturnData, - gasUsed: hexutil.Uint64(result.UsedGas), + gasUsed: Long(result.UsedGas), status: status, }, nil } func (b *Block) EstimateGas(ctx context.Context, args struct { Data ethapi.CallArgs -}) (hexutil.Uint64, error) { +}) (Long, error) { if b.numberOrHash == nil { _, err := b.resolveHeader(ctx) if err != nil { - return hexutil.Uint64(0), err + return 0, err } } gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.numberOrHash, b.backend.RPCGasCap()) - return gas, err + return Long(gas), err } type Pending struct { @@ -917,16 +917,17 @@ func (p *Pending) Call(ctx context.Context, args struct { return &CallResult{ data: result.ReturnData, - gasUsed: hexutil.Uint64(result.UsedGas), + gasUsed: Long(result.UsedGas), status: status, }, nil } func (p *Pending) EstimateGas(ctx context.Context, args struct { Data ethapi.CallArgs -}) (hexutil.Uint64, error) { +}) (Long, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - return ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap()) + gas, err := ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap()) + return Long(gas), err } // Resolver is the top-level object in the GraphQL hierarchy. diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 77129673c0..fc62da1813 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -117,6 +117,12 @@ func TestGraphQLBlockSerialization(t *testing.T) { want: `{"errors":[{"message":"Cannot query field \"bleh\" on type \"Query\".","locations":[{"line":1,"column":2}]}]}`, code: 400, }, + // should return `estimateGas` as decimal + { + body: `{"query": "{block{ estimateGas(data:{}) }}"}`, + want: `{"data":{"block":{"estimateGas":53000}}}`, + code: 200, + }, } { resp, err := http.Post(fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), "application/json", strings.NewReader(tt.body)) if err != nil { From d2e1b17f1828424906c9033b1dcbd3f2756a0c2c Mon Sep 17 00:00:00 2001 From: Melvin Junhee Woo Date: Thu, 7 Jan 2021 15:36:21 +0900 Subject: [PATCH 104/235] snapshot, trie: fixed typos, mostly in snapshot pkg (#22133) --- core/state/snapshot/conversion.go | 2 +- core/state/snapshot/difflayer.go | 10 +++++----- core/state/snapshot/difflayer_test.go | 2 +- core/state/snapshot/disklayer.go | 2 +- core/state/snapshot/disklayer_test.go | 2 +- core/state/snapshot/iterator.go | 4 ++-- core/state/snapshot/iterator_binary.go | 8 ++++---- core/state/snapshot/iterator_fast.go | 6 +++--- core/state/snapshot/snapshot.go | 4 ++-- trie/database.go | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index dee9ff0bf2..9832225345 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -240,7 +240,7 @@ func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGenerato } in <- leaf - // Accumulate the generaation statistic if it's required. + // Accumulate the generation statistic if it's required. processed++ if time.Since(logged) > 3*time.Second && stats != nil { if account == (common.Hash{}) { diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 0aef6cf570..3be78cae88 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -44,7 +44,7 @@ var ( // aggregatorItemLimit is an approximate number of items that will end up // in the agregator layer before it's flushed out to disk. A plain account // weighs around 14B (+hash), a storage slot 32B (+hash), a deleted slot - // 0B (+hash). Slots are mostly set/unset in lockstep, so thet average at + // 0B (+hash). Slots are mostly set/unset in lockstep, so that average at // 16B (+hash). All in all, the average entry seems to be 15+32=47B. Use a // smaller number to be on the safe side. aggregatorItemLimit = aggregatorMemoryLimit / 42 @@ -114,9 +114,9 @@ type diffLayer struct { // deleted, all data in other set belongs to the "new" A. destructSet map[common.Hash]struct{} // Keyed markers for deleted (and potentially) recreated accounts accountList []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil - accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted) + accountData map[common.Hash][]byte // Keyed accounts for direct retrieval (nil means deleted) storageList map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account. Any existing lists are sorted if non-nil - storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrival. one per account (nil means deleted) + storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrieval. one per account (nil means deleted) diffed *bloomfilter.Filter // Bloom filter tracking all the diffed items up to the disk layer @@ -482,7 +482,7 @@ func (dl *diffLayer) flatten() snapshot { } } -// AccountList returns a sorted list of all accounts in this difflayer, including +// AccountList returns a sorted list of all accounts in this diffLayer, including // the deleted ones. // // Note, the returned slice is not a copy, so do not modify it. @@ -513,7 +513,7 @@ func (dl *diffLayer) AccountList() []common.Hash { return dl.accountList } -// StorageList returns a sorted list of all storage slot hashes in this difflayer +// StorageList returns a sorted list of all storage slot hashes in this diffLayer // for the given account. If the whole storage is destructed in this layer, then // an additional flag *destructed = true* will be returned, otherwise the flag is // false. Besides, the returned list will include the hash of deleted storage slot. diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 31636ee133..919af5fa86 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -314,7 +314,7 @@ func BenchmarkSearchSlot(b *testing.B) { // With accountList and sorting // BenchmarkFlatten-6 50 29890856 ns/op // -// Without sorting and tracking accountlist +// Without sorting and tracking accountList // BenchmarkFlatten-6 300 5511511 ns/op func BenchmarkFlatten(b *testing.B) { fill := func(parent snapshot) *diffLayer { diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index e8f2bc853f..7cbf6e293d 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -31,7 +31,7 @@ import ( // diskLayer is a low level persistent snapshot built on top of a key-value store. type diskLayer struct { diskdb ethdb.KeyValueStore // Key-value store containing the base snapshot - triedb *trie.Database // Trie node cache for reconstuction purposes + triedb *trie.Database // Trie node cache for reconstruction purposes cache *fastcache.Cache // Cache to avoid hitting the disk for direct access root common.Hash // Root hash of the base snapshot diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index 40ff5ade4c..6beb944e07 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -482,7 +482,7 @@ func TestDiskGeneratorPersistence(t *testing.T) { if !bytes.Equal(generator.Marker, genMarker) { t.Fatalf("Generator marker is not matched") } - // Test senario 2, the disk layer is fully generated + // Test scenario 2, the disk layer is fully generated // Modify or delete some accounts, flatten everything onto disk if err := snaps.Update(diffTwoRoot, diffRoot, nil, map[common.Hash][]byte{ accThree: accThree.Bytes(), diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go index 5f943fea9f..1d9340bbad 100644 --- a/core/state/snapshot/iterator.go +++ b/core/state/snapshot/iterator.go @@ -133,7 +133,7 @@ func (it *diffAccountIterator) Hash() common.Hash { // Account returns the RLP encoded slim account the iterator is currently at. // This method may _fail_, if the underlying layer has been flattened between -// the call to Next and Acccount. That type of error will set it.Err. +// the call to Next and Account. That type of error will set it.Err. // This method assumes that flattening does not delete elements from // the accountdata mapping (writing nil into it is fine though), and will panic // if elements have been deleted. @@ -243,7 +243,7 @@ type diffStorageIterator struct { } // StorageIterator creates a storage iterator over a single diff layer. -// Execept the storage iterator is returned, there is an additional flag +// Except the storage iterator is returned, there is an additional flag // "destructed" returned. If it's true then it means the whole storage is // destructed in this layer(maybe recreated too), don't bother deeper layer // for storage retrieval. diff --git a/core/state/snapshot/iterator_binary.go b/core/state/snapshot/iterator_binary.go index f82f750029..22184b2545 100644 --- a/core/state/snapshot/iterator_binary.go +++ b/core/state/snapshot/iterator_binary.go @@ -37,7 +37,7 @@ type binaryIterator struct { } // initBinaryAccountIterator creates a simplistic iterator to step over all the -// accounts in a slow, but eaily verifiable way. Note this function is used for +// accounts in a slow, but easily verifiable way. Note this function is used for // initialization, use `newBinaryAccountIterator` as the API. func (dl *diffLayer) initBinaryAccountIterator() Iterator { parent, ok := dl.parent.(*diffLayer) @@ -62,7 +62,7 @@ func (dl *diffLayer) initBinaryAccountIterator() Iterator { } // initBinaryStorageIterator creates a simplistic iterator to step over all the -// storage slots in a slow, but eaily verifiable way. Note this function is used +// storage slots in a slow, but easily verifiable way. Note this function is used // for initialization, use `newBinaryStorageIterator` as the API. func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator { parent, ok := dl.parent.(*diffLayer) @@ -199,14 +199,14 @@ func (it *binaryIterator) Release() { } // newBinaryAccountIterator creates a simplistic account iterator to step over -// all the accounts in a slow, but eaily verifiable way. +// all the accounts in a slow, but easily verifiable way. func (dl *diffLayer) newBinaryAccountIterator() AccountIterator { iter := dl.initBinaryAccountIterator() return iter.(AccountIterator) } // newBinaryStorageIterator creates a simplistic account iterator to step over -// all the storage slots in a slow, but eaily verifiable way. +// all the storage slots in a slow, but easily verifiable way. func (dl *diffLayer) newBinaryStorageIterator(account common.Hash) StorageIterator { iter := dl.initBinaryStorageIterator(account) return iter.(StorageIterator) diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go index 291d52900d..48069b8fcf 100644 --- a/core/state/snapshot/iterator_fast.go +++ b/core/state/snapshot/iterator_fast.go @@ -75,7 +75,7 @@ type fastIterator struct { fail error } -// newFastIterator creates a new hierarhical account or storage iterator with one +// newFastIterator creates a new hierarchical account or storage iterator with one // element per diff layer. The returned combo iterator can be used to walk over // the entire snapshot diff stack simultaneously. func newFastIterator(tree *Tree, root common.Hash, account common.Hash, seek common.Hash, accountIterator bool) (*fastIterator, error) { @@ -335,14 +335,14 @@ func (fi *fastIterator) Debug() { fmt.Println() } -// newFastAccountIterator creates a new hierarhical account iterator with one +// newFastAccountIterator creates a new hierarchical account iterator with one // element per diff layer. The returned combo iterator can be used to walk over // the entire snapshot diff stack simultaneously. func newFastAccountIterator(tree *Tree, root common.Hash, seek common.Hash) (AccountIterator, error) { return newFastIterator(tree, root, common.Hash{}, seek, true) } -// newFastStorageIterator creates a new hierarhical storage iterator with one +// newFastStorageIterator creates a new hierarchical storage iterator with one // element per diff layer. The returned combo iterator can be used to walk over // the entire snapshot diff stack simultaneously. func newFastStorageIterator(tree *Tree, root common.Hash, account common.Hash, seek common.Hash) (StorageIterator, error) { diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 60b4158b56..443fc8e5c7 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -368,7 +368,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error { // crossed. All diffs beyond the permitted number are flattened downwards. If the // layer limit is reached, memory cap is also enforced (but not before). // -// The method returns the new disk layer if diffs were persistend into it. +// The method returns the new disk layer if diffs were persisted into it. func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer { // Dive until we run out of layers or reach the persistent database for ; layers > 2; layers-- { @@ -647,7 +647,7 @@ func (t *Tree) Rebuild(root common.Hash) { panic(fmt.Sprintf("unknown layer type: %T", layer)) } } - // Start generating a new snapshot from scratch on a backgroung thread. The + // Start generating a new snapshot from scratch on a background thread. The // generator will run a wiper first if there's not one running right now. log.Info("Rebuilding state snapshot") t.layers = map[common.Hash]snapshot{ diff --git a/trie/database.go b/trie/database.go index 7c2f207097..b18665770e 100644 --- a/trie/database.go +++ b/trie/database.go @@ -736,7 +736,7 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H batch.Replay(uncacher) batch.Reset() - // Reset the storage counters and bumpd metrics + // Reset the storage counters and bumped metrics if db.preimages != nil { db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 } From 44208d925811dc309b9e26df6dc1752ba359a0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 7 Jan 2021 10:23:50 +0200 Subject: [PATCH 105/235] cmd/faucet: fix websocket race regression after switching to gorilla --- cmd/faucet/faucet.go | 59 ++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 79a84fd24e..a0fc28c0d4 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -213,7 +213,7 @@ type faucet struct { nonce uint64 // Current pending nonce of the faucet price *big.Int // Current gas price to issue funds with - conns []*websocket.Conn // Currently live websocket connections + conns []*wsConn // Currently live websocket connections timeouts map[string]time.Time // History of users and their funding timeouts reqs []*request // Currently pending funding requests update chan struct{} // Channel to signal request updates @@ -221,6 +221,13 @@ type faucet struct { lock sync.RWMutex // Lock protecting the faucet's internals } +// wsConn wraps a websocket connection with a write mutex as the underlying +// websocket library does not synchronize access to the stream. +type wsConn struct { + conn *websocket.Conn + wlock sync.Mutex +} + func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { // Assemble the raw devp2p protocol stack stack, err := node.New(&node.Config{ @@ -321,13 +328,14 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { defer conn.Close() f.lock.Lock() - f.conns = append(f.conns, conn) + wsconn := &wsConn{conn: conn} + f.conns = append(f.conns, wsconn) f.lock.Unlock() defer func() { f.lock.Lock() for i, c := range f.conns { - if c == conn { + if c.conn == conn { f.conns = append(f.conns[:i], f.conns[i+1:]...) break } @@ -355,7 +363,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { if head == nil || balance == nil { // Report the faucet offline until initial stats are ready //lint:ignore ST1005 This error is to be displayed in the browser - if err = sendError(conn, errors.New("Faucet offline")); err != nil { + if err = sendError(wsconn, errors.New("Faucet offline")); err != nil { log.Warn("Failed to send faucet error to client", "err", err) return } @@ -366,7 +374,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { f.lock.RLock() reqs := f.reqs f.lock.RUnlock() - if err = send(conn, map[string]interface{}{ + if err = send(wsconn, map[string]interface{}{ "funds": new(big.Int).Div(balance, ether), "funded": nonce, "peers": f.stack.Server().PeerCount(), @@ -375,7 +383,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { log.Warn("Failed to send initial stats to client", "err", err) return } - if err = send(conn, head, 3*time.Second); err != nil { + if err = send(wsconn, head, 3*time.Second); err != nil { log.Warn("Failed to send initial header to client", "err", err) return } @@ -391,7 +399,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { return } if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://twitter.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") { - if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil { + if err = sendError(wsconn, errors.New("URL doesn't link to supported services")); err != nil { log.Warn("Failed to send URL error to client", "err", err) return } @@ -399,7 +407,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } if msg.Tier >= uint(*tiersFlag) { //lint:ignore ST1005 This error is to be displayed in the browser - if err = sendError(conn, errors.New("Invalid funding tier requested")); err != nil { + if err = sendError(wsconn, errors.New("Invalid funding tier requested")); err != nil { log.Warn("Failed to send tier error to client", "err", err) return } @@ -415,7 +423,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form) if err != nil { - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send captcha post error to client", "err", err) return } @@ -428,7 +436,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { err = json.NewDecoder(res.Body).Decode(&result) res.Body.Close() if err != nil { - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send captcha decode error to client", "err", err) return } @@ -437,7 +445,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { if !result.Success { log.Warn("Captcha verification failed", "err", string(result.Errors)) //lint:ignore ST1005 it's funny and the robot won't mind - if err = sendError(conn, errors.New("Beep-bop, you're a robot!")); err != nil { + if err = sendError(wsconn, errors.New("Beep-bop, you're a robot!")); err != nil { log.Warn("Failed to send captcha failure to client", "err", err) return } @@ -465,7 +473,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues") } if err != nil { - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send prefix error to client", "err", err) return } @@ -489,7 +497,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID) if err != nil { f.lock.Unlock() - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send transaction creation error to client", "err", err) return } @@ -498,7 +506,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { // Submit the transaction and mark as funded if successful if err := f.client.SendTransaction(context.Background(), signed); err != nil { f.lock.Unlock() - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send transaction transmission error to client", "err", err) return } @@ -520,13 +528,13 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { // Send an error if too frequent funding, othewise a success if !fund { - if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(time.Until(timeout)))); err != nil { // nolint: gosimple + if err = sendError(wsconn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(time.Until(timeout)))); err != nil { // nolint: gosimple log.Warn("Failed to send funding error to client", "err", err) return } continue } - if err = sendSuccess(conn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil { + if err = sendSuccess(wsconn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil { log.Warn("Failed to send funding success to client", "err", err) return } @@ -619,12 +627,12 @@ func (f *faucet) loop() { "requests": f.reqs, }, time.Second); err != nil { log.Warn("Failed to send stats to client", "err", err) - conn.Close() + conn.conn.Close() continue } if err := send(conn, head, time.Second); err != nil { log.Warn("Failed to send header to client", "err", err) - conn.Close() + conn.conn.Close() } } f.lock.RUnlock() @@ -646,7 +654,7 @@ func (f *faucet) loop() { for _, conn := range f.conns { if err := send(conn, map[string]interface{}{"requests": f.reqs}, time.Second); err != nil { log.Warn("Failed to send requests to client", "err", err) - conn.Close() + conn.conn.Close() } } f.lock.RUnlock() @@ -656,23 +664,26 @@ func (f *faucet) loop() { // sends transmits a data packet to the remote end of the websocket, but also // setting a write deadline to prevent waiting forever on the node. -func send(conn *websocket.Conn, value interface{}, timeout time.Duration) error { +func send(conn *wsConn, value interface{}, timeout time.Duration) error { if timeout == 0 { timeout = 60 * time.Second } - conn.SetWriteDeadline(time.Now().Add(timeout)) - return conn.WriteJSON(value) + conn.wlock.Lock() + defer conn.wlock.Unlock() + + conn.conn.SetWriteDeadline(time.Now().Add(timeout)) + return conn.conn.WriteJSON(value) } // sendError transmits an error to the remote end of the websocket, also setting // the write deadline to 1 second to prevent waiting forever. -func sendError(conn *websocket.Conn, err error) error { +func sendError(conn *wsConn, err error) error { return send(conn, map[string]string{"error": err.Error()}, time.Second) } // sendSuccess transmits a success message to the remote end of the websocket, also // setting the write deadline to 1 second to prevent waiting forever. -func sendSuccess(conn *websocket.Conn, msg string) error { +func sendSuccess(conn *wsConn, msg string) error { return send(conn, map[string]string{"success": msg}, time.Second) } From 58b9db5f7cf7a3db4d9d0afa3772fb8da32ebc3a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 7 Jan 2021 11:58:07 +0100 Subject: [PATCH 106/235] eth/protocols/snap: track reverts when peer rejects request (#22016) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * eth/protocols/snap: reschedule missed deliveries * eth/protocols/snap: clarify log message * eth/protocols/snap: revert failures async and update runloop Co-authored-by: Péter Szilágyi --- eth/protocols/snap/sync.go | 172 +++++++++++++++++++++++++------------ 1 file changed, 116 insertions(+), 56 deletions(-) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 437b0caab4..82b21c4701 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -792,10 +792,7 @@ func (s *Syncer) assignAccountTasks(cancel chan struct{}) { } req.timeout = time.AfterFunc(requestTimeout, func() { log.Debug("Account range request timed out") - select { - case s.accountReqFails <- req: - default: - } + s.scheduleRevertAccountRequest(req) }) s.accountReqs[reqid] = req delete(s.accountIdlers, idle) @@ -807,12 +804,8 @@ func (s *Syncer) assignAccountTasks(cancel chan struct{}) { // Attempt to send the remote request and revert if it fails if err := peer.RequestAccountRange(reqid, root, req.origin, req.limit, maxRequestSize); err != nil { peer.Log().Debug("Failed to request account range", "err", err) - select { - case s.accountReqFails <- req: - default: - } + s.scheduleRevertAccountRequest(req) } - // Request successfully sent, start a }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists // Inject the request into the task to block further assignments @@ -886,10 +879,7 @@ func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { } req.timeout = time.AfterFunc(requestTimeout, func() { log.Debug("Bytecode request timed out") - select { - case s.bytecodeReqFails <- req: - default: - } + s.scheduleRevertBytecodeRequest(req) }) s.bytecodeReqs[reqid] = req delete(s.bytecodeIdlers, idle) @@ -901,12 +891,8 @@ func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { // Attempt to send the remote request and revert if it fails if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil { log.Debug("Failed to request bytecodes", "err", err) - select { - case s.bytecodeReqFails <- req: - default: - } + s.scheduleRevertBytecodeRequest(req) } - // Request successfully sent, start a }(s.peers[idle]) // We're in the lock, peers[id] surely exists } } @@ -1018,10 +1004,7 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { } req.timeout = time.AfterFunc(requestTimeout, func() { log.Debug("Storage request timed out") - select { - case s.storageReqFails <- req: - default: - } + s.scheduleRevertStorageRequest(req) }) s.storageReqs[reqid] = req delete(s.storageIdlers, idle) @@ -1037,12 +1020,8 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { } if err := peer.RequestStorageRanges(reqid, root, accounts, origin, limit, maxRequestSize); err != nil { log.Debug("Failed to request storage", "err", err) - select { - case s.storageReqFails <- req: - default: - } + s.scheduleRevertStorageRequest(req) } - // Request successfully sent, start a }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists // Inject the request into the subtask to block further assignments @@ -1140,10 +1119,7 @@ func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { } req.timeout = time.AfterFunc(requestTimeout, func() { log.Debug("Trienode heal request timed out") - select { - case s.trienodeHealReqFails <- req: - default: - } + s.scheduleRevertTrienodeHealRequest(req) }) s.trienodeHealReqs[reqid] = req delete(s.trienodeHealIdlers, idle) @@ -1155,12 +1131,8 @@ func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { // Attempt to send the remote request and revert if it fails if err := peer.RequestTrieNodes(reqid, root, pathsets, maxRequestSize); err != nil { log.Debug("Failed to request trienode healers", "err", err) - select { - case s.trienodeHealReqFails <- req: - default: - } + s.scheduleRevertTrienodeHealRequest(req) } - // Request successfully sent, start a }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists } } @@ -1245,10 +1217,7 @@ func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { } req.timeout = time.AfterFunc(requestTimeout, func() { log.Debug("Bytecode heal request timed out") - select { - case s.bytecodeHealReqFails <- req: - default: - } + s.scheduleRevertBytecodeHealRequest(req) }) s.bytecodeHealReqs[reqid] = req delete(s.bytecodeHealIdlers, idle) @@ -1260,12 +1229,8 @@ func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { // Attempt to send the remote request and revert if it fails if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil { log.Debug("Failed to request bytecode healers", "err", err) - select { - case s.bytecodeHealReqFails <- req: - default: - } + s.scheduleRevertBytecodeHealRequest(req) } - // Request successfully sent, start a }(s.peers[idle]) // We're in the lock, peers[id] surely exists } } @@ -1325,10 +1290,26 @@ func (s *Syncer) revertRequests(peer string) { } } +// scheduleRevertAccountRequest asks the event loop to clean up an account range +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertAccountRequest(req *accountRequest) { + select { + case s.accountReqFails <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + // revertAccountRequest cleans up an account range request and returns all failed // retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertAccountRequest. func (s *Syncer) revertAccountRequest(req *accountRequest) { - log.Trace("Reverting account request", "peer", req.peer, "reqid", req.id) + log.Debug("Reverting account request", "peer", req.peer, "reqid", req.id) select { case <-req.stale: log.Trace("Account request already reverted", "peer", req.peer, "reqid", req.id) @@ -1350,10 +1331,26 @@ func (s *Syncer) revertAccountRequest(req *accountRequest) { } } -// revertBytecodeRequest cleans up an bytecode request and returns all failed +// scheduleRevertBytecodeRequest asks the event loop to clean up a bytecode request +// and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertBytecodeRequest(req *bytecodeRequest) { + select { + case s.bytecodeReqFails <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertBytecodeRequest cleans up a bytecode request and returns all failed // retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertBytecodeRequest. func (s *Syncer) revertBytecodeRequest(req *bytecodeRequest) { - log.Trace("Reverting bytecode request", "peer", req.peer) + log.Debug("Reverting bytecode request", "peer", req.peer) select { case <-req.stale: log.Trace("Bytecode request already reverted", "peer", req.peer, "reqid", req.id) @@ -1375,10 +1372,26 @@ func (s *Syncer) revertBytecodeRequest(req *bytecodeRequest) { } } +// scheduleRevertStorageRequest asks the event loop to clean up a storage range +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertStorageRequest(req *storageRequest) { + select { + case s.storageReqFails <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + // revertStorageRequest cleans up a storage range request and returns all failed // retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertStorageRequest. func (s *Syncer) revertStorageRequest(req *storageRequest) { - log.Trace("Reverting storage request", "peer", req.peer) + log.Debug("Reverting storage request", "peer", req.peer) select { case <-req.stale: log.Trace("Storage request already reverted", "peer", req.peer, "reqid", req.id) @@ -1404,10 +1417,26 @@ func (s *Syncer) revertStorageRequest(req *storageRequest) { } } -// revertTrienodeHealRequest cleans up an trienode heal request and returns all +// scheduleRevertTrienodeHealRequest asks the event loop to clean up a trienode heal +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertTrienodeHealRequest(req *trienodeHealRequest) { + select { + case s.trienodeHealReqFails <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertTrienodeHealRequest cleans up a trienode heal request and returns all // failed retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertTrienodeHealRequest. func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) { - log.Trace("Reverting trienode heal request", "peer", req.peer) + log.Debug("Reverting trienode heal request", "peer", req.peer) select { case <-req.stale: log.Trace("Trienode heal request already reverted", "peer", req.peer, "reqid", req.id) @@ -1429,10 +1458,26 @@ func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) { } } -// revertBytecodeHealRequest cleans up an bytecode request and returns all failed -// retrieval tasks to the scheduler for reassignment. +// scheduleRevertBytecodeHealRequest asks the event loop to clean up a bytecode heal +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertBytecodeHealRequest(req *bytecodeHealRequest) { + select { + case s.bytecodeHealReqFails <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertBytecodeHealRequest cleans up a bytecode heal request and returns all +// failed retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertBytecodeHealRequest. func (s *Syncer) revertBytecodeHealRequest(req *bytecodeHealRequest) { - log.Trace("Reverting bytecode heal request", "peer", req.peer) + log.Debug("Reverting bytecode heal request", "peer", req.peer) select { case <-req.stale: log.Trace("Bytecode heal request already reverted", "peer", req.peer, "reqid", req.id) @@ -1768,7 +1813,7 @@ func (s *Syncer) processTrienodeHealResponse(res *trienodeHealResponse) { if err := batch.Write(); err != nil { log.Crit("Failed to persist healing data", "err", err) } - log.Debug("Persisted set of healing data", "bytes", common.StorageSize(batch.ValueSize())) + log.Debug("Persisted set of healing data", "type", "trienodes", "bytes", common.StorageSize(batch.ValueSize())) } // processBytecodeHealResponse integrates an already validated bytecode response @@ -1804,7 +1849,7 @@ func (s *Syncer) processBytecodeHealResponse(res *bytecodeHealResponse) { if err := batch.Write(); err != nil { log.Crit("Failed to persist healing data", "err", err) } - log.Debug("Persisted set of healing data", "bytes", common.StorageSize(batch.ValueSize())) + log.Debug("Persisted set of healing data", "type", "bytecode", "bytes", common.StorageSize(batch.ValueSize())) } // forwardAccountTask takes a filled account task and persists anything available @@ -1940,6 +1985,9 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account logger.Debug("Peer rejected account range request", "root", s.root) s.statelessPeers[peer.id] = struct{}{} s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertAccountRequest(req) return nil } root := s.root @@ -2055,6 +2103,9 @@ func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { logger.Debug("Peer rejected bytecode request") s.statelessPeers[peer.id] = struct{}{} s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeRequest(req) return nil } s.lock.Unlock() @@ -2166,6 +2217,9 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots logger.Debug("Peer rejected storage request") s.statelessPeers[peer.id] = struct{}{} s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertStorageRequest(req) return nil } s.lock.Unlock() @@ -2287,6 +2341,9 @@ func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { logger.Debug("Peer rejected trienode heal request") s.statelessPeers[peer.id] = struct{}{} s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertTrienodeHealRequest(req) return nil } s.lock.Unlock() @@ -2371,6 +2428,9 @@ func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) erro logger.Debug("Peer rejected bytecode heal request") s.statelessPeers[peer.id] = struct{}{} s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeHealRequest(req) return nil } s.lock.Unlock() From 4bb5c6ca7a19f5ad0230879205380adaca12ef4f Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 7 Jan 2021 17:12:41 +0100 Subject: [PATCH 107/235] eth/protocols/snap: speed up hash checks (#22023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * eth/protocols/snap: speed up hash checks * eth/protocols/snap: nit fix Co-authored-by: Péter Szilágyi --- eth/protocols/snap/sync.go | 15 +++-- eth/protocols/snap/sync_test.go | 98 +++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 eth/protocols/snap/sync_test.go diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 82b21c4701..d6f0eb5472 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -2112,14 +2112,15 @@ func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { // Cross reference the requested bytecodes with the response to find gaps // that the serving node is missing - hasher := sha3.NewLegacyKeccak256() + hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) + hash := make([]byte, 32) codes := make([][]byte, len(req.hashes)) for i, j := 0, 0; i < len(bytecodes); i++ { // Find the next hash that we've been served, leaving misses with nils hasher.Reset() hasher.Write(bytecodes[i]) - hash := hasher.Sum(nil) + hasher.Read(hash) for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { j++ @@ -2350,14 +2351,15 @@ func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { // Cross reference the requested trienodes with the response to find gaps // that the serving node is missing - hasher := sha3.NewLegacyKeccak256() + hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) + hash := make([]byte, 32) nodes := make([][]byte, len(req.hashes)) for i, j := 0, 0; i < len(trienodes); i++ { // Find the next hash that we've been served, leaving misses with nils hasher.Reset() hasher.Write(trienodes[i]) - hash := hasher.Sum(nil) + hasher.Read(hash) for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { j++ @@ -2437,14 +2439,15 @@ func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) erro // Cross reference the requested bytecodes with the response to find gaps // that the serving node is missing - hasher := sha3.NewLegacyKeccak256() + hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) + hash := make([]byte, 32) codes := make([][]byte, len(req.hashes)) for i, j := 0, 0; i < len(bytecodes); i++ { // Find the next hash that we've been served, leaving misses with nils hasher.Reset() hasher.Write(bytecodes[i]) - hash := hasher.Sum(nil) + hasher.Read(hash) for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { j++ diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go new file mode 100644 index 0000000000..4f28b99bfe --- /dev/null +++ b/eth/protocols/snap/sync_test.go @@ -0,0 +1,98 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "crypto/rand" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + "golang.org/x/crypto/sha3" +) + +func TestHashing(t *testing.T) { + var bytecodes = make([][]byte, 10) + for i := 0; i < len(bytecodes); i++ { + buf := make([]byte, 100) + rand.Read(buf) + bytecodes[i] = buf + } + var want, got string + var old = func() { + hasher := sha3.NewLegacyKeccak256() + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hash := hasher.Sum(nil) + got = fmt.Sprintf("%v\n%v", got, hash) + } + } + var new = func() { + hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) + var hash = make([]byte, 32) + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Read(hash) + want = fmt.Sprintf("%v\n%v", want, hash) + } + } + old() + new() + if want != got { + t.Errorf("want\n%v\ngot\n%v\n", want, got) + } +} + +func BenchmarkHashing(b *testing.B) { + var bytecodes = make([][]byte, 10000) + for i := 0; i < len(bytecodes); i++ { + buf := make([]byte, 100) + rand.Read(buf) + bytecodes[i] = buf + } + var old = func() { + hasher := sha3.NewLegacyKeccak256() + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Sum(nil) + } + } + var new = func() { + hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) + var hash = make([]byte, 32) + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Read(hash) + } + } + b.Run("old", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + old() + } + }) + b.Run("new", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + new() + } + }) +} From 3c6665e7d62ed166d9f1cf4519ad23ab77c5cae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 7 Jan 2021 13:04:20 +0200 Subject: [PATCH 108/235] cmd/faucet: switch Facebook auth over to mobile site --- cmd/faucet/faucet.go | 10 +++++++-- cmd/faucet/faucet_test.go | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 cmd/faucet/faucet_test.go diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index a0fc28c0d4..2c0881f5a2 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with go-ethereum. If not, see . -// faucet is a Ether faucet backed by a light client. +// faucet is an Ether faucet backed by a light client. package main //go:generate go-bindata -nometadata -o website.go faucet.html @@ -847,7 +847,13 @@ func authFacebook(url string) (string, string, common.Address, error) { // Facebook's Graph API isn't really friendly with direct links. Still, we don't // want to do ask read permissions from users, so just load the public posts and // scrape it for the Ethereum address and profile URL. - res, err := http.Get(url) + // + // Facebook recently changed their desktop webpage to use AJAX for loading post + // content, so switch over to the mobile site for now. Will probably end up having + // to use the API eventually. + crawl := strings.Replace(url, "www.facebook.com", "m.facebook.com", 1) + + res, err := http.Get(crawl) if err != nil { return "", "", common.Address{}, err } diff --git a/cmd/faucet/faucet_test.go b/cmd/faucet/faucet_test.go new file mode 100644 index 0000000000..4f3e47084e --- /dev/null +++ b/cmd/faucet/faucet_test.go @@ -0,0 +1,43 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestFacebook(t *testing.T) { + for _, tt := range []struct { + url string + want common.Address + }{ + { + "https://www.facebook.com/fooz.gazonk/posts/2837228539847129", + common.HexToAddress("0xDeadDeaDDeaDbEefbEeFbEEfBeeFBeefBeeFbEEF"), + }, + } { + _, _, gotAddress, err := authFacebook(tt.url) + if err != nil { + t.Fatal(err) + } + if gotAddress != tt.want { + t.Fatalf("address wrong, have %v want %v", gotAddress, tt.want) + } + } +} From 165f53fc6e9e904054c67462000a19fc83dcc12f Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 8 Jan 2021 06:39:35 +0800 Subject: [PATCH 109/235] les: remove transaction propagation limits (#22125) --- les/txrelay.go | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/les/txrelay.go b/les/txrelay.go index 57f2412eba..9d29b2f234 100644 --- a/les/txrelay.go +++ b/les/txrelay.go @@ -25,13 +25,8 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -type ltrInfo struct { - tx *types.Transaction - sentTo map[*serverPeer]struct{} -} - type lesTxRelay struct { - txSent map[common.Hash]*ltrInfo + txSent map[common.Hash]*types.Transaction txPending map[common.Hash]struct{} peerList []*serverPeer peerStartPos int @@ -43,7 +38,7 @@ type lesTxRelay struct { func newLesTxRelay(ps *serverPeerSet, retriever *retrieveManager) *lesTxRelay { r := &lesTxRelay{ - txSent: make(map[common.Hash]*ltrInfo), + txSent: make(map[common.Hash]*types.Transaction), txPending: make(map[common.Hash]struct{}), retriever: retriever, stop: make(chan struct{}), @@ -80,8 +75,7 @@ func (ltrx *lesTxRelay) unregisterPeer(p *serverPeer) { } } -// send sends a list of transactions to at most a given number of peers at -// once, never resending any particular transaction to the same peer twice +// send sends a list of transactions to at most a given number of peers. func (ltrx *lesTxRelay) send(txs types.Transactions, count int) { sendTo := make(map[*serverPeer]types.Transactions) @@ -92,26 +86,18 @@ func (ltrx *lesTxRelay) send(txs types.Transactions, count int) { for _, tx := range txs { hash := tx.Hash() - ltr, ok := ltrx.txSent[hash] + _, ok := ltrx.txSent[hash] if !ok { - ltr = <rInfo{ - tx: tx, - sentTo: make(map[*serverPeer]struct{}), - } - ltrx.txSent[hash] = ltr + ltrx.txSent[hash] = tx ltrx.txPending[hash] = struct{}{} } - if len(ltrx.peerList) > 0 { cnt := count pos := ltrx.peerStartPos for { peer := ltrx.peerList[pos] - if _, ok := ltr.sentTo[peer]; !ok { - sendTo[peer] = append(sendTo[peer], tx) - ltr.sentTo[peer] = struct{}{} - cnt-- - } + sendTo[peer] = append(sendTo[peer], tx) + cnt-- if cnt == 0 { break // sent it to the desired number of peers } @@ -174,7 +160,7 @@ func (ltrx *lesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback txs := make(types.Transactions, len(ltrx.txPending)) i := 0 for hash := range ltrx.txPending { - txs[i] = ltrx.txSent[hash].tx + txs[i] = ltrx.txSent[hash] i++ } ltrx.send(txs, 1) From 6b88ab75bcbc5eaecaf5619ec730aa00f5e7c941 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 8 Jan 2021 11:17:15 +0100 Subject: [PATCH 110/235] cmd/faucet: fix nonce-gap problem (#22145) * cmd/faucet: avoid encoding for each client * cmd/faucet: fix flaw in clearing of txs, avoid sending more than necessary * cmd/faucet: fix flaw in tx cropping * cmd/faucet: revert change to not always send tx info * cmd/faucet: review fixes * cmd/faucet: revert #22018, fix order in UI * cmd/faucet: fix lock error * cmd/faucet: revert json changes * squashme --- cmd/faucet/faucet.go | 5 ++--- cmd/faucet/faucet.html | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 2c0881f5a2..b9c4e1819a 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -512,12 +512,12 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } continue } - f.reqs = append([]*request{{ + f.reqs = append(f.reqs, &request{ Avatar: avatar, Account: address, Time: time.Now(), Tx: signed, - }}, f.reqs...) + }) timeout := time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute grace := timeout / 288 // 24h timeout => 5m grace @@ -670,7 +670,6 @@ func send(conn *wsConn, value interface{}, timeout time.Duration) error { } conn.wlock.Lock() defer conn.wlock.Unlock() - conn.conn.SetWriteDeadline(time.Now().Add(timeout)) return conn.conn.WriteJSON(value) } diff --git a/cmd/faucet/faucet.html b/cmd/faucet/faucet.html index ba14333186..dad5ad84f2 100644 --- a/cmd/faucet/faucet.html +++ b/cmd/faucet/faucet.html @@ -177,7 +177,7 @@

    How does this work?

    } // Iterate over our entire local collection and re-render the funding table var content = ""; - for (var i=0; i= 0; i--) { var done = requests[i].time == ""; var elapsed = moment().unix()-moment(requests[i].time).unix(); From 889f5645b57dde5b5d4cccf1561bdb449293d2d8 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 8 Jan 2021 21:29:25 +0100 Subject: [PATCH 111/235] ethclient: better test suite for ethclient package (#22127) This commit extends the ethclient test suite and increases code coverage of the ethclient package from ~15% to >55%. These tests act as early smoke tests to signal issues in the RPC-interface. E.g. if a functionality like eth_chainId or eth_call breaks, the test will break. --- ethclient/ethclient_test.go | 259 ++++++++++++++++++++++++++++++++---- 1 file changed, 231 insertions(+), 28 deletions(-) diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 0ca72c6ee7..d700022e8f 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -17,6 +17,7 @@ package ethclient import ( + "bytes" "context" "errors" "fmt" @@ -35,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" ) // Verify that Client implements the ethereum interfaces. @@ -229,12 +231,48 @@ func generateTestChain() (*core.Genesis, []*types.Block) { return genesis, blocks } -func TestHeader(t *testing.T) { +func TestEthClient(t *testing.T) { backend, chain := newTestBackend(t) client, _ := backend.Attach() defer backend.Close() defer client.Close() + tests := map[string]struct { + test func(t *testing.T) + }{ + "TestHeader": { + func(t *testing.T) { testHeader(t, chain, client) }, + }, + "TestBalanceAt": { + func(t *testing.T) { testBalanceAt(t, client) }, + }, + "TestTxInBlockInterrupted": { + func(t *testing.T) { testTransactionInBlockInterrupted(t, client) }, + }, + "TestChainID": { + func(t *testing.T) { testChainID(t, client) }, + }, + "TestGetBlock": { + func(t *testing.T) { testGetBlock(t, client) }, + }, + "TestStatusFunctions": { + func(t *testing.T) { testStatusFunctions(t, client) }, + }, + "TestCallContract": { + func(t *testing.T) { testCallContract(t, client) }, + }, + "TestAtFunctions": { + func(t *testing.T) { testAtFunctions(t, client) }, + }, + } + + t.Parallel() + for name, tt := range tests { + t.Run(name, tt.test) + } +} + +func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) { tests := map[string]struct { block *big.Int want *types.Header @@ -273,12 +311,7 @@ func TestHeader(t *testing.T) { } } -func TestBalanceAt(t *testing.T) { - backend, _ := newTestBackend(t) - client, _ := backend.Attach() - defer backend.Close() - defer client.Close() - +func testBalanceAt(t *testing.T, client *rpc.Client) { tests := map[string]struct { account common.Address block *big.Int @@ -319,31 +352,32 @@ func TestBalanceAt(t *testing.T) { } } -func TestTransactionInBlockInterrupted(t *testing.T) { - backend, _ := newTestBackend(t) - client, _ := backend.Attach() - defer backend.Close() - defer client.Close() - +func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) { ec := NewClient(client) + + // Get current block by number + block, err := ec.BlockByNumber(context.Background(), nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + // Test tx in block interupted ctx, cancel := context.WithCancel(context.Background()) cancel() - tx, err := ec.TransactionInBlock(ctx, common.Hash{1}, 1) + tx, err := ec.TransactionInBlock(ctx, block.Hash(), 1) if tx != nil { t.Fatal("transaction should be nil") } - if err == nil { - t.Fatal("error should not be nil") + if err == nil || err == ethereum.NotFound { + t.Fatal("error should not be nil/notfound") + } + // Test tx in block not found + if _, err := ec.TransactionInBlock(context.Background(), block.Hash(), 1); err != ethereum.NotFound { + t.Fatal("error should be ethereum.NotFound") } } -func TestChainID(t *testing.T) { - backend, _ := newTestBackend(t) - client, _ := backend.Attach() - defer backend.Close() - defer client.Close() +func testChainID(t *testing.T, client *rpc.Client) { ec := NewClient(client) - id, err := ec.ChainID(context.Background()) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -353,13 +387,9 @@ func TestChainID(t *testing.T) { } } -func TestBlockNumber(t *testing.T) { - backend, _ := newTestBackend(t) - client, _ := backend.Attach() - defer backend.Close() - defer client.Close() +func testGetBlock(t *testing.T, client *rpc.Client) { ec := NewClient(client) - + // Get current block number blockNumber, err := ec.BlockNumber(context.Background()) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -367,4 +397,177 @@ func TestBlockNumber(t *testing.T) { if blockNumber != 1 { t.Fatalf("BlockNumber returned wrong number: %d", blockNumber) } + // Get current block by number + block, err := ec.BlockByNumber(context.Background(), new(big.Int).SetUint64(blockNumber)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.NumberU64() != blockNumber { + t.Fatalf("BlockByNumber returned wrong block: want %d got %d", blockNumber, block.NumberU64()) + } + // Get current block by hash + blockH, err := ec.BlockByHash(context.Background(), block.Hash()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.Hash() != blockH.Hash() { + t.Fatalf("BlockByHash returned wrong block: want %v got %v", block.Hash().Hex(), blockH.Hash().Hex()) + } + // Get header by number + header, err := ec.HeaderByNumber(context.Background(), new(big.Int).SetUint64(blockNumber)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.Header().Hash() != header.Hash() { + t.Fatalf("HeaderByNumber returned wrong header: want %v got %v", block.Header().Hash().Hex(), header.Hash().Hex()) + } + // Get header by hash + headerH, err := ec.HeaderByHash(context.Background(), block.Hash()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.Header().Hash() != headerH.Hash() { + t.Fatalf("HeaderByHash returned wrong header: want %v got %v", block.Header().Hash().Hex(), headerH.Hash().Hex()) + } +} + +func testStatusFunctions(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // Sync progress + progress, err := ec.SyncProgress(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if progress != nil { + t.Fatalf("unexpected progress: %v", progress) + } + // NetworkID + networkID, err := ec.NetworkID(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if networkID.Cmp(big.NewInt(0)) != 0 { + t.Fatalf("unexpected networkID: %v", networkID) + } + // SuggestGasPrice (should suggest 1 Gwei) + gasPrice, err := ec.SuggestGasPrice(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gasPrice.Cmp(big.NewInt(1000000000)) != 0 { + t.Fatalf("unexpected gas price: %v", gasPrice) + } +} + +func testCallContract(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // EstimateGas + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + GasPrice: big.NewInt(1), + Value: big.NewInt(1), + } + gas, err := ec.EstimateGas(context.Background(), msg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gas != 21000 { + t.Fatalf("unexpected gas price: %v", gas) + } + // CallContract + if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err != nil { + t.Fatalf("unexpected error: %v", err) + } + // PendingCallCOntract + if _, err := ec.PendingCallContract(context.Background(), msg); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func testAtFunctions(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + // send a transaction for some interesting pending status + sendTransaction(ec) + time.Sleep(100 * time.Millisecond) + // Check pending transaction count + pending, err := ec.PendingTransactionCount(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if pending != 1 { + t.Fatalf("unexpected pending, wanted 1 got: %v", pending) + } + // Query balance + balance, err := ec.BalanceAt(context.Background(), testAddr, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + penBalance, err := ec.PendingBalanceAt(context.Background(), testAddr) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if balance.Cmp(penBalance) == 0 { + t.Fatalf("unexpected balance: %v %v", balance, penBalance) + } + // NonceAt + nonce, err := ec.NonceAt(context.Background(), testAddr, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + penNonce, err := ec.PendingNonceAt(context.Background(), testAddr) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if penNonce != nonce+1 { + t.Fatalf("unexpected nonce: %v %v", nonce, penNonce) + } + // StorageAt + storage, err := ec.StorageAt(context.Background(), testAddr, common.Hash{}, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + penStorage, err := ec.PendingStorageAt(context.Background(), testAddr, common.Hash{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(storage, penStorage) { + t.Fatalf("unexpected storage: %v %v", storage, penStorage) + } + // CodeAt + code, err := ec.CodeAt(context.Background(), testAddr, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + penCode, err := ec.PendingCodeAt(context.Background(), testAddr) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(code, penCode) { + t.Fatalf("unexpected code: %v %v", code, penCode) + } +} + +func sendTransaction(ec *Client) error { + // Retrieve chainID + chainID, err := ec.ChainID(context.Background()) + if err != nil { + return err + } + // Create transaction + tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) + signer := types.NewEIP155Signer(chainID) + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) + if err != nil { + return err + } + signedTx, err := tx.WithSignature(signer, signature) + if err != nil { + return err + } + // Send transaction + return ec.SendTransaction(context.Background(), signedTx) } From 89030ec0b442a08fb82d4452f3132ab1602b8182 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Sat, 9 Jan 2021 18:29:19 +0200 Subject: [PATCH 112/235] eth/downloader: fix race condition in tests (#22140) * downloader: fix race condition in tests * eth/downloader: fix race condition in tests * Revert "downloader: fix race condition in tests" This reverts commit 108033ebc6985de83791d375b6e6647a77d28d5a. --- eth/downloader/downloader_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 6578275d0c..5de1ef3f8e 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -584,14 +584,15 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { time.Sleep(25 * time.Millisecond) tester.lock.Lock() + tester.downloader.queue.lock.Lock() + tester.downloader.queue.resultCache.lock.Lock() { - tester.downloader.queue.resultCache.lock.Lock() cached = tester.downloader.queue.resultCache.countCompleted() - tester.downloader.queue.resultCache.lock.Unlock() frozen = int(atomic.LoadUint32(&blocked)) retrieved = len(tester.ownBlocks) - } + tester.downloader.queue.resultCache.lock.Unlock() + tester.downloader.queue.lock.Unlock() tester.lock.Unlock() if cached == blockCacheMaxItems || From 5a1b38435270336fd86fe742c9951abad870b84d Mon Sep 17 00:00:00 2001 From: gary rong Date: Sun, 10 Jan 2021 19:54:15 +0800 Subject: [PATCH 113/235] core: persist bad blocks (#21827) * core: persist bad blocks * core, eth, internal: address comments * core/rawdb: add badblocks to inspector * core, eth: update * internal: revert * core, eth: only save 10 bad blocks * core/rawdb: address comments * core/rawdb: fix * core: address comments --- core/blockchain.go | 23 +------ core/rawdb/accessors_chain.go | 97 ++++++++++++++++++++++++++++++ core/rawdb/accessors_chain_test.go | 70 +++++++++++++++++++++ core/rawdb/database.go | 2 +- core/rawdb/schema.go | 8 ++- eth/api.go | 31 ++++++---- eth/api_tracer.go | 6 +- 7 files changed, 196 insertions(+), 41 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index d9505dcf69..b8f483b85e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -89,7 +89,6 @@ const ( txLookupCacheLimit = 1024 maxFutureBlocks = 256 maxTimeFutureBlocks = 30 - badBlockLimit = 10 TriesInMemory = 128 // BlockChainVersion ensures that an incompatible database forces a resync from scratch. @@ -208,7 +207,6 @@ type BlockChain struct { processor Processor // Block transaction processor interface vmConfig vm.Config - badBlocks *lru.Cache // Bad block cache shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. writeLegacyJournal bool // Testing flag used to flush the snapshot journal in legacy format. @@ -227,7 +225,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par blockCache, _ := lru.New(blockCacheLimit) txLookupCache, _ := lru.New(txLookupCacheLimit) futureBlocks, _ := lru.New(maxFutureBlocks) - badBlocks, _ := lru.New(badBlockLimit) bc := &BlockChain{ chainConfig: chainConfig, @@ -249,7 +246,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par futureBlocks: futureBlocks, engine: engine, vmConfig: vmConfig, - badBlocks: badBlocks, } bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) @@ -2374,26 +2370,9 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { } } -// BadBlocks returns a list of the last 'bad blocks' that the client has seen on the network -func (bc *BlockChain) BadBlocks() []*types.Block { - blocks := make([]*types.Block, 0, bc.badBlocks.Len()) - for _, hash := range bc.badBlocks.Keys() { - if blk, exist := bc.badBlocks.Peek(hash); exist { - block := blk.(*types.Block) - blocks = append(blocks, block) - } - } - return blocks -} - -// addBadBlock adds a bad block to the bad-block LRU cache -func (bc *BlockChain) addBadBlock(block *types.Block) { - bc.badBlocks.Add(block.Hash(), block) -} - // reportBlock logs a bad block error. func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) { - bc.addBadBlock(block) + rawdb.WriteBadBlock(bc.db, block) var receiptString string for i, receipt := range receipts { diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index c948cdc7c6..461e1cbb17 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/binary" "math/big" + "sort" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -702,6 +703,102 @@ func DeleteBlockWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number DeleteTd(db, hash, number) } +const badBlockToKeep = 10 + +type badBlock struct { + Header *types.Header + Body *types.Body +} + +// badBlockList implements the sort interface to allow sorting a list of +// bad blocks by their number in the reverse order. +type badBlockList []*badBlock + +func (s badBlockList) Len() int { return len(s) } +func (s badBlockList) Less(i, j int) bool { + return s[i].Header.Number.Uint64() < s[j].Header.Number.Uint64() +} +func (s badBlockList) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// ReadBadBlock retrieves the bad block with the corresponding block hash. +func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block { + blob, err := db.Get(badBlockKey) + if err != nil { + return nil + } + var badBlocks badBlockList + if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { + return nil + } + for _, bad := range badBlocks { + if bad.Header.Hash() == hash { + return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles) + } + } + return nil +} + +// ReadAllBadBlocks retrieves all the bad blocks in the database. +// All returned blocks are sorted in reverse order by number. +func ReadAllBadBlocks(db ethdb.Reader) []*types.Block { + blob, err := db.Get(badBlockKey) + if err != nil { + return nil + } + var badBlocks badBlockList + if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { + return nil + } + var blocks []*types.Block + for _, bad := range badBlocks { + blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles)) + } + return blocks +} + +// WriteBadBlock serializes the bad block into the database. If the cumulated +// bad blocks exceeds the limitation, the oldest will be dropped. +func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) { + blob, err := db.Get(badBlockKey) + if err != nil { + log.Warn("Failed to load old bad blocks", "error", err) + } + var badBlocks badBlockList + if len(blob) > 0 { + if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { + log.Crit("Failed to decode old bad blocks", "error", err) + } + } + for _, b := range badBlocks { + if b.Header.Number.Uint64() == block.NumberU64() && b.Header.Hash() == block.Hash() { + log.Info("Skip duplicated bad block", "number", block.NumberU64(), "hash", block.Hash()) + return + } + } + badBlocks = append(badBlocks, &badBlock{ + Header: block.Header(), + Body: block.Body(), + }) + sort.Sort(sort.Reverse(badBlocks)) + if len(badBlocks) > badBlockToKeep { + badBlocks = badBlocks[:badBlockToKeep] + } + data, err := rlp.EncodeToBytes(badBlocks) + if err != nil { + log.Crit("Failed to encode bad blocks", "err", err) + } + if err := db.Put(badBlockKey, data); err != nil { + log.Crit("Failed to write bad blocks", "err", err) + } +} + +// DeleteBadBlocks deletes all the bad blocks from the database +func DeleteBadBlocks(db ethdb.KeyValueWriter) { + if err := db.Delete(badBlockKey); err != nil { + log.Crit("Failed to delete bad blocks", "err", err) + } +} + // FindCommonAncestor returns the last common ancestor of two block headers func FindCommonAncestor(db ethdb.Reader, a, b *types.Header) *types.Header { for bn := b.Number.Uint64(); a.Number.Uint64() > bn; { diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 074c24d8fe..a5804cd309 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -22,6 +22,7 @@ import ( "fmt" "io/ioutil" "math/big" + "math/rand" "os" "reflect" "testing" @@ -188,6 +189,75 @@ func TestPartialBlockStorage(t *testing.T) { } } +// Tests block storage and retrieval operations. +func TestBadBlockStorage(t *testing.T) { + db := NewMemoryDatabase() + + // Create a test block to move around the database and make sure it's really new + block := types.NewBlockWithHeader(&types.Header{ + Number: big.NewInt(1), + Extra: []byte("bad block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyRootHash, + ReceiptHash: types.EmptyRootHash, + }) + if entry := ReadBadBlock(db, block.Hash()); entry != nil { + t.Fatalf("Non existent block returned: %v", entry) + } + // Write and verify the block in the database + WriteBadBlock(db, block) + if entry := ReadBadBlock(db, block.Hash()); entry == nil { + t.Fatalf("Stored block not found") + } else if entry.Hash() != block.Hash() { + t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block) + } + // Write one more bad block + blockTwo := types.NewBlockWithHeader(&types.Header{ + Number: big.NewInt(2), + Extra: []byte("bad block two"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyRootHash, + ReceiptHash: types.EmptyRootHash, + }) + WriteBadBlock(db, blockTwo) + + // Write the block one again, should be filtered out. + WriteBadBlock(db, block) + badBlocks := ReadAllBadBlocks(db) + if len(badBlocks) != 2 { + t.Fatalf("Failed to load all bad blocks") + } + + // Write a bunch of bad blocks, all the blocks are should sorted + // in reverse order. The extra blocks should be truncated. + for _, n := range rand.Perm(100) { + block := types.NewBlockWithHeader(&types.Header{ + Number: big.NewInt(int64(n)), + Extra: []byte("bad block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyRootHash, + ReceiptHash: types.EmptyRootHash, + }) + WriteBadBlock(db, block) + } + badBlocks = ReadAllBadBlocks(db) + if len(badBlocks) != badBlockToKeep { + t.Fatalf("The number of persised bad blocks in incorrect %d", len(badBlocks)) + } + for i := 0; i < len(badBlocks)-1; i++ { + if badBlocks[i].NumberU64() < badBlocks[i+1].NumberU64() { + t.Fatalf("The bad blocks are not sorted #[%d](%d) < #[%d](%d)", i, i+1, badBlocks[i].NumberU64(), badBlocks[i+1].NumberU64()) + } + } + + // Delete all bad blocks + DeleteBadBlocks(db) + badBlocks = ReadAllBadBlocks(db) + if len(badBlocks) != 0 { + t.Fatalf("Failed to delete bad blocks") + } +} + // Tests block total difficulty storage and retrieval operations. func TestTdStorage(t *testing.T) { db := NewMemoryDatabase() diff --git a/core/rawdb/database.go b/core/rawdb/database.go index b01a31ebcd..a2cc9c7be9 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -355,7 +355,7 @@ func InspectDatabase(db ethdb.Database) error { bloomTrieNodes.Add(size) default: var accounted bool - for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey} { + for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey, badBlockKey} { if bytes.Equal(key, meta) { metadata.Add(size) accounted = true diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 2aabfd3baa..9749a30d8a 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -66,6 +66,12 @@ var ( // fastTxLookupLimitKey tracks the transaction lookup limit during fast sync. fastTxLookupLimitKey = []byte("FastTransactionLookupLimit") + // badBlockKey tracks the list of bad blocks seen by local + badBlockKey = []byte("InvalidBlock") + + // uncleanShutdownKey tracks the list of local crashes + uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db + // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td @@ -84,8 +90,6 @@ var ( preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db - uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db - // Chain index prefixes (use `i` + single byte to avoid mixing data types). BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress diff --git a/eth/api.go b/eth/api.go index fd35656476..6cd0fd7005 100644 --- a/eth/api.go +++ b/eth/api.go @@ -331,22 +331,29 @@ type BadBlockArgs struct { // GetBadBlocks returns a list of the last 'bad blocks' that the client has seen on the network // and returns them as a JSON list of block-hashes func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) { - blocks := api.eth.BlockChain().BadBlocks() - results := make([]*BadBlockArgs, len(blocks)) - - var err error - for i, block := range blocks { - results[i] = &BadBlockArgs{ - Hash: block.Hash(), - } + var ( + err error + blocks = rawdb.ReadAllBadBlocks(api.eth.chainDb) + results = make([]*BadBlockArgs, 0, len(blocks)) + ) + for _, block := range blocks { + var ( + blockRlp string + blockJSON map[string]interface{} + ) if rlpBytes, err := rlp.EncodeToBytes(block); err != nil { - results[i].RLP = err.Error() // Hacky, but hey, it works + blockRlp = err.Error() // Hacky, but hey, it works } else { - results[i].RLP = fmt.Sprintf("0x%x", rlpBytes) + blockRlp = fmt.Sprintf("0x%x", rlpBytes) } - if results[i].Block, err = ethapi.RPCMarshalBlock(block, true, true); err != nil { - results[i].Block = map[string]interface{}{"error": err.Error()} + if blockJSON, err = ethapi.RPCMarshalBlock(block, true, true); err != nil { + blockJSON = map[string]interface{}{"error": err.Error()} } + results = append(results, &BadBlockArgs{ + Hash: block.Hash(), + RLP: blockRlp, + Block: blockJSON, + }) } return results, nil } diff --git a/eth/api_tracer.go b/eth/api_tracer.go index c68b762255..8e71945ee2 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -404,8 +404,7 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, // EVM against a block pulled from the pool of bad ones and returns them as a JSON // object. func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { - blocks := api.eth.blockchain.BadBlocks() - for _, block := range blocks { + for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) { if block.Hash() == hash { return api.traceBlock(ctx, block, config) } @@ -428,8 +427,7 @@ func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash c // execution of EVM against a block pulled from the pool of bad ones to the // local file system and returns a list of files to the caller. func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { - blocks := api.eth.blockchain.BadBlocks() - for _, block := range blocks { + for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) { if block.Hash() == hash { return api.standardTraceBlockToFile(ctx, block, config) } From ab5e3f400f9dd3832508208ecef8b75c681728ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 11 Jan 2021 10:31:03 +0200 Subject: [PATCH 114/235] common/prque: pull in tests and benchmarks from upstream --- common/prque/prque_test.go | 130 ++++++++++++++++++++++++++++++++++++ common/prque/sstack_test.go | 100 +++++++++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 common/prque/prque_test.go create mode 100644 common/prque/sstack_test.go diff --git a/common/prque/prque_test.go b/common/prque/prque_test.go new file mode 100644 index 0000000000..1cffcebad4 --- /dev/null +++ b/common/prque/prque_test.go @@ -0,0 +1,130 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +package prque + +import ( + "math/rand" + "testing" +) + +func TestPrque(t *testing.T) { + // Generate a batch of random data and a specific priority order + size := 16 * blockSize + prio := rand.Perm(size) + data := make([]int, size) + for i := 0; i < size; i++ { + data[i] = rand.Int() + } + queue := New(nil) + for rep := 0; rep < 2; rep++ { + // Fill a priority queue with the above data + for i := 0; i < size; i++ { + queue.Push(data[i], int64(prio[i])) + if queue.Size() != i+1 { + t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1) + } + } + // Create a map the values to the priorities for easier verification + dict := make(map[int64]int) + for i := 0; i < size; i++ { + dict[int64(prio[i])] = data[i] + } + // Pop out the elements in priority order and verify them + prevPrio := int64(size + 1) + for !queue.Empty() { + val, prio := queue.Pop() + if prio > prevPrio { + t.Errorf("invalid priority order: %v after %v.", prio, prevPrio) + } + prevPrio = prio + if val != dict[prio] { + t.Errorf("push/pop mismatch: have %v, want %v.", val, dict[prio]) + } + delete(dict, prio) + } + } +} + +func TestReset(t *testing.T) { + // Generate a batch of random data and a specific priority order + size := 16 * blockSize + prio := rand.Perm(size) + data := make([]int, size) + for i := 0; i < size; i++ { + data[i] = rand.Int() + } + queue := New(nil) + for rep := 0; rep < 2; rep++ { + // Fill a priority queue with the above data + for i := 0; i < size; i++ { + queue.Push(data[i], int64(prio[i])) + if queue.Size() != i+1 { + t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1) + } + } + // Create a map the values to the priorities for easier verification + dict := make(map[int64]int) + for i := 0; i < size; i++ { + dict[int64(prio[i])] = data[i] + } + // Pop out half the elements in priority order and verify them + prevPrio := int64(size + 1) + for i := 0; i < size/2; i++ { + val, prio := queue.Pop() + if prio > prevPrio { + t.Errorf("invalid priority order: %v after %v.", prio, prevPrio) + } + prevPrio = prio + if val != dict[prio] { + t.Errorf("push/pop mismatch: have %v, want %v.", val, dict[prio]) + } + delete(dict, prio) + } + // Reset and ensure it's empty + queue.Reset() + if !queue.Empty() { + t.Errorf("priority queue not empty after reset: %v", queue) + } + } +} + +func BenchmarkPush(b *testing.B) { + // Create some initial data + data := make([]int, b.N) + prio := make([]int64, b.N) + for i := 0; i < len(data); i++ { + data[i] = rand.Int() + prio[i] = rand.Int63() + } + // Execute the benchmark + b.ResetTimer() + queue := New(nil) + for i := 0; i < len(data); i++ { + queue.Push(data[i], prio[i]) + } +} + +func BenchmarkPop(b *testing.B) { + // Create some initial data + data := make([]int, b.N) + prio := make([]int64, b.N) + for i := 0; i < len(data); i++ { + data[i] = rand.Int() + prio[i] = rand.Int63() + } + queue := New(nil) + for i := 0; i < len(data); i++ { + queue.Push(data[i], prio[i]) + } + // Execute the benchmark + b.ResetTimer() + for !queue.Empty() { + queue.Pop() + } +} diff --git a/common/prque/sstack_test.go b/common/prque/sstack_test.go new file mode 100644 index 0000000000..2ff093579d --- /dev/null +++ b/common/prque/sstack_test.go @@ -0,0 +1,100 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +package prque + +import ( + "math/rand" + "sort" + "testing" +) + +func TestSstack(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item, size) + for i := 0; i < size; i++ { + data[i] = &item{rand.Int(), rand.Int63()} + } + stack := newSstack(nil) + for rep := 0; rep < 2; rep++ { + // Push all the data into the stack, pop out every second + secs := []*item{} + for i := 0; i < size; i++ { + stack.Push(data[i]) + if i%2 == 0 { + secs = append(secs, stack.Pop().(*item)) + } + } + rest := []*item{} + for stack.Len() > 0 { + rest = append(rest, stack.Pop().(*item)) + } + // Make sure the contents of the resulting slices are ok + for i := 0; i < size; i++ { + if i%2 == 0 && data[i] != secs[i/2] { + t.Errorf("push/pop mismatch: have %v, want %v.", secs[i/2], data[i]) + } + if i%2 == 1 && data[i] != rest[len(rest)-i/2-1] { + t.Errorf("push/pop mismatch: have %v, want %v.", rest[len(rest)-i/2-1], data[i]) + } + } + } +} + +func TestSstackSort(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item, size) + for i := 0; i < size; i++ { + data[i] = &item{rand.Int(), int64(i)} + } + // Push all the data into the stack + stack := newSstack(nil) + for _, val := range data { + stack.Push(val) + } + // Sort and pop the stack contents (should reverse the order) + sort.Sort(stack) + for _, val := range data { + out := stack.Pop() + if out != val { + t.Errorf("push/pop mismatch after sort: have %v, want %v.", out, val) + } + } +} + +func TestSstackReset(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item, size) + for i := 0; i < size; i++ { + data[i] = &item{rand.Int(), rand.Int63()} + } + stack := newSstack(nil) + for rep := 0; rep < 2; rep++ { + // Push all the data into the stack, pop out every second + secs := []*item{} + for i := 0; i < size; i++ { + stack.Push(data[i]) + if i%2 == 0 { + secs = append(secs, stack.Pop().(*item)) + } + } + // Reset and verify both pulled and stack contents + stack.Reset() + if stack.Len() != 0 { + t.Errorf("stack not empty after reset: %v", stack) + } + for i := 0; i < size; i++ { + if i%2 == 0 && data[i] != secs[i/2] { + t.Errorf("push/pop mismatch: have %v, want %v.", secs[i/2], data[i]) + } + } + } +} From 49c2816d5464ed208389d52ed7cf47f35630b1c2 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 11 Jan 2021 12:53:13 +0100 Subject: [PATCH 115/235] eth: improve log message (#22146) * eth: fixed typos * eth: fixed log message --- eth/handler.go | 6 +++--- eth/peerset.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index 76a429f6d3..a9506c4995 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -437,14 +437,14 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) // Broadcast transactions to a batch of peers not knowing about it if propagate { for _, tx := range txs { - peers := h.peers.ethPeersWithoutTransacion(tx.Hash()) + peers := h.peers.ethPeersWithoutTransaction(tx.Hash()) // Send the block to a subset of our peers transfer := peers[:int(math.Sqrt(float64(len(peers))))] for _, peer := range transfer { txset[peer] = append(txset[peer], tx.Hash()) } - log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(peers)) + log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(transfer)) } for peer, hashes := range txset { peer.AsyncSendTransactions(hashes) @@ -453,7 +453,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) } // Otherwise only broadcast the announcement to peers for _, tx := range txs { - peers := h.peers.ethPeersWithoutTransacion(tx.Hash()) + peers := h.peers.ethPeersWithoutTransaction(tx.Hash()) for _, peer := range peers { annos[peer] = append(annos[peer], tx.Hash()) } diff --git a/eth/peerset.go b/eth/peerset.go index 9b584ec320..bf5785ff3f 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -243,9 +243,9 @@ func (ps *peerSet) ethPeersWithoutBlock(hash common.Hash) []*ethPeer { return list } -// ethPeersWithoutTransacion retrieves a list of `eth` peers that do not have a +// ethPeersWithoutTransaction retrieves a list of `eth` peers that do not have a // given transaction in their set of known hashes. -func (ps *peerSet) ethPeersWithoutTransacion(hash common.Hash) []*ethPeer { +func (ps *peerSet) ethPeersWithoutTransaction(hash common.Hash) []*ethPeer { ps.lock.RLock() defer ps.lock.RUnlock() From 39b3b8ffb44983a260031d5077226d952ddfb9bf Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 11 Jan 2021 14:55:42 +0100 Subject: [PATCH 116/235] graphql: fix issue with unmarshalling int32 into `Long` type #22153 --- graphql/schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql/schema.go b/graphql/schema.go index 1fdc370040..6ea63db636 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -300,7 +300,7 @@ const schema string = ` block(number: Long, hash: Bytes32): Block # Blocks returns all the blocks between two numbers, inclusive. If # to is not supplied, it defaults to the most recent known block. - blocks(from: Long!, to: Long): [Block!]! + blocks(from: Long, to: Long): [Block!]! # Pending returns the current pending state. pending: Pending! # Transaction returns a transaction specified by its hash. From 984e752ce52b09142ed936c337456df27128e3bc Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 12 Jan 2021 10:52:13 +0100 Subject: [PATCH 117/235] eth: return error from eth_chainID during sync before EIP-155 activates (#21686) This changes the chainID RPC method to return an error when EIP-155 is not yet active at the current block height. It used to simply return zero in this case, but that's confusing. --- eth/api.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eth/api.go b/eth/api.go index 6cd0fd7005..be1dcbb52a 100644 --- a/eth/api.go +++ b/eth/api.go @@ -67,12 +67,12 @@ func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 { } // ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config. -func (api *PublicEthereumAPI) ChainId() hexutil.Uint64 { - chainID := new(big.Int) +func (api *PublicEthereumAPI) ChainId() (hexutil.Uint64, error) { + // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config if config := api.e.blockchain.Config(); config.IsEIP155(api.e.blockchain.CurrentBlock().Number()) { - chainID = config.ChainID + return (hexutil.Uint64)(config.ChainID.Uint64()), nil } - return (hexutil.Uint64)(chainID.Uint64()) + return hexutil.Uint64(0), fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block") } // PublicMinerAPI provides an API to control the miner. From 23f837c38827d7c1ea67b71eb6f79934562f0d98 Mon Sep 17 00:00:00 2001 From: meowsbits Date: Tue, 12 Jan 2021 08:50:11 -0600 Subject: [PATCH 118/235] cmd/utils: avoid making console preloads absolute (#22109) Resolves https://github.com/etclabscore/core-geth/issues/273 jsre.JSRE already handles establishing preload file paths relative to the 'assets' path (aka docroot), where it joins the assets dir and the file path if relative, or uses the file path only if absolute. The duplication of this logic by MakeConsolePreloads caused preloaded files to have paths which contained duplicate references to the assets dir path. Date: 2020-12-30 08:25:01-06:00 Signed-off-by: meows --- cmd/utils/flags.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 684e3428ba..fc64539a7c 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1908,9 +1908,8 @@ func MakeConsolePreloads(ctx *cli.Context) []string { // Otherwise resolve absolute paths and return them var preloads []string - assets := ctx.GlobalString(JSpathFlag.Name) for _, file := range strings.Split(ctx.GlobalString(PreloadJSFlag.Name), ",") { - preloads = append(preloads, common.AbsolutePath(assets, strings.TrimSpace(file))) + preloads = append(preloads, strings.TrimSpace(file)) } return preloads } From 93a89b26819c9b21ff32ab650b8916076b53b471 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 12 Jan 2021 17:39:31 +0100 Subject: [PATCH 119/235] go.mod: use github.com/holiman/bloomfilter/v2 (#22044) * deps: use improved bloom filter implementation * eth/handler, trie: use 4 keys for syncbloom + minor fixes * eth/protocols, trie: revert change on syncbloom method signature --- core/state/snapshot/difflayer.go | 2 +- go.mod | 7 ++--- go.sum | 34 ++------------------- trie/sync.go | 2 +- trie/sync_bloom.go | 51 ++++++++------------------------ 5 files changed, 18 insertions(+), 78 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 3be78cae88..cc82df9a54 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" - "github.com/steakknife/bloomfilter" + bloomfilter "github.com/holiman/bloomfilter/v2" ) var ( diff --git a/go.mod b/go.mod index 0f47809ef6..57c15a34f6 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,6 @@ require ( github.com/dlclark/regexp2 v1.2.0 // indirect github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 - github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 // indirect github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c github.com/fatih/color v1.3.0 github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc @@ -32,6 +31,7 @@ require ( github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 github.com/hashicorp/golang-lru v0.5.4 + github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.1.1 github.com/huin/goupnp v1.0.0 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 @@ -54,14 +54,11 @@ require ( github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect github.com/shirou/gopsutil v2.20.5+incompatible github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 - github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 - github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/mobile v0.0.0-20200801112145-973feb4309de // indirect golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 golang.org/x/text v0.3.3 @@ -69,5 +66,5 @@ require ( gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 - gotest.tools v2.2.0+incompatible + gotest.tools v2.2.0+incompatible // indirect ) diff --git a/go.sum b/go.sum index d1bd608585..d063cd8f08 100644 --- a/go.sum +++ b/go.sum @@ -20,7 +20,6 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -60,8 +59,6 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 h1:Y9vTBSsV4hSwPSj4bacAU/eSnV3dAxVpepaghAdhGoQ= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= -github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 h1:NgO45/5mBLRVfiXerEFzH6ikcZ7DNRPS639xFg3ENzU= -github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= @@ -95,8 +92,6 @@ github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 h1:lMm2hD9Fy0ynom5+85/pbdkiYcBqM1JWmhpAXLmy0fw= -github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 h1:ur2rms48b3Ep1dxh7aUV2FZEQ8jEVO2F6ILKx8ofkAg= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -104,8 +99,6 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54= @@ -114,6 +107,8 @@ github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKx github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -200,10 +195,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1vJb5vwEjIp5kBj/eu99p/bl0Ay2goiPe5xE= -github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= -github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM= -github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -217,26 +208,12 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20200801112145-973feb4309de h1:OVJ6QQUBAesB8CZijKDSsXX7xYVtUhrkY0gwMfbi4p4= -golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= @@ -244,7 +221,6 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -266,12 +242,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/trie/sync.go b/trie/sync.go index bc93ddd3fb..05b9f55d33 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -410,7 +410,7 @@ func (s *Sync) children(req *request, object node) ([]*request, error) { // Bloom filter says this might be a duplicate, double check. // If database says yes, then at least the trie node is present // and we hold the assumption that it's NOT legacy contract code. - if blob := rawdb.ReadTrieNode(s.database, common.BytesToHash(node)); len(blob) > 0 { + if blob := rawdb.ReadTrieNode(s.database, hash); len(blob) > 0 { continue } // False positive, bump fault meter diff --git a/trie/sync_bloom.go b/trie/sync_bloom.go index 979f4748f3..1afcce21da 100644 --- a/trie/sync_bloom.go +++ b/trie/sync_bloom.go @@ -19,7 +19,6 @@ package trie import ( "encoding/binary" "fmt" - "math" "sync" "sync/atomic" "time" @@ -29,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" - "github.com/steakknife/bloomfilter" + bloomfilter "github.com/holiman/bloomfilter/v2" ) var ( @@ -41,18 +40,6 @@ var ( bloomErrorGauge = metrics.NewRegisteredGauge("trie/bloom/error", nil) ) -// syncBloomHasher is a wrapper around a byte blob to satisfy the interface API -// requirements of the bloom library used. It's used to convert a trie hash or -// contract code hash into a 64 bit mini hash. -type syncBloomHasher []byte - -func (f syncBloomHasher) Write(p []byte) (n int, err error) { panic("not implemented") } -func (f syncBloomHasher) Sum(b []byte) []byte { panic("not implemented") } -func (f syncBloomHasher) Reset() { panic("not implemented") } -func (f syncBloomHasher) BlockSize() int { panic("not implemented") } -func (f syncBloomHasher) Size() int { return 8 } -func (f syncBloomHasher) Sum64() uint64 { return binary.BigEndian.Uint64(f) } - // SyncBloom is a bloom filter used during fast sync to quickly decide if a trie // node or contract code already exists on disk or not. It self populates from the // provided disk database on creation in a background thread and will only start @@ -69,7 +56,7 @@ type SyncBloom struct { // initializes it from the database. The bloom is hard coded to use 3 filters. func NewSyncBloom(memory uint64, database ethdb.Iteratee) *SyncBloom { // Create the bloom filter to track known trie nodes - bloom, err := bloomfilter.New(memory*1024*1024*8, 3) + bloom, err := bloomfilter.New(memory*1024*1024*8, 4) if err != nil { panic(fmt.Sprintf("failed to create bloom: %v", err)) } @@ -110,12 +97,11 @@ func (b *SyncBloom) init(database ethdb.Iteratee) { // If the database entry is a trie node, add it to the bloom key := it.Key() if len(key) == common.HashLength { - b.bloom.Add(syncBloomHasher(key)) + b.bloom.AddHash(binary.BigEndian.Uint64(key)) bloomLoadMeter.Mark(1) - } - // If the database entry is a contract code, add it to the bloom - if ok, hash := rawdb.IsCodeKey(key); ok { - b.bloom.Add(syncBloomHasher(hash)) + } else if ok, hash := rawdb.IsCodeKey(key); ok { + // If the database entry is a contract code, add it to the bloom + b.bloom.AddHash(binary.BigEndian.Uint64(hash)) bloomLoadMeter.Mark(1) } // If enough time elapsed since the last iterator swap, restart @@ -125,14 +111,14 @@ func (b *SyncBloom) init(database ethdb.Iteratee) { it.Release() it = database.NewIterator(nil, key) - log.Info("Initializing state bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) + log.Info("Initializing state bloom", "items", b.bloom.N(), "errorrate", b.bloom.FalsePosititveProbability(), "elapsed", common.PrettyDuration(time.Since(start))) swap = time.Now() } } it.Release() // Mark the bloom filter inited and return - log.Info("Initialized state bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) + log.Info("Initialized state bloom", "items", b.bloom.N(), "errorrate", b.bloom.FalsePosititveProbability(), "elapsed", common.PrettyDuration(time.Since(start))) atomic.StoreUint32(&b.inited, 1) } @@ -141,7 +127,7 @@ func (b *SyncBloom) init(database ethdb.Iteratee) { func (b *SyncBloom) meter() { for { // Report the current error ration. No floats, lame, scale it up. - bloomErrorGauge.Update(int64(b.errorRate() * 100000)) + bloomErrorGauge.Update(int64(b.bloom.FalsePosititveProbability() * 100000)) // Wait one second, but check termination more frequently for i := 0; i < 10; i++ { @@ -162,7 +148,7 @@ func (b *SyncBloom) Close() error { b.pend.Wait() // Wipe the bloom, but mark it "uninited" just in case someone attempts an access - log.Info("Deallocated state bloom", "items", b.bloom.N(), "errorrate", b.errorRate()) + log.Info("Deallocated state bloom", "items", b.bloom.N(), "errorrate", b.bloom.FalsePosititveProbability()) atomic.StoreUint32(&b.inited, 0) b.bloom = nil @@ -175,7 +161,7 @@ func (b *SyncBloom) Add(hash []byte) { if atomic.LoadUint32(&b.closed) == 1 { return } - b.bloom.Add(syncBloomHasher(hash)) + b.bloom.AddHash(binary.BigEndian.Uint64(hash)) bloomAddMeter.Mark(1) } @@ -193,22 +179,9 @@ func (b *SyncBloom) Contains(hash []byte) bool { return true } // Bloom initialized, check the real one and report any successful misses - maybe := b.bloom.Contains(syncBloomHasher(hash)) + maybe := b.bloom.ContainsHash(binary.BigEndian.Uint64(hash)) if !maybe { bloomMissMeter.Mark(1) } return maybe } - -// errorRate calculates the probability of a random containment test returning a -// false positive. -// -// We're calculating it ourselves because the bloom library we used missed a -// parentheses in the formula and calculates it wrong. And it's discontinued... -func (b *SyncBloom) errorRate() float64 { - k := float64(b.bloom.K()) - n := float64(b.bloom.N()) - m := float64(b.bloom.M()) - - return math.Pow(1.0-math.Exp((-k)*(n+0.5)/(m-1)), k) -} From c7a6be163f07c967172dfcb3fe2ef25ff08dd4de Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Wed, 13 Jan 2021 10:14:36 +0000 Subject: [PATCH 120/235] cmd/utils: don't enumerate USB unless --usb is set (#22130) USB enumeration still occured. Make sure it will only occur if --usb is set. This also deprecates the 'NoUSB' config file option in favor of a new option 'USB'. --- cmd/geth/accountcmd_test.go | 10 +++++----- cmd/geth/consolecmd_test.go | 2 +- cmd/geth/dao_test.go | 4 ++-- cmd/geth/genesis_test.go | 4 ++-- cmd/geth/les_test.go | 6 +++--- cmd/utils/flags.go | 8 ++++---- miner/stress_clique.go | 1 - miner/stress_ethash.go | 1 - node/api_test.go | 1 - node/config.go | 5 ++++- p2p/simulations/adapters/exec.go | 1 - p2p/simulations/adapters/inproc.go | 1 - 12 files changed, 21 insertions(+), 23 deletions(-) diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index 04f55e9e7a..9455eeda36 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -43,13 +43,13 @@ func tmpDatadirWithKeystore(t *testing.T) string { } func TestAccountListEmpty(t *testing.T) { - geth := runGeth(t, "--nousb", "account", "list") + geth := runGeth(t, "account", "list") geth.ExpectExit() } func TestAccountList(t *testing.T) { datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "--nousb", "account", "list", "--datadir", datadir) + geth := runGeth(t, "account", "list", "--datadir", datadir) defer geth.ExpectExit() if runtime.GOOS == "windows" { geth.Expect(` @@ -139,7 +139,7 @@ Fatal: Passwords do not match func TestAccountUpdate(t *testing.T) { datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "--nousb", "account", "update", + geth := runGeth(t, "account", "update", "--datadir", datadir, "--lightkdf", "f466859ead1932d743d622cb74fc058882e8648a") defer geth.ExpectExit() @@ -154,7 +154,7 @@ Repeat password: {{.InputLine "foobar2"}} } func TestWalletImport(t *testing.T) { - geth := runGeth(t, "--nousb", "wallet", "import", "--lightkdf", "testdata/guswallet.json") + geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.ExpectExit() geth.Expect(` !! Unsupported terminal, password will be echoed. @@ -169,7 +169,7 @@ Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f} } func TestWalletImportBadPassword(t *testing.T) { - geth := runGeth(t, "--nousb", "wallet", "import", "--lightkdf", "testdata/guswallet.json") + geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.ExpectExit() geth.Expect(` !! Unsupported terminal, password will be echoed. diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index b0555c45d7..93f9e3d0d9 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -42,7 +42,7 @@ func runMinimalGeth(t *testing.T, args ...string) *testgeth { // --ropsten to make the 'writing genesis to disk' faster (no accounts) // --networkid=1337 to avoid cache bump // --syncmode=full to avoid allocating fast sync bloom - allArgs := []string{"--ropsten", "--nousb", "--networkid", "1337", "--syncmode=full", "--port", "0", + allArgs := []string{"--ropsten", "--networkid", "1337", "--syncmode=full", "--port", "0", "--nat", "none", "--nodiscover", "--maxpeers", "0", "--cache", "64"} return runGeth(t, append(allArgs, args...)...) } diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index df7f14fdb8..29b1a7f474 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -115,10 +115,10 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil { t.Fatalf("test %d: failed to write genesis file: %v", test, err) } - runGeth(t, "--datadir", datadir, "--nousb", "--networkid", "1337", "init", json).WaitExit() + runGeth(t, "--datadir", datadir, "--networkid", "1337", "init", json).WaitExit() } else { // Force chain initialization - args := []string{"--port", "0", "--nousb", "--networkid", "1337", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} + args := []string{"--port", "0", "--networkid", "1337", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...).WaitExit() } // Retrieve the DAO config flag from the database diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go index 0651c32cad..cbc1b38374 100644 --- a/cmd/geth/genesis_test.go +++ b/cmd/geth/genesis_test.go @@ -81,10 +81,10 @@ func TestCustomGenesis(t *testing.T) { if err := ioutil.WriteFile(json, []byte(tt.genesis), 0600); err != nil { t.Fatalf("test %d: failed to write genesis file: %v", i, err) } - runGeth(t, "--nousb", "--datadir", datadir, "init", json).WaitExit() + runGeth(t, "--datadir", datadir, "init", json).WaitExit() // Query the custom genesis block - geth := runGeth(t, "--nousb", "--networkid", "1337", "--syncmode=full", + geth := runGeth(t, "--networkid", "1337", "--syncmode=full", "--datadir", datadir, "--maxpeers", "0", "--port", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--exec", tt.query, "console") diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index d2f63ac7bd..053ce96aa3 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -130,7 +130,7 @@ var nextIPC = uint32(0) func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { ipcName := fmt.Sprintf("geth-%d.ipc", atomic.AddUint32(&nextIPC, 1)) - args = append([]string{"--networkid=42", "--port=0", "--nousb", "--ipcpath", ipcName}, args...) + args = append([]string{"--networkid=42", "--port=0", "--ipcpath", ipcName}, args...) t.Logf("Starting %v with rpc: %v", name, args) g := &gethrpc{ @@ -148,7 +148,7 @@ func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { } func initGeth(t *testing.T) string { - args := []string{"--nousb", "--networkid=42", "init", "./testdata/clique.json"} + args := []string{"--networkid=42", "init", "./testdata/clique.json"} t.Logf("Initializing geth: %v ", args) g := runGeth(t, args...) datadir := g.Datadir @@ -159,7 +159,7 @@ func initGeth(t *testing.T) string { func startLightServer(t *testing.T) *gethrpc { datadir := initGeth(t) t.Logf("Importing keys to geth") - runGeth(t, "--nousb", "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv", "--lightkdf").WaitExit() + runGeth(t, "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv", "--lightkdf").WaitExit() account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105" server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1", "--verbosity=4") return server diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index fc64539a7c..6f4da58320 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1233,12 +1233,12 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LightKDFFlag.Name) { cfg.UseLightweightKDF = ctx.GlobalBool(LightKDFFlag.Name) } - if ctx.GlobalIsSet(USBFlag.Name) { - cfg.NoUSB = !ctx.GlobalBool(USBFlag.Name) - } - if ctx.GlobalIsSet(NoUSBFlag.Name) { + if ctx.GlobalIsSet(NoUSBFlag.Name) || cfg.NoUSB { log.Warn("Option nousb is deprecated and USB is deactivated by default. Use --usb to enable") } + if ctx.GlobalIsSet(USBFlag.Name) { + cfg.USB = ctx.GlobalBool(USBFlag.Name) + } if ctx.GlobalIsSet(InsecureUnlockAllowedFlag.Name) { cfg.InsecureUnlockAllowed = ctx.GlobalBool(InsecureUnlockAllowedFlag.Name) } diff --git a/miner/stress_clique.go b/miner/stress_clique.go index 21538aaaed..a0fd596098 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -178,7 +178,6 @@ func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { NoDiscovery: true, MaxPeers: 25, }, - NoUSB: true, } // Start the node and configure a full Ethereum node on it stack, err := node.New(config) diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 5a7e7685a6..1713af9d23 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -155,7 +155,6 @@ func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { NoDiscovery: true, MaxPeers: 25, }, - NoUSB: true, UseLightweightKDF: true, } // Create the node and configure a full Ethereum node on it diff --git a/node/api_test.go b/node/api_test.go index e4c08962c3..a07ce833c4 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -248,7 +248,6 @@ func TestStartRPC(t *testing.T) { // Apply some sane defaults. config := test.cfg // config.Logger = testlog.Logger(t, log.LvlDebug) - config.NoUSB = true config.P2P.NoDiscovery = true // Create Node. diff --git a/node/config.go b/node/config.go index 55532632cd..61e41cd7d0 100644 --- a/node/config.go +++ b/node/config.go @@ -95,6 +95,9 @@ type Config struct { // NoUSB disables hardware wallet monitoring and connectivity. NoUSB bool `toml:",omitempty"` + // USB enables hardware wallet monitoring and connectivity. + USB bool `toml:",omitempty"` + // SmartCardDaemonPath is the path to the smartcard daemon's socket SmartCardDaemonPath string `toml:",omitempty"` @@ -476,7 +479,7 @@ func makeAccountManager(conf *Config) (*accounts.Manager, string, error) { // we can have both, but it's very confusing for the user to see the same // accounts in both externally and locally, plus very racey. backends = append(backends, keystore.NewKeyStore(keydir, scryptN, scryptP)) - if !conf.NoUSB { + if conf.USB { // Start a USB hub for Ledger hardware wallets if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 0ed3deab38..35ccdfb068 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -115,7 +115,6 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { conf.Stack.P2P.EnableMsgEvents = config.EnableMsgEvents conf.Stack.P2P.NoDiscovery = true conf.Stack.P2P.NAT = nil - conf.Stack.NoUSB = true // Listen on a localhost port, which we set when we // initialise NodeConfig (usually a random port) diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 4fc7abc06a..1cb26a8ea0 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -100,7 +100,6 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { EnableMsgEvents: config.EnableMsgEvents, }, ExternalSigner: config.ExternalSigner, - NoUSB: true, Logger: log.New("node.id", id.String()), }) if err != nil { From c94081774f6c8c43e10deb5735c3f9a09a7bcd04 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 13 Jan 2021 11:29:28 +0100 Subject: [PATCH 121/235] tests: update the reference tests (#22009) --- tests/testdata | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata b/tests/testdata index 7497b116a0..6c863f03be 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit 7497b116a019beb26215cbea4028df068dea06be +Subproject commit 6c863f03bee8d7a66bb7a028a9f880a86a5f4975 From 6296211a3ee2502bdec73256eadaf9185fb4d946 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 13 Jan 2021 11:42:26 +0100 Subject: [PATCH 122/235] graphql: fix spurious error in test (#22164) This solves an issue in graphql tests: graphql_test.go:38: could not create new node: datadir already used by another process --- graphql/graphql_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index fc62da1813..4a1644a61b 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -33,7 +33,14 @@ import ( ) func TestBuildSchema(t *testing.T) { - stack, err := node.New(&node.DefaultConfig) + ddir, err := ioutil.TempDir("", "graphql-buildschema") + if err != nil { + t.Fatalf("failed to create temporary datadir: %v", err) + } + // Copy config + conf := node.DefaultConfig + conf.DataDir = ddir + stack, err := node.New(&conf) if err != nil { t.Fatalf("could not create new node: %v", err) } @@ -157,6 +164,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { if err != nil { t.Fatalf("could not read from response body: %v", err) } + resp.Body.Close() // make sure the request is not handled successfully if want, have := "404 page not found\n", string(bodyBytes); have != want { t.Errorf("have:\n%v\nwant:\n%v", have, want) From 2aaff0ad76991be8851ae30454d2e2e967704102 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 13 Jan 2021 11:44:20 +0100 Subject: [PATCH 123/235] consensus/ethash: increase seal timeout for tests (#22162) It seems that the 2 second timeout is not enough for Travis CI: --- FAIL: TestTestMode (2.00s) ethash_test.go:53: sealing result timeout --- consensus/ethash/ethash_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/ethash/ethash_test.go b/consensus/ethash/ethash_test.go index fdfd81320f..adbf1ccfeb 100644 --- a/consensus/ethash/ethash_test.go +++ b/consensus/ethash/ethash_test.go @@ -49,7 +49,7 @@ func TestTestMode(t *testing.T) { if err := ethash.VerifySeal(nil, header); err != nil { t.Fatalf("unexpected verification error: %v", err) } - case <-time.NewTimer(2 * time.Second).C: + case <-time.NewTimer(4 * time.Second).C: t.Error("sealing result timeout") } } From 96157a897be2032be5fdb87f947fbe5df8a53bd4 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 13 Jan 2021 22:43:07 +0100 Subject: [PATCH 124/235] graphql: fix spurious travis failure (#22166) The tests sometimes failed with certain go versions because the behavior of http.Server.Shutdown changed over time. A bug that was fixed in Go 1.15 could cause active connections on unrelated servers to close unexpectedly. This is fixed by avoiding use of the same port number in all tests. --- graphql/graphql_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 4a1644a61b..e9c129c44c 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -131,7 +131,7 @@ func TestGraphQLBlockSerialization(t *testing.T) { code: 200, }, } { - resp, err := http.Post(fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), "application/json", strings.NewReader(tt.body)) + resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body)) if err != nil { t.Fatalf("could not post: %v", err) } @@ -156,7 +156,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { t.Fatalf("could not start node: %v", err) } body := strings.NewReader(`{"query": "{block{number}}","variables": null}`) - resp, err := http.Post(fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), "application/json", body) + resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", body) if err != nil { t.Fatalf("could not post: %v", err) } @@ -177,9 +177,9 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { func createNode(t *testing.T, gqlEnabled bool) *node.Node { stack, err := node.New(&node.Config{ HTTPHost: "127.0.0.1", - HTTPPort: 9393, + HTTPPort: 0, WSHost: "127.0.0.1", - WSPort: 9393, + WSPort: 0, }) if err != nil { t.Fatalf("could not create node: %v", err) @@ -187,11 +187,11 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { if !gqlEnabled { return stack } - createGQLService(t, stack, "127.0.0.1:9393") + createGQLService(t, stack) return stack } -func createGQLService(t *testing.T, stack *node.Node, endpoint string) { +func createGQLService(t *testing.T, stack *node.Node) { // create backend ethConf := ð.Config{ Genesis: &core.Genesis{ From 12969084d17878ccb7978f24574bafacfe99f4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 14 Jan 2021 12:10:52 +0200 Subject: [PATCH 125/235] cmd/faucet: update the embedded website asset --- cmd/faucet/website.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/faucet/website.go b/cmd/faucet/website.go index a091d24919..aed067893a 100644 --- a/cmd/faucet/website.go +++ b/cmd/faucet/website.go @@ -1,6 +1,6 @@ // Code generated by go-bindata. DO NOT EDIT. // sources: -// faucet.html (11.27kB) +// faucet.html (11.276kB) package main @@ -69,7 +69,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _faucetHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\x6d\x93\xdb\x36\x92\xfe\x3c\xfe\x15\x1d\x9e\xbd\x92\xce\x43\x52\x33\x63\x7b\x7d\x12\xa9\x94\xd7\x9b\xdd\xf3\xd5\x5d\x92\x4a\x9c\xba\xdb\xca\xa6\xae\x40\xb2\x25\xc2\x03\x02\x0c\x00\x4a\xa3\x4c\xe9\xbf\x5f\x35\x40\x52\xd4\xcb\x4c\xec\xb5\xaf\x6a\xfd\x61\x4c\x02\x8d\x46\xa3\xfb\x69\xf4\x0b\x95\x7c\xf5\xe7\xef\xde\xbe\xff\xdb\xf7\xdf\x40\x69\x2b\xb1\x78\x92\xd0\x7f\x20\x98\x5c\xa5\x01\xca\x60\xf1\xe4\x22\x29\x91\x15\x8b\x27\x17\x17\x49\x85\x96\x41\x5e\x32\x6d\xd0\xa6\x41\x63\x97\xe1\xeb\x60\x3f\x51\x5a\x5b\x87\xf8\x6b\xc3\xd7\x69\xf0\x3f\xe1\x4f\x6f\xc2\xb7\xaa\xaa\x99\xe5\x99\xc0\x00\x72\x25\x2d\x4a\x9b\x06\xef\xbe\x49\xb1\x58\xe1\x60\x9d\x64\x15\xa6\xc1\x9a\xe3\xa6\x56\xda\x0e\x48\x37\xbc\xb0\x65\x5a\xe0\x9a\xe7\x18\xba\x97\x4b\xe0\x92\x5b\xce\x44\x68\x72\x26\x30\xbd\x0a\x16\x4f\x88\x8f\xe5\x56\xe0\xe2\xfe\x3e\xfa\x16\xed\x46\xe9\xdb\xdd\x6e\x06\x6f\x1a\x5b\xa2\xb4\x3c\x67\x16\x0b\xf8\x0b\x6b\x72\xb4\x49\xec\x29\xdd\x22\xc1\xe5\x2d\x94\x1a\x97\x69\x40\xa2\x9b\x59\x1c\xe7\x85\xfc\x60\xa2\x5c\xa8\xa6\x58\x0a\xa6\x31\xca\x55\x15\xb3\x0f\xec\x2e\x16\x3c\x33\xb1\xdd\x70\x6b\x51\x87\x99\x52\xd6\x58\xcd\xea\xf8\x26\xba\x89\xfe\x18\xe7\xc6\xc4\xfd\x58\x54\x71\x19\xe5\xc6\x04\xa0\x51\xa4\x81\xb1\x5b\x81\xa6\x44\xb4\x01\xc4\x8b\x7f\x6c\xdf\xa5\x92\x36\x64\x1b\x34\xaa\xc2\xf8\x45\xf4\xc7\x68\xea\xb6\x1c\x0e\x3f\xbe\x2b\x6d\x6b\x72\xcd\x6b\x0b\x46\xe7\x1f\xbd\xef\x87\x5f\x1b\xd4\xdb\xf8\x26\xba\x8a\xae\xda\x17\xb7\xcf\x07\x13\x2c\x92\xd8\x33\x5c\x7c\x16\xef\x50\x2a\xbb\x8d\xaf\xa3\x17\xd1\x55\x5c\xb3\xfc\x96\xad\xb0\xe8\x76\xa2\xa9\xa8\x1b\xfc\x62\xfb\x3e\x64\xc3\x0f\xc7\x26\xfc\x12\x9b\x55\xaa\x42\x69\xa3\x0f\x26\xbe\x8e\xae\x5e\x47\xd3\x6e\xe0\x94\xbf\xdb\x80\x8c\x46\x5b\x5d\x44\x6b\xd4\x84\x5c\x11\xe6\x28\x2d\x6a\xb8\xa7\xd1\x8b\x8a\xcb\xb0\x44\xbe\x2a\xed\x0c\xae\xa6\xd3\x67\xf3\x73\xa3\xeb\xd2\x0f\x17\xdc\xd4\x82\x6d\x67\xb0\x14\x78\xe7\x87\x98\xe0\x2b\x19\x72\x8b\x95\x99\x81\xe7\xec\x26\x76\x6e\xcf\x5a\xab\x95\x46\x63\xda\xcd\x6a\x65\xb8\xe5\x4a\xce\x08\x51\xcc\xf2\x35\x9e\xa3\x35\x35\x93\x27\x0b\x58\x66\x94\x68\x2c\x1e\x09\x92\x09\x95\xdf\xfa\x31\xe7\xcd\xc3\x43\xe4\x4a\x28\x3d\x83\x4d\xc9\xdb\x65\xe0\x36\x82\x5a\x63\xcb\x1e\x6a\x56\x14\x5c\xae\x66\xf0\xaa\x6e\xcf\x03\x15\xd3\x2b\x2e\x67\x30\xdd\x2f\x49\xe2\x4e\x8d\x49\xec\x2f\xae\x27\x17\x49\xa6\x8a\xad\xb3\x61\xc1\xd7\x90\x0b\x66\x4c\x1a\x1c\xa9\xd8\x5d\x48\x07\x04\x74\x0f\x31\x2e\xbb\xa9\x83\x39\xad\x36\x01\xb8\x8d\xd2\xc0\x0b\x11\x66\xca\x5a\x55\xcd\xe0\x8a\xc4\x6b\x97\x1c\xf1\x13\xa1\x58\x85\x57\xd7\xdd\xe4\x45\x52\x5e\x75\x4c\x2c\xde\xd9\xd0\xd9\xa7\xb7\x4c\xb0\x48\x78\xb7\x76\xc9\x60\xc9\xc2\x8c\xd9\x32\x00\xa6\x39\x0b\x4b\x5e\x14\x28\xd3\xc0\xea\x06\x09\x47\x7c\x01\xc3\xeb\xef\x81\xdb\xaf\xbc\xea\xe4\x8a\x0b\xbe\x6e\x8f\x35\x78\x3c\x3a\xe1\xc3\x87\x78\x0d\xed\x83\x5a\x2e\x0d\xda\x70\x70\xa6\x01\x31\x97\x75\x63\xc3\x95\x56\x4d\xdd\xcf\x5f\x24\x6e\x14\x78\x91\x06\x8d\x16\x41\x7b\xfd\xbb\x47\xbb\xad\x5b\x55\x04\xfd\xc1\x95\xae\x42\xb2\x84\x56\x22\x80\x5a\xb0\x1c\x4b\x25\x0a\xd4\x69\xf0\xa3\xca\x39\x13\x20\xfd\x99\xe1\xa7\x1f\xfe\x13\x5a\x93\x71\xb9\x82\xad\x6a\x34\x7c\x63\x4b\xd4\xd8\x54\xc0\x8a\x82\xe0\x1a\x45\xd1\x40\x10\x87\xdd\x53\x51\xc3\xcc\xca\x3d\xd5\x45\x92\x35\xd6\xaa\x9e\x30\xb3\x12\x32\x2b\xc3\x02\x97\xac\x11\x16\x0a\xad\xea\x42\x6d\x64\x68\xd5\x6a\x45\x91\xce\x1f\xc2\x2f\x0a\xa0\x60\x96\xb5\x53\x69\xd0\xd1\x76\x36\x64\xa6\x56\x75\x53\xb7\x56\xf4\x83\x78\x57\x33\x59\x60\x41\x36\x17\x06\x83\xc5\x5f\xf9\x1a\xa1\x42\x7f\x96\x8b\x63\x48\xe4\x4c\xa3\x0d\x87\x4c\x4f\x80\x91\xc4\x5e\x18\x7f\x24\x68\xff\x25\x8d\xe8\x38\xf5\x47\xa8\x50\x36\x70\xf0\x16\x6a\xba\x57\x82\xc5\xfd\xbd\x66\x72\x85\xf0\x94\x17\x77\x97\xf0\x94\x55\xaa\x91\x16\x66\x29\x44\x6f\xdc\xa3\xd9\xed\x0e\xb8\x03\x24\x82\x2f\x12\xf6\x18\xbc\x41\xc9\x5c\xf0\xfc\x36\x0d\x2c\x47\x9d\xde\xdf\x13\xf3\xdd\x6e\x0e\xf7\xf7\x7c\x09\x4f\xa3\x1f\x30\x67\xb5\xcd\x4b\xb6\xdb\xad\x74\xf7\x1c\xe1\x1d\xe6\x8d\xc5\xf1\xe4\xfe\x1e\x85\xc1\xdd\xce\x34\x59\xc5\xed\xb8\x5b\x4e\xe3\xb2\xd8\xed\x48\xe6\x56\xce\xdd\x0e\x62\x62\x2a\x0b\xbc\x83\xa7\xd1\xf7\xa8\xb9\x2a\x0c\x78\xfa\x24\x66\x8b\x24\x16\x7c\xd1\xae\x3b\x54\x52\xdc\x88\x3d\x5e\x62\x02\x4c\x8f\x73\xe7\x36\x4e\xd4\xa1\xa4\x67\xbc\x60\x15\xf6\xd2\xb7\x78\x30\xdc\xe2\x2d\x6e\xd3\xe0\xfe\x7e\xb8\xb6\x9d\xcd\x99\x10\x19\x23\xbd\xf8\xa3\xf5\x8b\x7e\x43\xc2\xe9\x9a\x1b\x97\x52\x2d\x3a\x09\xf6\x62\x7f\xa4\x5b\x1f\x5d\x5c\x56\xd5\x33\xb8\xb9\x1e\xdc\x5a\xe7\x3c\xfe\xd5\x91\xc7\xdf\x9c\x25\xae\x99\x44\x01\xee\x6f\x68\x2a\x26\xba\xe7\xd6\x5b\x06\xce\x77\xbc\x28\xa4\x3b\xba\x17\xad\xbf\xeb\xa7\x73\x50\x6b\xd4\x4b\xa1\x36\x33\x60\x8d\x55\x73\xa8\xd8\x5d\x1f\xef\x6e\xa6\xd3\xa1\xdc\x94\x0a\xb2\x4c\xa0\xbb\x5d\x34\xfe\xda\xa0\xb1\xa6\xbf\x4b\xfc\x94\xfb\x4b\x57\x4a\x81\xd2\x60\x71\xa4\x0d\xda\x91\x54\xeb\xa8\x06\xa6\xef\x95\x79\x56\xf6\xa5\x52\x7d\x08\x19\x8a\xd1\xb2\x1e\x44\xbb\x60\x91\x58\xbd\xa7\xbb\x48\x6c\xf1\x49\x21\x40\x53\x8a\xf7\x50\x04\xf0\x37\x1a\x9d\xbd\x46\xd4\x3e\xbf\x20\xc8\x82\x7b\x4d\x62\x5b\x7c\xc6\xce\x04\xc2\x8c\x19\xfc\x98\xed\x5d\xa4\xdf\x6f\xef\x5e\x3f\x77\xff\x12\x99\xb6\x19\x32\xfb\x31\x02\x2c\x1b\x59\x0c\xce\xef\xee\xce\xcf\x15\xa0\x91\x7c\x8d\xda\x70\xbb\xfd\x58\x09\xb0\xd8\x8b\xe0\xdf\x0f\x45\x48\x62\xab\x1f\xc7\xda\xf0\xe5\x0b\x39\xf7\xef\xa5\x24\x37\x8b\x7f\x57\x1b\x28\x14\x1a\xb0\x25\x37\x40\xc1\xf5\xeb\x24\x2e\x6f\x7a\x92\x7a\xf1\x9e\x26\x9c\x52\x61\xe9\x52\x0b\xe0\x06\x74\x23\x5d\xe4\x55\x12\x6c\x89\x87\xe9\x48\x1b\xa4\x23\x78\xaf\x28\xa5\x5b\xa3\xb4\x50\x31\xc1\x73\xae\x1a\x03\x2c\xb7\x4a\x1b\x58\x6a\x55\x01\xde\x95\xac\x31\x96\x18\xd1\xf5\xc1\xd6\x8c\x0b\xe7\x4b\xce\xa4\xa0\x34\xb0\x3c\x6f\xaa\x86\x52\x52\xb9\x02\x94\xaa\x59\x95\xad\x2c\x56\x81\x0f\x4c\x42\xc9\x55\x2f\x8f\xa9\x59\x05\xcc\x5a\x96\xdf\x9a\x4b\xe8\x6e\x05\x60\x1a\xc1\x72\x2c\x68\x55\xae\xaa\x4a\x49\xb8\xd1\x05\xd4\x4c\xdb\x2d\x98\xc3\xdc\x82\xe5\xb9\x8b\x72\x11\xbc\x91\x5b\x25\x11\x4a\xb6\x76\x12\xc2\x7b\x5f\x4e\x90\x5c\x7f\x61\x39\x66\x4a\xf5\xd4\x50\xb1\x6d\xb7\x5d\x2b\xfd\x86\xdb\x92\x7b\xf5\xd4\xa8\x2b\x5a\x5a\x80\xe0\x15\xb7\x26\x4a\xe2\x7a\x7f\xa3\xee\x63\xb3\x08\x4b\xa5\xf9\x6f\x94\xd8\x88\xe1\xf5\x69\x8f\x2e\x97\xee\x6e\x74\x56\x17\xb8\xb4\x33\x78\xe1\xef\xc6\x63\x1c\xb7\x15\xd0\x39\x10\x77\x3c\x5d\x65\x49\x01\x67\x06\x37\x3e\x9d\xf5\x89\x44\x61\x07\x12\x14\x47\x50\xf3\x9b\xbe\x7e\x5d\xdf\xf5\x72\xf4\x39\xf1\xb4\x67\x42\x08\x38\x54\xca\x9a\xf7\x6a\xbc\x84\x8a\xdd\x22\x30\x48\xd8\x51\x85\xdc\x0a\xed\xea\x2b\xee\xfa\x03\xb1\xdd\x20\xda\xaf\xc9\x75\xd3\x1f\x3c\x43\x2e\x57\xcf\xae\xa7\x1e\x91\xf4\x40\xec\x9f\x5d\x4f\xb9\xb4\xea\xd9\xf5\x74\x7a\x37\xfd\xc8\x7f\xcf\xae\xa7\x4a\x3e\xbb\x9e\xda\x12\x9f\x5d\x4f\x9f\x5d\xdf\x0c\xb1\xec\x47\xba\xcc\x92\xa8\xd0\xd0\x6e\x1d\xc4\x03\xb0\x4c\xaf\xd0\xa6\xc1\xff\xb2\x4c\x35\x76\x96\x09\x26\x6f\x83\x85\x13\x97\xb2\x0d\x87\x82\xf3\xf9\x29\xd4\xcc\x10\x24\x48\x62\x87\x92\xb6\x17\x62\x60\x6c\x1a\xad\x55\x23\x29\x2a\x02\x9d\xd9\x79\xa8\x1c\x11\xca\x48\x31\x93\x28\xc9\x74\xbc\x78\xab\xea\x6d\xe8\x98\xb8\xe5\x27\x6a\x34\x4d\x5d\x2b\x6d\xa3\xa1\x3a\x19\xd5\x41\x02\x4d\xfc\x7a\xfa\xf2\xf5\xab\x47\xc5\x37\x94\x65\xbb\x33\xf4\x12\xb2\x4c\xad\x11\x7c\x4e\x9f\xa9\x3b\x60\xb2\x80\x25\xd7\x08\x6c\xc3\xb6\x5f\x25\x71\xe1\x2a\xb0\xcf\x47\xed\xb2\xf5\xae\x7f\x2a\xd8\x76\x2e\x7f\x09\x75\x93\x09\x6e\x4a\x60\x20\x71\x03\x89\xb1\x5a\xc9\xd5\xc2\x8d\xe6\x54\x92\xba\x57\xa8\x95\xb1\x8f\x99\x1f\xab\x0c\x8b\xe2\x0c\x00\xbe\x94\xfd\x37\x9b\x4d\xd4\x69\xd2\x19\xbf\x44\x51\xc7\x74\xfd\x35\x92\xdb\x6d\xec\xdd\x48\xc9\xf8\x6b\x5e\xa4\xd7\xaf\xaf\x5f\xbd\xba\x7e\xf1\x6f\xaf\x5f\xbe\xbc\x7e\xfd\xe2\xe5\x43\xc8\xa0\x43\x7d\x26\x30\x7c\x1a\xfd\xad\xa2\xaa\xb5\xcf\xa1\x3d\x5e\xba\xdc\x8d\x22\x74\x41\x35\x88\x0e\xfe\x61\x0c\x35\x92\x12\x91\x90\x89\xb3\x39\xc4\x27\xa0\xc8\xc1\xe8\x11\xc9\x3e\x13\x5a\x1d\x7c\x08\x29\xaa\xb1\x74\xc2\xae\x98\xe7\x4a\xf6\x70\xba\x04\xc3\xab\x5a\x6c\x21\xdf\x5b\xfd\x3c\xae\x1e\x34\xca\xef\xc2\xea\xd0\x6c\x1e\x64\x2e\xfa\x57\xaa\x40\x8a\xfa\xa6\x31\x39\xd6\xae\xcb\x4b\x91\xf4\x4f\xdb\xdf\x98\xb4\x5c\x62\x17\x71\x23\xf8\x4e\x8a\x2d\x34\x06\x61\xa9\x34\x14\x98\x35\xab\x95\x4b\x13\x34\xd4\x9a\xaf\x99\xc5\x2e\xcc\x9a\x16\x15\x3d\x28\x06\x95\x0d\xa5\x3c\x62\x90\x81\xfc\x4d\x35\x90\x33\x09\x56\xb3\xfc\xd6\x7b\x4a\xa3\x35\x79\x4a\x8d\xfe\x34\x7d\xa0\xcf\x50\xa8\x8d\x23\xf1\xe7\x5e\x72\x14\x2e\xea\x1b\x44\x28\xd5\x06\xaa\x26\x77\x0e\x49\x51\xdd\x1d\x62\xc3\xb8\x85\x46\x5a\x2e\xbc\x3e\x6d\xa3\x25\xe5\x08\x78\x10\xa5\x4f\x6a\xbf\x04\xab\xc5\xfb\x12\xcf\xa4\x44\x7d\xd5\x06\x1a\xdf\x7a\x72\xa8\xb5\xb2\x98\x93\x41\x81\xad\x18\x97\x86\x2c\xe2\xf2\x00\xac\x3e\xa2\xaa\xeb\x9f\xda\x87\x7d\x87\xd2\x4d\xc7\x31\xfc\x55\xa8\x8c\x09\x58\x13\xd2\x33\x41\xe9\x9c\x82\x52\xd1\xd1\x07\xda\x32\x96\xd9\xc6\x80\x5a\xba\x51\x2f\x39\xad\x5f\x33\x4d\x16\xc4\xaa\xb6\x90\xb6\xfd\x35\x1a\x33\xa8\xd7\x6d\xd7\x90\x5e\xa9\x72\x3f\x98\xef\xb5\x9e\xc2\xcf\xbf\xcc\x9f\xb4\xa2\xfc\x19\x97\x0e\x12\x84\x6f\x7f\x64\x5b\x32\x0b\xb9\x46\x66\xd1\x40\x2e\x94\x69\xb4\x97\xb0\xd0\xaa\x06\x92\xb2\xe3\xd4\x71\xa6\x89\xda\xed\xd6\x31\x19\x97\xcc\x94\x93\xb6\x3d\xa8\xd1\x59\xa9\x9f\xeb\xc6\x2f\x08\x75\x63\x62\xc0\xd3\xe9\x1c\x78\xd2\xf1\x8d\x04\xca\x95\x2d\xe7\xc0\x9f\x3f\xef\x89\x2f\xf8\x12\xc6\x1d\xc5\xcf\xfc\x97\xc8\xde\x45\xb4\x0b\xa4\x29\x0c\x77\x73\x1b\xb6\x7c\x4c\x2d\x78\x8e\x63\x7e\x09\x57\x93\x79\x37\x9b\x69\x64\xb7\xdd\x5b\x6b\x47\xff\x9f\xfb\xbb\x9b\x1f\x6a\xc6\x29\xff\x40\x37\xbe\xf6\x37\xc0\x60\xc5\x8d\x85\x46\x0b\x68\x7d\xd8\x9b\xa0\x37\x88\xa3\x1b\x6a\xe5\x04\x97\xed\x43\x8b\xa9\xee\x08\x9e\x4d\x64\x50\x16\xe3\xff\xf8\xf1\xbb\x6f\x23\x63\x35\x97\x2b\xbe\xdc\x8e\xef\x1b\x2d\x66\xf0\x74\x1c\xfc\x4b\xa3\x45\x30\xf9\x79\xfa\x4b\xb4\x66\xa2\xc1\x4b\x67\xef\x99\xfb\x7b\xb2\xcb\x25\xb4\x8f\x33\x38\xdc\x70\x37\x99\xcc\xcf\xf7\x49\x06\x6d\x1d\x8d\x06\xed\x98\x08\x7b\xe0\x1f\xeb\x88\x41\x85\xb6\x54\xce\x75\x35\xe6\x4a\x4a\xcc\x2d\x34\xb5\x92\xad\x4a\x40\x28\x63\xf6\x40\xec\x28\xd2\x53\x50\xb4\xf4\xa9\x0b\xd6\xff\x8d\xd9\x8f\x2a\xbf\x45\x3b\x1e\x8f\x37\x5c\x16\x6a\x13\x09\xe5\xaf\xda\x88\x9c\x54\xe5\x4a\x40\x9a\xa6\xd0\x46\xd1\x60\x02\x5f\x43\xb0\x31\x14\x4f\x03\x98\xd1\x23\x3d\x4d\xe0\x39\x1c\x2f\x2f\x29\xde\x3f\x87\x20\x66\x35\x0f\x26\xde\x1d\x3a\xc5\x2b\x59\xa1\x31\x6c\x85\x43\x01\x5d\x65\xd4\x83\x8c\xce\x51\x99\x15\xa4\xe0\x0c\x54\x33\x6d\xd0\x93\x44\x54\x8d\x77\x68\x23\xcc\x3a\xb2\x34\x05\xd9\x08\xb1\x07\xa9\x77\x8a\x79\x07\xbf\x03\xf2\xc8\xc7\x9a\xaf\xd2\x14\xa8\x34\x25\x15\x17\xfb\x95\x64\x7c\x5f\x44\x4f\x22\x8a\x0b\xfb\x15\x93\xf9\x10\xcd\x07\xdc\xb0\xf8\x3d\x76\x58\x1c\xf3\xc3\xe2\x01\x86\xae\x67\xf1\x18\x3f\xdf\xe3\x18\xb0\x73\x03\x0f\x70\x93\x4d\x95\xa1\x7e\x8c\x9d\xef\x59\xb4\xec\x9c\xaa\xdf\x49\x3b\x58\x7b\x09\x57\xaf\x26\x0f\x70\x47\xad\xd5\x83\xcc\xa5\xb2\xdb\xf1\xbd\x60\x5b\xca\x99\x60\x64\x55\xfd\xd6\xb5\x18\x46\x97\x2e\xe2\xce\xa0\xe7\x70\xe9\x9a\xc7\x33\x18\xb9\x37\x9a\xe7\x15\xba\x55\x2f\xa7\xd3\xe9\x25\x74\x5f\x5d\xfe\xc4\xc8\x09\x75\x83\xbb\x07\xe4\x31\x4d\x9e\x53\xdc\xff\x1c\x89\x5a\x1e\xbd\x4c\xed\xfb\x67\x48\xd5\xc7\x86\x03\xb1\xe0\x0f\x7f\x80\x93\xd9\x43\x18\xc7\x31\xfc\x17\xa3\x32\x5c\x08\xd7\x3d\x70\x4d\x83\x9e\xbe\xe2\xc6\xb8\x62\xdc\x40\xa1\x24\xb6\x6b\x3e\xed\xda\x3f\x91\xb1\x25\x83\x05\x4c\x8f\x05\xa4\xeb\x70\x10\x16\xce\x44\x8b\x01\xdf\xc3\x40\x70\xb1\x1b\xee\x77\xb0\x92\x57\x08\x5f\xa5\x10\x04\xc3\xc5\x27\x14\x44\xd0\x33\xbb\x30\x68\xdf\x7b\x5b\x8c\xdb\xe8\x78\x2e\x76\x4d\x2e\xe1\x66\x3a\x9d\x4e\x4e\x84\xd8\xed\xd5\xfb\xa6\xa6\xb4\x09\x98\xdc\xba\x2b\xb1\xd7\xad\x4b\x1c\x29\x05\xa2\x2b\x4d\x40\xae\x84\xf0\x39\x4b\xbb\x94\x14\xdc\x36\x4f\x52\x08\xaf\xe6\x67\xa2\xe8\x40\x93\x83\xa3\x1d\x9b\xe7\x8c\xee\x8f\x4d\x74\xa8\xb3\x23\xe2\xf0\xea\xc0\x28\x07\xf6\x3a\x6f\x98\x8b\x5e\x6e\xbe\xd7\xe8\x91\xb9\xf6\xf6\x3a\xd6\xd9\x40\x7e\xcf\xe7\xf9\xd5\x47\x1e\xa3\x9f\xae\x1b\x53\x8e\x8f\x04\x9d\xcc\x4f\x6d\xf3\xce\xa2\xa6\x2c\x59\x51\xc8\x22\x5b\x50\x29\xa0\xf1\xc4\x24\x2e\x55\xd7\x18\x6a\x94\x05\xea\x2e\xa5\xf0\x99\x3d\x25\x80\x07\x26\xf3\x55\xe5\x10\x4e\x9f\xe8\x30\x2e\x25\x53\x12\x01\x00\x8e\x9c\xc0\x01\xf5\x00\xa9\x44\x8c\x82\xd5\x06\x0b\x48\xc1\x7f\x04\x1f\x4f\xa2\x46\xf2\xbb\xf1\x24\x6c\xdf\x8f\x79\x74\xf3\xf3\xbe\x4c\xec\xc4\x7e\x9e\x42\x90\x58\x0d\xbc\x48\x47\x01\x3c\x3f\xe7\x82\x14\x75\x47\x8b\xbd\x04\xc3\xa5\x00\x89\x2d\x16\xae\x0f\xea\xeb\xb5\xbf\x07\x19\xcb\x6f\x57\xae\x10\x9a\x51\xaa\x35\x3e\x61\xcb\xd6\xcc\x32\xed\xb8\x4e\xe6\xb0\x27\x6f\x0b\xc5\x9c\x8c\x33\x07\x5f\x91\xba\x76\x2b\xf4\x9f\x28\xdc\x5b\xa6\x74\x81\x3a\xd4\xac\xe0\x8d\x99\xc1\x8b\xfa\x6e\xfe\xf7\xee\x13\x8e\x6b\x0a\x3f\x2a\x6a\xad\x71\x71\x22\x51\xdb\x65\x7c\x0e\x41\x12\x13\xc1\xef\xb1\xe9\x0f\x3b\xfc\xf8\x0e\x67\x5a\xdf\xd0\x7f\x1a\x6f\xc7\x2b\x5e\x14\x02\x49\xe0\x3d\x7b\x72\x46\xb2\xff\xd0\xa5\x0e\xb7\x84\xb6\xe7\xbd\x5f\xb3\x03\x14\x06\x1f\x59\xd0\xb7\xcf\x47\x04\x80\x90\x8e\xcc\x9d\xce\xdb\x62\xdb\x0d\xeb\x91\xd3\x45\xfb\x53\x8a\xa2\xd1\x2e\xd7\x1a\x87\x2d\xc0\x2e\x61\x64\x28\xf7\x2b\xcc\x68\x12\x95\x4d\xc5\x24\xff\x0d\xc7\x14\x97\x26\x5e\x57\xae\x1f\x1f\x9c\x5e\xc9\x27\xc2\xec\x1b\xe5\xa3\x2e\xc6\x8d\x5a\x25\x8e\x3a\xeb\xbe\xd8\xd7\xf6\x33\x98\xce\x47\x9f\xa8\xa1\xf3\xbb\x84\x19\xd3\x30\x7c\x09\xbb\xe0\x0b\x5a\xd1\xee\xdd\x5c\xc6\xf4\xc8\x77\x32\x5c\x7e\x2e\xd5\x26\x1d\xdd\x4c\x7b\x21\xbd\xa1\x9d\x9d\x47\x2d\xd6\x4e\x8c\x41\x52\x76\xae\xb9\x80\x9b\xe9\x97\x90\xd6\x77\x43\x8e\x4e\x60\x35\xaf\xb1\x00\x96\x5b\xbe\xc6\xff\x87\x83\x7c\x01\x25\x7f\xb2\x88\x84\xc3\x4e\x79\x0e\xa6\x07\xf2\xd2\x6c\xaf\xdb\x7f\x25\x7f\x83\xd8\x69\xf8\x39\x04\x67\x0f\xf2\x20\x12\x8f\x08\x8f\x5c\xfb\x61\xbf\x77\x1f\x98\x82\xe3\x98\x42\xd9\x6e\xff\x71\x74\x12\x95\xb6\x12\xe3\x20\xb1\xee\x47\x32\x24\x73\xcf\xc1\x31\xf0\xc3\x87\x29\xdd\xee\xb0\x90\xa1\xfa\x1d\x8f\xea\x2c\x18\x24\x27\x7d\x2d\xd6\x65\x22\xb0\xdb\xff\x96\x28\x8e\xe1\x47\xcb\xb4\x05\x06\x3f\xbd\x83\xa6\x2e\x98\xf5\x9f\x72\x28\x3e\xfa\x4f\x25\xdd\x8f\x8d\x32\xa6\x0d\x2c\x95\xde\x30\x5d\xb4\xfd\x19\x5b\xe2\xd6\x7d\xca\xe9\x52\x3f\x83\xf6\x1d\xdd\x62\x6b\x26\xc6\x27\x75\xdf\xd3\xf1\x28\x1a\x9a\x7c\x34\x89\x90\xe5\xe5\x29\xa1\x8b\x58\xfd\xbe\x29\x7c\xeb\x4a\x80\xf1\xd3\xb1\x2d\xb9\x99\x44\xcc\x5a\x3d\x1e\x1d\x80\x61\x34\x21\xbb\x5e\x0d\x4a\xb2\x7e\x79\x72\xe0\x56\x8f\xf1\xd8\x27\xd3\x7d\x22\xd0\x91\xe7\xc6\x8c\x3d\xae\x46\x97\x03\xde\x87\xb0\x1a\x3d\x1b\xf5\x86\xda\xbb\xf7\xfe\x1c\xe9\x59\x49\x0e\x58\x8f\xc8\xcb\x46\x27\xdb\xb3\xa2\x78\x4b\xfe\x33\x0e\xce\x78\xfa\x31\x3a\x26\xbd\xb2\xfd\x7d\xfd\xa8\x96\xfd\xcf\x32\x1e\x50\x31\x2f\x46\x93\xc8\x34\x99\xef\x4d\x8c\x5f\xf6\x05\x58\x47\xe6\xc0\x7b\x1c\x0a\x4e\x12\x0a\xda\xe2\x30\xa9\x08\x8f\x92\x90\x47\xa2\x46\xbb\xa5\x3f\xd5\xee\x92\x14\x3e\x9d\xf4\xad\xad\x6f\x0c\x25\x57\xbe\xf5\xbf\xc1\xcc\xb8\x4e\x02\xb4\x78\x77\xdd\x1c\xdf\xb5\x79\xf3\xfd\xbb\x41\xe7\xa6\xf7\x88\xb1\xe3\xde\xff\x0e\xf0\x5c\x9f\xe4\xec\x0f\x0f\x37\x9b\x4d\xb4\x52\x6a\x25\xfc\x4f\x0e\xfb\x46\x4a\xcc\x6a\x1e\x7d\x30\x01\x30\xb3\x95\x39\x14\xb8\x44\xbd\x18\xb0\x6f\xbb\x2b\x49\xec\x7f\x12\x97\xc4\xfe\x57\xbf\xff\x17\x00\x00\xff\xff\x31\x9f\x54\x5e\x06\x2c\x00\x00") +var _faucetHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\x7b\x93\xdb\x36\x92\xff\x7b\xfc\x29\x3a\x3c\x7b\x25\x9d\x87\xa4\x66\xc6\xf6\xfa\x24\x52\x29\xaf\x37\xbb\xe7\xab\xbb\x24\x95\x38\x75\xb7\x95\x4d\x5d\x81\x64\x4b\x84\x07\x04\x18\x00\x94\x46\x99\xd2\x77\xbf\x6a\x80\xa4\xa8\xc7\x4c\xec\xb5\xaf\x6a\xfd\xc7\x98\xc4\xa3\xd1\x8f\x5f\xa3\x1f\x54\xf2\xd5\x9f\xbf\x7b\xfb\xfe\x6f\xdf\x7f\x03\xa5\xad\xc4\xe2\x49\x42\xff\x81\x60\x72\x95\x06\x28\x83\xc5\x93\x8b\xa4\x44\x56\x2c\x9e\x5c\x5c\x24\x15\x5a\x06\x79\xc9\xb4\x41\x9b\x06\x8d\x5d\x86\xaf\x83\xfd\x44\x69\x6d\x1d\xe2\xaf\x0d\x5f\xa7\xc1\xff\x84\x3f\xbd\x09\xdf\xaa\xaa\x66\x96\x67\x02\x03\xc8\x95\xb4\x28\x6d\x1a\xbc\xfb\x26\xc5\x62\x85\x83\x7d\x92\x55\x98\x06\x6b\x8e\x9b\x5a\x69\x3b\x58\xba\xe1\x85\x2d\xd3\x02\xd7\x3c\xc7\xd0\xbd\x5c\x02\x97\xdc\x72\x26\x42\x93\x33\x81\xe9\x55\xb0\x78\x42\x74\x2c\xb7\x02\x17\xf7\xf7\xd1\xb7\x68\x37\x4a\xdf\xee\x76\x33\x78\xd3\xd8\x12\xa5\xe5\x39\xb3\x58\xc0\x5f\x58\x93\xa3\x4d\x62\xbf\xd2\x6d\x12\x5c\xde\x42\xa9\x71\x99\x06\xc4\xba\x99\xc5\x71\x5e\xc8\x0f\x26\xca\x85\x6a\x8a\xa5\x60\x1a\xa3\x5c\x55\x31\xfb\xc0\xee\x62\xc1\x33\x13\xdb\x0d\xb7\x16\x75\x98\x29\x65\x8d\xd5\xac\x8e\x6f\xa2\x9b\xe8\x8f\x71\x6e\x4c\xdc\x8f\x45\x15\x97\x51\x6e\x4c\x00\x1a\x45\x1a\x18\xbb\x15\x68\x4a\x44\x1b\x40\xbc\xf8\xc7\xce\x5d\x2a\x69\x43\xb6\x41\xa3\x2a\x8c\x5f\x44\x7f\x8c\xa6\xee\xc8\xe1\xf0\xe3\xa7\xd2\xb1\x26\xd7\xbc\xb6\x60\x74\xfe\xd1\xe7\x7e\xf8\xb5\x41\xbd\x8d\x6f\xa2\xab\xe8\xaa\x7d\x71\xe7\x7c\x30\xc1\x22\x89\x3d\xc1\xc5\x67\xd1\x0e\xa5\xb2\xdb\xf8\x3a\x7a\x11\x5d\xc5\x35\xcb\x6f\xd9\x0a\x8b\xee\x24\x9a\x8a\xba\xc1\x2f\x76\xee\x43\x36\xfc\x70\x6c\xc2\x2f\x71\x58\xa5\x2a\x94\x36\xfa\x60\xe2\xeb\xe8\xea\x75\x34\xed\x06\x4e\xe9\xbb\x03\xc8\x68\x74\xd4\x45\xb4\x46\x4d\xc8\x15\x61\x8e\xd2\xa2\x86\x7b\x1a\xbd\xa8\xb8\x0c\x4b\xe4\xab\xd2\xce\xe0\x6a\x3a\x7d\x36\x3f\x37\xba\x2e\xfd\x70\xc1\x4d\x2d\xd8\x76\x06\x4b\x81\x77\x7e\x88\x09\xbe\x92\x21\xb7\x58\x99\x19\x78\xca\x6e\x62\xe7\xce\xac\xb5\x5a\x69\x34\xa6\x3d\xac\x56\x86\x5b\xae\xe4\x8c\x10\xc5\x2c\x5f\xe3\xb9\xb5\xa6\x66\xf2\x64\x03\xcb\x8c\x12\x8d\xc5\x23\x46\x32\xa1\xf2\x5b\x3f\xe6\xbc\x79\x28\x44\xae\x84\xd2\x33\xd8\x94\xbc\xdd\x06\xee\x20\xa8\x35\xb6\xe4\xa1\x66\x45\xc1\xe5\x6a\x06\xaf\xea\x56\x1e\xa8\x98\x5e\x71\x39\x83\xe9\x7e\x4b\x12\x77\x6a\x4c\x62\x7f\x71\x3d\xb9\x48\x32\x55\x6c\x9d\x0d\x0b\xbe\x86\x5c\x30\x63\xd2\xe0\x48\xc5\xee\x42\x3a\x58\x40\xf7\x10\xe3\xb2\x9b\x3a\x98\xd3\x6a\x13\x80\x3b\x28\x0d\x3c\x13\x61\xa6\xac\x55\xd5\x0c\xae\x88\xbd\x76\xcb\x11\x3d\x11\x8a\x55\x78\x75\xdd\x4d\x5e\x24\xe5\x55\x47\xc4\xe2\x9d\x0d\x9d\x7d\x7a\xcb\x04\x8b\x84\x77\x7b\x97\x0c\x96\x2c\xcc\x98\x2d\x03\x60\x9a\xb3\xb0\xe4\x45\x81\x32\x0d\xac\x6e\x90\x70\xc4\x17\x30\xbc\xfe\x1e\xb8\xfd\xca\xab\x8e\xaf\xb8\xe0\xeb\x56\xac\xc1\xe3\x91\x84\x0f\x0b\xf1\x1a\xda\x07\xb5\x5c\x1a\xb4\xe1\x40\xa6\xc1\x62\x2e\xeb\xc6\x86\x2b\xad\x9a\xba\x9f\xbf\x48\xdc\x28\xf0\x22\x0d\x1a\x2d\x82\xf6\xfa\x77\x8f\x76\x5b\xb7\xaa\x08\x7a\xc1\x95\xae\x42\xb2\x84\x56\x22\x80\x5a\xb0\x1c\x4b\x25\x0a\xd4\x69\xf0\xa3\xca\x39\x13\x20\xbd\xcc\xf0\xd3\x0f\xff\x09\xad\xc9\xb8\x5c\xc1\x56\x35\x1a\xbe\xb1\x25\x6a\x6c\x2a\x60\x45\x41\x70\x8d\xa2\x28\x88\xf7\x9c\x38\xf0\x9e\xf2\x1a\x66\x56\xee\xf9\xbd\x48\xb2\xc6\x5a\xd5\x2f\xcc\xac\x84\xcc\xca\xb0\xc0\x25\x6b\x84\x85\x42\xab\xba\x50\x1b\x19\x5a\xb5\x5a\x51\xa8\xf3\x52\xf8\x4d\x01\x14\xcc\xb2\x76\x2a\x0d\xba\xb5\x9d\x11\x99\xa9\x55\xdd\xd4\xad\x19\xfd\x20\xde\xd5\x4c\x16\x58\x90\xd1\x85\xc1\x60\xf1\x57\xbe\x46\xa8\xd0\x0b\x73\x71\x8c\x89\x9c\x69\xb4\xe1\x90\xe8\x09\x32\x92\xd8\x33\xe3\x45\x82\xf6\x5f\xd2\x88\x8e\x52\x2f\x42\x85\xb2\x81\x83\xb7\x50\xd3\xc5\x12\x2c\xee\xef\x35\x93\x2b\x84\xa7\xbc\xb8\xbb\x84\xa7\xac\x52\x8d\xb4\x30\x4b\x21\x7a\xe3\x1e\xcd\x6e\x77\x40\x1d\x20\x11\x7c\x91\xb0\xc7\xf0\x0d\x4a\xe6\x82\xe7\xb7\x69\x60\x39\xea\xf4\xfe\x9e\x88\xef\x76\x73\xb8\xbf\xe7\x4b\x78\x1a\xfd\x80\x39\xab\x6d\x5e\xb2\xdd\x6e\xa5\xbb\xe7\x08\xef\x30\x6f\x2c\x8e\x27\xf7\xf7\x28\x0c\xee\x76\xa6\xc9\x2a\x6e\xc7\xdd\x76\x1a\x97\xc5\x6e\x47\x3c\xb7\x7c\xee\x76\x10\x13\x51\x59\xe0\x1d\x3c\x8d\xbe\x47\xcd\x55\x61\xc0\xaf\x4f\x62\xb6\x48\x62\xc1\x17\xed\xbe\x43\x25\xc5\x8d\xd8\xe3\x25\x26\xc0\xf4\x40\x77\x7e\xe3\x58\x1d\x72\x7a\xc6\x0d\x56\x61\xcf\x7d\x8b\x07\xc3\x2d\xde\xe2\x36\x0d\xee\xef\x87\x7b\xdb\xd9\x9c\x09\x91\x31\xd2\x8b\x17\xad\xdf\xf4\x1b\x12\x4e\xd7\xdc\xb8\x9c\x6a\xd1\x71\xb0\x67\xfb\x23\xfd\xfa\xe8\xe6\xb2\xaa\x9e\xc1\xcd\xf5\xe0\xda\x3a\xe7\xf2\xaf\x8e\x5c\xfe\xe6\xec\xe2\x9a\x49\x14\xe0\xfe\x86\xa6\x62\xa2\x7b\x6e\xbd\x65\x70\x0d\x1c\x6f\x0a\xe9\x92\xee\x59\xeb\x2f\xfb\xe9\x1c\xd4\x1a\xf5\x52\xa8\xcd\x0c\x58\x63\xd5\x1c\x2a\x76\xd7\x07\xbc\x9b\xe9\x74\xc8\x37\xe5\x82\x2c\x13\xe8\xae\x17\x8d\xbf\x36\x68\xac\xe9\x2f\x13\x3f\xe5\xfe\xd2\x9d\x52\xa0\x34\x58\x1c\x69\x83\x4e\x24\xd5\xba\x55\x03\xd3\xf7\xca\x3c\xcb\xfb\x52\xa9\x3e\x86\x0c\xd9\x68\x49\x0f\xc2\x5d\xb0\x48\xac\xde\xaf\xbb\x48\x6c\xf1\x49\x31\x40\x53\x8e\xf7\x50\x08\xf0\x37\x1a\xc9\x5e\x23\x6a\x9f\x60\x10\x64\xc1\xbd\x26\xb1\x2d\x3e\xe3\x64\x02\x61\xc6\x0c\x7e\xcc\xf1\x2e\xd4\xef\x8f\x77\xaf\x9f\x7b\x7e\x89\x4c\xdb\x0c\x99\xfd\x18\x06\x96\x8d\x2c\x06\xf2\xbb\xbb\xf3\x73\x19\x68\x24\x5f\xa3\x36\xdc\x6e\x3f\x96\x03\x2c\xf6\x2c\xf8\xf7\x43\x16\x92\xd8\xea\xc7\xb1\x36\x7c\xf9\x42\xce\xfd\x7b\x39\xc9\xcd\xe2\xdf\xd5\x06\x0a\x85\x06\x6c\xc9\x0d\x50\x74\xfd\x3a\x89\xcb\x9b\x7e\x49\xbd\x78\x4f\x13\x4e\xa9\xb0\x74\xb9\x05\x70\x03\xba\x91\x2e\xf4\x2a\x09\xb6\xc4\xc3\x7c\xa4\x8d\xd2\x11\xbc\x57\x94\xd3\xad\x51\x5a\xa8\x98\xe0\x39\x57\x8d\x01\x96\x5b\xa5\x0d\x2c\xb5\xaa\x00\xef\x4a\xd6\x18\x4b\x84\xe8\xfa\x60\x6b\xc6\x85\xf3\x25\x67\x52\x50\x1a\x58\x9e\x37\x55\x43\x39\xa9\x5c\x01\x4a\xd5\xac\xca\x96\x17\xab\xc0\x07\x26\xa1\xe4\xaa\xe7\xc7\xd4\xac\x02\x66\x2d\xcb\x6f\xcd\x25\x74\xb7\x02\x30\x8d\x60\x39\x16\xb4\x2b\x57\x55\xa5\x24\xdc\xe8\x02\x6a\xa6\xed\x16\xcc\x61\x72\xc1\xf2\xdc\x45\xb9\x08\xde\xc8\xad\x92\x08\x25\x5b\x3b\x0e\xe1\xbd\xaf\x27\x88\xaf\xbf\xb0\x1c\x33\xa5\xfa\xd5\x50\xb1\x6d\x77\x5c\xcb\xfd\x86\xdb\x92\x7b\xf5\xd4\xa8\x2b\xda\x5a\x80\xe0\x15\xb7\x26\x4a\xe2\x7a\x7f\xa3\xee\x63\xb3\x08\x4b\xa5\xf9\x6f\x94\xd9\x88\xe1\xf5\x69\x8f\x2e\x97\xee\x6e\x74\x56\x17\xb8\xb4\x33\x78\xe1\xef\xc6\x63\x1c\xb7\x25\xd0\x39\x10\x77\x34\x5d\x69\x49\x01\x67\x06\x37\x3e\x9f\xf5\x89\x44\x61\x07\x1c\x14\x47\x50\xf3\x87\xbe\x7e\x5d\xdf\xf5\x7c\xf4\x49\xf1\xb4\x27\x42\x08\x38\x54\xca\x9a\xf7\x6a\xbc\x84\x8a\xdd\x22\x30\x48\xd8\x51\x89\xdc\x32\xed\x0a\x2c\xee\x1a\x04\xb1\xdd\x20\xda\xaf\xc9\x75\xd3\x1f\x3c\x41\x2e\x57\xcf\xae\xa7\x1e\x91\xf4\x40\xe4\x9f\x5d\x4f\xb9\xb4\xea\xd9\xf5\x74\x7a\x37\xfd\xc8\x7f\xcf\xae\xa7\x4a\x3e\xbb\x9e\xda\x12\x9f\x5d\x4f\x9f\x5d\xdf\x0c\xb1\xec\x47\xba\xd4\x92\x56\xa1\xa1\xd3\x3a\x88\x07\x60\x99\x5e\xa1\x4d\x83\xff\x65\x99\x6a\xec\x2c\x13\x4c\xde\x06\x0b\xc7\x2e\x65\x1b\x0e\x05\xe7\x13\x54\xa8\x99\x21\x48\x10\xc7\x0e\x25\x6d\x33\xc4\xc0\xd8\x34\x5a\xab\x46\x52\x54\x04\x92\xd9\x79\xa8\x1c\x11\xca\x48\x31\x93\x28\xc9\x74\xbc\x78\xab\xea\x6d\xe8\x88\xb8\xed\x27\x6a\x34\x4d\x5d\x2b\x6d\xa3\xa1\x3a\x19\x15\x42\x02\x4d\xfc\x7a\xfa\xf2\xf5\xab\x47\xd9\x37\x94\x66\x3b\x19\x7a\x0e\x59\xa6\xd6\x08\x3e\xa9\xcf\xd4\x1d\x30\x59\xc0\x92\x6b\x04\xb6\x61\xdb\xaf\x92\xb8\x70\x25\xd8\xe7\xa3\x76\xd9\x7a\xd7\x3f\x15\x6c\x3b\x97\xbf\x84\xba\xc9\x04\x37\x25\x30\x90\xb8\x81\xc4\x58\xad\xe4\x6a\xe1\x46\x73\xaa\x49\xdd\x2b\xd4\xca\xd8\xc7\xcc\x8f\x55\x86\x45\x71\x06\x00\x5f\xca\xfe\x9b\xcd\x26\xea\x34\xe9\x8c\x5f\xa2\xa8\x63\xba\xfe\x1a\xc9\xed\x36\xf6\x6e\xa4\x64\xfc\x35\x2f\xd2\xeb\xd7\xd7\xaf\x5e\x5d\xbf\xf8\xb7\xd7\x2f\x5f\x5e\xbf\x7e\xf1\xf2\x21\x64\x90\x50\x9f\x09\x0c\x9f\x46\x7f\xab\xa8\x6c\xed\x73\x68\x8f\x97\x2e\x77\xa3\x08\x5d\x50\x0d\xa2\x83\x7f\x18\x43\x8d\xa4\x44\x24\x64\xe2\x6c\x0e\xf1\x09\x28\x72\x30\x7a\x84\xb3\xcf\x84\x56\x07\x1f\x42\x8a\x6a\x2c\x49\xd8\x55\xf3\x5c\xc9\x1e\x4e\x97\x60\x78\x55\x8b\x2d\xe4\x7b\xab\x9f\xc7\xd5\x83\x46\xf9\x5d\x58\x1d\x9a\xcd\x83\xcc\x45\xff\x4a\x15\x48\x51\xdf\x34\x26\xc7\xda\xb5\x79\x29\x92\xfe\x69\xfb\x1b\x93\x96\x4b\xec\x22\x6e\x04\xdf\x49\xb1\x85\xc6\x20\x2c\x95\x86\x02\xb3\x66\xb5\x72\x69\x82\x86\x5a\xf3\x35\xb3\xd8\x85\x59\xd3\xa2\xa2\x07\xc5\xa0\xb2\xa1\x94\x47\x0c\x32\x90\xbf\xa9\x06\x72\x26\xc1\x6a\x96\xdf\x7a\x4f\x69\xb4\x26\x4f\xa9\xd1\x4b\xd3\x07\xfa\x0c\x85\xda\xb8\x25\x5e\xee\x25\x47\xe1\xa2\xbe\x41\x84\x52\x6d\xa0\x6a\x72\xe7\x90\x14\xd5\x9d\x10\x1b\xc6\x2d\x34\xd2\x72\xe1\xf5\x69\x1b\x2d\x29\x47\xc0\x83\x28\x7d\x52\xfb\x25\x58\x2d\xde\x97\x78\x26\x25\xea\xab\x36\xd0\xf8\xd6\x2f\x87\x5a\x2b\x8b\x39\x19\x14\xd8\x8a\x71\x69\xc8\x22\x2e\x0f\xc0\xea\x23\xaa\xba\xfe\xa9\x7d\xd8\xb7\x28\xdd\x74\x1c\xc3\x5f\x85\xca\x98\x80\x35\x21\x3d\x13\x94\xce\x29\x28\x15\x89\x3e\xd0\x96\xb1\xcc\x36\x06\xd4\xd2\x8d\x7a\xce\x69\xff\x9a\x69\xb2\x20\x56\xb5\x85\xb4\x6d\xb0\xd1\x98\x41\xbd\x6e\xdb\x86\xf4\x4a\x95\xfb\xc1\x7c\xaf\xf5\x14\x7e\xfe\x65\xfe\xa4\x65\xe5\xcf\xb8\x74\x90\x20\x7c\x7b\x91\x6d\xc9\x2c\xe4\x1a\x99\x45\x03\xb9\x50\xa6\xd1\x9e\xc3\x42\xab\x1a\x88\xcb\x8e\x52\x47\x99\x26\x6a\x77\x5a\x47\x64\x5c\x32\x53\x4e\xda\xfe\xa0\x46\x67\xa5\x7e\xae\x1b\xbf\x20\xd4\x8d\x89\x00\x4f\xa7\x73\xe0\x49\x47\x37\x12\x28\x57\xb6\x9c\x03\x7f\xfe\xbc\x5f\x7c\xc1\x97\x30\xee\x56\xfc\xcc\x7f\x89\xec\x5d\x44\xa7\x40\x9a\xc2\xf0\x34\x77\x60\x4b\xc7\xd4\x82\xe7\x38\xe6\x97\x70\x35\x99\x77\xb3\x99\x46\x76\xdb\xbd\xb5\x76\xf4\xff\xb9\xbf\xbb\xf9\xa1\x66\x9c\xf2\x0f\x74\xe3\x6b\x7f\x03\x0c\x56\xdc\x58\x68\xb4\x80\xd6\x87\xbd\x09\x7a\x83\xb8\x75\x43\xad\x9c\xe0\xb2\x7d\x68\x31\xd5\x89\xe0\xc9\x44\x06\x65\x31\xfe\x8f\x1f\xbf\xfb\x36\x32\x56\x73\xb9\xe2\xcb\xed\xf8\xbe\xd1\x62\x06\x4f\xc7\xc1\xbf\x34\x5a\x04\x93\x9f\xa7\xbf\x44\x6b\x26\x1a\xbc\x74\xf6\x9e\xb9\xbf\x27\xa7\x5c\x42\xfb\x38\x83\xc3\x03\x77\x93\xc9\xfc\x7c\x9f\x64\xd0\xd6\xd1\x68\xd0\x8e\x69\x61\x0f\xfc\x63\x1d\x31\xa8\xd0\x96\xca\xb9\xae\xc6\x5c\x49\x89\xb9\x85\xa6\x56\xb2\x55\x09\x08\x65\xcc\x1e\x88\xdd\x8a\xf4\x14\x14\xed\xfa\xd4\x05\xeb\xff\xc6\xec\x47\x95\xdf\xa2\x1d\x8f\xc7\x1b\x2e\x0b\xb5\x89\x84\xf2\x57\x6d\x44\x4e\xaa\x72\x25\x20\x4d\x53\x68\xa3\x68\x30\x81\xaf\x21\xd8\x18\x8a\xa7\x01\xcc\xe8\x91\x9e\x26\xf0\x1c\x8e\xb7\x97\x14\xef\x9f\x43\x10\xb3\x9a\x07\x13\xef\x0e\x9d\xe2\x95\xac\xd0\x18\xb6\xc2\x21\x83\xae\x32\xea\x41\x46\x72\x54\x66\x05\x29\x38\x03\xd5\x4c\x1b\xf4\x4b\x22\xaa\xc6\x3b\xb4\x11\x66\xdd\xb2\x34\x05\xd9\x08\xb1\x07\xa9\x77\x8a\x79\x07\xbf\x83\xe5\x91\x8f\x35\x5f\xa5\x29\x50\x69\x4a\x2a\x2e\xf6\x3b\xc9\xf8\xbe\x88\x9e\x44\x14\x17\xf6\x3b\x26\xf3\x21\x9a\x0f\xa8\x61\xf1\x7b\xe4\xb0\x38\xa6\x87\xc5\x03\x04\x5d\xcf\xe2\x31\x7a\xbe\xc7\x31\x20\xe7\x06\x1e\xa0\x26\x9b\x2a\x43\xfd\x18\x39\xdf\xb3\x68\xc9\x39\x55\xbf\x93\x76\xb0\xf7\x12\xae\x5e\x4d\x1e\xa0\x8e\x5a\xab\x07\x89\x4b\x65\xb7\xe3\x7b\xc1\xb6\x94\x33\xc1\xc8\xaa\xfa\xad\x6b\x31\x8c\x2e\x5d\xc4\x9d\x41\x4f\xe1\xd2\x35\x8f\x67\x30\x72\x6f\x34\xcf\x2b\x74\xbb\x5e\x4e\xa7\xd3\x4b\xe8\x3e\xbb\xfc\x89\x91\x13\xea\x06\x77\x0f\xf0\x63\x9a\x3c\xa7\xb8\xff\x39\x1c\xb5\x34\x7a\x9e\xda\xf7\xcf\xe0\xaa\x8f\x0d\x07\x6c\xc1\x1f\xfe\x00\x27\xb3\x87\x30\x8e\x63\xf8\x2f\x46\x65\xb8\x10\xae\x7b\xe0\x9a\x06\xfd\xfa\x8a\x1b\xe3\x8a\x71\x03\x85\x92\xd8\xee\xf9\xb4\x6b\xff\x84\xc7\x76\x19\x2c\x60\x7a\xcc\x20\x5d\x87\x83\xb0\x70\x26\x5a\x0c\xe8\x1e\x06\x82\x8b\xdd\xf0\xbc\x83\x9d\xbc\x42\xf8\x2a\x85\x20\x18\x6e\x3e\x59\x41\x0b\x7a\x62\x17\x06\xed\x7b\x6f\x8b\x71\x1b\x1d\xcf\xc5\xae\xc9\x25\xdc\x4c\xa7\xd3\xc9\x09\x13\xbb\xbd\x7a\xdf\xd4\x94\x36\x01\x93\x5b\x77\x25\xf6\xba\x75\x89\x23\xa5\x40\x74\xa5\x09\xc8\x95\x10\x3e\x67\x69\xb7\x92\x82\xdb\xe6\x49\x0a\xe1\xd5\xfc\x4c\x14\x1d\x68\x72\x20\xda\xb1\x79\xce\xe8\xfe\xd8\x44\x87\x3a\x3b\x5a\x1c\x5e\x1d\x18\xe5\xc0\x5e\xe7\x0d\x73\xd1\xf3\xcd\xf7\x1a\x3d\x32\xd7\xde\x5e\xc7\x3a\x1b\xf0\xef\xe9\x3c\xbf\xfa\x48\x31\xfa\xe9\xba\x31\xe5\xf8\x88\xd1\xc9\xfc\xd4\x36\xef\x2c\x6a\xca\x92\x15\x85\x2c\xb2\x05\x95\x02\x1a\x4f\x4c\xe2\x52\x75\x8d\xa1\x46\x59\xa0\xee\x52\x0a\x9f\xd9\x53\x02\x78\x60\x32\x5f\x55\x0e\xe1\x34\x90\xe8\x44\xb7\x73\xe0\xb0\xa0\x34\x0f\x78\x18\x0e\x64\x71\x79\x99\x92\x08\x00\x70\xe4\x09\x0e\xad\x07\x70\xa5\xc5\x28\x58\x6d\xb0\x80\x14\xfc\xa7\xf0\xf1\x24\x6a\x24\xbf\x1b\x4f\xc2\xf6\xfd\x98\x46\x37\x3f\xef\x6b\xc5\x8e\xf7\xe7\x29\x04\x89\xd5\xc0\x8b\x74\x14\xc0\xf3\x73\x7e\x48\xa1\x77\xb4\xd8\x73\x30\xdc\x0a\x90\xd8\x62\xe1\x9a\xa1\xbe\x68\xfb\x7b\x90\xb1\xfc\x76\xe5\xaa\xa1\x19\xe5\x5b\xe3\x13\xb2\x6c\xcd\x2c\xd3\x8e\xea\x64\x0e\xfb\xe5\x6d\xb5\x98\x93\x85\xe6\xe0\xcb\x52\xd7\x73\x85\xfe\x3b\x85\x7b\xcb\x94\x2e\x50\x87\x9a\x15\xbc\x31\x33\x78\x51\xdf\xcd\xff\xde\x7d\xc7\x71\x9d\xe1\x47\x59\xad\x35\x2e\x4e\x38\x6a\x5b\x8d\xcf\x21\x48\x62\x5a\xf0\x7b\x64\x7a\x61\x87\x9f\xe0\xe1\x4c\xff\x1b\xfa\x0f\xe4\xed\x78\xc5\x8b\x42\x20\x31\xbc\x27\x4f\x1e\x49\xf6\x1f\xfa\xd5\xe1\x91\xd0\x36\xbe\xf7\x7b\x76\x80\xc2\xe0\x23\x1b\xfa\x1e\xfa\x88\x00\x10\x92\xc8\xdc\xe9\xbc\xad\xb8\xdd\xb0\x1e\x39\x5d\xb4\x3f\xa8\x28\x1a\xed\x12\xae\x71\xd8\x02\xec\x12\x46\x86\x12\xc0\xc2\x8c\x26\x51\xd9\x54\x4c\xf2\xdf\x70\x4c\xc1\x69\xe2\x75\xe5\x9a\xf2\xc1\xe9\xbd\x7c\xc2\xcc\xbe\x5b\x3e\xea\x02\xdd\xa8\x55\xe2\xa8\xb3\xee\x8b\x7d\x81\x3f\x83\xe9\x7c\xf4\x89\x1a\x3a\x7f\x4a\x98\x31\x0d\xc3\x97\xb0\x8b\xc0\xa0\x15\x9d\xde\xcd\x65\x4c\x8f\x7c\x3b\xc3\x25\xe9\x52\x6d\xd2\xd1\xcd\xb4\x67\xd2\x1b\xda\xd9\x79\xd4\x62\xed\xc4\x18\xc4\x65\xe7\x9a\x0b\xb8\x99\x7e\x09\x6e\x7d\x4b\xe4\x48\x02\xab\x79\x8d\x05\xb0\xdc\xf2\x35\xfe\x3f\x08\xf2\x05\x94\xfc\xc9\x2c\x12\x0e\x3b\xe5\x39\x98\x1e\xf0\x4b\xb3\xbd\x6e\xff\x95\xfc\x0d\x62\xa7\xe1\xe7\x10\x9c\x15\xe4\x41\x24\x1e\x2d\x3c\x72\xed\x87\xfd\xde\x7d\x65\x0a\x8e\x03\x0b\xa5\xbc\xfd\x17\xd2\x49\x54\xda\x4a\x8c\x83\xc4\xba\x9f\xca\x10\xcf\x3d\x05\x47\xc0\x0f\x1f\xe6\x75\xbb\xc3\x6a\x86\x8a\x78\x3c\x2a\xb6\x60\x90\xa1\xf4\x05\x59\x97\x8e\xc0\x6e\xff\x8b\xa2\x38\x86\x1f\x2d\xd3\x16\x18\xfc\xf4\x0e\x9a\xba\x60\xd6\x7f\xcf\xa1\x20\xe9\xbf\x97\x74\x3f\x39\xca\x98\x36\xb0\x54\x7a\xc3\x74\xd1\x36\x69\x6c\x89\x5b\xf7\x3d\xa7\xcb\xff\x0c\xda\x77\x74\x8b\xad\x99\x18\x9f\x14\x7f\x4f\xc7\xa3\x68\x68\xf2\xd1\x24\x42\x96\x97\xa7\x0b\x5d\xc4\xea\xcf\x4d\xe1\x5b\x57\x07\x8c\x9f\x8e\x6d\xc9\xcd\x24\x62\xd6\xea\xf1\xe8\x00\x0c\xa3\x09\xd9\xf5\x6a\x50\x97\xf5\xdb\x93\x03\xb7\x7a\x8c\xc6\x3e\xa3\xee\xb3\x81\x6e\x79\x6e\xcc\xd8\xe3\x6a\x74\x39\xa0\x7d\x08\xab\xd1\xb3\x51\x6f\xa8\xbd\x7b\xef\xe5\x48\xcf\x72\x72\x40\x7a\x44\x5e\x36\x3a\x39\x9e\x15\xc5\x5b\xf2\x9f\x71\x70\xc6\xd3\x8f\xd1\x31\xe9\x95\xed\xef\xeb\x47\xb5\xec\x7f\x9b\xf1\x80\x8a\x79\x31\x9a\x44\xa6\xc9\x7c\x83\x62\xfc\xb2\xaf\xc2\xba\x65\x0e\xbc\xc7\xa1\xe0\x24\xa1\xa0\x23\x0e\x93\x8a\xf0\x28\x09\x79\x24\x6a\xb4\x47\x7a\xa9\x76\x97\xa4\xf0\xe9\xa4\xef\x6f\x7d\x63\x28\xc3\xf2\xfd\xff\x0d\x66\xc6\xb5\x13\xa0\xc5\xbb\x6b\xe9\xf8\xd6\xcd\x9b\xef\xdf\x0d\xda\x37\xbd\x47\x8c\x1d\xf5\xfe\xd7\x80\xe7\x9a\x25\x67\x7f\x7e\xb8\xd9\x6c\xa2\x95\x52\x2b\xe1\x7f\x78\xd8\x77\x53\x62\x56\xf3\xe8\x83\x09\x80\x99\xad\xcc\xa1\xc0\x25\xea\xc5\x80\x7c\xdb\x62\x49\x62\xff\xc3\xb8\x24\xf6\xbf\xfd\xfd\xbf\x00\x00\x00\xff\xff\xb2\x1e\x6f\x68\x0c\x2c\x00\x00") func faucetHtmlBytes() ([]byte, error) { return bindataRead( @@ -85,7 +85,7 @@ func faucetHtml() (*asset, error) { } info := bindataFileInfo{name: "faucet.html", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xdb, 0xa2, 0x98, 0x44, 0x4b, 0x50, 0xf8, 0xa1, 0xac, 0x4a, 0x76, 0x2e, 0xcc, 0x3d, 0xcb, 0x81, 0x9e, 0x2a, 0xaa, 0x87, 0xf5, 0x9d, 0x53, 0x4, 0x8a, 0xdd, 0x5a, 0xfe, 0xd3, 0xc3, 0xf, 0x11}} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc5, 0x8d, 0xb, 0x7a, 0xfd, 0x70, 0x68, 0x68, 0xd2, 0xd8, 0xf3, 0xf6, 0xac, 0x72, 0xed, 0xc2, 0x76, 0x18, 0x2d, 0x1, 0xe5, 0x3b, 0x55, 0xb, 0xce, 0xfc, 0xb6, 0xd5, 0x59, 0xc3, 0x94, 0x5b}} return a, nil } From c4deebbf1e186e3b7e96c4e0ab395d3207cec55e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 15 Jan 2021 12:26:46 +0200 Subject: [PATCH 126/235] core/state/snapshot: add generation logs to storage too --- core/state/snapshot/generate.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 17f1ca6078..fcc6b44cb6 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -281,6 +281,10 @@ func (dl *diskLayer) generate(stats *generatorStats) { abort <- stats return } + if time.Since(logged) > 8*time.Second { + stats.Log("Generating state snapshot", dl.root, append(accountHash[:], storeIt.Key...)) + logged = time.Now() + } } } if err := storeIt.Err; err != nil { From 8d62ee65b2d3100da0292232f8169282237f5487 Mon Sep 17 00:00:00 2001 From: gary rong Date: Sat, 16 Jan 2021 06:04:38 +0800 Subject: [PATCH 127/235] les: don't drop sentTo for normal cases (#22048) --- les/retrieve.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/les/retrieve.go b/les/retrieve.go index ca4f867ea8..3174d49878 100644 --- a/les/retrieve.go +++ b/les/retrieve.go @@ -337,7 +337,6 @@ func (r *sentReq) tryRequest() { } defer func() { - // send feedback to server pool and remove peer if hard timeout happened pp, ok := p.(*serverPeer) if hrto && ok { pp.Log().Debug("Request timed out hard") @@ -345,10 +344,6 @@ func (r *sentReq) tryRequest() { r.rm.peers.unregister(pp.id) } } - - r.lock.Lock() - delete(r.sentTo, p) - r.lock.Unlock() }() select { From c76573a97b15e28c0d5c783cab3a62e9203db1c9 Mon Sep 17 00:00:00 2001 From: Dan DeGreef Date: Sat, 16 Jan 2021 11:15:18 -0600 Subject: [PATCH 128/235] eth/protocols/eth: fix slice resize flaw (#22181) --- eth/protocols/eth/broadcast.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go index 2349398fae..74ec2f0654 100644 --- a/eth/protocols/eth/broadcast.go +++ b/eth/protocols/eth/broadcast.go @@ -179,7 +179,7 @@ func (p *Peer) announceTransactions() { queue = append(queue, hashes...) if len(queue) > maxQueuedTxAnns { // Fancy copy and resize to ensure buffer doesn't grow indefinitely - queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])] + queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxAnns:])] } case <-done: From 034ecc3210623d1585f0ffd6a2c8a64d4b7ee0c1 Mon Sep 17 00:00:00 2001 From: gary rong Date: Sun, 17 Jan 2021 02:06:18 +0800 Subject: [PATCH 129/235] les: remove useless protocol defines (#22115) This PR has two changes in the les protocol: - the auxRoot is not supported. See ethereum/devp2p#171 for more information - the empty response will be returned in GetHelperTrieProofsMsg request if the merkle proving is failed. note, for backward compatibility, the empty merkle proof as well as the request auxiliary data will still be returned in les2/3 protocol no matter the proving is successful or not. the proving failure can happen e.g. request the proving for a non-included entry in helper trie (unstable header). --- les/benchmark.go | 2 +- les/handler_test.go | 2 +- les/odr_requests.go | 9 ++++----- les/server_handler.go | 32 +++++++++++++++++--------------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/les/benchmark.go b/les/benchmark.go index 312d4533df..6255c1049e 100644 --- a/les/benchmark.go +++ b/les/benchmark.go @@ -156,7 +156,7 @@ func (b *benchmarkHelperTrie) request(peer *serverPeer, index int) error { for i := range reqs { key := make([]byte, 8) binary.BigEndian.PutUint64(key[:], uint64(rand.Int63n(int64(b.headNum)))) - reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: auxHeader} + reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: htAuxHeader} } } diff --git a/les/handler_test.go b/les/handler_test.go index 04277f661b..f5cbeb8efc 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -443,7 +443,7 @@ func testGetCHTProofs(t *testing.T, protocol int) { Type: htCanonical, TrieIdx: 0, Key: key, - AuxReq: auxHeader, + AuxReq: htAuxHeader, }} // Send the proof request and verify the response sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, requestsV2) diff --git a/les/odr_requests.go b/les/odr_requests.go index a8cf8f50a9..962b88a322 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -295,10 +295,9 @@ const ( htCanonical = iota // Canonical hash trie htBloomBits // BloomBits trie - // applicable for all helper trie requests - auxRoot = 1 - // applicable for htCanonical - auxHeader = 2 + // helper trie auxiliary types + // htAuxNone = 1 ; deprecated number, used in les2/3 previously. + htAuxHeader = 2 // applicable for htCanonical, requests for relevant headers ) type HelperTrieReq struct { @@ -339,7 +338,7 @@ func (r *ChtRequest) Request(reqID uint64, peer *serverPeer) error { Type: htCanonical, TrieIdx: r.ChtNum, Key: encNum[:], - AuxReq: auxHeader, + AuxReq: htAuxHeader, } return peer.requestHelperTrieProofs(reqID, []HelperTrieReq{req}) } diff --git a/les/server_handler.go b/les/server_handler.go index 2316c9c5a4..bec4206e2b 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -741,22 +741,24 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { auxTrie, _ = trie.New(root, trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix))) } } - if request.AuxReq == auxRoot { - var data []byte - if root != (common.Hash{}) { - data = root[:] - } + if auxTrie == nil { + sendResponse(req.ReqID, 0, nil, task.servingTime) + return + } + // TODO(rjl493456442) short circuit if the proving is failed. + // The original client side code has a dirty hack to retrieve + // the headers with no valid proof. Keep the compatibility for + // legacy les protocol and drop this hack when the les2/3 are + // not supported. + err := auxTrie.Prove(request.Key, request.FromLevel, nodes) + if p.version >= lpv4 && err != nil { + sendResponse(req.ReqID, 0, nil, task.servingTime) + return + } + if request.AuxReq == htAuxHeader { + data := h.getAuxiliaryHeaders(request) auxData = append(auxData, data) auxBytes += len(data) - } else { - if auxTrie != nil { - auxTrie.Prove(request.Key, request.FromLevel, nodes) - } - if request.AuxReq != 0 { - data := h.getAuxiliaryHeaders(request) - auxData = append(auxData, data) - auxBytes += len(data) - } } if nodes.DataSize()+auxBytes >= softResponseLimit { break @@ -904,7 +906,7 @@ func (h *serverHandler) getHelperTrie(typ uint, index uint64) (common.Hash, stri // getAuxiliaryHeaders returns requested auxiliary headers for the CHT request. func (h *serverHandler) getAuxiliaryHeaders(req HelperTrieReq) []byte { - if req.Type == htCanonical && req.AuxReq == auxHeader && len(req.Key) == 8 { + if req.Type == htCanonical && req.AuxReq == htAuxHeader && len(req.Key) == 8 { blockNum := binary.BigEndian.Uint64(req.Key) hash := rawdb.ReadCanonicalHash(h.chainDb, blockNum) return rawdb.ReadHeaderRLP(h.chainDb, hash, blockNum) From 398182284cb1635be833017e87d484795a5e5c56 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 18 Jan 2021 14:33:15 +0100 Subject: [PATCH 130/235] tests/fuzzers/abi: better test generation (#22158) * tests/fuzzers/abi: better test generation * tests/fuzzers/abi: fixed packing issue * oss-fuzz: enable abi fuzzer --- oss-fuzz.sh | 6 +- tests/fuzzers/abi/abifuzzer.go | 115 ++++++++++++---------------- tests/fuzzers/abi/abifuzzer_test.go | 9 +-- 3 files changed, 51 insertions(+), 79 deletions(-) diff --git a/oss-fuzz.sh b/oss-fuzz.sh index 5919b2077f..7ae4d77b29 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -100,6 +100,7 @@ compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty +compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul @@ -113,8 +114,3 @@ compile_fuzzer tests/fuzzers/bls12381 FuzzMapG2 fuzz_map_g2 #TODO: move this to tests/fuzzers, if possible compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b - - -# This doesn't work very well @TODO -#compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi - diff --git a/tests/fuzzers/abi/abifuzzer.go b/tests/fuzzers/abi/abifuzzer.go index 76d3c800f7..8c083b371e 100644 --- a/tests/fuzzers/abi/abifuzzer.go +++ b/tests/fuzzers/abi/abifuzzer.go @@ -17,38 +17,53 @@ package abi import ( - "bytes" "fmt" - "math/rand" "reflect" "strings" "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/crypto" fuzz "github.com/google/gofuzz" ) -func unpackPack(abi abi.ABI, method string, inputType []interface{}, input []byte) bool { - outptr := reflect.New(reflect.TypeOf(inputType)) - if err := abi.UnpackIntoInterface(outptr.Interface(), method, input); err == nil { - output, err := abi.Pack(method, input) +var ( + names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"} + stateMut = []string{"", "pure", "view", "payable"} + stateMutabilites = []*string{&stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]} + pays = []string{"", "true", "false"} + payables = []*string{&pays[0], &pays[1]} + vNames = []string{"a", "b", "c", "d", "e", "f", "g"} + varNames = append(vNames, names...) + varTypes = []string{"bool", "address", "bytes", "string", + "uint8", "int8", "uint8", "int8", "uint16", "int16", + "uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56", + "uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96", + "uint104", "int104", "uint112", "int112", "uint120", "int120", "uint128", "int128", "uint136", "int136", + "uint144", "int144", "uint152", "int152", "uint160", "int160", "uint168", "int168", "uint176", "int176", + "uint184", "int184", "uint192", "int192", "uint200", "int200", "uint208", "int208", "uint216", "int216", + "uint224", "int224", "uint232", "int232", "uint240", "int240", "uint248", "int248", "uint256", "int256", + "bytes1", "bytes2", "bytes3", "bytes4", "bytes5", "bytes6", "bytes7", "bytes8", "bytes9", "bytes10", "bytes11", + "bytes12", "bytes13", "bytes14", "bytes15", "bytes16", "bytes17", "bytes18", "bytes19", "bytes20", "bytes21", + "bytes22", "bytes23", "bytes24", "bytes25", "bytes26", "bytes27", "bytes28", "bytes29", "bytes30", "bytes31", + "bytes32", "bytes"} +) + +func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool) { + if out, err := abi.Unpack(method, input); err == nil { + _, err := abi.Pack(method, out...) if err != nil { // We have some false positives as we can unpack these type successfully, but not pack them if err.Error() == "abi: cannot use []uint8 as type [0]int8 as argument" || err.Error() == "abi: cannot use uint8 as type int8 as argument" { - return false + return out, false } panic(err) } - if !bytes.Equal(input, output[4:]) { - panic(fmt.Sprintf("unpackPack is not equal, \ninput : %x\noutput: %x", input, output[4:])) - } - return true + return out, true } - return false + return nil, false } -func packUnpack(abi abi.ABI, method string, input []interface{}) bool { +func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool { if packed, err := abi.Pack(method, input); err == nil { outptr := reflect.New(reflect.TypeOf(input)) err := abi.UnpackIntoInterface(outptr.Interface(), method, packed) @@ -100,64 +115,23 @@ func createABI(name string, stateMutability, payable *string, inputs []args) (ab return abi.JSON(strings.NewReader(sig)) } -func fillStruct(structs []interface{}, data []byte) { - if structs != nil && len(data) != 0 { - fuzz.NewFromGoFuzz(data).Fuzz(&structs) - } -} - -func createStructs(args []args) []interface{} { - structs := make([]interface{}, len(args)) - for i, arg := range args { - t, err := abi.NewType(arg.typ, "", nil) - if err != nil { - panic(err) - } - structs[i] = reflect.New(t.GetType()).Elem() - } - return structs -} - func runFuzzer(input []byte) int { good := false + fuzzer := fuzz.NewFromGoFuzz(input) - names := []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"} - stateMut := []string{"", "pure", "view", "payable"} - stateMutabilites := []*string{nil, &stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]} - pays := []string{"true", "false"} - payables := []*string{nil, &pays[0], &pays[1]} - varNames := []string{"a", "b", "c", "d", "e", "f", "g"} - varNames = append(varNames, names...) - varTypes := []string{"bool", "address", "bytes", "string", - "uint8", "int8", "uint8", "int8", "uint16", "int16", - "uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56", - "uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96", - "uint104", "int104", "uint112", "int112", "uint120", "int120", "uint128", "int128", "uint136", "int136", - "uint144", "int144", "uint152", "int152", "uint160", "int160", "uint168", "int168", "uint176", "int176", - "uint184", "int184", "uint192", "int192", "uint200", "int200", "uint208", "int208", "uint216", "int216", - "uint224", "int224", "uint232", "int232", "uint240", "int240", "uint248", "int248", "uint256", "int256", - "bytes1", "bytes2", "bytes3", "bytes4", "bytes5", "bytes6", "bytes7", "bytes8", "bytes9", "bytes10", "bytes11", - "bytes12", "bytes13", "bytes14", "bytes15", "bytes16", "bytes17", "bytes18", "bytes19", "bytes20", "bytes21", - "bytes22", "bytes23", "bytes24", "bytes25", "bytes26", "bytes27", "bytes28", "bytes29", "bytes30", "bytes31", - "bytes32", "bytes"} - rnd := rand.New(rand.NewSource(123456)) - if len(input) > 0 { - kec := crypto.Keccak256(input) - rnd = rand.New(rand.NewSource(int64(kec[0]))) - } - name := names[rnd.Intn(len(names))] - stateM := stateMutabilites[rnd.Intn(len(stateMutabilites))] - payable := payables[rnd.Intn(len(payables))] + name := names[getUInt(fuzzer)%len(names)] + stateM := stateMutabilites[getUInt(fuzzer)%len(stateMutabilites)] + payable := payables[getUInt(fuzzer)%len(payables)] maxLen := 5 for k := 1; k < maxLen; k++ { var arg []args for i := k; i > 0; i-- { argName := varNames[i] - argTyp := varTypes[rnd.Int31n(int32(len(varTypes)))] - if rnd.Int31n(10) == 0 { + argTyp := varTypes[getUInt(fuzzer)%len(varTypes)] + if getUInt(fuzzer)%10 == 0 { argTyp += "[]" - } else if rnd.Int31n(10) == 0 { - arrayArgs := rnd.Int31n(30) + 1 + } else if getUInt(fuzzer)%10 == 0 { + arrayArgs := getUInt(fuzzer)%30 + 1 argTyp += fmt.Sprintf("[%d]", arrayArgs) } arg = append(arg, args{ @@ -169,10 +143,8 @@ func runFuzzer(input []byte) int { if err != nil { continue } - structs := createStructs(arg) - b := unpackPack(abi, name, structs, input) - fillStruct(structs, input) - c := packUnpack(abi, name, structs) + structs, b := unpackPack(abi, name, input) + c := packUnpack(abi, name, &structs) good = good || b || c } if good { @@ -184,3 +156,12 @@ func runFuzzer(input []byte) int { func Fuzz(input []byte) int { return runFuzzer(input) } + +func getUInt(fuzzer *fuzz.Fuzzer) int { + var i int + fuzzer.Fuzz(&i) + if i < 0 { + i *= -1 + } + return i +} diff --git a/tests/fuzzers/abi/abifuzzer_test.go b/tests/fuzzers/abi/abifuzzer_test.go index c72577e9ee..c59c45ab1a 100644 --- a/tests/fuzzers/abi/abifuzzer_test.go +++ b/tests/fuzzers/abi/abifuzzer_test.go @@ -23,13 +23,8 @@ import ( // TestReplicate can be used to replicate crashers from the fuzzing tests. // Just replace testString with the data in .quoted func TestReplicate(t *testing.T) { - testString := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00000000000" + - "00000000000000000000" + - "00000000000000000000" + - "00000001" + testString := "N\xef\xbf0\xef\xbf99000000000000" + + "000000000000" data := []byte(testString) runFuzzer(data) From 10555d46849fc805aa28921fed4d46e4bdaf0c4c Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 18 Jan 2021 14:36:05 +0100 Subject: [PATCH 131/235] cmd/geth: dump config for metrics (#22083) * cmd/geth: dump config * cmd/geth: dump config * cmd/geth: properly read config again * cmd/geth: override metrics if flags are set * cmd/geth: write metrics regardless if enabled * cmd/geth: renamed to metricsfromcliargs * metrics: add default configuration --- cmd/geth/config.go | 43 ++++++++++++++++++++++++++++++++++++++++--- cmd/utils/flags.go | 14 +++++++------- metrics/config.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 metrics/config.go diff --git a/cmd/geth/config.go b/cmd/geth/config.go index bf1fc55b17..c5b330b2d8 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/naoina/toml" @@ -88,6 +89,7 @@ type gethConfig struct { Shh whisperDeprecatedConfig Node node.Config Ethstats ethstatsConfig + Metrics metrics.Config } func loadConfig(file string, cfg *gethConfig) error { @@ -119,8 +121,9 @@ func defaultNodeConfig() node.Config { func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // Load defaults. cfg := gethConfig{ - Eth: eth.DefaultConfig, - Node: defaultNodeConfig(), + Eth: eth.DefaultConfig, + Node: defaultNodeConfig(), + Metrics: metrics.DefaultConfig, } // Load config file. @@ -133,7 +136,6 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { log.Warn("Deprecated whisper config detected. Whisper has been moved to github.com/ethereum/whisper") } } - // Apply flags. utils.SetNodeConfig(ctx, &cfg.Node) stack, err := node.New(&cfg.Node) @@ -146,6 +148,8 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { } utils.SetShhConfig(ctx, stack) + applyMetricConfig(ctx, &cfg) + return stack, cfg } @@ -204,3 +208,36 @@ func dumpConfig(ctx *cli.Context) error { return nil } + +func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) { + if ctx.GlobalIsSet(utils.MetricsEnabledFlag.Name) { + cfg.Metrics.Enabled = ctx.GlobalBool(utils.MetricsEnabledFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsEnabledExpensiveFlag.Name) { + cfg.Metrics.EnabledExpensive = ctx.GlobalBool(utils.MetricsEnabledExpensiveFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsHTTPFlag.Name) { + cfg.Metrics.HTTP = ctx.GlobalString(utils.MetricsHTTPFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsPortFlag.Name) { + cfg.Metrics.Port = ctx.GlobalInt(utils.MetricsPortFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsEnableInfluxDBFlag.Name) { + cfg.Metrics.EnableInfluxDB = ctx.GlobalBool(utils.MetricsEnableInfluxDBFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBEndpointFlag.Name) { + cfg.Metrics.InfluxDBEndpoint = ctx.GlobalString(utils.MetricsInfluxDBEndpointFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBDatabaseFlag.Name) { + cfg.Metrics.InfluxDBDatabase = ctx.GlobalString(utils.MetricsInfluxDBDatabaseFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBUsernameFlag.Name) { + cfg.Metrics.InfluxDBUsername = ctx.GlobalString(utils.MetricsInfluxDBUsernameFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBPasswordFlag.Name) { + cfg.Metrics.InfluxDBPassword = ctx.GlobalString(utils.MetricsInfluxDBPasswordFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBTagsFlag.Name) { + cfg.Metrics.InfluxDBTags = ctx.GlobalString(utils.MetricsInfluxDBTagsFlag.Name) + } +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 6f4da58320..688e25618d 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -684,12 +684,12 @@ var ( MetricsHTTPFlag = cli.StringFlag{ Name: "metrics.addr", Usage: "Enable stand-alone metrics HTTP server listening interface", - Value: "127.0.0.1", + Value: metrics.DefaultConfig.HTTP, } MetricsPortFlag = cli.IntFlag{ Name: "metrics.port", Usage: "Metrics HTTP server listening port", - Value: 6060, + Value: metrics.DefaultConfig.Port, } MetricsEnableInfluxDBFlag = cli.BoolFlag{ Name: "metrics.influxdb", @@ -698,22 +698,22 @@ var ( MetricsInfluxDBEndpointFlag = cli.StringFlag{ Name: "metrics.influxdb.endpoint", Usage: "InfluxDB API endpoint to report metrics to", - Value: "http://localhost:8086", + Value: metrics.DefaultConfig.InfluxDBEndpoint, } MetricsInfluxDBDatabaseFlag = cli.StringFlag{ Name: "metrics.influxdb.database", Usage: "InfluxDB database name to push reported metrics to", - Value: "geth", + Value: metrics.DefaultConfig.InfluxDBDatabase, } MetricsInfluxDBUsernameFlag = cli.StringFlag{ Name: "metrics.influxdb.username", Usage: "Username to authorize access to the database", - Value: "test", + Value: metrics.DefaultConfig.InfluxDBUsername, } MetricsInfluxDBPasswordFlag = cli.StringFlag{ Name: "metrics.influxdb.password", Usage: "Password to authorize access to the database", - Value: "test", + Value: metrics.DefaultConfig.InfluxDBPassword, } // Tags are part of every measurement sent to InfluxDB. Queries on tags are faster in InfluxDB. // For example `host` tag could be used so that we can group all nodes and average a measurement @@ -722,7 +722,7 @@ var ( MetricsInfluxDBTagsFlag = cli.StringFlag{ Name: "metrics.influxdb.tags", Usage: "Comma-separated InfluxDB tags (key/values) attached to all measurements", - Value: "host=localhost", + Value: metrics.DefaultConfig.InfluxDBTags, } EWASMInterpreterFlag = cli.StringFlag{ Name: "vm.ewasm", diff --git a/metrics/config.go b/metrics/config.go new file mode 100644 index 0000000000..d05d664265 --- /dev/null +++ b/metrics/config.go @@ -0,0 +1,45 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of go-ethereum. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package metrics + +// Config contains the configuration for the metric collection. +type Config struct { + Enabled bool `toml:",omitempty"` + EnabledExpensive bool `toml:",omitempty"` + HTTP string `toml:",omitempty"` + Port int `toml:",omitempty"` + EnableInfluxDB bool `toml:",omitempty"` + InfluxDBEndpoint string `toml:",omitempty"` + InfluxDBDatabase string `toml:",omitempty"` + InfluxDBUsername string `toml:",omitempty"` + InfluxDBPassword string `toml:",omitempty"` + InfluxDBTags string `toml:",omitempty"` +} + +// DefaultConfig is the default config for metrics used in go-ethereum. +var DefaultConfig = Config{ + Enabled: false, + EnabledExpensive: false, + HTTP: "127.0.0.1", + Port: 6060, + EnableInfluxDB: false, + InfluxDBEndpoint: "http://localhost:8086", + InfluxDBDatabase: "geth", + InfluxDBUsername: "test", + InfluxDBPassword: "test", + InfluxDBTags: "host=localhost", +} From 5e9f5ca5d302298b933668af539ad1e213bdfa6e Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 18 Jan 2021 21:39:43 +0800 Subject: [PATCH 132/235] core/state/snapshot: write snapshot generator in batch (#22163) * core/state/snapshot: write snapshot generator in batch * core: refactor the tests * core: update tests * core: update tests --- core/blockchain_snapshot_test.go | 1033 ++++++++++++++++++------------ core/state/snapshot/generate.go | 35 +- core/state/snapshot/journal.go | 2 +- 3 files changed, 653 insertions(+), 417 deletions(-) diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index f35dae1678..cb634a451d 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -28,27 +28,19 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" ) -// snapshotTest is a test case for snapshot recovery. It can be used for -// simulating these scenarios: -// (i) Geth restarts normally with valid legacy snapshot -// (ii) Geth restarts normally with valid new-format snapshot -// (iii) Geth restarts after the crash, with broken legacy snapshot -// (iv) Geth restarts after the crash, with broken new-format snapshot -// (v) Geth restarts normally, but it's requested to be rewound to a lower point via SetHead -// (vi) Geth restarts normally with a stale snapshot -type snapshotTest struct { - legacy bool // Flag whether the loaded snapshot is in legacy format - crash bool // Flag whether the Geth restarts from the previous crash - restartCrash int // Number of blocks to insert after the normal stop, then the crash happens - gapped int // Number of blocks to insert without enabling snapshot - setHead uint64 // Block number to set head back to - +// snapshotTestBasic wraps the common testing fields in the snapshot tests. +type snapshotTestBasic struct { + legacy bool // Wether write the snapshot journal in legacy format chainBlocks int // Number of blocks to generate for the canonical chain snapshotBlock uint64 // Block number of the relevant snapshot disk layer commitBlock uint64 // Block number for which to commit the state to disk @@ -58,56 +50,418 @@ type snapshotTest struct { expHeadFastBlock uint64 // Block number of the expected head fast sync block expHeadBlock uint64 // Block number of the expected head full block expSnapshotBottom uint64 // The block height corresponding to the snapshot disk layer + + // share fields, set in runtime + datadir string + db ethdb.Database + gendb ethdb.Database + engine consensus.Engine +} + +func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Block) { + // Create a temporary persistent database + datadir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Failed to create temporary datadir: %v", err) + } + os.RemoveAll(datadir) + + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") + if err != nil { + t.Fatalf("Failed to create persistent database: %v", err) + } + // Initialize a fresh chain + var ( + genesis = new(Genesis).MustCommit(db) + engine = ethash.NewFullFaker() + gendb = rawdb.NewMemoryDatabase() + + // Snapshot is enabled, the first snapshot is created from the Genesis. + // The snapshot memory allowance is 256MB, it means no snapshot flush + // will happen during the block insertion. + cacheConfig = defaultCacheConfig + ) + chain, err := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to create chain: %v", err) + } + blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, gendb, basic.chainBlocks, func(i int, b *BlockGen) {}) + + // Insert the blocks with configured settings. + var breakpoints []uint64 + if basic.commitBlock > basic.snapshotBlock { + breakpoints = append(breakpoints, basic.snapshotBlock, basic.commitBlock) + } else { + breakpoints = append(breakpoints, basic.commitBlock, basic.snapshotBlock) + } + var startPoint uint64 + for _, point := range breakpoints { + if _, err := chain.InsertChain(blocks[startPoint:point]); err != nil { + t.Fatalf("Failed to import canonical chain start: %v", err) + } + startPoint = point + + if basic.commitBlock > 0 && basic.commitBlock == point { + chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), true, nil) + } + if basic.snapshotBlock > 0 && basic.snapshotBlock == point { + if basic.legacy { + // Here we commit the snapshot disk root to simulate + // committing the legacy snapshot. + rawdb.WriteSnapshotRoot(db, blocks[point-1].Root()) + } else { + // Flushing the entire snap tree into the disk, the + // relavant (a) snapshot root and (b) snapshot generator + // will be persisted atomically. + chain.snaps.Cap(blocks[point-1].Root(), 0) + diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root() + if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) { + t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot) + } + } + } + } + if _, err := chain.InsertChain(blocks[startPoint:]); err != nil { + t.Fatalf("Failed to import canonical chain tail: %v", err) + } + + // Set runtime fields + basic.datadir = datadir + basic.db = db + basic.gendb = gendb + basic.engine = engine + + // Ugly hack, notify the chain to flush the journal in legacy format + // if it's requested. + if basic.legacy { + chain.writeLegacyJournal = true + } + return chain, blocks } -func (tt *snapshotTest) dump() string { +func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks []*types.Block) { + // Iterate over all the remaining blocks and ensure there are no gaps + verifyNoGaps(t, chain, true, blocks) + verifyCutoff(t, chain, true, blocks, basic.expCanonicalBlocks) + + if head := chain.CurrentHeader(); head.Number.Uint64() != basic.expHeadHeader { + t.Errorf("Head header mismatch: have %d, want %d", head.Number, basic.expHeadHeader) + } + if head := chain.CurrentFastBlock(); head.NumberU64() != basic.expHeadFastBlock { + t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadFastBlock) + } + if head := chain.CurrentBlock(); head.NumberU64() != basic.expHeadBlock { + t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadBlock) + } + + // Check the disk layer, ensure they are matched + block := chain.GetBlockByNumber(basic.expSnapshotBottom) + if block == nil { + t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom) + } else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) { + t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot()) + } + + // Check the snapshot, ensure it's integrated + if err := snapshot.VerifyState(chain.snaps, block.Root()); err != nil { + t.Errorf("The disk layer is not integrated %v", err) + } +} + +func (basic *snapshotTestBasic) dump() string { buffer := new(strings.Builder) fmt.Fprint(buffer, "Chain:\n G") - for i := 0; i < tt.chainBlocks; i++ { + for i := 0; i < basic.chainBlocks; i++ { fmt.Fprintf(buffer, "->C%d", i+1) } fmt.Fprint(buffer, " (HEAD)\n\n") fmt.Fprintf(buffer, "Commit: G") - if tt.commitBlock > 0 { - fmt.Fprintf(buffer, ", C%d", tt.commitBlock) + if basic.commitBlock > 0 { + fmt.Fprintf(buffer, ", C%d", basic.commitBlock) } fmt.Fprint(buffer, "\n") fmt.Fprintf(buffer, "Snapshot: G") - if tt.snapshotBlock > 0 { - fmt.Fprintf(buffer, ", C%d", tt.snapshotBlock) + if basic.snapshotBlock > 0 { + fmt.Fprintf(buffer, ", C%d", basic.snapshotBlock) } fmt.Fprint(buffer, "\n") - if tt.crash { - fmt.Fprintf(buffer, "\nCRASH\n\n") - } else { - fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", tt.setHead) - } + //if crash { + // fmt.Fprintf(buffer, "\nCRASH\n\n") + //} else { + // fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", basic.setHead) + //} fmt.Fprintf(buffer, "------------------------------\n\n") fmt.Fprint(buffer, "Expected in leveldb:\n G") - for i := 0; i < tt.expCanonicalBlocks; i++ { + for i := 0; i < basic.expCanonicalBlocks; i++ { fmt.Fprintf(buffer, "->C%d", i+1) } fmt.Fprintf(buffer, "\n\n") - fmt.Fprintf(buffer, "Expected head header : C%d\n", tt.expHeadHeader) - fmt.Fprintf(buffer, "Expected head fast block: C%d\n", tt.expHeadFastBlock) - if tt.expHeadBlock == 0 { + fmt.Fprintf(buffer, "Expected head header : C%d\n", basic.expHeadHeader) + fmt.Fprintf(buffer, "Expected head fast block: C%d\n", basic.expHeadFastBlock) + if basic.expHeadBlock == 0 { fmt.Fprintf(buffer, "Expected head block : G\n") } else { - fmt.Fprintf(buffer, "Expected head block : C%d\n", tt.expHeadBlock) + fmt.Fprintf(buffer, "Expected head block : C%d\n", basic.expHeadBlock) } - if tt.expSnapshotBottom == 0 { + if basic.expSnapshotBottom == 0 { fmt.Fprintf(buffer, "Expected snapshot disk : G\n") } else { - fmt.Fprintf(buffer, "Expected snapshot disk : C%d\n", tt.expSnapshotBottom) + fmt.Fprintf(buffer, "Expected snapshot disk : C%d\n", basic.expSnapshotBottom) } return buffer.String() } +func (basic *snapshotTestBasic) teardown() { + basic.db.Close() + basic.gendb.Close() + os.RemoveAll(basic.datadir) +} + +// snapshotTest is a test case type for normal snapshot recovery. +// It can be used for testing that restart Geth normally. +type snapshotTest struct { + snapshotTestBasic +} + +func (snaptest *snapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Restart the chain normally + chain.Stop() + newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer newchain.Stop() + + snaptest.verify(t, newchain, blocks) +} + +// crashSnapshotTest is a test case type for innormal snapshot recovery. +// It can be used for testing that restart Geth after the crash. +type crashSnapshotTest struct { + snapshotTestBasic +} + +func (snaptest *crashSnapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Pull the plug on the database, simulating a hard crash + db := chain.db + db.Close() + + // Start a new blockchain back up and see where the repair leads us + newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "") + if err != nil { + t.Fatalf("Failed to reopen persistent database: %v", err) + } + defer newdb.Close() + + // The interesting thing is: instead of starting the blockchain after + // the crash, we do restart twice here: one after the crash and one + // after the normal stop. It's used to ensure the broken snapshot + // can be detected all the time. + newchain, err := NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + newchain.Stop() + + newchain, err = NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer newchain.Stop() + + snaptest.verify(t, newchain, blocks) +} + +// gappedSnapshotTest is a test type used to test this scenario: +// - have a complete snapshot +// - restart without enabling the snapshot +// - insert a few blocks +// - restart with enabling the snapshot again +type gappedSnapshotTest struct { + snapshotTestBasic + gapped int // Number of blocks to insert without enabling snapshot +} + +func (snaptest *gappedSnapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Insert blocks without enabling snapshot if gapping is required. + chain.Stop() + gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.gapped, func(i int, b *BlockGen) {}) + + // Insert a few more blocks without enabling snapshot + var cacheConfig = &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, + } + newchain, err := NewBlockChain(snaptest.db, cacheConfig, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + newchain.InsertChain(gappedBlocks) + newchain.Stop() + + // Restart the chain with enabling the snapshot + newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer newchain.Stop() + + snaptest.verify(t, newchain, blocks) +} + +// setHeadSnapshotTest is the test type used to test this scenario: +// - have a complete snapshot +// - set the head to a lower point +// - restart +type setHeadSnapshotTest struct { + snapshotTestBasic + setHead uint64 // Block number to set head back to +} + +func (snaptest *setHeadSnapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Rewind the chain if setHead operation is required. + chain.SetHead(snaptest.setHead) + chain.Stop() + + newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer newchain.Stop() + + snaptest.verify(t, newchain, blocks) +} + +// restartCrashSnapshotTest is the test type used to test this scenario: +// - have a complete snapshot +// - restart chain +// - insert more blocks with enabling the snapshot +// - commit the snapshot +// - crash +// - restart again +type restartCrashSnapshotTest struct { + snapshotTestBasic + newBlocks int +} + +func (snaptest *restartCrashSnapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Firstly, stop the chain properly, with all snapshot journal + // and state committed. + chain.Stop() + + newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *BlockGen) {}) + newchain.InsertChain(newBlocks) + + // Commit the entire snapshot into the disk if requested. Note only + // (a) snapshot root and (b) snapshot generator will be committed, + // the diff journal is not. + newchain.Snapshots().Cap(newBlocks[len(newBlocks)-1].Root(), 0) + + // Simulate the blockchain crash + // Don't call chain.Stop here, so that no snapshot + // journal and latest state will be committed + + // Restart the chain after the crash + newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer newchain.Stop() + + snaptest.verify(t, newchain, blocks) +} + +// wipeCrashSnapshotTest is the test type used to test this scenario: +// - have a complete snapshot +// - restart, insert more blocks without enabling the snapshot +// - restart again with enabling the snapshot +// - crash +type wipeCrashSnapshotTest struct { + snapshotTestBasic + newBlocks int +} + +func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Firstly, stop the chain properly, with all snapshot journal + // and state committed. + chain.Stop() + + config := &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, + } + newchain, err := NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *BlockGen) {}) + newchain.InsertChain(newBlocks) + newchain.Stop() + + // Restart the chain, the wiper should starts working + config = &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 256, + SnapshotWait: false, // Don't wait rebuild + } + newchain, err = NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + // Simulate the blockchain crash. + + newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + snaptest.verify(t, newchain, blocks) +} + // Tests a Geth restart with valid snapshot. Before the shutdown, all snapshot // journal will be persisted correctly. In this case no snapshot recovery is // required. @@ -129,20 +483,21 @@ func TestRestartWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : C8 // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: false, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 8, - expSnapshotBottom: 0, // Initial disk layer built from genesis - }) + test := &snapshotTest{ + snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 8, + expSnapshotBottom: 0, // Initial disk layer built from genesis + }, + } + test.test(t) + test.teardown() } // Tests a Geth restart with valid but "legacy" snapshot. Before the shutdown, @@ -166,20 +521,22 @@ func TestRestartWithLegacySnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : C8 // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: false, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 8, - expSnapshotBottom: 0, // Initial disk layer built from genesis - }) + t.Skip("Legacy format testing is not supported") + test := &snapshotTest{ + snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 8, + expSnapshotBottom: 0, // Initial disk layer built from genesis + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken snapshot. In this case the @@ -205,20 +562,21 @@ func TestNoCommitCrashWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : G // Expected snapshot disk : C4 - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 4, // Last committed disk layer, wait recovery - }) + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken snapshot. In this case the @@ -244,20 +602,21 @@ func TestLowCommitCrashWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : C2 // Expected snapshot disk : C4 - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 2, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 2, - expSnapshotBottom: 4, // Last committed disk layer, wait recovery - }) + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 2, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 2, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken snapshot. In this case @@ -283,20 +642,21 @@ func TestHighCommitCrashWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : G // Expected snapshot disk : C4 - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 6, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 4, // Last committed disk layer, wait recovery - }) + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 6, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken and "legacy format" @@ -321,20 +681,22 @@ func TestNoCommitCrashWithLegacySnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : G // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) - }) + t.Skip("Legacy format testing is not supported") + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken and "legacy format" @@ -359,20 +721,22 @@ func TestLowCommitCrashWithLegacySnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : C2 // Expected snapshot disk : C2 - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 2, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 2, - expSnapshotBottom: 2, // Rebuilt snapshot from the latest HEAD - }) + t.Skip("Legacy format testing is not supported") + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 2, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 2, + expSnapshotBottom: 2, // Rebuilt snapshot from the latest HEAD + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken and "legacy format" @@ -402,20 +766,22 @@ func TestHighCommitCrashWithLegacySnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : G // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 6, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) - }) + t.Skip("Legacy format testing is not supported") + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 6, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) + }, + } + test.test(t) + test.teardown() } // Tests a Geth was running with snapshot enabled. Then restarts without @@ -439,20 +805,22 @@ func TestGappedNewSnapshot(t *testing.T) { // Expected head fast block: C10 // Expected head block : C10 // Expected snapshot disk : C10 - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: false, - gapped: 2, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 10, - expHeadHeader: 10, - expHeadFastBlock: 10, - expHeadBlock: 10, - expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD - }) + test := &gappedSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 10, + expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD + }, + gapped: 2, + } + test.test(t) + test.teardown() } // Tests a Geth was running with leagcy snapshot enabled. Then restarts @@ -476,20 +844,23 @@ func TestGappedLegacySnapshot(t *testing.T) { // Expected head fast block: C10 // Expected head block : C10 // Expected snapshot disk : C10 - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: false, - gapped: 2, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 10, - expHeadHeader: 10, - expHeadFastBlock: 10, - expHeadBlock: 10, - expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD - }) + t.Skip("Legacy format testing is not supported") + test := &gappedSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 10, + expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD + }, + gapped: 2, + } + test.test(t) + test.teardown() } // Tests the Geth was running with snapshot enabled and resetHead is applied. @@ -513,20 +884,22 @@ func TestSetHeadWithNewSnapshot(t *testing.T) { // Expected head fast block: C4 // Expected head block : C4 // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: false, - gapped: 0, - setHead: 4, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 4, - expHeadHeader: 4, - expHeadFastBlock: 4, - expHeadBlock: 4, - expSnapshotBottom: 0, // The initial disk layer is built from the genesis - }) + test := &setHeadSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 4, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + expSnapshotBottom: 0, // The initial disk layer is built from the genesis + }, + setHead: 4, + } + test.test(t) + test.teardown() } // Tests the Geth was running with snapshot(legacy-format) enabled and resetHead @@ -550,20 +923,23 @@ func TestSetHeadWithLegacySnapshot(t *testing.T) { // Expected head fast block: C4 // Expected head block : C4 // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: false, - gapped: 0, - setHead: 4, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 4, - expHeadHeader: 4, - expHeadFastBlock: 4, - expHeadBlock: 4, - expSnapshotBottom: 0, // The initial disk layer is built from the genesis - }) + t.Skip("Legacy format testing is not supported") + test := &setHeadSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 4, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + expSnapshotBottom: 0, // The initial disk layer is built from the genesis + }, + setHead: 4, + } + test.test(t) + test.teardown() } // Tests the Geth was running with snapshot(legacy-format) enabled and upgrades @@ -589,209 +965,60 @@ func TestRecoverSnapshotFromCrashWithLegacyDiffJournal(t *testing.T) { // Expected head fast block: C10 // Expected head block : C8 // Expected snapshot disk : C10 - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: false, - restartCrash: 2, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 10, - expHeadHeader: 10, - expHeadFastBlock: 10, - expHeadBlock: 8, // The persisted state in the first running - expSnapshotBottom: 10, // The persisted disk layer in the second running - }) -} - -func testSnapshot(t *testing.T, tt *snapshotTest) { - // It's hard to follow the test case, visualize the input - // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) - // fmt.Println(tt.dump()) - - // Create a temporary persistent database - datadir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Failed to create temporary datadir: %v", err) - } - os.RemoveAll(datadir) - - db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") - if err != nil { - t.Fatalf("Failed to create persistent database: %v", err) - } - defer db.Close() // Might double close, should be fine - - // Initialize a fresh chain - var ( - genesis = new(Genesis).MustCommit(db) - engine = ethash.NewFullFaker() - gendb = rawdb.NewMemoryDatabase() - - // Snapshot is enabled, the first snapshot is created from the Genesis. - // The snapshot memory allowance is 256MB, it means no snapshot flush - // will happen during the block insertion. - cacheConfig = defaultCacheConfig - ) - chain, err := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to create chain: %v", err) - } - blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, gendb, tt.chainBlocks, func(i int, b *BlockGen) {}) - - // Insert the blocks with configured settings. - var breakpoints []uint64 - if tt.commitBlock > tt.snapshotBlock { - breakpoints = append(breakpoints, tt.snapshotBlock, tt.commitBlock) - } else { - breakpoints = append(breakpoints, tt.commitBlock, tt.snapshotBlock) - } - var startPoint uint64 - for _, point := range breakpoints { - if _, err := chain.InsertChain(blocks[startPoint:point]); err != nil { - t.Fatalf("Failed to import canonical chain start: %v", err) - } - startPoint = point - - if tt.commitBlock > 0 && tt.commitBlock == point { - chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), true, nil) - } - if tt.snapshotBlock > 0 && tt.snapshotBlock == point { - if tt.legacy { - // Here we commit the snapshot disk root to simulate - // committing the legacy snapshot. - rawdb.WriteSnapshotRoot(db, blocks[point-1].Root()) - } else { - chain.snaps.Cap(blocks[point-1].Root(), 0) - diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root() - if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) { - t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot) - } - } - } - } - if _, err := chain.InsertChain(blocks[startPoint:]); err != nil { - t.Fatalf("Failed to import canonical chain tail: %v", err) + t.Skip("Legacy format testing is not supported") + test := &restartCrashSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 8, // The persisted state in the first running + expSnapshotBottom: 10, // The persisted disk layer in the second running + }, + newBlocks: 2, } - // Set the flag for writing legacy journal if necessary - if tt.legacy { - chain.writeLegacyJournal = true - } - // Pull the plug on the database, simulating a hard crash - if tt.crash { - db.Close() - - // Start a new blockchain back up and see where the repair leads us - db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") - if err != nil { - t.Fatalf("Failed to reopen persistent database: %v", err) - } - defer db.Close() - - // The interesting thing is: instead of start the blockchain after - // the crash, we do restart twice here: one after the crash and one - // after the normal stop. It's used to ensure the broken snapshot - // can be detected all the time. - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - chain.Stop() - - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - defer chain.Stop() - } else if tt.gapped > 0 { - // Insert blocks without enabling snapshot if gapping is required. - chain.Stop() - gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], engine, gendb, tt.gapped, func(i int, b *BlockGen) {}) - - // Insert a few more blocks without enabling snapshot - var cacheConfig = &CacheConfig{ - TrieCleanLimit: 256, - TrieDirtyLimit: 256, - TrieTimeLimit: 5 * time.Minute, - SnapshotLimit: 0, - } - chain, err = NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - chain.InsertChain(gappedBlocks) - chain.Stop() - - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - defer chain.Stop() - } else if tt.setHead != 0 { - // Rewind the chain if setHead operation is required. - chain.SetHead(tt.setHead) - chain.Stop() - - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - defer chain.Stop() - } else if tt.restartCrash != 0 { - // Firstly, stop the chain properly, with all snapshot journal - // and state committed. - chain.Stop() - - // Restart chain, forcibly flush the disk layer journal with new format - newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], engine, gendb, tt.restartCrash, func(i int, b *BlockGen) {}) - chain, err = NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - chain.InsertChain(newBlocks) - chain.Snapshots().Cap(newBlocks[len(newBlocks)-1].Root(), 0) - - // Simulate the blockchain crash - // Don't call chain.Stop here, so that no snapshot - // journal and latest state will be committed - - // Restart the chain after the crash - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - defer chain.Stop() - } else { - chain.Stop() - - // Restart the chain normally - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - defer chain.Stop() - } - - // Iterate over all the remaining blocks and ensure there are no gaps - verifyNoGaps(t, chain, true, blocks) - verifyCutoff(t, chain, true, blocks, tt.expCanonicalBlocks) + test.test(t) + test.teardown() +} - if head := chain.CurrentHeader(); head.Number.Uint64() != tt.expHeadHeader { - t.Errorf("Head header mismatch: have %d, want %d", head.Number, tt.expHeadHeader) - } - if head := chain.CurrentFastBlock(); head.NumberU64() != tt.expHeadFastBlock { - t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadFastBlock) - } - if head := chain.CurrentBlock(); head.NumberU64() != tt.expHeadBlock { - t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadBlock) - } - // Check the disk layer, ensure they are matched - block := chain.GetBlockByNumber(tt.expSnapshotBottom) - if block == nil { - t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", tt.expSnapshotBottom) - } else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) { - t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot()) +// Tests the Geth was running with a complete snapshot and then imports a few +// more new blocks on top without enabling the snapshot. After the restart, +// crash happens. Check everything is ok after the restart. +func TestRecoverSnapshotFromWipingCrash(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(0) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 + // + // Expected head header : C10 + // Expected head fast block: C10 + // Expected head block : C8 + // Expected snapshot disk : C10 + test := &wipeCrashSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 10, + expSnapshotBottom: 10, + }, + newBlocks: 2, } + test.test(t) + test.teardown() } diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index fcc6b44cb6..2b41dd5513 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -101,18 +101,26 @@ func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache i wiper = wipeSnapshot(diskdb, true) } // Create a new disk layer with an initialized state marker at zero - rawdb.WriteSnapshotRoot(diskdb, root) - + var ( + stats = &generatorStats{wiping: wiper, start: time.Now()} + batch = diskdb.NewBatch() + genMarker = []byte{} // Initialized but empty! + ) + rawdb.WriteSnapshotRoot(batch, root) + journalProgress(batch, genMarker, stats) + if err := batch.Write(); err != nil { + log.Crit("Failed to write initialized state marker", "error", err) + } base := &diskLayer{ diskdb: diskdb, triedb: triedb, root: root, cache: fastcache.New(cache * 1024 * 1024), - genMarker: []byte{}, // Initialized but empty! + genMarker: genMarker, genPending: make(chan struct{}), genAbort: make(chan chan *generatorStats), } - go base.generate(&generatorStats{wiping: wiper, start: time.Now()}) + go base.generate(stats) log.Debug("Start snapshot generation", "root", root) return base } @@ -137,10 +145,12 @@ func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorSta panic(err) // Cannot happen, here to catch dev errors } var logstr string - switch len(marker) { - case 0: + switch { + case marker == nil: logstr = "done" - case common.HashLength: + case bytes.Equal(marker, []byte{}): + logstr = "empty" + case len(marker) == common.HashLength: logstr = fmt.Sprintf("%#x", marker) default: logstr = fmt.Sprintf("%#x:%#x", marker[:common.HashLength], marker[common.HashLength:]) @@ -307,13 +317,12 @@ func (dl *diskLayer) generate(stats *generatorStats) { abort <- stats return } - // Snapshot fully generated, set the marker to nil - if batch.ValueSize() > 0 { - // Ensure the generator entry is in sync with the data - journalProgress(batch, nil, stats) + // Snapshot fully generated, set the marker to nil. + // Note even there is nothing to commit, persist the + // generator anyway to mark the snapshot is complete. + journalProgress(batch, nil, stats) + batch.Write() - batch.Write() - } log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots, "storage", stats.storage, "elapsed", common.PrettyDuration(time.Since(stats.start))) diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index 178ba08902..d7e454cceb 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -441,6 +441,6 @@ func (dl *diffLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) { if err := rlp.Encode(buffer, storage); err != nil { return common.Hash{}, err } - log.Debug("Legacy journalled disk layer", "root", dl.root, "parent", dl.parent.Root()) + log.Debug("Legacy journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) return base, nil } From 24c1e3053b6767add29bf257c7943dc6aa5fa91d Mon Sep 17 00:00:00 2001 From: Alex Mazalov Date: Tue, 19 Jan 2021 08:26:42 +0000 Subject: [PATCH 133/235] cmd/geth: graceful shutdown if disk is full (#22103) Adding warnings of free disk space left and graceful shutdown when there is not enough space left. This also adds a flag datadir.minfreedisk which can be used to set the trigger for low disk space, and setting it to zero disables the check. Co-authored-by: Martin Holst Swende Co-authored-by: Felix Lange --- cmd/geth/main.go | 3 ++- cmd/geth/usage.go | 1 + cmd/utils/cmd.go | 34 +++++++++++++++++++++++++++++- cmd/utils/diskusage.go | 35 +++++++++++++++++++++++++++++++ cmd/utils/diskusage_windows.go | 38 ++++++++++++++++++++++++++++++++++ cmd/utils/flags.go | 4 ++++ 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 cmd/utils/diskusage.go create mode 100644 cmd/utils/diskusage_windows.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 7af24e6523..e577ab370d 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -65,6 +65,7 @@ var ( utils.LegacyBootnodesV5Flag, utils.DataDirFlag, utils.AncientFlag, + utils.MinFreeDiskSpaceFlag, utils.KeyStoreDirFlag, utils.ExternalSignerFlag, utils.NoUSBFlag, @@ -368,7 +369,7 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { debug.Memsize.Add("node", stack) // Start up the node itself - utils.StartNode(stack) + utils.StartNode(ctx, stack) // Unlock any account specifically requested unlockAccounts(ctx, stack) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 78ebb807e1..25accc9b79 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -36,6 +36,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ configFileFlag, utils.DataDirFlag, utils.AncientFlag, + utils.MinFreeDiskSpaceFlag, utils.KeyStoreDirFlag, utils.USBFlag, utils.SmartCardDaemonPathFlag, diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 869cf90ea5..4306216892 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -26,17 +26,20 @@ import ( "runtime" "strings" "syscall" + "time" "github.com/ethereum/go-ethereum/common" "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/crypto" + "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rlp" + "gopkg.in/urfave/cli.v1" ) const ( @@ -63,7 +66,7 @@ func Fatalf(format string, args ...interface{}) { os.Exit(1) } -func StartNode(stack *node.Node) { +func StartNode(ctx *cli.Context, stack *node.Node) { if err := stack.Start(); err != nil { Fatalf("Error starting protocol stack: %v", err) } @@ -71,6 +74,17 @@ func StartNode(stack *node.Node) { sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) defer signal.Stop(sigc) + + minFreeDiskSpace := eth.DefaultConfig.TrieDirtyCache + if ctx.GlobalIsSet(MinFreeDiskSpaceFlag.Name) { + minFreeDiskSpace = ctx.GlobalInt(MinFreeDiskSpaceFlag.Name) + } else if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) { + minFreeDiskSpace = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100 + } + if minFreeDiskSpace > 0 { + go monitorFreeDiskSpace(sigc, stack.InstanceDir(), uint64(minFreeDiskSpace)*1024*1024) + } + <-sigc log.Info("Got interrupt, shutting down...") go stack.Close() @@ -85,6 +99,24 @@ func StartNode(stack *node.Node) { }() } +func monitorFreeDiskSpace(sigc chan os.Signal, path string, freeDiskSpaceCritical uint64) { + for { + freeSpace, err := getFreeDiskSpace(path) + if err != nil { + log.Warn("Failed to get free disk space", "path", path, "err", err) + break + } + if freeSpace < freeDiskSpaceCritical { + log.Error("Low disk space. Gracefully shutting down Geth to prevent database corruption.", "available", common.StorageSize(freeSpace)) + sigc <- syscall.SIGTERM + break + } else if freeSpace < 2*freeDiskSpaceCritical { + log.Warn("Disk space is running low. Geth will shutdown if disk space runs below critical level.", "available", common.StorageSize(freeSpace), "critical_level", common.StorageSize(freeDiskSpaceCritical)) + } + time.Sleep(60 * time.Second) + } +} + func ImportChain(chain *core.BlockChain, fn string) error { // Watch for Ctrl-C while the import is running. // If a signal is received, the import will stop at the next batch. diff --git a/cmd/utils/diskusage.go b/cmd/utils/diskusage.go new file mode 100644 index 0000000000..a822118a39 --- /dev/null +++ b/cmd/utils/diskusage.go @@ -0,0 +1,35 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build !windows + +package utils + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +func getFreeDiskSpace(path string) (uint64, error) { + var stat unix.Statfs_t + if err := unix.Statfs(path, &stat); err != nil { + return 0, fmt.Errorf("failed to call Statfs: %v", err) + } + + // Available blocks * size per block = available space in bytes + return stat.Bavail * uint64(stat.Bsize), nil +} diff --git a/cmd/utils/diskusage_windows.go b/cmd/utils/diskusage_windows.go new file mode 100644 index 0000000000..9bf7740b99 --- /dev/null +++ b/cmd/utils/diskusage_windows.go @@ -0,0 +1,38 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utils + +import ( + "fmt" + + "golang.org/x/sys/windows" +) + +func getFreeDiskSpace(path string) (uint64, error) { + + cwd, err := windows.UTF16PtrFromString(path) + if err != nil { + return 0, fmt.Errorf("failed to call UTF16PtrFromString: %v", err) + } + + var freeBytesAvailableToCaller, totalNumberOfBytes, totalNumberOfFreeBytes uint64 + if err := windows.GetDiskFreeSpaceEx(cwd, &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes); err != nil { + return 0, fmt.Errorf("failed to call GetDiskFreeSpaceEx: %v", err) + } + + return freeBytesAvailableToCaller, nil +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 688e25618d..df036cbbf0 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -113,6 +113,10 @@ var ( Name: "datadir.ancient", Usage: "Data directory for ancient chain segments (default = inside chaindata)", } + MinFreeDiskSpaceFlag = DirectoryFlag{ + Name: "datadir.minfreedisk", + Usage: "Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled)", + } KeyStoreDirFlag = DirectoryFlag{ Name: "keystore", Usage: "Directory for the keystore (default = inside the datadir)", From 45cb1a580abad0d4e8caa1c8b7dfacd5ef3d27bc Mon Sep 17 00:00:00 2001 From: gary rong Date: Tue, 19 Jan 2021 17:52:45 +0800 Subject: [PATCH 134/235] eth, les: add new config field SyncFromCheckpoint (#22123) This PR introduces a new config field SyncFromCheckpoint for light client. In some special scenarios, it's required to start synchronization from some arbitrary checkpoint or even from the scratch. So this PR offers this flexibility to users so that the synchronization start point can be configured. There are two relevant configs: SyncFromCheckpoint and Checkpoint. - If the SyncFromCheckpoint is true, the light client will try to sync from the specified checkpoint. - If the Checkpoint is not configured, then the light client will sync from the scratch(from the latest header if the database is not empty) Additional notes: these two configs are not visible in the CLI flags but only accessable in the config file. Example Usage: [Eth] SyncFromCheckpoint = true [Eth.Checkpoint] SectionIndex = 100 SectionHead = "0xabc" CHTRoot = "0xabc" BloomRoot = "0xabc" PS. Historical checkpoint can be retrieved from the synced full node or light client via les_getCheckpoint API. --- eth/config.go | 11 +-- eth/gen_config.go | 12 ++++ les/client_handler.go | 9 ++- les/commons.go | 6 +- les/sync.go | 52 ++++++++++----- les/sync_test.go | 152 ++++++++++++++++++++++++++++++++++++++++-- les/test_helper.go | 2 +- 7 files changed, 209 insertions(+), 35 deletions(-) diff --git a/eth/config.go b/eth/config.go index 77d03e9569..446467d364 100644 --- a/eth/config.go +++ b/eth/config.go @@ -127,11 +127,12 @@ type Config struct { Whitelist map[uint64]common.Hash `toml:"-"` // Light client options - LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests - LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers - LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers - LightPeers int `toml:",omitempty"` // Maximum number of LES client peers - LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning + LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests + LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers + LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers + LightPeers int `toml:",omitempty"` // Maximum number of LES client peers + LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning + SyncFromCheckpoint bool `toml:",omitempty"` // Whether to sync the header chain from the configured checkpoint // Ultra Light client options UltraLightServers []string `toml:",omitempty"` // List of trusted ultra light servers diff --git a/eth/gen_config.go b/eth/gen_config.go index dd04635eee..e68b29ce5e 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -21,6 +21,7 @@ func (c Config) MarshalTOML() (interface{}, error) { NetworkId uint64 SyncMode downloader.SyncMode EthDiscoveryURLs []string + SnapDiscoveryURLs []string NoPruning bool NoPrefetch bool TxLookupLimit uint64 `toml:",omitempty"` @@ -30,6 +31,7 @@ func (c Config) MarshalTOML() (interface{}, error) { LightEgress int `toml:",omitempty"` LightPeers int `toml:",omitempty"` LightNoPrune bool `toml:",omitempty"` + SyncFromCheckpoint bool `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"` UltraLightFraction int `toml:",omitempty"` UltraLightOnlyAnnounce bool `toml:",omitempty"` @@ -62,6 +64,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.NetworkId = c.NetworkId enc.SyncMode = c.SyncMode enc.EthDiscoveryURLs = c.EthDiscoveryURLs + enc.SnapDiscoveryURLs = c.SnapDiscoveryURLs enc.NoPruning = c.NoPruning enc.NoPrefetch = c.NoPrefetch enc.TxLookupLimit = c.TxLookupLimit @@ -71,6 +74,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.LightEgress = c.LightEgress enc.LightPeers = c.LightPeers enc.LightNoPrune = c.LightNoPrune + enc.SyncFromCheckpoint = c.SyncFromCheckpoint enc.UltraLightServers = c.UltraLightServers enc.UltraLightFraction = c.UltraLightFraction enc.UltraLightOnlyAnnounce = c.UltraLightOnlyAnnounce @@ -107,6 +111,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { NetworkId *uint64 SyncMode *downloader.SyncMode EthDiscoveryURLs []string + SnapDiscoveryURLs []string NoPruning *bool NoPrefetch *bool TxLookupLimit *uint64 `toml:",omitempty"` @@ -116,6 +121,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { LightEgress *int `toml:",omitempty"` LightPeers *int `toml:",omitempty"` LightNoPrune *bool `toml:",omitempty"` + SyncFromCheckpoint *bool `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"` UltraLightFraction *int `toml:",omitempty"` UltraLightOnlyAnnounce *bool `toml:",omitempty"` @@ -159,6 +165,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.EthDiscoveryURLs != nil { c.EthDiscoveryURLs = dec.EthDiscoveryURLs } + if dec.SnapDiscoveryURLs != nil { + c.SnapDiscoveryURLs = dec.SnapDiscoveryURLs + } if dec.NoPruning != nil { c.NoPruning = *dec.NoPruning } @@ -186,6 +195,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.LightNoPrune != nil { c.LightNoPrune = *dec.LightNoPrune } + if dec.SyncFromCheckpoint != nil { + c.SyncFromCheckpoint = *dec.SyncFromCheckpoint + } if dec.UltraLightServers != nil { c.UltraLightServers = dec.UltraLightServers } diff --git a/les/client_handler.go b/les/client_handler.go index 6de5766961..6cd786cda0 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -44,9 +44,12 @@ type clientHandler struct { downloader *downloader.Downloader backend *LightEthereum - closeCh chan struct{} - wg sync.WaitGroup // WaitGroup used to track all connected peers. - syncDone func() // Test hooks when syncing is done. + closeCh chan struct{} + wg sync.WaitGroup // WaitGroup used to track all connected peers. + + // Hooks used in the testing + syncStart func(header *types.Header) // Hook called when the syncing is started + syncEnd func(header *types.Header) // Hook called when the syncing is done } func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.TrustedCheckpoint, backend *LightEthereum) *clientHandler { diff --git a/les/commons.go b/les/commons.go index 003e196d2b..8de1057d26 100644 --- a/les/commons.go +++ b/les/commons.go @@ -157,17 +157,17 @@ func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, ethconfig config = params.CheckpointOracles[genesis] } if config == nil { - log.Info("Checkpoint registrar is not enabled") + log.Info("Checkpoint oracle is not enabled") return nil } if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold { - log.Warn("Invalid checkpoint registrar config") + log.Warn("Invalid checkpoint oracle config") return nil } oracle := checkpointoracle.New(config, c.localCheckpoint) rpcClient, _ := node.Attach() client := ethclient.NewClient(rpcClient) oracle.Start(client) - log.Info("Configured checkpoint registrar", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold) + log.Info("Configured checkpoint oracle", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold) return oracle } diff --git a/les/sync.go b/les/sync.go index ad3a0e0f3c..fa5ef4ff82 100644 --- a/les/sync.go +++ b/les/sync.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" ) var errInvalidCheckpoint = errors.New("invalid advertised checkpoint") @@ -98,22 +99,33 @@ func (h *clientHandler) synchronise(peer *serverPeer) { if currentTd != nil && peer.Td().Cmp(currentTd) < 0 { return } - // Recap the checkpoint. - // - // The light client may be connected to several different versions of the server. - // (1) Old version server which can not provide stable checkpoint in the handshake packet. - // => Use hardcoded checkpoint or empty checkpoint - // (2) New version server but simple checkpoint syncing is not enabled(e.g. mainnet, new testnet or private network) - // => Use hardcoded checkpoint or empty checkpoint - // (3) New version server but the provided stable checkpoint is even lower than the hardcoded one. - // => Use hardcoded checkpoint + // Recap the checkpoint. The light client may be connected to several different + // versions of the server. + // (1) Old version server which can not provide stable checkpoint in the + // handshake packet. + // => Use local checkpoint or empty checkpoint + // (2) New version server but simple checkpoint syncing is not enabled + // (e.g. mainnet, new testnet or private network) + // => Use local checkpoint or empty checkpoint + // (3) New version server but the provided stable checkpoint is even lower + // than the local one. + // => Use local checkpoint // (4) New version server with valid and higher stable checkpoint // => Use provided checkpoint - var checkpoint = &peer.checkpoint - var hardcoded bool + var ( + local bool + checkpoint = &peer.checkpoint + ) if h.checkpoint != nil && h.checkpoint.SectionIndex >= peer.checkpoint.SectionIndex { - checkpoint = h.checkpoint // Use the hardcoded one. - hardcoded = true + local, checkpoint = true, h.checkpoint + } + // Replace the checkpoint with locally configured one If it's required by + // users. Nil checkpoint means synchronization from the scratch. + if h.backend.config.SyncFromCheckpoint { + local, checkpoint = true, h.backend.config.Checkpoint + if h.backend.config.Checkpoint == nil { + checkpoint = ¶ms.TrustedCheckpoint{} + } } // Determine whether we should run checkpoint syncing or normal light syncing. // @@ -121,7 +133,7 @@ func (h *clientHandler) synchronise(peer *serverPeer) { // // 1. The checkpoint is empty // 2. The latest head block of the local chain is above the checkpoint. - // 3. The checkpoint is hardcoded(recap with local hardcoded checkpoint) + // 3. The checkpoint is local(replaced with local checkpoint) // 4. For some networks the checkpoint syncing is not activated. mode := checkpointSync switch { @@ -131,7 +143,7 @@ func (h *clientHandler) synchronise(peer *serverPeer) { case latest.Number.Uint64() >= (checkpoint.SectionIndex+1)*h.backend.iConfig.ChtSize-1: mode = lightSync log.Debug("Disable checkpoint syncing", "reason", "local chain beyond the checkpoint") - case hardcoded: + case local: mode = legacyCheckpointSync log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded") case h.backend.oracle == nil || !h.backend.oracle.IsRunning(): @@ -143,12 +155,14 @@ func (h *clientHandler) synchronise(peer *serverPeer) { } log.Debug("Disable checkpoint syncing", "reason", "checkpoint syncing is not activated") } + // Notify testing framework if syncing has completed(for testing purpose). defer func() { - if h.syncDone != nil { - h.syncDone() + if h.syncEnd != nil { + h.syncEnd(h.backend.blockchain.CurrentHeader()) } }() + start := time.Now() if mode == checkpointSync || mode == legacyCheckpointSync { // Validate the advertised checkpoint @@ -177,6 +191,10 @@ func (h *clientHandler) synchronise(peer *serverPeer) { return } } + + if h.syncStart != nil { + h.syncStart(h.backend.blockchain.CurrentHeader()) + } // Fetch the remaining block headers based on the current chain header. if err := h.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync); err != nil { log.Debug("Synchronise failed", "reason", err) diff --git a/les/sync_test.go b/les/sync_test.go index 2eb0f88bf9..64e7283663 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/params" @@ -53,7 +54,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { time.Sleep(10 * time.Millisecond) } } - // Generate 128+1 blocks (totally 1 CHT sections) + // Generate 128+1 blocks (totally 1 CHT section) server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, 0, false, false, true) defer tearDown() @@ -100,8 +101,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { } done := make(chan error) - client.handler.syncDone = func() { - header := client.handler.backend.blockchain.CurrentHeader() + client.handler.syncEnd = func(header *types.Header) { if header.Number.Uint64() == expected { done <- nil } else { @@ -144,7 +144,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { time.Sleep(10 * time.Millisecond) } } - // Generate 512+4 blocks (totally 1 CHT sections) + // Generate 128+1 blocks (totally 1 CHT section) server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) defer tearDown() @@ -198,8 +198,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { } done := make(chan error) - client.handler.syncDone = func() { - header := client.handler.backend.blockchain.CurrentHeader() + client.handler.syncEnd = func(header *types.Header) { if header.Number.Uint64() == expected { done <- nil } else { @@ -220,3 +219,144 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { t.Error("checkpoint syncing timeout") } } + +func TestSyncFromConfiguredCheckpoint(t *testing.T) { + config := light.TestServerIndexerConfig + + waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + cs, _, _ := cIndexer.Sections() + bts, _, _ := btIndexer.Sections() + if cs >= 2 && bts >= 2 { + break + } + time.Sleep(10 * time.Millisecond) + } + } + // Generate 256+1 blocks (totally 2 CHT sections) + server, client, tearDown := newClientServerEnv(t, int(2*config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) + defer tearDown() + + // Configure the local checkpoint(the first section) + head := server.handler.blockchain.GetHeaderByNumber(config.ChtSize - 1).Hash() + cp := ¶ms.TrustedCheckpoint{ + SectionIndex: 0, + SectionHead: head, + CHTRoot: light.GetChtRoot(server.db, 0, head), + BloomRoot: light.GetBloomTrieRoot(server.db, 0, head), + } + client.handler.backend.config.SyncFromCheckpoint = true + client.handler.backend.config.Checkpoint = cp + client.handler.checkpoint = cp + client.handler.backend.blockchain.AddTrustedCheckpoint(cp) + + var ( + start = make(chan error, 1) + end = make(chan error, 1) + expectStart = config.ChtSize - 1 + expectEnd = 2*config.ChtSize + config.ChtConfirms + ) + client.handler.syncStart = func(header *types.Header) { + if header.Number.Uint64() == expectStart { + start <- nil + } else { + start <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectStart, header.Number) + } + } + client.handler.syncEnd = func(header *types.Header) { + if header.Number.Uint64() == expectEnd { + end <- nil + } else { + end <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectEnd, header.Number) + } + } + // Create connected peer pair. + if _, _, err := newTestPeerPair("peer", 2, server.handler, client.handler); err != nil { + t.Fatalf("Failed to connect testing peers %v", err) + } + + select { + case err := <-start: + if err != nil { + t.Error("sync failed", err) + } + return + case <-time.NewTimer(10 * time.Second).C: + t.Error("checkpoint syncing timeout") + } + + select { + case err := <-end: + if err != nil { + t.Error("sync failed", err) + } + return + case <-time.NewTimer(10 * time.Second).C: + t.Error("checkpoint syncing timeout") + } +} + +func TestSyncAll(t *testing.T) { + config := light.TestServerIndexerConfig + + waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + cs, _, _ := cIndexer.Sections() + bts, _, _ := btIndexer.Sections() + if cs >= 2 && bts >= 2 { + break + } + time.Sleep(10 * time.Millisecond) + } + } + // Generate 256+1 blocks (totally 2 CHT sections) + server, client, tearDown := newClientServerEnv(t, int(2*config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) + defer tearDown() + + client.handler.backend.config.SyncFromCheckpoint = true + + var ( + start = make(chan error, 1) + end = make(chan error, 1) + expectStart = uint64(0) + expectEnd = 2*config.ChtSize + config.ChtConfirms + ) + client.handler.syncStart = func(header *types.Header) { + if header.Number.Uint64() == expectStart { + start <- nil + } else { + start <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectStart, header.Number) + } + } + client.handler.syncEnd = func(header *types.Header) { + if header.Number.Uint64() == expectEnd { + end <- nil + } else { + end <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectEnd, header.Number) + } + } + // Create connected peer pair. + if _, _, err := newTestPeerPair("peer", 2, server.handler, client.handler); err != nil { + t.Fatalf("Failed to connect testing peers %v", err) + } + + select { + case err := <-start: + if err != nil { + t.Error("sync failed", err) + } + return + case <-time.NewTimer(10 * time.Second).C: + t.Error("checkpoint syncing timeout") + } + + select { + case err := <-end: + if err != nil { + t.Error("sync failed", err) + } + return + case <-time.NewTimer(10 * time.Second).C: + t.Error("checkpoint syncing timeout") + } +} diff --git a/les/test_helper.go b/les/test_helper.go index d108a8dace..04482ba68e 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -541,7 +541,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer ) if connect { done := make(chan struct{}) - client.syncDone = func() { close(done) } + client.syncEnd = func(_ *types.Header) { close(done) } cpeer, speer, err = newTestPeerPair("peer", protocol, server, client) if err != nil { t.Fatalf("Failed to connect testing peers %v", err) From d1301eb0df07219d576c56400128c56f4f65beab Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 20 Jan 2021 18:21:13 +0100 Subject: [PATCH 135/235] oss-fuzz: fix abi fuzzer (#22199) --- oss-fuzz.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oss-fuzz.sh b/oss-fuzz.sh index 7ae4d77b29..860ce0952f 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -100,7 +100,7 @@ compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty -compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi +compile_fuzzer tests/fuzzers/abi Fuzz fuzzAbi compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul From 7da8f75d5bab50ad5477d50bc96d383e5c8359b8 Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 21 Jan 2021 02:34:21 +0800 Subject: [PATCH 136/235] go.mod: upgrade golang-lru (#22134) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 57c15a34f6..d630dc6996 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 - github.com/hashicorp/golang-lru v0.5.4 + github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.1.1 github.com/huin/goupnp v1.0.0 diff --git a/go.sum b/go.sum index d063cd8f08..f8939df2ba 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDF github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= From 81bf9f97c9edf0169e47b5b700715e2eae58e08a Mon Sep 17 00:00:00 2001 From: meowsbits Date: Wed, 20 Jan 2021 15:45:01 -0600 Subject: [PATCH 137/235] downloader: extract findAncestor search functions (#21744) This is a simple refactoring, extracting common ancestor negotiation logic to named function --- eth/downloader/downloader.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 315354ea99..31c1cb47c3 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -90,6 +90,7 @@ var ( errCanceled = errors.New("syncing canceled (requested)") errNoSyncActive = errors.New("no sync active") errTooOld = errors.New("peer's protocol version too old") + errNoAncestorFound = errors.New("no common ancestor found") ) type Downloader struct { @@ -814,6 +815,26 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) } } + ancestor, err := d.findAncestorSpanSearch(p, mode, remoteHeight, localHeight, floor) + if err == nil { + return ancestor, nil + } + // The returned error was not nil. + // If the error returned does not reflect that a common ancestor was not found, return it. + // If the error reflects that a common ancestor was not found, continue to binary search, + // where the error value will be reassigned. + if !errors.Is(err, errNoAncestorFound) { + return 0, err + } + + ancestor, err = d.findAncestorBinarySearch(p, mode, remoteHeight, floor) + if err != nil { + return 0, err + } + return ancestor, nil +} + +func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, remoteHeight, localHeight uint64, floor int64) (commonAncestor uint64, err error) { from, count, skip, max := calculateRequestSpan(remoteHeight, localHeight) p.log.Trace("Span searching for common ancestor", "count", count, "from", from, "skip", skip) @@ -894,6 +915,12 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) p.log.Debug("Found common ancestor", "number", number, "hash", hash) return number, nil } + return 0, errNoAncestorFound +} + +func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, remoteHeight uint64, floor int64) (commonAncestor uint64, err error) { + hash := common.Hash{} + // Ancestor not found, we need to binary search over our chain start, end := uint64(0), remoteHeight if floor > 0 { From 1e1865b73f6b0d2fef656d2f37e20e41d13a5ef0 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 5 Feb 2020 13:12:09 +0100 Subject: [PATCH 138/235] core: implement background trie prefetcher Squashed from the following commits: core/state: lazily init snapshot storage map core/state: fix flawed meter on storage reads core/state: make statedb/stateobjects reuse a hasher core/blockchain, core/state: implement new trie prefetcher core: make trie prefetcher deliver tries to statedb core/state: refactor trie_prefetcher, export storage tries blockchain: re-enable the next-block-prefetcher state: remove panics in trie prefetcher core/state/trie_prefetcher: address some review concerns sq --- core/blockchain.go | 27 +++- core/state/database.go | 12 +- core/state/state_object.go | 72 +++++++--- core/state/statedb.go | 45 +++++- core/state/trie_prefetcher.go | 249 ++++++++++++++++++++++++++++++++++ crypto/crypto.go | 17 ++- crypto/crypto_test.go | 7 + 7 files changed, 395 insertions(+), 34 deletions(-) create mode 100644 core/state/trie_prefetcher.go diff --git a/core/blockchain.go b/core/blockchain.go index b8f483b85e..ccb99bded5 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -201,11 +201,12 @@ type BlockChain struct { running int32 // 0 if chain is running, 1 when stopped procInterrupt int32 // interrupt signaler for block processing - engine consensus.Engine - validator Validator // Block and state validator interface - prefetcher Prefetcher // Block state prefetcher interface - processor Processor // Block transaction processor interface - vmConfig vm.Config + engine consensus.Engine + validator Validator // Block and state validator interface + triePrefetcher *state.TriePrefetcher // Trie prefetcher interface + prefetcher Prefetcher + processor Processor // Block transaction processor interface + vmConfig vm.Config shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. @@ -249,6 +250,15 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) + tp := state.NewTriePrefetcher(bc.stateCache) + + bc.wg.Add(1) + go func() { + tp.Loop() + bc.wg.Done() + }() + bc.triePrefetcher = tp + bc.processor = NewStateProcessor(chainConfig, bc, engine) var err error @@ -991,6 +1001,9 @@ func (bc *BlockChain) Stop() { bc.scope.Close() close(bc.quit) bc.StopInsert() + if bc.triePrefetcher != nil { + bc.triePrefetcher.Close() + } bc.wg.Wait() // Ensure that the entirety of the state snapshot is journalled to disk. @@ -1857,6 +1870,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) } statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps) + statedb.UsePrefetcher(bc.triePrefetcher) if err != nil { return it.index, err } @@ -1891,8 +1905,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete, we can mark them snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete, we can mark them - - triehash := statedb.AccountHashes + statedb.StorageHashes // Save to not double count in validation + triehash := statedb.AccountHashes + statedb.StorageHashes // Save to not double count in validation trieproc := statedb.SnapshotAccountReads + statedb.AccountReads + statedb.AccountUpdates trieproc += statedb.SnapshotStorageReads + statedb.StorageReads + statedb.StorageUpdates diff --git a/core/state/database.go b/core/state/database.go index 83f7b2a839..1a06e33409 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -129,12 +129,20 @@ type cachingDB struct { // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { - return trie.NewSecure(root, db.db) + tr, err := trie.NewSecure(root, db.db) + if err != nil { + return nil, err + } + return tr, nil } // OpenStorageTrie opens the storage trie of an account. func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) { - return trie.NewSecure(root, db.db) + tr, err := trie.NewSecure(root, db.db) + if err != nil { + return nil, err + } + return tr, nil } // CopyTrie returns an independent copy of the given trie. diff --git a/core/state/state_object.go b/core/state/state_object.go index d0d3b4513e..43c5074d92 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -157,11 +157,20 @@ func (s *stateObject) touch() { func (s *stateObject) getTrie(db Database) Trie { if s.trie == nil { - var err error - s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root) - if err != nil { - s.trie, _ = db.OpenStorageTrie(s.addrHash, common.Hash{}) - s.setError(fmt.Errorf("can't create storage trie: %v", err)) + // Try fetching from prefetcher first + // We don't prefetch empty tries + if s.data.Root != emptyRoot && s.db.prefetcher != nil { + // When the miner is creating the pending state, there is no + // prefetcher + s.trie = s.db.prefetcher.GetTrie(s.data.Root) + } + if s.trie == nil { + var err error + s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root) + if err != nil { + s.trie, _ = db.OpenStorageTrie(s.addrHash, common.Hash{}) + s.setError(fmt.Errorf("can't create storage trie: %v", err)) + } } } return s.trie @@ -197,12 +206,24 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has } // If no live objects are available, attempt to use snapshots var ( - enc []byte - err error + enc []byte + err error + meter *time.Duration ) + readStart := time.Now() + if metrics.EnabledExpensive { + // If the snap is 'under construction', the first lookup may fail. If that + // happens, we don't want to double-count the time elapsed. Thus this + // dance with the metering. + defer func() { + if meter != nil { + *meter += time.Since(readStart) + } + }() + } if s.db.snap != nil { if metrics.EnabledExpensive { - defer func(start time.Time) { s.db.SnapshotStorageReads += time.Since(start) }(time.Now()) + meter = &s.db.SnapshotStorageReads } // If the object was destructed in *this* block (and potentially resurrected), // the storage has been cleared out, and we should *not* consult the previous @@ -217,8 +238,14 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has } // If snapshot unavailable or reading from it failed, load from the database if s.db.snap == nil || err != nil { + if meter != nil { + // If we already spent time checking the snapshot, account for it + // and reset the readStart + *meter += time.Since(readStart) + readStart = time.Now() + } if metrics.EnabledExpensive { - defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now()) + meter = &s.db.StorageReads } if enc, err = s.getTrie(db).TryGet(key.Bytes()); err != nil { s.setError(err) @@ -283,8 +310,13 @@ func (s *stateObject) setState(key, value common.Hash) { // finalise moves all dirty storage slots into the pending area to be hashed or // committed later. It is invoked at the end of every transaction. func (s *stateObject) finalise() { + trieChanges := make([]common.Hash, 0, len(s.dirtyStorage)) for key, value := range s.dirtyStorage { s.pendingStorage[key] = value + trieChanges = append(trieChanges, key) + } + if len(trieChanges) > 0 && s.db.prefetcher != nil && s.data.Root != emptyRoot { + s.db.prefetcher.PrefetchStorage(s.data.Root, trieChanges) } if len(s.dirtyStorage) > 0 { s.dirtyStorage = make(Storage) @@ -303,18 +335,11 @@ func (s *stateObject) updateTrie(db Database) Trie { if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now()) } - // Retrieve the snapshot storage map for the object + // The snapshot storage map for the object var storage map[common.Hash][]byte - if s.db.snap != nil { - // Retrieve the old storage map, if available, create a new one otherwise - storage = s.db.snapStorage[s.addrHash] - if storage == nil { - storage = make(map[common.Hash][]byte) - s.db.snapStorage[s.addrHash] = storage - } - } // Insert all the pending updates into the trie tr := s.getTrie(db) + hasher := s.db.hasher for key, value := range s.pendingStorage { // Skip noop changes, persist actual changes if value == s.originStorage[key] { @@ -331,8 +356,15 @@ func (s *stateObject) updateTrie(db Database) Trie { s.setError(tr.TryUpdate(key[:], v)) } // If state snapshotting is active, cache the data til commit - if storage != nil { - storage[crypto.Keccak256Hash(key[:])] = v // v will be nil if value is 0x00 + if s.db.snap != nil { + if storage == nil { + // Retrieve the old storage map, if available, create a new one otherwise + if storage = s.db.snapStorage[s.addrHash]; storage == nil { + storage = make(map[common.Hash][]byte) + s.db.snapStorage[s.addrHash] = storage + } + } + storage[crypto.HashData(hasher, key[:])] = v // v will be nil if value is 0x00 } } if len(s.pendingStorage) > 0 { diff --git a/core/state/statedb.go b/core/state/statedb.go index a9d1de2e06..ce50962e8b 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -62,8 +62,11 @@ func (n *proofList) Delete(key []byte) error { // * Contracts // * Accounts type StateDB struct { - db Database - trie Trie + db Database + prefetcher *TriePrefetcher + originalRoot common.Hash // The pre-state root, before any changes were made + trie Trie + hasher crypto.KeccakState snaps *snapshot.Tree snap snapshot.Snapshot @@ -125,6 +128,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) sdb := &StateDB{ db: db, trie: tr, + originalRoot: root, snaps: snaps, stateObjects: make(map[common.Address]*stateObject), stateObjectsPending: make(map[common.Address]struct{}), @@ -133,6 +137,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) preimages: make(map[common.Hash][]byte), journal: newJournal(), accessList: newAccessList(), + hasher: crypto.NewKeccakState(), } if sdb.snaps != nil { if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil { @@ -144,6 +149,13 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return sdb, nil } +func (s *StateDB) UsePrefetcher(prefetcher *TriePrefetcher) { + if prefetcher != nil { + s.prefetcher = prefetcher + s.prefetcher.Resume(s.originalRoot) + } +} + // setError remembers the first non-nil error it is called with. func (s *StateDB) setError(err error) { if s.dbErr == nil { @@ -532,7 +544,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now()) } var acc *snapshot.Account - if acc, err = s.snap.Account(crypto.Keccak256Hash(addr.Bytes())); err == nil { + if acc, err = s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())); err == nil { if acc == nil { return nil } @@ -675,6 +687,7 @@ func (s *StateDB) Copy() *StateDB { logSize: s.logSize, preimages: make(map[common.Hash][]byte, len(s.preimages)), journal: newJournal(), + hasher: crypto.NewKeccakState(), } // Copy the dirty states, logs, and preimages for addr := range s.journal.dirties { @@ -760,6 +773,7 @@ func (s *StateDB) GetRefund() uint64 { // the journal as well as the refunds. Finalise, however, will not push any updates // into the tries just yet. Only IntermediateRoot or Commit will do that. func (s *StateDB) Finalise(deleteEmptyObjects bool) { + var addressesToPrefetch []common.Address for addr := range s.journal.dirties { obj, exist := s.stateObjects[addr] if !exist { @@ -788,7 +802,17 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { } s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} + // At this point, also ship the address off to the precacher. The precacher + // will start loading tries, and when the change is eventually committed, + // the commit-phase will be a lot faster + if s.prefetcher != nil { + addressesToPrefetch = append(addressesToPrefetch, addr) + } + } + if s.prefetcher != nil { + s.prefetcher.PrefetchAddresses(addressesToPrefetch) } + // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() } @@ -800,6 +824,21 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Finalise all the dirty storage states and write them into the tries s.Finalise(deleteEmptyObjects) + // Now we're about to start to write changes to the trie. The trie is so + // far _untouched_. We can check with the prefetcher, if it can give us + // a trie which has the same root, but also has some content loaded into it. + // If so, use that one instead. + if s.prefetcher != nil { + s.prefetcher.Pause() + // We only want to do this _once_, if someone calls IntermediateRoot again, + // we shouldn't fetch the trie again + if s.originalRoot != (common.Hash{}) { + if trie := s.prefetcher.GetTrie(s.originalRoot); trie != nil { + s.trie = trie + } + s.originalRoot = common.Hash{} + } + } for addr := range s.stateObjectsPending { obj := s.stateObjects[addr] if obj.deleted { diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go new file mode 100644 index 0000000000..8a1aab3253 --- /dev/null +++ b/core/state/trie_prefetcher.go @@ -0,0 +1,249 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" +) + +var ( + // trieDeliveryMeter counts how many times the prefetcher was unable to supply + // the statedb with a prefilled trie. This meter should be zero -- if it's not, that + // needs to be investigated + trieDeliveryMissMeter = metrics.NewRegisteredMeter("trie/prefetch/deliverymiss", nil) + + triePrefetchFetchMeter = metrics.NewRegisteredMeter("trie/prefetch/fetch", nil) + triePrefetchSkipMeter = metrics.NewRegisteredMeter("trie/prefetch/skip", nil) + triePrefetchDropMeter = metrics.NewRegisteredMeter("trie/prefetch/drop", nil) +) + +// TriePrefetcher is an active prefetcher, which receives accounts or storage +// items on two channels, and does trie-loading of the items. +// The goal is to get as much useful content into the caches as possible +type TriePrefetcher struct { + requestCh chan (fetchRequest) // Chan to receive requests for data to fetch + cmdCh chan (*cmd) // Chan to control activity, pause/new root + quitCh chan (struct{}) + deliveryCh chan (struct{}) + db Database + + paused bool + + storageTries map[common.Hash]Trie + accountTrie Trie + accountTrieRoot common.Hash +} + +func NewTriePrefetcher(db Database) *TriePrefetcher { + return &TriePrefetcher{ + requestCh: make(chan fetchRequest, 200), + cmdCh: make(chan *cmd), + quitCh: make(chan struct{}), + deliveryCh: make(chan struct{}), + db: db, + } +} + +type cmd struct { + root common.Hash +} + +type fetchRequest struct { + slots []common.Hash + storageRoot *common.Hash + addresses []common.Address +} + +func (p *TriePrefetcher) Loop() { + var ( + accountTrieRoot common.Hash + accountTrie Trie + storageTries map[common.Hash]Trie + + err error + // Some tracking of performance + skipped int64 + fetched int64 + + paused = true + ) + // The prefetcher loop has two distinct phases: + // 1: Paused: when in this state, the accumulated tries are accessible to outside + // callers. + // 2: Active prefetching, awaiting slots and accounts to prefetch + for { + select { + case <-p.quitCh: + return + case cmd := <-p.cmdCh: + // Clear out any old requests + drain: + for { + select { + case req := <-p.requestCh: + if req.slots != nil { + skipped += int64(len(req.slots)) + } else { + skipped += int64(len(req.addresses)) + } + default: + break drain + } + } + if paused { + // Clear old data + p.storageTries = nil + p.accountTrie = nil + p.accountTrieRoot = common.Hash{} + // Resume again + storageTries = make(map[common.Hash]Trie) + accountTrieRoot = cmd.root + accountTrie, err = p.db.OpenTrie(accountTrieRoot) + if err != nil { + log.Error("Trie prefetcher failed opening trie", "root", accountTrieRoot, "err", err) + } + if accountTrieRoot == (common.Hash{}) { + log.Error("Trie prefetcher unpaused with bad root") + } + paused = false + } else { + // Update metrics at new block events + triePrefetchFetchMeter.Mark(fetched) + triePrefetchSkipMeter.Mark(skipped) + fetched, skipped = 0, 0 + // Make the tries accessible + p.accountTrie = accountTrie + p.storageTries = storageTries + p.accountTrieRoot = accountTrieRoot + if cmd.root != (common.Hash{}) { + log.Error("Trie prefetcher paused with non-empty root") + } + paused = true + } + p.deliveryCh <- struct{}{} + case req := <-p.requestCh: + if paused { + continue + } + if sRoot := req.storageRoot; sRoot != nil { + // Storage slots to fetch + var ( + storageTrie Trie + err error + ) + if storageTrie = storageTries[*sRoot]; storageTrie == nil { + if storageTrie, err = p.db.OpenTrie(*sRoot); err != nil { + log.Warn("trie prefetcher failed opening storage trie", "root", *sRoot, "err", err) + skipped += int64(len(req.slots)) + continue + } + storageTries[*sRoot] = storageTrie + } + for _, key := range req.slots { + storageTrie.TryGet(key[:]) + } + fetched += int64(len(req.slots)) + } else { // an account + for _, addr := range req.addresses { + accountTrie.TryGet(addr[:]) + } + fetched += int64(len(req.addresses)) + } + } + } +} + +// Close stops the prefetcher +func (p *TriePrefetcher) Close() { + if p.quitCh != nil { + close(p.quitCh) + p.quitCh = nil + } +} + +// Resume causes the prefetcher to clear out old data, and get ready to +// fetch data concerning the new root +func (p *TriePrefetcher) Resume(root common.Hash) { + p.paused = false + p.cmdCh <- &cmd{ + root: root, + } + // Wait for it + <-p.deliveryCh +} + +// Pause causes the prefetcher to pause prefetching, and make tries +// accessible to callers via GetTrie +func (p *TriePrefetcher) Pause() { + if p.paused { + return + } + p.paused = true + p.cmdCh <- &cmd{ + root: common.Hash{}, + } + // Wait for it + <-p.deliveryCh +} + +// PrefetchAddresses adds an address for prefetching +func (p *TriePrefetcher) PrefetchAddresses(addresses []common.Address) { + cmd := fetchRequest{ + addresses: addresses, + } + // We do an async send here, to not cause the caller to block + //p.requestCh <- cmd + select { + case p.requestCh <- cmd: + default: + triePrefetchDropMeter.Mark(int64(len(addresses))) + } +} + +// PrefetchStorage adds a storage root and a set of keys for prefetching +func (p *TriePrefetcher) PrefetchStorage(root common.Hash, slots []common.Hash) { + cmd := fetchRequest{ + storageRoot: &root, + slots: slots, + } + // We do an async send here, to not cause the caller to block + //p.requestCh <- cmd + select { + case p.requestCh <- cmd: + default: + triePrefetchDropMeter.Mark(int64(len(slots))) + } +} + +// GetTrie returns the trie matching the root hash, or nil if the prefetcher +// doesn't have it. +func (p *TriePrefetcher) GetTrie(root common.Hash) Trie { + if root == p.accountTrieRoot { + return p.accountTrie + } + if storageTrie, ok := p.storageTries[root]; ok { + // Two accounts may well have the same storage root, but we cannot allow + // them both to make updates to the same trie instance. Therefore, + // we need to either delete the trie now, or deliver a copy of the trie. + delete(p.storageTries, root) + return storageTrie + } + trieDeliveryMissMeter.Mark(1) + return nil +} diff --git a/crypto/crypto.go b/crypto/crypto.go index a4a49136a8..40969a2895 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -60,10 +60,23 @@ type KeccakState interface { Read([]byte) (int, error) } +// NewKeccakState creates a new KeccakState +func NewKeccakState() KeccakState { + return sha3.NewLegacyKeccak256().(KeccakState) +} + +// HashData hashes the provided data using the KeccakState and returns a 32 byte hash +func HashData(kh KeccakState, data []byte) (h common.Hash) { + kh.Reset() + kh.Write(data) + kh.Read(h[:]) + return h +} + // Keccak256 calculates and returns the Keccak256 hash of the input data. func Keccak256(data ...[]byte) []byte { b := make([]byte, 32) - d := sha3.NewLegacyKeccak256().(KeccakState) + d := NewKeccakState() for _, b := range data { d.Write(b) } @@ -74,7 +87,7 @@ func Keccak256(data ...[]byte) []byte { // Keccak256Hash calculates and returns the Keccak256 hash of the input data, // converting it to an internal Hash data structure. func Keccak256Hash(data ...[]byte) (h common.Hash) { - d := sha3.NewLegacyKeccak256().(KeccakState) + d := NewKeccakState() for _, b := range data { d.Write(b) } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index f71ae8232a..f9b0d3e834 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -42,6 +42,13 @@ func TestKeccak256Hash(t *testing.T) { checkhash(t, "Sha3-256-array", func(in []byte) []byte { h := Keccak256Hash(in); return h[:] }, msg, exp) } +func TestKeccak256Hasher(t *testing.T) { + msg := []byte("abc") + exp, _ := hex.DecodeString("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") + hasher := NewKeccakState() + checkhash(t, "Sha3-256-array", func(in []byte) []byte { h := HashData(hasher, in); return h[:] }, msg, exp) +} + func TestToECDSAErrors(t *testing.T) { if _, err := HexToECDSA("0000000000000000000000000000000000000000000000000000000000000000"); err == nil { t.Fatal("HexToECDSA should've returned error") From 42f9f1f0738bde1126eaa6f6bed9c1ae03e304a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 8 Jan 2021 15:01:49 +0200 Subject: [PATCH 139/235] core/state: convert prefetcher to concurrent per-trie loader --- accounts/abi/bind/backends/simulated.go | 3 +- core/blockchain.go | 30 +- core/state/state_object.go | 22 +- core/state/state_test.go | 2 +- core/state/statedb.go | 127 ++++--- core/state/statedb_test.go | 6 +- core/state/trie_prefetcher.go | 453 ++++++++++++++---------- eth/api_tracer.go | 6 +- miner/worker.go | 16 +- 9 files changed, 384 insertions(+), 281 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 6e87e037f0..8be364d08f 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -125,10 +125,9 @@ func (b *SimulatedBackend) Rollback() { func (b *SimulatedBackend) rollback() { blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) - stateDB, _ := b.blockchain.State() b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) + b.pendingState, _ = state.New(b.pendingBlock.Root(), b.blockchain.StateCache(), nil) } // stateByBlockNumber retrieves a state by a given blocknumber. diff --git a/core/blockchain.go b/core/blockchain.go index ccb99bded5..d6668cdcd2 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -201,12 +201,11 @@ type BlockChain struct { running int32 // 0 if chain is running, 1 when stopped procInterrupt int32 // interrupt signaler for block processing - engine consensus.Engine - validator Validator // Block and state validator interface - triePrefetcher *state.TriePrefetcher // Trie prefetcher interface - prefetcher Prefetcher - processor Processor // Block transaction processor interface - vmConfig vm.Config + engine consensus.Engine + validator Validator // Block and state validator interface + prefetcher Prefetcher + processor Processor // Block transaction processor interface + vmConfig vm.Config shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. @@ -250,15 +249,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) - tp := state.NewTriePrefetcher(bc.stateCache) - - bc.wg.Add(1) - go func() { - tp.Loop() - bc.wg.Done() - }() - bc.triePrefetcher = tp - bc.processor = NewStateProcessor(chainConfig, bc, engine) var err error @@ -1001,9 +991,6 @@ func (bc *BlockChain) Stop() { bc.scope.Close() close(bc.quit) bc.StopInsert() - if bc.triePrefetcher != nil { - bc.triePrefetcher.Close() - } bc.wg.Wait() // Ensure that the entirety of the state snapshot is journalled to disk. @@ -1870,16 +1857,20 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) } statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps) - statedb.UsePrefetcher(bc.triePrefetcher) if err != nil { return it.index, err } + // Enable prefetching to pull in trie node paths while processing transactions + statedb.StartPrefetcher("chain") + defer statedb.StopPrefetcher() // stopped on write anyway, defer meant to catch early error returns + // If we have a followup block, run that against the current state to pre-cache // transactions and probabilistically some of the account/storage trie nodes. var followupInterrupt uint32 if !bc.cacheConfig.TrieCleanNoPrefetch { if followup, err := it.peek(); followup != nil && err == nil { throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) + go func(start time.Time, followup *types.Block, throwaway *state.StateDB, interrupt *uint32) { bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt) @@ -1933,7 +1924,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er if err != nil { return it.index, err } - // Update the metrics touched during block commit accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them diff --git a/core/state/state_object.go b/core/state/state_object.go index 43c5074d92..f93f47d5f5 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -162,7 +162,7 @@ func (s *stateObject) getTrie(db Database) Trie { if s.data.Root != emptyRoot && s.db.prefetcher != nil { // When the miner is creating the pending state, there is no // prefetcher - s.trie = s.db.prefetcher.GetTrie(s.data.Root) + s.trie = s.db.prefetcher.trie(s.data.Root) } if s.trie == nil { var err error @@ -309,14 +309,16 @@ func (s *stateObject) setState(key, value common.Hash) { // finalise moves all dirty storage slots into the pending area to be hashed or // committed later. It is invoked at the end of every transaction. -func (s *stateObject) finalise() { - trieChanges := make([]common.Hash, 0, len(s.dirtyStorage)) +func (s *stateObject) finalise(prefetch bool) { + slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage)) for key, value := range s.dirtyStorage { s.pendingStorage[key] = value - trieChanges = append(trieChanges, key) + if value != s.originStorage[key] { + slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure + } } - if len(trieChanges) > 0 && s.db.prefetcher != nil && s.data.Root != emptyRoot { - s.db.prefetcher.PrefetchStorage(s.data.Root, trieChanges) + if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != emptyRoot { + s.db.prefetcher.prefetch(s.data.Root, slotsToPrefetch) } if len(s.dirtyStorage) > 0 { s.dirtyStorage = make(Storage) @@ -327,7 +329,7 @@ func (s *stateObject) finalise() { // It will return nil if the trie has not been loaded and no changes have been made func (s *stateObject) updateTrie(db Database) Trie { // Make sure all dirty slots are finalized into the pending storage area - s.finalise() + s.finalise(false) // Don't prefetch any more, pull directly if need be if len(s.pendingStorage) == 0 { return s.trie } @@ -340,6 +342,8 @@ func (s *stateObject) updateTrie(db Database) Trie { // Insert all the pending updates into the trie tr := s.getTrie(db) hasher := s.db.hasher + + usedStorage := make([][]byte, 0, len(s.pendingStorage)) for key, value := range s.pendingStorage { // Skip noop changes, persist actual changes if value == s.originStorage[key] { @@ -366,6 +370,10 @@ func (s *stateObject) updateTrie(db Database) Trie { } storage[crypto.HashData(hasher, key[:])] = v // v will be nil if value is 0x00 } + usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure + } + if s.db.prefetcher != nil { + s.db.prefetcher.used(s.data.Root, usedStorage) } if len(s.pendingStorage) > 0 { s.pendingStorage = make(Storage) diff --git a/core/state/state_test.go b/core/state/state_test.go index 526d7f8177..22e93d7a95 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -170,7 +170,7 @@ func TestSnapshot2(t *testing.T) { state.setStateObject(so0) root, _ := state.Commit(false) - state.Reset(root) + state, _ = New(root, state.db, state.snaps) // and one with deleted == true so1 := state.getStateObject(stateobjaddr1) diff --git a/core/state/statedb.go b/core/state/statedb.go index ce50962e8b..49f457a99f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -63,7 +63,7 @@ func (n *proofList) Delete(key []byte) error { // * Accounts type StateDB struct { db Database - prefetcher *TriePrefetcher + prefetcher *triePrefetcher originalRoot common.Hash // The pre-state root, before any changes were made trie Trie hasher crypto.KeccakState @@ -149,10 +149,25 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return sdb, nil } -func (s *StateDB) UsePrefetcher(prefetcher *TriePrefetcher) { - if prefetcher != nil { - s.prefetcher = prefetcher - s.prefetcher.Resume(s.originalRoot) +// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the +// state trie concurrently while the state is mutated so that when we reach the +// commit phase, most of the needed data is already hot. +func (s *StateDB) StartPrefetcher(namespace string) { + if s.prefetcher != nil { + s.prefetcher.close() + s.prefetcher = nil + } + if s.snap != nil { + s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace) + } +} + +// StopPrefetcher terminates a running prefetcher and reports any leftover stats +// from the gathered metrics. +func (s *StateDB) StopPrefetcher() { + if s.prefetcher != nil { + s.prefetcher.close() + s.prefetcher = nil } } @@ -167,37 +182,6 @@ func (s *StateDB) Error() error { return s.dbErr } -// Reset clears out all ephemeral state objects from the state db, but keeps -// the underlying state trie to avoid reloading data for the next operations. -func (s *StateDB) Reset(root common.Hash) error { - tr, err := s.db.OpenTrie(root) - if err != nil { - return err - } - s.trie = tr - s.stateObjects = make(map[common.Address]*stateObject) - s.stateObjectsPending = make(map[common.Address]struct{}) - s.stateObjectsDirty = make(map[common.Address]struct{}) - s.thash = common.Hash{} - s.bhash = common.Hash{} - s.txIndex = 0 - s.logs = make(map[common.Hash][]*types.Log) - s.logSize = 0 - s.preimages = make(map[common.Hash][]byte) - s.clearJournalAndRefund() - - if s.snaps != nil { - s.snapAccounts, s.snapDestructs, s.snapStorage = nil, nil, nil - if s.snap = s.snaps.Snapshot(root); s.snap != nil { - s.snapDestructs = make(map[common.Hash]struct{}) - s.snapAccounts = make(map[common.Hash][]byte) - s.snapStorage = make(map[common.Hash]map[common.Hash][]byte) - } - } - s.accessList = newAccessList() - return nil -} - func (s *StateDB) AddLog(log *types.Log) { s.journal.append(addLogChange{txhash: s.thash}) @@ -737,6 +721,13 @@ func (s *StateDB) Copy() *StateDB { // However, it doesn't cost us much to copy an empty list, so we do it anyway // to not blow up if we ever decide copy it in the middle of a transaction state.accessList = s.accessList.Copy() + + // If there's a prefetcher running, make an inactive copy of it that can + // only access data but does not actively preload (since the user will not + // know that they need to explicitly terminate an active copy). + if s.prefetcher != nil { + state.prefetcher = s.prefetcher.copy() + } return state } @@ -773,7 +764,7 @@ func (s *StateDB) GetRefund() uint64 { // the journal as well as the refunds. Finalise, however, will not push any updates // into the tries just yet. Only IntermediateRoot or Commit will do that. func (s *StateDB) Finalise(deleteEmptyObjects bool) { - var addressesToPrefetch []common.Address + addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) for addr := range s.journal.dirties { obj, exist := s.stateObjects[addr] if !exist { @@ -798,21 +789,19 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a ressurrect) } } else { - obj.finalise() + obj.finalise(true) // Prefetch slots in the background } s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} + // At this point, also ship the address off to the precacher. The precacher // will start loading tries, and when the change is eventually committed, // the commit-phase will be a lot faster - if s.prefetcher != nil { - addressesToPrefetch = append(addressesToPrefetch, addr) - } + addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure } - if s.prefetcher != nil { - s.prefetcher.PrefetchAddresses(addressesToPrefetch) + if s.prefetcher != nil && len(addressesToPrefetch) > 0 { + s.prefetcher.prefetch(s.originalRoot, addressesToPrefetch) } - // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() } @@ -824,29 +813,49 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Finalise all the dirty storage states and write them into the tries s.Finalise(deleteEmptyObjects) - // Now we're about to start to write changes to the trie. The trie is so - // far _untouched_. We can check with the prefetcher, if it can give us - // a trie which has the same root, but also has some content loaded into it. - // If so, use that one instead. + // If there was a trie prefetcher operating, it gets aborted and irrevocably + // modified after we start retrieving tries. Remove it from the statedb after + // this round of use. + // + // This is weird pre-byzantium since the first tx runs with a prefetcher and + // the remainder without, but pre-byzantium even the initial prefetcher is + // useless, so no sleep lost. + prefetcher := s.prefetcher if s.prefetcher != nil { - s.prefetcher.Pause() - // We only want to do this _once_, if someone calls IntermediateRoot again, - // we shouldn't fetch the trie again - if s.originalRoot != (common.Hash{}) { - if trie := s.prefetcher.GetTrie(s.originalRoot); trie != nil { - s.trie = trie - } - s.originalRoot = common.Hash{} + defer func() { + s.prefetcher.close() + s.prefetcher = nil + }() + } + // Although naively it makes sense to retrieve the account trie and then do + // the contract storage and account updates sequentially, that short circuits + // the account prefetcher. Instead, let's process all the storage updates + // first, giving the account prefeches just a few more milliseconds of time + // to pull useful data from disk. + for addr := range s.stateObjectsPending { + if obj := s.stateObjects[addr]; !obj.deleted { + obj.updateRoot(s.db) + } + } + // Now we're about to start to write changes to the trie. The trie is so far + // _untouched_. We can check with the prefetcher, if it can give us a trie + // which has the same root, but also has some content loaded into it. + if prefetcher != nil { + if trie := prefetcher.trie(s.originalRoot); trie != nil { + s.trie = trie } } + usedAddrs := make([][]byte, 0, len(s.stateObjectsPending)) for addr := range s.stateObjectsPending { - obj := s.stateObjects[addr] - if obj.deleted { + if obj := s.stateObjects[addr]; obj.deleted { s.deleteStateObject(obj) } else { - obj.updateRoot(s.db) s.updateStateObject(obj) } + usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure + } + if prefetcher != nil { + prefetcher.used(s.originalRoot, usedAddrs) } if len(s.stateObjectsPending) > 0 { s.stateObjectsPending = make(map[common.Address]struct{}) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 70d01ff3dd..220e28525c 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -474,7 +474,7 @@ func TestTouchDelete(t *testing.T) { s := newStateTest() s.state.GetOrNewStateObject(common.Address{}) root, _ := s.state.Commit(false) - s.state.Reset(root) + s.state, _ = New(root, s.state.db, s.state.snaps) snapshot := s.state.Snapshot() s.state.AddBalance(common.Address{}, new(big.Int)) @@ -676,7 +676,7 @@ func TestDeleteCreateRevert(t *testing.T) { state.SetBalance(addr, big.NewInt(1)) root, _ := state.Commit(false) - state.Reset(root) + state, _ = New(root, state.db, state.snaps) // Simulate self-destructing in one transaction, then create-reverting in another state.Suicide(addr) @@ -688,7 +688,7 @@ func TestDeleteCreateRevert(t *testing.T) { // Commit the entire state and make sure we don't crash and have the correct state root, _ = state.Commit(true) - state.Reset(root) + state, _ = New(root, state.db, state.snaps) if state.getStateObject(addr) != nil { t.Fatalf("self-destructed contract came alive") diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 8a1aab3253..ac5e95c5c2 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -17,233 +17,318 @@ package state import ( + "sync" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" ) var ( - // trieDeliveryMeter counts how many times the prefetcher was unable to supply - // the statedb with a prefilled trie. This meter should be zero -- if it's not, that - // needs to be investigated - trieDeliveryMissMeter = metrics.NewRegisteredMeter("trie/prefetch/deliverymiss", nil) - - triePrefetchFetchMeter = metrics.NewRegisteredMeter("trie/prefetch/fetch", nil) - triePrefetchSkipMeter = metrics.NewRegisteredMeter("trie/prefetch/skip", nil) - triePrefetchDropMeter = metrics.NewRegisteredMeter("trie/prefetch/drop", nil) + // triePrefetchMetricsPrefix is the prefix under which to publis the metrics. + triePrefetchMetricsPrefix = "trie/prefetch/" ) -// TriePrefetcher is an active prefetcher, which receives accounts or storage -// items on two channels, and does trie-loading of the items. -// The goal is to get as much useful content into the caches as possible -type TriePrefetcher struct { - requestCh chan (fetchRequest) // Chan to receive requests for data to fetch - cmdCh chan (*cmd) // Chan to control activity, pause/new root - quitCh chan (struct{}) - deliveryCh chan (struct{}) - db Database - - paused bool - - storageTries map[common.Hash]Trie - accountTrie Trie - accountTrieRoot common.Hash +// triePrefetcher is an active prefetcher, which receives accounts or storage +// items and does trie-loading of them. The goal is to get as much useful content +// into the caches as possible. +// +// Note, the prefetcher's API is not thread safe. +type triePrefetcher struct { + db Database // Database to fetch trie nodes through + root common.Hash // Root hash of theaccount trie for metrics + fetches map[common.Hash]Trie // Partially or fully fetcher tries + fetchers map[common.Hash]*subfetcher // Subfetchers for each trie + + deliveryMissMeter metrics.Meter + accountLoadMeter metrics.Meter + accountDupMeter metrics.Meter + accountSkipMeter metrics.Meter + accountWasteMeter metrics.Meter + storageLoadMeter metrics.Meter + storageDupMeter metrics.Meter + storageSkipMeter metrics.Meter + storageWasteMeter metrics.Meter } -func NewTriePrefetcher(db Database) *TriePrefetcher { - return &TriePrefetcher{ - requestCh: make(chan fetchRequest, 200), - cmdCh: make(chan *cmd), - quitCh: make(chan struct{}), - deliveryCh: make(chan struct{}), - db: db, +// newTriePrefetcher +func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher { + prefix := triePrefetchMetricsPrefix + namespace + p := &triePrefetcher{ + db: db, + root: root, + fetchers: make(map[common.Hash]*subfetcher), // Active prefetchers use the fetchers map + + deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil), + accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil), + accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil), + accountSkipMeter: metrics.GetOrRegisterMeter(prefix+"/account/skip", nil), + accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil), + storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil), + storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil), + storageSkipMeter: metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil), + storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil), } + return p } -type cmd struct { - root common.Hash -} +// close iterates over all the subfetchers, aborts any that were left spinning +// and reports the stats to the metrics subsystem. +func (p *triePrefetcher) close() { + for _, fetcher := range p.fetchers { + fetcher.abort() // safe to do multiple times -type fetchRequest struct { - slots []common.Hash - storageRoot *common.Hash - addresses []common.Address -} + if metrics.Enabled { + if fetcher.root == p.root { + p.accountLoadMeter.Mark(int64(len(fetcher.seen))) + p.accountDupMeter.Mark(int64(fetcher.dups)) + p.accountSkipMeter.Mark(int64(len(fetcher.tasks))) -func (p *TriePrefetcher) Loop() { - var ( - accountTrieRoot common.Hash - accountTrie Trie - storageTries map[common.Hash]Trie - - err error - // Some tracking of performance - skipped int64 - fetched int64 - - paused = true - ) - // The prefetcher loop has two distinct phases: - // 1: Paused: when in this state, the accumulated tries are accessible to outside - // callers. - // 2: Active prefetching, awaiting slots and accounts to prefetch - for { - select { - case <-p.quitCh: - return - case cmd := <-p.cmdCh: - // Clear out any old requests - drain: - for { - select { - case req := <-p.requestCh: - if req.slots != nil { - skipped += int64(len(req.slots)) - } else { - skipped += int64(len(req.addresses)) - } - default: - break drain + for _, key := range fetcher.used { + delete(fetcher.seen, string(key)) } - } - if paused { - // Clear old data - p.storageTries = nil - p.accountTrie = nil - p.accountTrieRoot = common.Hash{} - // Resume again - storageTries = make(map[common.Hash]Trie) - accountTrieRoot = cmd.root - accountTrie, err = p.db.OpenTrie(accountTrieRoot) - if err != nil { - log.Error("Trie prefetcher failed opening trie", "root", accountTrieRoot, "err", err) - } - if accountTrieRoot == (common.Hash{}) { - log.Error("Trie prefetcher unpaused with bad root") - } - paused = false + p.accountWasteMeter.Mark(int64(len(fetcher.seen))) } else { - // Update metrics at new block events - triePrefetchFetchMeter.Mark(fetched) - triePrefetchSkipMeter.Mark(skipped) - fetched, skipped = 0, 0 - // Make the tries accessible - p.accountTrie = accountTrie - p.storageTries = storageTries - p.accountTrieRoot = accountTrieRoot - if cmd.root != (common.Hash{}) { - log.Error("Trie prefetcher paused with non-empty root") - } - paused = true - } - p.deliveryCh <- struct{}{} - case req := <-p.requestCh: - if paused { - continue - } - if sRoot := req.storageRoot; sRoot != nil { - // Storage slots to fetch - var ( - storageTrie Trie - err error - ) - if storageTrie = storageTries[*sRoot]; storageTrie == nil { - if storageTrie, err = p.db.OpenTrie(*sRoot); err != nil { - log.Warn("trie prefetcher failed opening storage trie", "root", *sRoot, "err", err) - skipped += int64(len(req.slots)) - continue - } - storageTries[*sRoot] = storageTrie - } - for _, key := range req.slots { - storageTrie.TryGet(key[:]) - } - fetched += int64(len(req.slots)) - } else { // an account - for _, addr := range req.addresses { - accountTrie.TryGet(addr[:]) + p.storageLoadMeter.Mark(int64(len(fetcher.seen))) + p.storageDupMeter.Mark(int64(fetcher.dups)) + p.storageSkipMeter.Mark(int64(len(fetcher.tasks))) + + for _, key := range fetcher.used { + delete(fetcher.seen, string(key)) } - fetched += int64(len(req.addresses)) + p.storageWasteMeter.Mark(int64(len(fetcher.seen))) } } } + // Clear out all fetchers (will crash on a second call, deliberate) + p.fetchers = nil } -// Close stops the prefetcher -func (p *TriePrefetcher) Close() { - if p.quitCh != nil { - close(p.quitCh) - p.quitCh = nil +// copy creates a deep-but-inactive copy of the trie prefetcher. Any trie data +// already loaded will be copied over, but no goroutines will be started. This +// is mostly used in the miner which creates a copy of it's actively mutated +// state to be sealed while it may further mutate the state. +func (p *triePrefetcher) copy() *triePrefetcher { + copy := &triePrefetcher{ + db: p.db, + root: p.root, + fetches: make(map[common.Hash]Trie), // Active prefetchers use the fetches map + + deliveryMissMeter: p.deliveryMissMeter, + accountLoadMeter: p.accountLoadMeter, + accountDupMeter: p.accountDupMeter, + accountSkipMeter: p.accountSkipMeter, + accountWasteMeter: p.accountWasteMeter, + storageLoadMeter: p.storageLoadMeter, + storageDupMeter: p.storageDupMeter, + storageSkipMeter: p.storageSkipMeter, + storageWasteMeter: p.storageWasteMeter, + } + // If the prefetcher is already a copy, duplicate the data + if p.fetches != nil { + for root, fetch := range p.fetches { + copy.fetches[root] = p.db.CopyTrie(fetch) + } + return copy + } + // Otherwise we're copying an active fetcher, retrieve the current states + for root, fetcher := range p.fetchers { + copy.fetches[root] = fetcher.peek() } + return copy } -// Resume causes the prefetcher to clear out old data, and get ready to -// fetch data concerning the new root -func (p *TriePrefetcher) Resume(root common.Hash) { - p.paused = false - p.cmdCh <- &cmd{ - root: root, +// prefetch schedules a batch of trie items to prefetch. +func (p *triePrefetcher) prefetch(root common.Hash, keys [][]byte) { + // If the prefetcher is an inactive one, bail out + if p.fetches != nil { + return + } + // Active fetcher, schedule the retrievals + fetcher := p.fetchers[root] + if fetcher == nil { + fetcher = newSubfetcher(p.db, root) + p.fetchers[root] = fetcher } - // Wait for it - <-p.deliveryCh + fetcher.schedule(keys) } -// Pause causes the prefetcher to pause prefetching, and make tries -// accessible to callers via GetTrie -func (p *TriePrefetcher) Pause() { - if p.paused { - return +// trie returns the trie matching the root hash, or nil if the prefetcher doesn't +// have it. +func (p *triePrefetcher) trie(root common.Hash) Trie { + // If the prefetcher is inactive, return from existing deep copies + if p.fetches != nil { + trie := p.fetches[root] + if trie == nil { + p.deliveryMissMeter.Mark(1) + return nil + } + return p.db.CopyTrie(trie) } - p.paused = true - p.cmdCh <- &cmd{ - root: common.Hash{}, + // Otherwise the prefetcher is active, bail if no trie was prefetched for this root + fetcher := p.fetchers[root] + if fetcher == nil { + p.deliveryMissMeter.Mark(1) + return nil } - // Wait for it - <-p.deliveryCh + // Interrupt the prefetcher if it's by any chance still running and return + // a copy of any pre-loaded trie. + fetcher.abort() // safe to do multiple times + + trie := fetcher.peek() + if trie == nil { + p.deliveryMissMeter.Mark(1) + return nil + } + return trie } -// PrefetchAddresses adds an address for prefetching -func (p *TriePrefetcher) PrefetchAddresses(addresses []common.Address) { - cmd := fetchRequest{ - addresses: addresses, +// used marks a batch of state items used to allow creating statistics as to +// how useful or wasteful the prefetcher is. +func (p *triePrefetcher) used(root common.Hash, used [][]byte) { + if fetcher := p.fetchers[root]; fetcher != nil { + fetcher.used = used } - // We do an async send here, to not cause the caller to block - //p.requestCh <- cmd +} + +// subfetcher is a trie fetcher goroutine responsible for pulling entries for a +// single trie. It is spawned when a new root is encountered and lives until the +// main prefetcher is paused and either all requested items are processed or if +// the trie being worked on is retrieved from the prefetcher. +type subfetcher struct { + db Database // Database to load trie nodes through + root common.Hash // Root hash of the trie to prefetch + trie Trie // Trie being populated with nodes + + tasks [][]byte // Items queued up for retrieval + lock sync.Mutex // Lock protecting the task queue + + wake chan struct{} // Wake channel if a new task is scheduled + stop chan struct{} // Channel to interrupt processing + term chan struct{} // Channel to signal iterruption + copy chan chan Trie // Channel to request a copy of the current trie + + seen map[string]struct{} // Tracks the entries already loaded + dups int // Number of duplicate preload tasks + used [][]byte // Tracks the entries used in the end +} + +// newSubfetcher creates a goroutine to prefetch state items belonging to a +// particular root hash. +func newSubfetcher(db Database, root common.Hash) *subfetcher { + sf := &subfetcher{ + db: db, + root: root, + wake: make(chan struct{}, 1), + stop: make(chan struct{}), + term: make(chan struct{}), + copy: make(chan chan Trie), + seen: make(map[string]struct{}), + } + go sf.loop() + return sf +} + +// schedule adds a batch of trie keys to the queue to prefetch. +func (sf *subfetcher) schedule(keys [][]byte) { + // Append the tasks to the current queue + sf.lock.Lock() + sf.tasks = append(sf.tasks, keys...) + sf.lock.Unlock() + + // Notify the prefetcher, it's fine if it's already terminated select { - case p.requestCh <- cmd: + case sf.wake <- struct{}{}: default: - triePrefetchDropMeter.Mark(int64(len(addresses))) } } -// PrefetchStorage adds a storage root and a set of keys for prefetching -func (p *TriePrefetcher) PrefetchStorage(root common.Hash, slots []common.Hash) { - cmd := fetchRequest{ - storageRoot: &root, - slots: slots, +// peek tries to retrieve a deep copy of the fetcher's trie in whatever form it +// is currently. +func (sf *subfetcher) peek() Trie { + ch := make(chan Trie) + select { + case sf.copy <- ch: + // Subfetcher still alive, return copy from it + return <-ch + + case <-sf.term: + // Subfetcher already terminated, return a copy directly + if sf.trie == nil { + return nil + } + return sf.db.CopyTrie(sf.trie) } - // We do an async send here, to not cause the caller to block - //p.requestCh <- cmd +} + +// abort interrupts the subfetcher immediately. It is safe to call abort multiple +// times but it is not thread safe. +func (sf *subfetcher) abort() { select { - case p.requestCh <- cmd: + case <-sf.stop: default: - triePrefetchDropMeter.Mark(int64(len(slots))) + close(sf.stop) } + <-sf.term } -// GetTrie returns the trie matching the root hash, or nil if the prefetcher -// doesn't have it. -func (p *TriePrefetcher) GetTrie(root common.Hash) Trie { - if root == p.accountTrieRoot { - return p.accountTrie - } - if storageTrie, ok := p.storageTries[root]; ok { - // Two accounts may well have the same storage root, but we cannot allow - // them both to make updates to the same trie instance. Therefore, - // we need to either delete the trie now, or deliver a copy of the trie. - delete(p.storageTries, root) - return storageTrie - } - trieDeliveryMissMeter.Mark(1) - return nil +// loop waits for new tasks to be scheduled and keeps loading them until it runs +// out of tasks or its underlying trie is retrieved for committing. +func (sf *subfetcher) loop() { + // No matter how the loop stops, signal anyone waiting that it's terminated + defer close(sf.term) + + // Start by opening the trie and stop processing if it fails + trie, err := sf.db.OpenTrie(sf.root) + if err != nil { + log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) + return + } + sf.trie = trie + + // Trie opened successfully, keep prefetching items + for { + select { + case <-sf.wake: + // Subfetcher was woken up, retrieve any tasks to avoid spinning the lock + sf.lock.Lock() + tasks := sf.tasks + sf.tasks = nil + sf.lock.Unlock() + + // Prefetch any tasks until the loop is interrupted + for i, task := range tasks { + select { + case <-sf.stop: + // If termination is requested, add any leftover back and return + sf.lock.Lock() + sf.tasks = append(sf.tasks, tasks[i:]...) + sf.lock.Unlock() + return + + case ch := <-sf.copy: + // Somebody wants a copy of the current trie, grant them + ch <- sf.db.CopyTrie(sf.trie) + + default: + // No termination request yet, prefetch the next entry + taskid := string(task) + if _, ok := sf.seen[taskid]; ok { + sf.dups++ + } else { + sf.trie.TryGet(task) + sf.seen[taskid] = struct{}{} + } + } + } + + case ch := <-sf.copy: + // Somebody wants a copy of the current trie, grant them + ch <- sf.db.CopyTrie(sf.trie) + + case <-sf.stop: + // Termination is requested, abort and leave remaining tasks + return + } + } } diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 8e71945ee2..5dffb2a464 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -299,7 +299,8 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl failed = err break } - if err := statedb.Reset(root); err != nil { + statedb, err = state.New(root, database, nil) + if err != nil { failed = err break } @@ -699,7 +700,8 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* if err != nil { return nil, err } - if err := statedb.Reset(root); err != nil { + statedb, err = state.New(root, database, nil) + if err != nil { return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) } database.TrieDB().Reference(root, common.Hash{}) diff --git a/miner/worker.go b/miner/worker.go index 2c5032c656..82d08d4c7e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -303,6 +303,9 @@ func (w *worker) isRunning() bool { // close terminates all background threads maintained by the worker. // Note the worker does not support being closed multiple times. func (w *worker) close() { + if w.current != nil && w.current.state != nil { + w.current.state.StopPrefetcher() + } atomic.StoreInt32(&w.running, 0) close(w.exitCh) } @@ -642,10 +645,14 @@ func (w *worker) resultLoop() { // makeCurrent creates a new environment for the current cycle. func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { + // Retrieve the parent state to execute on top and start a prefetcher for + // the miner to speed block sealing up a bit state, err := w.chain.StateAt(parent.Root()) if err != nil { return err } + state.StartPrefetcher("miner") + env := &environment{ signer: types.NewEIP155Signer(w.chainConfig.ChainID), state: state, @@ -654,7 +661,6 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { uncles: mapset.NewSet(), header: header, } - // when 08 is processed ancestors contain 07 (quick block) for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { for _, uncle := range ancestor.Uncles() { @@ -663,9 +669,14 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { env.family.Add(ancestor.Hash()) env.ancestors.Add(ancestor.Hash()) } - // Keep track of transactions which return errors so they can be removed env.tcount = 0 + + // Swap out the old work with the new one, terminating any leftover prefetcher + // processes in the mean time and starting a new one. + if w.current != nil && w.current.state != nil { + w.current.state.StopPrefetcher() + } w.current = env return nil } @@ -719,7 +730,6 @@ func (w *worker) updateSnapshot() { w.current.receipts, new(trie.Trie), ) - w.snapshotState = w.current.state.Copy() } From c4307a9339fd4e66598fd5c378e8f6f97ad878e2 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Thu, 21 Jan 2021 12:17:10 +0100 Subject: [PATCH 140/235] eth/filters: fix potential deadlock in filter timeout loop (#22178) This fixes #22131 and adds a test reproducing the issue. --- eth/backend.go | 2 +- eth/filters/api.go | 33 +++++++----- eth/filters/filter_system_test.go | 86 ++++++++++++++++++++++++++++--- les/client.go | 2 +- 4 files changed, 101 insertions(+), 22 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index c1732d3ceb..a6390facb3 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -335,7 +335,7 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "eth", Version: "1.0", - Service: filters.NewPublicFilterAPI(s.APIBackend, false), + Service: filters.NewPublicFilterAPI(s.APIBackend, false, 5*time.Minute), Public: true, }, { Namespace: "admin", diff --git a/eth/filters/api.go b/eth/filters/api.go index b6f974c3ba..4b36a5379e 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -34,10 +34,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -var ( - deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline -) - // filter is a helper struct that holds meta information over the filter type // and associated subscription in the event system. type filter struct { @@ -59,25 +55,28 @@ type PublicFilterAPI struct { events *EventSystem filtersMu sync.Mutex filters map[rpc.ID]*filter + timeout time.Duration } // NewPublicFilterAPI returns a new PublicFilterAPI instance. -func NewPublicFilterAPI(backend Backend, lightMode bool) *PublicFilterAPI { +func NewPublicFilterAPI(backend Backend, lightMode bool, timeout time.Duration) *PublicFilterAPI { api := &PublicFilterAPI{ backend: backend, chainDb: backend.ChainDb(), events: NewEventSystem(backend, lightMode), filters: make(map[rpc.ID]*filter), + timeout: timeout, } - go api.timeoutLoop() + go api.timeoutLoop(timeout) return api } // timeoutLoop runs every 5 minutes and deletes filters that have not been recently used. // Tt is started when the api is created. -func (api *PublicFilterAPI) timeoutLoop() { - ticker := time.NewTicker(5 * time.Minute) +func (api *PublicFilterAPI) timeoutLoop(timeout time.Duration) { + var toUninstall []*Subscription + ticker := time.NewTicker(timeout) defer ticker.Stop() for { <-ticker.C @@ -85,13 +84,21 @@ func (api *PublicFilterAPI) timeoutLoop() { for id, f := range api.filters { select { case <-f.deadline.C: - f.s.Unsubscribe() + toUninstall = append(toUninstall, f.s) delete(api.filters, id) default: continue } } api.filtersMu.Unlock() + + // Unsubscribes are processed outside the lock to avoid the following scenario: + // event loop attempts broadcasting events to still active filters while + // Unsubscribe is waiting for it to process the uninstall request. + for _, s := range toUninstall { + s.Unsubscribe() + } + toUninstall = nil } } @@ -109,7 +116,7 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { ) api.filtersMu.Lock() - api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub} + api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: pendingTxSub} api.filtersMu.Unlock() go func() { @@ -179,7 +186,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { ) api.filtersMu.Lock() - api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub} + api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: headerSub} api.filtersMu.Unlock() go func() { @@ -296,7 +303,7 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { } api.filtersMu.Lock() - api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub} + api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(api.timeout), logs: make([]*types.Log, 0), s: logsSub} api.filtersMu.Unlock() go func() { @@ -421,7 +428,7 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { // receive timer value and reset timer <-f.deadline.C } - f.deadline.Reset(deadline) + f.deadline.Reset(api.timeout) switch f.typ { case PendingTransactionsSubscription, BlocksSubscription: diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index b534c1d47f..52150366c1 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -22,6 +22,7 @@ import ( "math/big" "math/rand" "reflect" + "runtime" "testing" "time" @@ -38,6 +39,10 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +var ( + deadline = 5 * time.Minute +) + type testBackend struct { mux *event.TypeMux db ethdb.Database @@ -163,7 +168,7 @@ func TestBlockSubscription(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) genesis = new(core.Genesis).MustCommit(db) chain, _ = core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 10, func(i int, gen *core.BlockGen) {}) chainEvents = []core.ChainEvent{} @@ -215,7 +220,7 @@ func TestPendingTxFilter(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) transactions = []*types.Transaction{ types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), @@ -270,7 +275,7 @@ func TestLogFilterCreation(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) testCases = []struct { crit FilterCriteria @@ -314,7 +319,7 @@ func TestInvalidLogFilterCreation(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) ) // different situations where log filter creation should fail. @@ -336,7 +341,7 @@ func TestInvalidGetLogsRequest(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) blockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111") ) @@ -361,7 +366,7 @@ func TestLogFilter(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222") @@ -475,7 +480,7 @@ func TestPendingLogsSubscription(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222") @@ -601,6 +606,73 @@ func TestPendingLogsSubscription(t *testing.T) { } } +// TestPendingTxFilterDeadlock tests if the event loop hangs when pending +// txes arrive at the same time that one of multiple filters is timing out. +// Please refer to #22131 for more details. +func TestPendingTxFilterDeadlock(t *testing.T) { + t.Parallel() + timeout := 100 * time.Millisecond + + var ( + db = rawdb.NewMemoryDatabase() + backend = &testBackend{db: db} + api = NewPublicFilterAPI(backend, false, timeout) + done = make(chan struct{}) + ) + + go func() { + // Bombard feed with txes until signal was received to stop + i := uint64(0) + for { + select { + case <-done: + return + default: + } + + tx := types.NewTransaction(i, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil) + backend.txFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx}}) + i++ + } + }() + + // Create a bunch of filters that will + // timeout either in 100ms or 200ms + fids := make([]rpc.ID, 20) + for i := 0; i < len(fids); i++ { + fid := api.NewPendingTransactionFilter() + fids[i] = fid + // Wait for at least one tx to arrive in filter + for { + hashes, err := api.GetFilterChanges(fid) + if err != nil { + t.Fatalf("Filter should exist: %v\n", err) + } + if len(hashes.([]common.Hash)) > 0 { + break + } + runtime.Gosched() + } + } + + // Wait until filters have timed out + time.Sleep(3 * timeout) + + // If tx loop doesn't consume `done` after a second + // it's hanging. + select { + case done <- struct{}{}: + // Check that all filters have been uninstalled + for _, fid := range fids { + if _, err := api.GetFilterChanges(fid); err == nil { + t.Errorf("Filter %s should have been uninstalled\n", fid) + } + } + case <-time.After(1 * time.Second): + t.Error("Tx sending loop hangs") + } +} + func flattenLogs(pl [][]*types.Log) []*types.Log { var logs []*types.Log for _, l := range pl { diff --git a/les/client.go b/les/client.go index 47997a098b..5ee50a7c3a 100644 --- a/les/client.go +++ b/les/client.go @@ -252,7 +252,7 @@ func (s *LightEthereum) APIs() []rpc.API { }, { Namespace: "eth", Version: "1.0", - Service: filters.NewPublicFilterAPI(s.ApiBackend, true), + Service: filters.NewPublicFilterAPI(s.ApiBackend, true, 5*time.Minute), Public: true, }, { Namespace: "net", From 231040c633f24b2b0d56aaeb704a0738ba4adb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Zimnoch?= Date: Thu, 21 Jan 2021 13:47:38 +0100 Subject: [PATCH 141/235] event: add ResubscribeErr (#22191) This adds a way to get the error of the failing subscription for logging/debugging purposes. Co-authored-by: Felix Lange --- event/subscription.go | 32 ++++++++++++++++++++++++++++---- event/subscription_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/event/subscription.go b/event/subscription.go index c80d171f3a..6c62874719 100644 --- a/event/subscription.go +++ b/event/subscription.go @@ -95,6 +95,26 @@ func (s *funcSub) Err() <-chan error { // Resubscribe applies backoff between calls to fn. The time between calls is adapted // based on the error rate, but will never exceed backoffMax. func Resubscribe(backoffMax time.Duration, fn ResubscribeFunc) Subscription { + return ResubscribeErr(backoffMax, func(ctx context.Context, _ error) (Subscription, error) { + return fn(ctx) + }) +} + +// A ResubscribeFunc attempts to establish a subscription. +type ResubscribeFunc func(context.Context) (Subscription, error) + +// ResubscribeErr calls fn repeatedly to keep a subscription established. When the +// subscription is established, ResubscribeErr waits for it to fail and calls fn again. This +// process repeats until Unsubscribe is called or the active subscription ends +// successfully. +// +// The difference between Resubscribe and ResubscribeErr is that with ResubscribeErr, +// the error of the failing subscription is available to the callback for logging +// purposes. +// +// ResubscribeErr applies backoff between calls to fn. The time between calls is adapted +// based on the error rate, but will never exceed backoffMax. +func ResubscribeErr(backoffMax time.Duration, fn ResubscribeErrFunc) Subscription { s := &resubscribeSub{ waitTime: backoffMax / 10, backoffMax: backoffMax, @@ -106,15 +126,18 @@ func Resubscribe(backoffMax time.Duration, fn ResubscribeFunc) Subscription { return s } -// A ResubscribeFunc attempts to establish a subscription. -type ResubscribeFunc func(context.Context) (Subscription, error) +// A ResubscribeErrFunc attempts to establish a subscription. +// For every call but the first, the second argument to this function is +// the error that occurred with the previous subscription. +type ResubscribeErrFunc func(context.Context, error) (Subscription, error) type resubscribeSub struct { - fn ResubscribeFunc + fn ResubscribeErrFunc err chan error unsub chan struct{} unsubOnce sync.Once lastTry mclock.AbsTime + lastSubErr error waitTime, backoffMax time.Duration } @@ -149,7 +172,7 @@ func (s *resubscribeSub) subscribe() Subscription { s.lastTry = mclock.Now() ctx, cancel := context.WithCancel(context.Background()) go func() { - rsub, err := s.fn(ctx) + rsub, err := s.fn(ctx, s.lastSubErr) sub = rsub subscribed <- err }() @@ -178,6 +201,7 @@ func (s *resubscribeSub) waitForError(sub Subscription) bool { defer sub.Unsubscribe() select { case err := <-sub.Err(): + s.lastSubErr = err return err == nil case <-s.unsub: return true diff --git a/event/subscription_test.go b/event/subscription_test.go index c48be3aa30..ba081705c4 100644 --- a/event/subscription_test.go +++ b/event/subscription_test.go @@ -19,6 +19,8 @@ package event import ( "context" "errors" + "fmt" + "reflect" "testing" "time" ) @@ -118,3 +120,37 @@ func TestResubscribeAbort(t *testing.T) { t.Fatal(err) } } + +func TestResubscribeWithErrorHandler(t *testing.T) { + t.Parallel() + + var i int + nfails := 6 + subErrs := make([]string, 0) + sub := ResubscribeErr(100*time.Millisecond, func(ctx context.Context, lastErr error) (Subscription, error) { + i++ + var lastErrVal string + if lastErr != nil { + lastErrVal = lastErr.Error() + } + subErrs = append(subErrs, lastErrVal) + sub := NewSubscription(func(unsubscribed <-chan struct{}) error { + if i < nfails { + return fmt.Errorf("err-%v", i) + } else { + return nil + } + }) + return sub, nil + }) + + <-sub.Err() + if i != nfails { + t.Fatalf("resubscribe function called %d times, want %d times", i, nfails) + } + + expectedSubErrs := []string{"", "err-1", "err-2", "err-3", "err-4", "err-5"} + if !reflect.DeepEqual(subErrs, expectedSubErrs) { + t.Fatalf("unexpected subscription errors %v, want %v", subErrs, expectedSubErrs) + } +} From 9e1bd0f3671d19d4964ed8c8a95edfd12413d8c3 Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 22 Jan 2021 17:11:24 +0800 Subject: [PATCH 142/235] trie: fix range prover (#22210) Fixes a special case when the trie only has a single trie node and the range proof only contains a single element. --- trie/proof.go | 44 +++++++++++++++++++++++++++++--------------- trie/proof_test.go | 19 +++++++++++++++++++ 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/trie/proof.go b/trie/proof.go index e7102f12b7..61c35a8423 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -216,7 +216,7 @@ func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyV // // Note we have the assumption here the given boundary keys are different // and right is larger than left. -func unsetInternal(n node, left []byte, right []byte) error { +func unsetInternal(n node, left []byte, right []byte) (bool, error) { left, right = keybytesToHex(left), keybytesToHex(right) // Step down to the fork point. There are two scenarios can happen: @@ -278,45 +278,55 @@ findFork: // - left proof points to the shortnode, but right proof is greater // - right proof points to the shortnode, but left proof is less if shortForkLeft == -1 && shortForkRight == -1 { - return errors.New("empty range") + return false, errors.New("empty range") } if shortForkLeft == 1 && shortForkRight == 1 { - return errors.New("empty range") + return false, errors.New("empty range") } if shortForkLeft != 0 && shortForkRight != 0 { + // The fork point is root node, unset the entire trie + if parent == nil { + return true, nil + } parent.(*fullNode).Children[left[pos-1]] = nil - return nil + return false, nil } // Only one proof points to non-existent key. if shortForkRight != 0 { - // Unset left proof's path if _, ok := rn.Val.(valueNode); ok { + // The fork point is root node, unset the entire trie + if parent == nil { + return true, nil + } parent.(*fullNode).Children[left[pos-1]] = nil - return nil + return false, nil } - return unset(rn, rn.Val, left[pos:], len(rn.Key), false) + return false, unset(rn, rn.Val, left[pos:], len(rn.Key), false) } if shortForkLeft != 0 { - // Unset right proof's path. if _, ok := rn.Val.(valueNode); ok { + // The fork point is root node, unset the entire trie + if parent == nil { + return true, nil + } parent.(*fullNode).Children[right[pos-1]] = nil - return nil + return false, nil } - return unset(rn, rn.Val, right[pos:], len(rn.Key), true) + return false, unset(rn, rn.Val, right[pos:], len(rn.Key), true) } - return nil + return false, nil case *fullNode: // unset all internal nodes in the forkpoint for i := left[pos] + 1; i < right[pos]; i++ { rn.Children[i] = nil } if err := unset(rn, rn.Children[left[pos]], left[pos:], 1, false); err != nil { - return err + return false, err } if err := unset(rn, rn.Children[right[pos]], right[pos:], 1, true); err != nil { - return err + return false, err } - return nil + return false, nil default: panic(fmt.Sprintf("%T: invalid node: %v", n, n)) } @@ -560,7 +570,8 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key } // Remove all internal references. All the removed parts should // be re-filled(or re-constructed) by the given leaves range. - if err := unsetInternal(root, firstKey, lastKey); err != nil { + empty, err := unsetInternal(root, firstKey, lastKey) + if err != nil { return nil, nil, nil, false, err } // Rebuild the trie with the leaf stream, the shape of trie @@ -570,6 +581,9 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key triedb = NewDatabase(diskdb) ) tr := &Trie{root: root, db: triedb} + if empty { + tr.root = nil + } for index, key := range keys { tr.TryUpdate(key, values[index]) } diff --git a/trie/proof_test.go b/trie/proof_test.go index 3ecd318886..304affa9f2 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -384,6 +384,25 @@ func TestOneElementRangeProof(t *testing.T) { if err != nil { t.Fatalf("Expected no error, got %v", err) } + + // Test the mini trie with only a single element. + tinyTrie := new(Trie) + entry := &kv{randBytes(32), randBytes(20), false} + tinyTrie.Update(entry.k, entry.v) + + first = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() + last = entry.k + proof = memorydb.New() + if err := tinyTrie.Prove(first, 0, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := tinyTrie.Prove(last, 0, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + _, _, _, _, err = VerifyRangeProof(tinyTrie.Hash(), first, last, [][]byte{entry.k}, [][]byte{entry.v}, proof) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } } // TestAllElementsProof tests the range proof with all elements. From f26c19cbcd60175598824c7e09b64b8896daf721 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 22 Jan 2021 20:15:27 +0100 Subject: [PATCH 143/235] common/mclock: remove dependency on github.com/aristanetworks/goarista (#22211) It takes three lines of code to get to runtime.nanotime, no need to pull a dependency for that. --- common/mclock/mclock.go | 12 ++++++++---- common/mclock/mclock.s | 1 + go.mod | 1 - go.sum | 2 -- 4 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 common/mclock/mclock.s diff --git a/common/mclock/mclock.go b/common/mclock/mclock.go index 3aca257cb3..c05738cf2b 100644 --- a/common/mclock/mclock.go +++ b/common/mclock/mclock.go @@ -20,15 +20,19 @@ package mclock import ( "time" - "github.com/aristanetworks/goarista/monotime" + _ "unsafe" // for go:linkname ) +//go:noescape +//go:linkname nanotime runtime.nanotime +func nanotime() int64 + // AbsTime represents absolute monotonic time. -type AbsTime time.Duration +type AbsTime int64 // Now returns the current absolute monotonic time. func Now() AbsTime { - return AbsTime(monotime.Now()) + return AbsTime(nanotime()) } // Add returns t + d as absolute time. @@ -74,7 +78,7 @@ type System struct{} // Now returns the current monotonic time. func (c System) Now() AbsTime { - return AbsTime(monotime.Now()) + return Now() } // Sleep blocks for the given duration. diff --git a/common/mclock/mclock.s b/common/mclock/mclock.s new file mode 100644 index 0000000000..99a7a878f0 --- /dev/null +++ b/common/mclock/mclock.s @@ -0,0 +1 @@ +// This file exists in order to be able to use go:linkname. diff --git a/go.mod b/go.mod index d630dc6996..9162754ea3 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.5.7 - github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 github.com/aws/aws-sdk-go v1.25.48 github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 github.com/cespare/cp v0.1.0 diff --git a/go.sum b/go.sum index f8939df2ba..327af03735 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4oG/8eVokGVPYJEW1F88p1ZNgXiEIs9thEE4A= -github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= From db35d77b63ab462990ad95481e116f9340d52de2 Mon Sep 17 00:00:00 2001 From: ligi Date: Sun, 24 Jan 2021 11:37:08 +0100 Subject: [PATCH 144/235] cmd, geth: CLI help fixes (#22220) * cmd, geth: Reflect command being optional - closes 22218 * cmd, geth: Set current year to 2021 --- internal/flags/helpers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go index f61ed4b689..eb5f5547b1 100644 --- a/internal/flags/helpers.go +++ b/internal/flags/helpers.go @@ -51,10 +51,10 @@ OPTIONS: AppHelpTemplate = `NAME: {{.App.Name}} - {{.App.Usage}} - Copyright 2013-2019 The go-ethereum Authors + Copyright 2013-2021 The go-ethereum Authors USAGE: - {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} + {{.App.HelpName}} [options]{{if .App.Commands}} [command] [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} {{if .App.Version}} VERSION: {{.App.Version}} @@ -77,7 +77,7 @@ COPYRIGHT: ClefAppHelpTemplate = `NAME: {{.App.Name}} - {{.App.Usage}} - Copyright 2013-2019 The go-ethereum Authors + Copyright 2013-2021 The go-ethereum Authors USAGE: {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} From 3708454f58923582585c5a231243324729011a39 Mon Sep 17 00:00:00 2001 From: ligi Date: Sun, 24 Jan 2021 11:37:39 +0100 Subject: [PATCH 145/235] cmd, geth: CLI help fixes (#22220) * cmd, geth: Reflect command being optional - closes 22218 * cmd, geth: Set current year to 2021 From 797b0812ab742b2a24e3c9277fa6564ff9eb9094 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 25 Jan 2021 07:17:05 +0100 Subject: [PATCH 146/235] eth/protocols/snap: snap sync testing (#22179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * eth/protocols/snap: make timeout configurable * eth/protocols/snap: snap sync testing * eth/protocols/snap: test to trigger panic * eth/protocols/snap: fix race condition on timeouts * eth/protocols/snap: return error on cancelled sync * squashme: updates + test causing panic + properly serve accounts in order * eth/protocols/snap: revert failing storage response * eth/protocols/snap: revert on bad responses (storage, code) * eth/protocols/snap: fix account handling stall * eth/protocols/snap: fix remaining revertal-issues * eth/protocols/snap: timeouthandler for bytecode requests * eth/protocols/snap: debugging + fix log message * eth/protocols/snap: fix misspelliings in docs * eth/protocols/snap: fix race in bytecode handling * eth/protocols/snap: undo deduplication of storage roots * synctests: refactor + minify panic testcase * eth/protocols/snap: minor polishes * eth: minor polishes to make logs more useful * eth/protocols/snap: remove excessive logs from the test runs * eth/protocols/snap: stress tests with concurrency * eth/protocols/snap: further fixes to test cancel channel handling * eth/protocols/snap: extend test timeouts on CI Co-authored-by: Péter Szilágyi --- eth/downloader/downloader.go | 4 +- eth/handler.go | 16 +- eth/protocols/snap/peer.go | 5 + eth/protocols/snap/protocol.go | 1 + eth/protocols/snap/sync.go | 349 ++++++----- eth/protocols/snap/sync_test.go | 1020 +++++++++++++++++++++++++++++++ 6 files changed, 1248 insertions(+), 147 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 31c1cb47c3..421803876e 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -298,7 +298,7 @@ func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { // Tests use short IDs, don't choke on them logger = log.New("peer", id) } else { - logger = log.New("peer", id[:16]) + logger = log.New("peer", id[:8]) } logger.Trace("Registering sync peer") if err := d.peers.Register(newPeerConnection(id, version, peer, logger)); err != nil { @@ -325,7 +325,7 @@ func (d *Downloader) UnregisterPeer(id string) error { // Tests use short IDs, don't choke on them logger = log.New("peer", id) } else { - logger = log.New("peer", id[:16]) + logger = log.New("peer", id[:8]) } logger.Trace("Unregistering sync peer") if err := d.peers.Unregister(id); err != nil { diff --git a/eth/handler.go b/eth/handler.go index a9506c4995..f6366d9af1 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -326,24 +326,32 @@ func (h *handler) runSnapPeer(peer *snap.Peer, handler snap.Handler) error { } func (h *handler) removePeer(id string) { + // Create a custom logger to avoid printing the entire id + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:8]) + } // Remove the eth peer if it exists eth := h.peers.ethPeer(id) if eth != nil { - log.Debug("Removing Ethereum peer", "peer", id) + logger.Debug("Removing Ethereum peer") h.downloader.UnregisterPeer(id) h.txFetcher.Drop(id) if err := h.peers.unregisterEthPeer(id); err != nil { - log.Error("Peer removal failed", "peer", id, "err", err) + logger.Error("Ethereum peer removal failed", "err", err) } } // Remove the snap peer if it exists snap := h.peers.snapPeer(id) if snap != nil { - log.Debug("Removing Snapshot peer", "peer", id) + logger.Debug("Removing Snapshot peer") h.downloader.SnapSyncer.Unregister(id) if err := h.peers.unregisterSnapPeer(id); err != nil { - log.Error("Peer removal failed", "peer", id, "err", err) + logger.Error("Snapshot peer removel failed", "err", err) } } // Hard disconnect at the networking layer diff --git a/eth/protocols/snap/peer.go b/eth/protocols/snap/peer.go index 73eaaadd09..4f3d550f1f 100644 --- a/eth/protocols/snap/peer.go +++ b/eth/protocols/snap/peer.go @@ -56,6 +56,11 @@ func (p *Peer) Version() uint { return p.version } +// Log overrides the P2P logget with the higher level one containing only the id. +func (p *Peer) Log() log.Logger { + return p.logger +} + // RequestAccountRange fetches a batch of accounts rooted in a specific account // trie, starting with the origin. func (p *Peer) RequestAccountRange(id uint64, root common.Hash, origin, limit common.Hash, bytes uint64) error { diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go index a1e4349691..f1a25a2066 100644 --- a/eth/protocols/snap/protocol.go +++ b/eth/protocols/snap/protocol.go @@ -61,6 +61,7 @@ var ( errDecode = errors.New("invalid message") errInvalidMsgCode = errors.New("invalid message code") errBadRequest = errors.New("bad request") + errCancelled = errors.New("sync cancelled") ) // Packet represents a p2p message in the `snap` protocol. diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index d6f0eb5472..e7720026bf 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -73,10 +73,6 @@ const ( // waste bandwidth. maxTrieRequestCount = 512 - // requestTimeout is the maximum time a peer is allowed to spend on serving - // a single network request. - requestTimeout = 10 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? - // accountConcurrency is the number of chunks to split the account trie into // to allow concurrent retrievals. accountConcurrency = 16 @@ -86,6 +82,12 @@ const ( storageConcurrency = 16 ) +var ( + // requestTimeout is the maximum time a peer is allowed to spend on serving + // a single network request. + requestTimeout = 10 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? +) + // accountRequest tracks a pending account range request to ensure responses are // to actual requests and to validate any security constraints. // @@ -331,6 +333,33 @@ type syncProgress struct { BytecodeHealNops uint64 // Number of bytecodes not requested } +// SyncPeer abstracts out the methods required for a peer to be synced against +// with the goal of allowing the construction of mock peers without the full +// blown networking. +type SyncPeer interface { + // ID retrieves the peer's unique identifier. + ID() string + + // RequestAccountRange fetches a batch of accounts rooted in a specific account + // trie, starting with the origin. + RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error + + // RequestStorageRange fetches a batch of storage slots belonging to one or + // more accounts. If slots from only one accout is requested, an origin marker + // may also be used to retrieve from there. + RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error + + // RequestByteCodes fetches a batch of bytecodes by hash. + RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error + + // RequestTrieNodes fetches a batch of account or storage trie nodes rooted in + // a specificstate trie. + RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error + + // Log retrieves the peer's own contextual logger. + Log() log.Logger +} + // Syncer is an Ethereum account and storage trie syncer based on snapshots and // the snap protocol. It's purpose is to download all the accounts and storage // slots from remote peers and reassemble chunks of the state trie, on top of @@ -346,14 +375,15 @@ type Syncer struct { db ethdb.KeyValueStore // Database to store the trie nodes into (and dedup) bloom *trie.SyncBloom // Bloom filter to deduplicate nodes for state fixup - root common.Hash // Current state trie root being synced - tasks []*accountTask // Current account task set being synced - healer *healTask // Current state healing task being executed - update chan struct{} // Notification channel for possible sync progression + root common.Hash // Current state trie root being synced + tasks []*accountTask // Current account task set being synced + snapped bool // Flag to signal that snap phase is done + healer *healTask // Current state healing task being executed + update chan struct{} // Notification channel for possible sync progression - peers map[string]*Peer // Currently active peers to download from - peerJoin *event.Feed // Event feed to react to peers joining - peerDrop *event.Feed // Event feed to react to peers dropping + peers map[string]SyncPeer // Currently active peers to download from + peerJoin *event.Feed // Event feed to react to peers joining + peerDrop *event.Feed // Event feed to react to peers dropping // Request tracking during syncing phase statelessPeers map[string]struct{} // Peers that failed to deliver state data @@ -410,12 +440,14 @@ type Syncer struct { lock sync.RWMutex // Protects fields that can change outside of sync (peers, reqs, root) } +// NewSyncer creates a new snapshot syncer to download the Ethereum state over the +// snap protocol. func NewSyncer(db ethdb.KeyValueStore, bloom *trie.SyncBloom) *Syncer { return &Syncer{ db: db, bloom: bloom, - peers: make(map[string]*Peer), + peers: make(map[string]SyncPeer), peerJoin: new(event.Feed), peerDrop: new(event.Feed), update: make(chan struct{}, 1), @@ -447,27 +479,29 @@ func NewSyncer(db ethdb.KeyValueStore, bloom *trie.SyncBloom) *Syncer { } // Register injects a new data source into the syncer's peerset. -func (s *Syncer) Register(peer *Peer) error { +func (s *Syncer) Register(peer SyncPeer) error { // Make sure the peer is not registered yet + id := peer.ID() + s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - log.Error("Snap peer already registered", "id", peer.id) + if _, ok := s.peers[id]; ok { + log.Error("Snap peer already registered", "id", id) s.lock.Unlock() return errors.New("already registered") } - s.peers[peer.id] = peer + s.peers[id] = peer // Mark the peer as idle, even if no sync is running - s.accountIdlers[peer.id] = struct{}{} - s.storageIdlers[peer.id] = struct{}{} - s.bytecodeIdlers[peer.id] = struct{}{} - s.trienodeHealIdlers[peer.id] = struct{}{} - s.bytecodeHealIdlers[peer.id] = struct{}{} + s.accountIdlers[id] = struct{}{} + s.storageIdlers[id] = struct{}{} + s.bytecodeIdlers[id] = struct{}{} + s.trienodeHealIdlers[id] = struct{}{} + s.bytecodeHealIdlers[id] = struct{}{} s.lock.Unlock() // Notify any active syncs that a new peer can be assigned data - s.peerJoin.Send(peer.id) + s.peerJoin.Send(id) return nil } @@ -566,6 +600,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { s.assignAccountTasks(cancel) s.assignBytecodeTasks(cancel) s.assignStorageTasks(cancel) + if len(s.tasks) == 0 { // Sync phase done, run heal phase s.assignTrienodeHealTasks(cancel) @@ -580,7 +615,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { case id := <-peerDrop: s.revertRequests(id) case <-cancel: - return nil + return errCancelled case req := <-s.accountReqFails: s.revertAccountRequest(req) @@ -622,6 +657,7 @@ func (s *Syncer) loadSyncStatus() { log.Debug("Scheduled account sync task", "from", task.Next, "last", task.Last) } s.tasks = progress.Tasks + s.snapped = len(s.tasks) == 0 s.accountSynced = progress.AccountSynced s.accountBytes = progress.AccountBytes @@ -701,6 +737,11 @@ func (s *Syncer) cleanAccountTasks() { i-- } } + if len(s.tasks) == 0 { + s.lock.Lock() + s.snapped = true + s.lock.Unlock() + } } // cleanStorageTasks iterates over all the account tasks and storage sub-tasks @@ -798,7 +839,7 @@ func (s *Syncer) assignAccountTasks(cancel chan struct{}) { delete(s.accountIdlers, idle) s.pend.Add(1) - go func(peer *Peer, root common.Hash) { + go func(peer SyncPeer, root common.Hash) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -885,7 +926,7 @@ func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { delete(s.bytecodeIdlers, idle) s.pend.Add(1) - go func(peer *Peer) { + go func(peer SyncPeer) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -962,7 +1003,6 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { // Found an incomplete storage chunk, schedule it accounts = append(accounts, account) roots = append(roots, st.root) - subtask = st break // Large contract chunks are downloaded individually } @@ -1010,7 +1050,7 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { delete(s.storageIdlers, idle) s.pend.Add(1) - go func(peer *Peer, root common.Hash) { + go func(peer SyncPeer, root common.Hash) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -1125,7 +1165,7 @@ func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { delete(s.trienodeHealIdlers, idle) s.pend.Add(1) - go func(peer *Peer, root common.Hash) { + go func(peer SyncPeer, root common.Hash) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -1223,7 +1263,7 @@ func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { delete(s.bytecodeHealIdlers, idle) s.pend.Add(1) - go func(peer *Peer) { + go func(peer SyncPeer) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -1522,7 +1562,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { break } } - // Itereate over all the accounts and assemble which ones need further sub- + // Iterate over all the accounts and assemble which ones need further sub- // filling before the entire account range can be persisted. res.task.needCode = make([]bool, len(res.accounts)) res.task.needState = make([]bool, len(res.accounts)) @@ -1566,7 +1606,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { } } // Delete any subtasks that have been aborted but not resumed. This may undo - // some progress if a newpeer gives us less accounts than an old one, but for + // some progress if a new peer gives us less accounts than an old one, but for // now we have to live with that. for hash := range res.task.SubTasks { if _, ok := resumed[hash]; !ok { @@ -1650,95 +1690,92 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { ) // Iterate over all the accounts and reconstruct their storage tries from the // delivered slots - delivered := make(map[common.Hash]bool) - for i := 0; i < len(res.hashes); i++ { - delivered[res.roots[i]] = true - } for i, account := range res.accounts { // If the account was not delivered, reschedule it if i >= len(res.hashes) { - if !delivered[res.roots[i]] { - res.mainTask.stateTasks[account] = res.roots[i] - } + res.mainTask.stateTasks[account] = res.roots[i] continue } // State was delivered, if complete mark as not needed any more, otherwise // mark the account as needing healing - for j, acc := range res.mainTask.res.accounts { - if res.roots[i] == acc.Root { - // If the packet contains multiple contract storage slots, all - // but the last are surely complete. The last contract may be - // chunked, so check it's continuation flag. - if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) { - res.mainTask.needState[j] = false - res.mainTask.pend-- - } - // If the last contract was chunked, mark it as needing healing - // to avoid writing it out to disk prematurely. - if res.subTask == nil && !res.mainTask.needHeal[j] && i == len(res.hashes)-1 && res.cont { - res.mainTask.needHeal[j] = true - } - // If the last contract was chunked, we need to switch to large - // contract handling mode - if res.subTask == nil && i == len(res.hashes)-1 && res.cont { - // If we haven't yet started a large-contract retrieval, create - // the subtasks for it within the main account task - if tasks, ok := res.mainTask.SubTasks[account]; !ok { - var ( - next common.Hash - ) - step := new(big.Int).Sub( - new(big.Int).Div( - new(big.Int).Exp(common.Big2, common.Big256, nil), - big.NewInt(storageConcurrency), - ), common.Big1, - ) - for k := 0; k < storageConcurrency; k++ { - last := common.BigToHash(new(big.Int).Add(next.Big(), step)) - if k == storageConcurrency-1 { - // Make sure we don't overflow if the step is not a proper divisor - last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - } - tasks = append(tasks, &storageTask{ - Next: next, - Last: last, - root: acc.Root, - }) - log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", next, "last", last) - next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + for j, hash := range res.mainTask.res.hashes { + if account != hash { + continue + } + acc := res.mainTask.res.accounts[j] + + // If the packet contains multiple contract storage slots, all + // but the last are surely complete. The last contract may be + // chunked, so check it's continuation flag. + if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) { + res.mainTask.needState[j] = false + res.mainTask.pend-- + } + // If the last contract was chunked, mark it as needing healing + // to avoid writing it out to disk prematurely. + if res.subTask == nil && !res.mainTask.needHeal[j] && i == len(res.hashes)-1 && res.cont { + res.mainTask.needHeal[j] = true + } + // If the last contract was chunked, we need to switch to large + // contract handling mode + if res.subTask == nil && i == len(res.hashes)-1 && res.cont { + // If we haven't yet started a large-contract retrieval, create + // the subtasks for it within the main account task + if tasks, ok := res.mainTask.SubTasks[account]; !ok { + var ( + next common.Hash + ) + step := new(big.Int).Sub( + new(big.Int).Div( + new(big.Int).Exp(common.Big2, common.Big256, nil), + big.NewInt(storageConcurrency), + ), common.Big1, + ) + for k := 0; k < storageConcurrency; k++ { + last := common.BigToHash(new(big.Int).Add(next.Big(), step)) + if k == storageConcurrency-1 { + // Make sure we don't overflow if the step is not a proper divisor + last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") } - res.mainTask.SubTasks[account] = tasks - - // Since we've just created the sub-tasks, this response - // is surely for the first one (zero origin) - res.subTask = tasks[0] + tasks = append(tasks, &storageTask{ + Next: next, + Last: last, + root: acc.Root, + }) + log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", next, "last", last) + next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) } + res.mainTask.SubTasks[account] = tasks + + // Since we've just created the sub-tasks, this response + // is surely for the first one (zero origin) + res.subTask = tasks[0] } - // If we're in large contract delivery mode, forward the subtask - if res.subTask != nil { - // Ensure the response doesn't overflow into the subsequent task - last := res.subTask.Last.Big() - for k, hash := range res.hashes[i] { - if hash.Big().Cmp(last) > 0 { - // Chunk overflown, cut off excess, but also update the boundary - for l := k; l < len(res.hashes[i]); l++ { - if err := res.tries[i].Prove(res.hashes[i][l][:], 0, res.overflow); err != nil { - panic(err) // Account range was already proven, what happened - } + } + // If we're in large contract delivery mode, forward the subtask + if res.subTask != nil { + // Ensure the response doesn't overflow into the subsequent task + last := res.subTask.Last.Big() + for k, hash := range res.hashes[i] { + if hash.Big().Cmp(last) > 0 { + // Chunk overflown, cut off excess, but also update the boundary + for l := k; l < len(res.hashes[i]); l++ { + if err := res.tries[i].Prove(res.hashes[i][l][:], 0, res.overflow); err != nil { + panic(err) // Account range was already proven, what happened } - res.hashes[i] = res.hashes[i][:k] - res.slots[i] = res.slots[i][:k] - res.cont = false // Mark range completed - break } - } - // Forward the relevant storage chunk (even if created just now) - if res.cont { - res.subTask.Next = common.BigToHash(new(big.Int).Add(res.hashes[i][len(res.hashes[i])-1].Big(), big.NewInt(1))) - } else { - res.subTask.done = true + res.hashes[i] = res.hashes[i][:k] + res.slots[i] = res.slots[i][:k] + res.cont = false // Mark range completed + break } } + // Forward the relevant storage chunk (even if created just now) + if res.cont { + res.subTask.Next = common.BigToHash(new(big.Int).Add(res.hashes[i][len(res.hashes[i])-1].Big(), big.NewInt(1))) + } else { + res.subTask.done = true + } } } // Iterate over all the reconstructed trie nodes and push them to disk @@ -1941,7 +1978,7 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { // OnAccounts is a callback method to invoke when a range of accounts are // received from a remote peer. -func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, accounts [][]byte, proof [][]byte) error { +func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, accounts [][]byte, proof [][]byte) error { size := common.StorageSize(len(hashes) * common.HashLength) for _, account := range accounts { size += common.StorageSize(len(account)) @@ -1949,15 +1986,15 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account for _, node := range proof { size += common.StorageSize(len(node)) } - logger := peer.logger.New("reqid", id) + logger := peer.Log().New("reqid", id) logger.Trace("Delivering range of accounts", "hashes", len(hashes), "accounts", len(accounts), "proofs", len(proof), "bytes", size) // Whether or not the response is valid, we can mark the peer as idle and // notify the scheduler to assign a new task. If the response is invalid, // we'll drop the peer in a bit. s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - s.accountIdlers[peer.id] = struct{}{} + if _, ok := s.peers[peer.ID()]; ok { + s.accountIdlers[peer.ID()] = struct{}{} } select { case s.update <- struct{}{}: @@ -1975,7 +2012,11 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content - req.timeout.Stop() + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } // Response is valid, but check if peer is signalling that it does not have // the requested data. For account range queries that means the state being @@ -1983,7 +2024,7 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account // synced to our head. if len(hashes) == 0 && len(accounts) == 0 && len(proof) == 0 { logger.Debug("Peer rejected account range request", "root", s.root) - s.statelessPeers[peer.id] = struct{}{} + s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() // Signal this request as failed, and ready for rescheduling @@ -2011,6 +2052,8 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account db, tr, notary, cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb) if err != nil { logger.Warn("Account range failed proof", "err", err) + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertAccountRequest(req) return err } // Partial trie reconstructed, send it to the scheduler for storage filling @@ -2050,9 +2093,9 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account // OnByteCodes is a callback method to invoke when a batch of contract // bytes codes are received from a remote peer. -func (s *Syncer) OnByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { +func (s *Syncer) OnByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error { s.lock.RLock() - syncing := len(s.tasks) > 0 + syncing := !s.snapped s.lock.RUnlock() if syncing { @@ -2063,20 +2106,20 @@ func (s *Syncer) OnByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { // onByteCodes is a callback method to invoke when a batch of contract // bytes codes are received from a remote peer in the syncing phase. -func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { +func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error { var size common.StorageSize for _, code := range bytecodes { size += common.StorageSize(len(code)) } - logger := peer.logger.New("reqid", id) + logger := peer.Log().New("reqid", id) logger.Trace("Delivering set of bytecodes", "bytecodes", len(bytecodes), "bytes", size) // Whether or not the response is valid, we can mark the peer as idle and // notify the scheduler to assign a new task. If the response is invalid, // we'll drop the peer in a bit. s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - s.bytecodeIdlers[peer.id] = struct{}{} + if _, ok := s.peers[peer.ID()]; ok { + s.bytecodeIdlers[peer.ID()] = struct{}{} } select { case s.update <- struct{}{}: @@ -2094,14 +2137,18 @@ func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content - req.timeout.Stop() + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } // Response is valid, but check if peer is signalling that it does not have // the requested data. For bytecode range queries that means the peer is not // yet synced. if len(bytecodes) == 0 { logger.Debug("Peer rejected bytecode request") - s.statelessPeers[peer.id] = struct{}{} + s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() // Signal this request as failed, and ready for rescheduling @@ -2132,6 +2179,8 @@ func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { } // We've either ran out of hashes, or got unrequested data logger.Warn("Unexpected bytecodes", "count", len(bytecodes)-i) + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeRequest(req) return errors.New("unexpected bytecode") } // Response validated, send it to the scheduler for filling @@ -2150,7 +2199,7 @@ func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { // OnStorage is a callback method to invoke when ranges of storage slots // are received from a remote peer. -func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots [][][]byte, proof [][]byte) error { +func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slots [][][]byte, proof [][]byte) error { // Gather some trace stats to aid in debugging issues var ( hashCount int @@ -2170,15 +2219,15 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots for _, node := range proof { size += common.StorageSize(len(node)) } - logger := peer.logger.New("reqid", id) + logger := peer.Log().New("reqid", id) logger.Trace("Delivering ranges of storage slots", "accounts", len(hashes), "hashes", hashCount, "slots", slotCount, "proofs", len(proof), "size", size) // Whether or not the response is valid, we can mark the peer as idle and // notify the scheduler to assign a new task. If the response is invalid, // we'll drop the peer in a bit. s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - s.storageIdlers[peer.id] = struct{}{} + if _, ok := s.peers[peer.ID()]; ok { + s.storageIdlers[peer.ID()] = struct{}{} } select { case s.update <- struct{}{}: @@ -2196,17 +2245,23 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content - req.timeout.Stop() + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } // Reject the response if the hash sets and slot sets don't match, or if the // peer sent more data than requested. if len(hashes) != len(slots) { s.lock.Unlock() + s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Hash and slot set size mismatch", "hashset", len(hashes), "slotset", len(slots)) return errors.New("hash and slot set size mismatch") } if len(hashes) > len(req.accounts) { s.lock.Unlock() + s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Hash set larger than requested", "hashset", len(hashes), "requested", len(req.accounts)) return errors.New("hash set larger than requested") } @@ -2216,11 +2271,9 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots // synced to our head. if len(hashes) == 0 { logger.Debug("Peer rejected storage request") - s.statelessPeers[peer.id] = struct{}{} + s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() - - // Signal this request as failed, and ready for rescheduling - s.scheduleRevertStorageRequest(req) + s.scheduleRevertStorageRequest(req) // reschedule request return nil } s.lock.Unlock() @@ -2250,6 +2303,7 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots // space and hash to the origin root. dbs[i], tries[i], _, _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) if err != nil { + s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage slots failed proof", "err", err) return err } @@ -2264,6 +2318,7 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots } dbs[i], tries[i], notary, cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb) if err != nil { + s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage range failed proof", "err", err) return err } @@ -2302,20 +2357,20 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots // OnTrieNodes is a callback method to invoke when a batch of trie nodes // are received from a remote peer. -func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { +func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error { var size common.StorageSize for _, node := range trienodes { size += common.StorageSize(len(node)) } - logger := peer.logger.New("reqid", id) + logger := peer.Log().New("reqid", id) logger.Trace("Delivering set of healing trienodes", "trienodes", len(trienodes), "bytes", size) // Whether or not the response is valid, we can mark the peer as idle and // notify the scheduler to assign a new task. If the response is invalid, // we'll drop the peer in a bit. s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - s.trienodeHealIdlers[peer.id] = struct{}{} + if _, ok := s.peers[peer.ID()]; ok { + s.trienodeHealIdlers[peer.ID()] = struct{}{} } select { case s.update <- struct{}{}: @@ -2333,14 +2388,18 @@ func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content - req.timeout.Stop() + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } // Response is valid, but check if peer is signalling that it does not have // the requested data. For bytecode range queries that means the peer is not // yet synced. if len(trienodes) == 0 { logger.Debug("Peer rejected trienode heal request") - s.statelessPeers[peer.id] = struct{}{} + s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() // Signal this request as failed, and ready for rescheduling @@ -2371,6 +2430,8 @@ func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { } // We've either ran out of hashes, or got unrequested data logger.Warn("Unexpected healing trienodes", "count", len(trienodes)-i) + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertTrienodeHealRequest(req) return errors.New("unexpected healing trienode") } // Response validated, send it to the scheduler for filling @@ -2390,20 +2451,20 @@ func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { // onHealByteCodes is a callback method to invoke when a batch of contract // bytes codes are received from a remote peer in the healing phase. -func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { +func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error { var size common.StorageSize for _, code := range bytecodes { size += common.StorageSize(len(code)) } - logger := peer.logger.New("reqid", id) + logger := peer.Log().New("reqid", id) logger.Trace("Delivering set of healing bytecodes", "bytecodes", len(bytecodes), "bytes", size) // Whether or not the response is valid, we can mark the peer as idle and // notify the scheduler to assign a new task. If the response is invalid, // we'll drop the peer in a bit. s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - s.bytecodeHealIdlers[peer.id] = struct{}{} + if _, ok := s.peers[peer.ID()]; ok { + s.bytecodeHealIdlers[peer.ID()] = struct{}{} } select { case s.update <- struct{}{}: @@ -2421,14 +2482,18 @@ func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) erro // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content - req.timeout.Stop() + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } // Response is valid, but check if peer is signalling that it does not have // the requested data. For bytecode range queries that means the peer is not // yet synced. if len(bytecodes) == 0 { logger.Debug("Peer rejected bytecode heal request") - s.statelessPeers[peer.id] = struct{}{} + s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() // Signal this request as failed, and ready for rescheduling @@ -2459,6 +2524,8 @@ func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) erro } // We've either ran out of hashes, or got unrequested data logger.Warn("Unexpected healing bytecodes", "count", len(bytecodes)-i) + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeHealRequest(req) return errors.New("unexpected healing bytecode") } // Response validated, send it to the scheduler for filling diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index 4f28b99bfe..0b048786e8 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -17,15 +17,29 @@ package snap import ( + "bytes" "crypto/rand" + "encoding/binary" "fmt" + "math/big" + "sort" "testing" + "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" "golang.org/x/crypto/sha3" ) func TestHashing(t *testing.T) { + t.Parallel() + var bytecodes = make([][]byte, 10) for i := 0; i < len(bytecodes); i++ { buf := make([]byte, 100) @@ -96,3 +110,1009 @@ func BenchmarkHashing(b *testing.B) { } }) } + +type storageHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error +type accountHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error +type trieHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error +type codeHandlerFunc func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error + +type testPeer struct { + id string + test *testing.T + remote *Syncer + logger log.Logger + accountTrie *trie.Trie + accountValues entrySlice + storageTries map[common.Hash]*trie.Trie + storageValues map[common.Hash]entrySlice + + accountRequestHandler accountHandlerFunc + storageRequestHandler storageHandlerFunc + trieRequestHandler trieHandlerFunc + codeRequestHandler codeHandlerFunc + cancelCh chan struct{} +} + +func newTestPeer(id string, t *testing.T, cancelCh chan struct{}) *testPeer { + peer := &testPeer{ + id: id, + test: t, + logger: log.New("id", id), + accountRequestHandler: defaultAccountRequestHandler, + trieRequestHandler: defaultTrieRequestHandler, + storageRequestHandler: defaultStorageRequestHandler, + codeRequestHandler: defaultCodeRequestHandler, + cancelCh: cancelCh, + } + //stderrHandler := log.StreamHandler(os.Stderr, log.TerminalFormat(true)) + //peer.logger.SetHandler(stderrHandler) + return peer + +} + +func (t *testPeer) ID() string { return t.id } +func (t *testPeer) Log() log.Logger { return t.logger } + +func (t *testPeer) RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error { + t.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes)) + go t.accountRequestHandler(t, id, root, origin, bytes) + return nil +} + +func (t *testPeer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error { + t.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes)) + go t.trieRequestHandler(t, id, root, paths, bytes) + return nil +} + +func (t *testPeer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error { + if len(accounts) == 1 && origin != nil { + t.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes)) + } else { + t.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes)) + } + go t.storageRequestHandler(t, id, root, accounts, origin, limit, bytes) + return nil +} + +func (t *testPeer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error { + t.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes)) + go t.codeRequestHandler(t, id, hashes, bytes) + return nil +} + +// defaultTrieRequestHandler is a well-behaving handler for trie healing requests +func defaultTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error { + // Pass the response + var nodes [][]byte + for _, pathset := range paths { + switch len(pathset) { + case 1: + blob, _, err := t.accountTrie.TryGetNode(pathset[0]) + if err != nil { + t.logger.Info("Error handling req", "error", err) + break + } + nodes = append(nodes, blob) + default: + account := t.storageTries[(common.BytesToHash(pathset[0]))] + for _, path := range pathset[1:] { + blob, _, err := account.TryGetNode(path) + if err != nil { + t.logger.Info("Error handling req", "error", err) + break + } + nodes = append(nodes, blob) + } + } + } + t.remote.OnTrieNodes(t, requestId, nodes) + return nil +} + +// defaultAccountRequestHandler is a well-behaving handler for AccountRangeRequests +func defaultAccountRequestHandler(t *testPeer, id uint64, root common.Hash, origin common.Hash, cap uint64) error { + keys, vals, proofs := createAccountRequestResponse(t, root, origin, cap) + if err := t.remote.OnAccounts(t, id, keys, vals, proofs); err != nil { + t.logger.Error("remote error on delivery", "error", err) + t.test.Errorf("Remote side rejected our delivery: %v", err) + t.remote.Unregister(t.id) + close(t.cancelCh) + return err + } + return nil +} + +func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.Hash, cap uint64) (keys []common.Hash, vals [][]byte, proofs [][]byte) { + var size uint64 + for _, entry := range t.accountValues { + if size > cap { + break + } + if bytes.Compare(origin[:], entry.k) <= 0 { + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) + size += uint64(32 + len(entry.v)) + } + } + // Unless we send the entire trie, we need to supply proofs + // Actually, we need to supply proofs either way! This seems tob be an implementation + // quirk in go-ethereum + proof := light.NewNodeSet() + if err := t.accountTrie.Prove(origin[:], 0, proof); err != nil { + t.logger.Error("Could not prove inexistence of origin", "origin", origin, + "error", err) + } + if len(keys) > 0 { + lastK := (keys[len(keys)-1])[:] + if err := t.accountTrie.Prove(lastK, 0, proof); err != nil { + t.logger.Error("Could not prove last item", + "error", err) + } + } + for _, blob := range proof.NodeList() { + proofs = append(proofs, blob) + } + return keys, vals, proofs +} + +// defaultStorageRequestHandler is a well-behaving storage request handler +func defaultStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) error { + hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, bOrigin, bLimit, max) + if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil { + t.logger.Error("remote error on delivery", "error", err) + t.test.Errorf("Remote side rejected our delivery: %v", err) + close(t.cancelCh) + } + return nil +} + +func defaultCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + var bytecodes [][]byte + for _, h := range hashes { + bytecodes = append(bytecodes, getCode(h)) + } + if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { + t.logger.Error("remote error on delivery", "error", err) + t.test.Errorf("Remote side rejected our delivery: %v", err) + close(t.cancelCh) + } + return nil +} + +func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) { + var ( + size uint64 + limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + ) + if len(bLimit) > 0 { + limit = common.BytesToHash(bLimit) + } + var origin common.Hash + if len(bOrigin) > 0 { + origin = common.BytesToHash(bOrigin) + } + + var limitExceeded bool + var incomplete bool + for _, account := range accounts { + + var keys []common.Hash + var vals [][]byte + for _, entry := range t.storageValues[account] { + if limitExceeded { + incomplete = true + break + } + if bytes.Compare(entry.k, origin[:]) < 0 { + incomplete = true + continue + } + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) + size += uint64(32 + len(entry.v)) + if bytes.Compare(entry.k, limit[:]) >= 0 { + limitExceeded = true + } + if size > max { + limitExceeded = true + } + } + hashes = append(hashes, keys) + slots = append(slots, vals) + + if incomplete { + // If we're aborting, we need to prove the first and last item + // This terminates the response (and thus the loop) + proof := light.NewNodeSet() + stTrie := t.storageTries[account] + + // Here's a potential gotcha: when constructing the proof, we cannot + // use the 'origin' slice directly, but must use the full 32-byte + // hash form. + if err := stTrie.Prove(origin[:], 0, proof); err != nil { + t.logger.Error("Could not prove inexistence of origin", "origin", origin, + "error", err) + } + if len(keys) > 0 { + lastK := (keys[len(keys)-1])[:] + if err := stTrie.Prove(lastK, 0, proof); err != nil { + t.logger.Error("Could not prove last item", "error", err) + } + } + for _, blob := range proof.NodeList() { + proofs = append(proofs, blob) + } + break + } + } + return hashes, slots, proofs +} + +// emptyRequestAccountRangeFn is a rejects AccountRangeRequests +func emptyRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { + var proofs [][]byte + var keys []common.Hash + var vals [][]byte + t.remote.OnAccounts(t, requestId, keys, vals, proofs) + return nil +} + +func nonResponsiveRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { + return nil +} + +func emptyTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error { + var nodes [][]byte + t.remote.OnTrieNodes(t, requestId, nodes) + return nil +} + +func nonResponsiveTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error { + return nil +} + +func emptyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + var hashes [][]common.Hash + var slots [][][]byte + var proofs [][]byte + t.remote.OnStorage(t, requestId, hashes, slots, proofs) + return nil +} + +func nonResponsiveStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + return nil +} + +//func emptyCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { +// var bytecodes [][]byte +// t.remote.OnByteCodes(t, id, bytecodes) +// return nil +//} + +func corruptCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + var bytecodes [][]byte + for _, h := range hashes { + // Send back the hashes + bytecodes = append(bytecodes, h[:]) + } + if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { + t.logger.Error("remote error on delivery", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +func cappedCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + var bytecodes [][]byte + for _, h := range hashes[:1] { + bytecodes = append(bytecodes, getCode(h)) + } + if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { + t.logger.Error("remote error on delivery", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +// starvingStorageRequestHandler is somewhat well-behaving storage handler, but it caps the returned results to be very small +func starvingStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + return defaultStorageRequestHandler(t, requestId, root, accounts, origin, limit, 500) +} + +func starvingAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { + return defaultAccountRequestHandler(t, requestId, root, origin, 500) +} + +//func misdeliveringAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { +// return defaultAccountRequestHandler(t, requestId-1, root, origin, 500) +//} + +func corruptAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { + hashes, accounts, proofs := createAccountRequestResponse(t, root, origin, cap) + if len(proofs) > 0 { + proofs = proofs[1:] + } + if err := t.remote.OnAccounts(t, requestId, hashes, accounts, proofs); err != nil { + t.logger.Info("remote error on delivery (as expected)", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +// corruptStorageRequestHandler doesn't provide good proofs +func corruptStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, origin, limit, max) + if len(proofs) > 0 { + proofs = proofs[1:] + } + if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil { + t.logger.Info("remote error on delivery (as expected)", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +func noProofStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + hashes, slots, _ := createStorageRequestResponse(t, root, accounts, origin, limit, max) + if err := t.remote.OnStorage(t, requestId, hashes, slots, nil); err != nil { + t.logger.Info("remote error on delivery (as expected)", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +// TestSyncBloatedProof tests a scenario where we provide only _one_ value, but +// also ship the entire trie inside the proof. If the attack is successful, +// the remote side does not do any follow-up requests +func TestSyncBloatedProof(t *testing.T) { + t.Parallel() + + sourceAccountTrie, elems := makeAccountTrieNoStorage(100) + cancel := make(chan struct{}) + source := newTestPeer("source", t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + + source.accountRequestHandler = func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { + var proofs [][]byte + var keys []common.Hash + var vals [][]byte + + // The values + for _, entry := range t.accountValues { + if bytes.Compare(origin[:], entry.k) <= 0 { + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) + } + } + // The proofs + proof := light.NewNodeSet() + if err := t.accountTrie.Prove(origin[:], 0, proof); err != nil { + t.logger.Error("Could not prove origin", "origin", origin, "error", err) + } + // The bloat: add proof of every single element + for _, entry := range t.accountValues { + if err := t.accountTrie.Prove(entry.k, 0, proof); err != nil { + t.logger.Error("Could not prove item", "error", err) + } + } + // And remove one item from the elements + if len(keys) > 2 { + keys = append(keys[:1], keys[2:]...) + vals = append(vals[:1], vals[2:]...) + } + for _, blob := range proof.NodeList() { + proofs = append(proofs, blob) + } + if err := t.remote.OnAccounts(t, requestId, keys, vals, proofs); err != nil { + t.logger.Info("remote error on delivery", "error", err) + // This is actually correct, signal to exit the test successfully + close(t.cancelCh) + } + return nil + } + syncer := setupSyncer(source) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err == nil { + t.Fatal("No error returned from incomplete/cancelled sync") + } +} + +func setupSyncer(peers ...*testPeer) *Syncer { + stateDb := rawdb.NewMemoryDatabase() + syncer := NewSyncer(stateDb, trie.NewSyncBloom(1, stateDb)) + for _, peer := range peers { + syncer.Register(peer) + peer.remote = syncer + } + return syncer +} + +// TestSync tests a basic sync with one peer +func TestSync(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + sourceAccountTrie, elems := makeAccountTrieNoStorage(100) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + return source + } + + syncer := setupSyncer(mkSource("sourceA")) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +// TestSyncTinyTriePanic tests a basic sync with one peer, and a tiny trie. This caused a +// panic within the prover +func TestSyncTinyTriePanic(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems := makeAccountTrieNoStorage(1) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + return source + } + + syncer := setupSyncer( + mkSource("nice-a"), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +// TestMultiSync tests a basic sync with multiple peers +func TestMultiSync(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + sourceAccountTrie, elems := makeAccountTrieNoStorage(100) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + return source + } + + syncer := setupSyncer(mkSource("sourceA"), mkSource("sourceB")) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +// TestSyncWithStorage tests basic sync using accounts + storage + code +func TestSyncWithStorage(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(3, 3000, true) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + return source + } + syncer := setupSyncer(mkSource("sourceA")) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +// TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all +func TestMultiSyncManyUseless(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + + mkSource := func(name string, a, b, c bool) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + + if !a { + source.accountRequestHandler = emptyRequestAccountRangeFn + } + if !b { + source.storageRequestHandler = emptyStorageRequestHandler + } + if !c { + source.trieRequestHandler = emptyTrieRequestHandler + } + return source + } + + syncer := setupSyncer( + mkSource("full", true, true, true), + mkSource("noAccounts", false, true, true), + mkSource("noStorage", true, false, true), + mkSource("noTrie", true, true, false), + ) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +// TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all +func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { + // We're setting the timeout to very low, to increase the chance of the timeout + // being triggered. This was previously a cause of panic, when a response + // arrived simultaneously as a timeout was triggered. + defer func(old time.Duration) { requestTimeout = old }(requestTimeout) + requestTimeout = time.Millisecond + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + + mkSource := func(name string, a, b, c bool) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + + if !a { + source.accountRequestHandler = emptyRequestAccountRangeFn + } + if !b { + source.storageRequestHandler = emptyStorageRequestHandler + } + if !c { + source.trieRequestHandler = emptyTrieRequestHandler + } + return source + } + + syncer := setupSyncer( + mkSource("full", true, true, true), + mkSource("noAccounts", false, true, true), + mkSource("noStorage", true, false, true), + mkSource("noTrie", true, true, false), + ) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +// TestMultiSyncManyUnresponsive contains one good peer, and many which doesn't respond at all +func TestMultiSyncManyUnresponsive(t *testing.T) { + // We're setting the timeout to very low, to make the test run a bit faster + defer func(old time.Duration) { requestTimeout = old }(requestTimeout) + requestTimeout = time.Millisecond + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + + mkSource := func(name string, a, b, c bool) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + + if !a { + source.accountRequestHandler = nonResponsiveRequestAccountRangeFn + } + if !b { + source.storageRequestHandler = nonResponsiveStorageRequestHandler + } + if !c { + source.trieRequestHandler = nonResponsiveTrieRequestHandler + } + return source + } + + syncer := setupSyncer( + mkSource("full", true, true, true), + mkSource("noAccounts", false, true, true), + mkSource("noStorage", true, false, true), + mkSource("noTrie", true, true, false), + ) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +func checkStall(t *testing.T, cancel chan struct{}) chan struct{} { + testDone := make(chan struct{}) + go func() { + select { + case <-time.After(time.Minute): // TODO(karalabe): Make tests smaller, this is too much + t.Log("Sync stalled") + close(cancel) + case <-testDone: + return + } + }() + return testDone +} + +// TestSyncNoStorageAndOneCappedPeer tests sync using accounts and no storage, where one peer is +// consistently returning very small results +func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + + mkSource := func(name string, slow bool) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + + if slow { + source.accountRequestHandler = starvingAccountRequestHandler + } + return source + } + + syncer := setupSyncer( + mkSource("nice-a", false), + mkSource("nice-b", false), + mkSource("nice-c", false), + mkSource("capped", true), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +// TestSyncNoStorageAndOneCodeCorruptPeer has one peer which doesn't deliver +// code requests properly. +func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + + mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.codeRequestHandler = codeFn + return source + } + // One is capped, one is corrupt. If we don't use a capped one, there's a 50% + // chance that the full set of codes requested are sent only to the + // non-corrupt peer, which delivers everything in one go, and makes the + // test moot + syncer := setupSyncer( + mkSource("capped", cappedCodeRequestHandler), + mkSource("corrupt", corruptCodeRequestHandler), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + + mkSource := func(name string, accFn accountHandlerFunc) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.accountRequestHandler = accFn + return source + } + // One is capped, one is corrupt. If we don't use a capped one, there's a 50% + // chance that the full set of codes requested are sent only to the + // non-corrupt peer, which delivers everything in one go, and makes the + // test moot + syncer := setupSyncer( + mkSource("capped", defaultAccountRequestHandler), + mkSource("corrupt", corruptAccountRequestHandler), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +// TestSyncNoStorageAndOneCodeCappedPeer has one peer which delivers code hashes +// one by one +func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + + mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.codeRequestHandler = codeFn + return source + } + // Count how many times it's invoked. Remember, there are only 8 unique hashes, + // so it shouldn't be more than that + var counter int + syncer := setupSyncer( + mkSource("capped", func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + counter++ + return cappedCodeRequestHandler(t, id, hashes, max) + }), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + // There are only 8 unique hashes, and 3K accounts. However, the code + // deduplication is per request batch. If it were a perfect global dedup, + // we would expect only 8 requests. If there were no dedup, there would be + // 3k requests. + // We expect somewhere below 100 requests for these 8 unique hashes. + if threshold := 100; counter > threshold { + t.Fatalf("Error, expected < %d invocations, got %d", threshold, counter) + } +} + +// TestSyncWithStorageAndOneCappedPeer tests sync using accounts + storage, where one peer is +// consistently returning very small results +func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(300, 1000, false) + + mkSource := func(name string, slow bool) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + + if slow { + source.storageRequestHandler = starvingStorageRequestHandler + } + return source + } + + syncer := setupSyncer( + mkSource("nice-a", false), + mkSource("slow", true), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +// TestSyncWithStorageAndCorruptPeer tests sync using accounts + storage, where one peer is +// sometimes sending bad proofs +func TestSyncWithStorageAndCorruptPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + + mkSource := func(name string, handler storageHandlerFunc) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + source.storageRequestHandler = handler + return source + } + + syncer := setupSyncer( + mkSource("nice-a", defaultStorageRequestHandler), + mkSource("nice-b", defaultStorageRequestHandler), + mkSource("nice-c", defaultStorageRequestHandler), + mkSource("corrupt", corruptStorageRequestHandler), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + + mkSource := func(name string, handler storageHandlerFunc) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + source.storageRequestHandler = handler + return source + } + + syncer := setupSyncer( + mkSource("nice-a", defaultStorageRequestHandler), + mkSource("nice-b", defaultStorageRequestHandler), + mkSource("nice-c", defaultStorageRequestHandler), + mkSource("corrupt", noProofStorageRequestHandler), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +type kv struct { + k, v []byte + t bool +} + +// Some helpers for sorting +type entrySlice []*kv + +func (p entrySlice) Len() int { return len(p) } +func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 } +func (p entrySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +func key32(i uint64) []byte { + key := make([]byte, 32) + binary.LittleEndian.PutUint64(key, i) + return key +} + +var ( + codehashes = []common.Hash{ + crypto.Keccak256Hash([]byte{0}), + crypto.Keccak256Hash([]byte{1}), + crypto.Keccak256Hash([]byte{2}), + crypto.Keccak256Hash([]byte{3}), + crypto.Keccak256Hash([]byte{4}), + crypto.Keccak256Hash([]byte{5}), + crypto.Keccak256Hash([]byte{6}), + crypto.Keccak256Hash([]byte{7}), + } +) + +// getACodeHash returns a pseudo-random code hash +func getACodeHash(i uint64) []byte { + h := codehashes[int(i)%len(codehashes)] + return common.CopyBytes(h[:]) +} + +// convenience function to lookup the code from the code hash +func getCode(hash common.Hash) []byte { + if hash == emptyCode { + return nil + } + for i, h := range codehashes { + if h == hash { + return []byte{byte(i)} + } + } + return nil +} + +// makeAccountTrieNoStorage spits out a trie, along with the leafs +func makeAccountTrieNoStorage(n int) (*trie.Trie, entrySlice) { + db := trie.NewDatabase(rawdb.NewMemoryDatabase()) + accTrie, _ := trie.New(common.Hash{}, db) + var entries entrySlice + for i := uint64(1); i <= uint64(n); i++ { + value, _ := rlp.EncodeToBytes(state.Account{ + Nonce: i, + Balance: big.NewInt(int64(i)), + Root: emptyRoot, + CodeHash: getACodeHash(i), + }) + key := key32(i) + elem := &kv{key, value, false} + accTrie.Update(elem.k, elem.v) + entries = append(entries, elem) + } + sort.Sort(entries) + // Push to disk layer + accTrie.Commit(nil) + return accTrie, entries +} + +// makeAccountTrieWithStorage spits out a trie, along with the leafs +func makeAccountTrieWithStorage(accounts, slots int, code bool) (*trie.Trie, entrySlice, + map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) { + + var ( + db = trie.NewDatabase(rawdb.NewMemoryDatabase()) + accTrie, _ = trie.New(common.Hash{}, db) + entries entrySlice + storageTries = make(map[common.Hash]*trie.Trie) + storageEntries = make(map[common.Hash]entrySlice) + ) + + // Make a storage trie which we reuse for the whole lot + stTrie, stEntries := makeStorageTrie(slots, db) + stRoot := stTrie.Hash() + // Create n accounts in the trie + for i := uint64(1); i <= uint64(accounts); i++ { + key := key32(i) + codehash := emptyCode[:] + if code { + codehash = getACodeHash(i) + } + value, _ := rlp.EncodeToBytes(state.Account{ + Nonce: i, + Balance: big.NewInt(int64(i)), + Root: stRoot, + CodeHash: codehash, + }) + elem := &kv{key, value, false} + accTrie.Update(elem.k, elem.v) + entries = append(entries, elem) + // we reuse the same one for all accounts + storageTries[common.BytesToHash(key)] = stTrie + storageEntries[common.BytesToHash(key)] = stEntries + } + sort.Sort(entries) + stTrie.Commit(nil) + accTrie.Commit(nil) + return accTrie, entries, storageTries, storageEntries +} + +// makeStorageTrie fills a storage trie with n items, returning the +// not-yet-committed trie and the sorted entries +func makeStorageTrie(n int, db *trie.Database) (*trie.Trie, entrySlice) { + trie, _ := trie.New(common.Hash{}, db) + var entries entrySlice + for i := uint64(1); i <= uint64(n); i++ { + // store 'i' at slot 'i' + slotValue := key32(i) + rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:])) + + slotKey := key32(i) + key := crypto.Keccak256Hash(slotKey[:]) + + elem := &kv{key[:], rlpSlotValue, false} + trie.Update(elem.k, elem.v) + entries = append(entries, elem) + } + sort.Sort(entries) + return trie, entries +} From 1770fe718af661334391766455c43157e378b9fa Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 25 Jan 2021 10:42:07 +0100 Subject: [PATCH 147/235] go.mod: update dependencies (#22216) This updates go module dependencies as discussed in #22050. --- go.mod | 22 ++-- go.sum | 346 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 338 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 9162754ea3..d3c75420b8 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.5.7 github.com/aws/aws-sdk-go v1.25.48 - github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 + github.com/btcsuite/btcd v0.20.1-beta github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 github.com/davecgh/go-spew v1.1.1 @@ -17,28 +17,27 @@ require ( github.com/dlclark/regexp2 v1.2.0 // indirect github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 - github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c + github.com/edsrzf/mmap-go v1.0.0 github.com/fatih/color v1.3.0 - github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc + github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-stack/stack v1.8.0 - github.com/golang/protobuf v1.4.2 + github.com/golang/protobuf v1.4.3 github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa - github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 - github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 + github.com/gorilla/websocket v1.4.2 + github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.1.1 github.com/huin/goupnp v1.0.0 - github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 + github.com/influxdata/influxdb v1.8.3 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e - github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 + github.com/julienschmidt/httprouter v1.2.0 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 - github.com/kr/pretty v0.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.0 github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 @@ -49,11 +48,10 @@ require ( github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 github.com/rjeczalik/notify v0.9.1 - github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 - github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect + github.com/rs/cors v1.7.0 github.com/shirou/gopsutil v2.20.5+incompatible github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 diff --git a/go.sum b/go.sum index 327af03735..98420f89f9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,23 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= @@ -20,29 +40,61 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 h1:Eey/GGQ/E5Xp1P2Lyx1qj007hLZfbi0+CoVeJruGCtI= -github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 h1:J82+/8rub3qSy0HxEnoYD8cs+HDlHWYrqYXe2Vqxluk= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -50,6 +102,7 @@ github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= @@ -57,30 +110,53 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 h1:Y9vTBSsV4hSwPSj4bacAU/eSnV3dAxVpepaghAdhGoQ= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= -github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM= -github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc h1:jtW8jbpkO4YirRSyepBOH8E+2HEw6/hKkBvFPwhUN8c= -github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -88,10 +164,17 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 h1:ur2rms48b3Ep1dxh7aUV2FZEQ8jEVO2F6ILKx8ofkAg= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -99,10 +182,20 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54= -github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM= -github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 h1:sezaKhEfPFg8W0Enm61B9Gs911H8iesGY5R8NDPtd1M= +github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= @@ -114,18 +207,47 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= -github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 h1:FSeK4fZCo8u40n2JMnyAsd6x7+SbvoOMHvQOU/n10P4= -github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8= +github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= +github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= +github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89 h1:12K8AlpT0/6QUXSfV0yi4Q0jkbq8NDtIKFtF61AoqV0= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 h1:F/iKcka0K2LgnKy/fgSBf235AETtm1n1TvBzqu40LE0= -github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -135,17 +257,26 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d h1:oNAwILwmgWKFpuU+dXvI6dl9jG2mAWAZLX3r9s0PPiw= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 h1:USWjF42jDCSEeikX/G1g40ZWnsPXN5WkZ4jMHZWyBK4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0= @@ -157,75 +288,178 @@ github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXW github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c h1:1RHs3tNxjXGHeul8z2t6H2N2TlAqpKe5yryJztRx4Jk= github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 h1:goeTyGkArOZIVOMA0dQbyuPWGNQJZGPwPu/QS9GlpnA= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 h1:ZeU+auZj1iNzN8iVhff6M38Mfu73FQiJve/GEXYJBjE= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= -github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 h1:8DPul/X0IT/1TNMIxoKLwdemEOBBHDC/K4EB16Cw5WE= -github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 h1:3hxavr+IHMsQBrYUPQM5v0CgENFktkkbg1sfpgM3h20= -github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D69SiV4JoN7kkfvJdOWlPpfxrzxpLMoUk= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= +github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -233,17 +467,82 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -255,6 +554,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= @@ -265,10 +565,20 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From c0862f4f4c21c0ff54636d5122f09f310133d504 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 25 Jan 2021 11:31:18 +0100 Subject: [PATCH 148/235] graphql: change receipt status to decimal instead of hex (#22187) This PR fixes the receipt status field to be decimal instead of a hex string, as called for by the spec. Co-authored-by: Martin Holst Swende --- graphql/graphql.go | 16 ++++++++-------- graphql/graphql_test.go | 6 ++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/graphql/graphql.go b/graphql/graphql.go index ea587106b4..5dc0f723d3 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -292,12 +292,12 @@ func (t *Transaction) getReceipt(ctx context.Context) (*types.Receipt, error) { return receipts[t.index], nil } -func (t *Transaction) Status(ctx context.Context) (*hexutil.Uint64, error) { +func (t *Transaction) Status(ctx context.Context) (*Long, error) { receipt, err := t.getReceipt(ctx) if err != nil || receipt == nil { return nil, err } - ret := hexutil.Uint64(receipt.Status) + ret := Long(receipt.Status) return &ret, nil } @@ -810,9 +810,9 @@ type CallData struct { // CallResult encapsulates the result of an invocation of the `call` accessor. type CallResult struct { - data hexutil.Bytes // The return data from the call - gasUsed Long // The amount of gas used - status hexutil.Uint64 // The return status of the call - 0 for failure or 1 for success. + data hexutil.Bytes // The return data from the call + gasUsed Long // The amount of gas used + status Long // The return status of the call - 0 for failure or 1 for success. } func (c *CallResult) Data() hexutil.Bytes { @@ -823,7 +823,7 @@ func (c *CallResult) GasUsed() Long { return c.gasUsed } -func (c *CallResult) Status() hexutil.Uint64 { +func (c *CallResult) Status() Long { return c.status } @@ -840,7 +840,7 @@ func (b *Block) Call(ctx context.Context, args struct { if err != nil { return nil, err } - status := hexutil.Uint64(1) + status := Long(1) if result.Failed() { status = 0 } @@ -910,7 +910,7 @@ func (p *Pending) Call(ctx context.Context, args struct { if err != nil { return nil, err } - status := hexutil.Uint64(1) + status := Long(1) if result.Failed() { status = 0 } diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index e9c129c44c..71320012d5 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -130,6 +130,12 @@ func TestGraphQLBlockSerialization(t *testing.T) { want: `{"data":{"block":{"estimateGas":53000}}}`, code: 200, }, + // should return `status` as decimal + { + body: `{"query": "{block {number call (data : {from : \"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\", to: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", data :\"0x12a7b914\"}){data status}}}"}`, + want: `{"data":{"block":{"number":10,"call":{"data":"0x","status":1}}}}`, + code: 200, + }, } { resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body)) if err != nil { From 59a79137b9c8740a3623cdf35512e1eb49c8a20b Mon Sep 17 00:00:00 2001 From: ucwong Date: Mon, 25 Jan 2021 19:46:09 +0800 Subject: [PATCH 149/235] go.mod: upgrade github.com/huin/goupnp (#22227) This updates the goupnp dependency, fixing huin/goupnp#33 --- go.mod | 2 +- go.sum | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index d3c75420b8..ffea94f6cf 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.1.1 - github.com/huin/goupnp v1.0.0 + github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 github.com/influxdata/influxdb v1.8.3 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e diff --git a/go.sum b/go.sum index 98420f89f9..fefe5afb14 100644 --- a/go.sum +++ b/go.sum @@ -204,8 +204,8 @@ github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= -github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= +github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 h1:HarGZ5h9HD9LgEg1yRVMXyfiw4wlXiLiYM2oMjeA/SE= +github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -441,6 +441,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -460,7 +462,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -468,7 +469,6 @@ golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kY golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -502,7 +502,6 @@ golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -555,7 +554,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= @@ -566,7 +564,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= From 04a72260c5bcca0ec7c4a63532fb29f68db03384 Mon Sep 17 00:00:00 2001 From: Melvin Junhee Woo Date: Mon, 25 Jan 2021 22:25:55 +0900 Subject: [PATCH 150/235] snapshot: merge loops for better performance (#22160) --- core/state/snapshot/difflayer.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index cc82df9a54..9c86a679d1 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -191,19 +191,15 @@ func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]s if blob == nil { panic(fmt.Sprintf("account %#x nil", accountHash)) } + // Determine memory size and track the dirty writes + dl.memory += uint64(common.HashLength + len(blob)) + snapshotDirtyAccountWriteMeter.Mark(int64(len(blob))) } for accountHash, slots := range storage { if slots == nil { panic(fmt.Sprintf("storage %#x nil", accountHash)) } - } - // Determine memory size and track the dirty writes - for _, data := range accounts { - dl.memory += uint64(common.HashLength + len(data)) - snapshotDirtyAccountWriteMeter.Mark(int64(len(data))) - } - // Determine memory size and track the dirty writes - for _, slots := range storage { + // Determine memory size and track the dirty writes for _, data := range slots { dl.memory += uint64(common.HashLength + len(data)) snapshotDirtyStorageWriteMeter.Mark(int64(len(data))) From 49cdcf5c70735dc85bd9c22b45811a3ec7cef54d Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 25 Jan 2021 21:29:45 +0800 Subject: [PATCH 151/235] core: reset to genesis when middle block is missing (#22135) When a sethead/rewind finds that the targeted block is missing, it resets to genesis instead of crashing. Closes #22129 --- core/blockchain.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index d6668cdcd2..c05ebfd549 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -524,8 +524,13 @@ func (bc *BlockChain) SetHeadBeyondRoot(head uint64, root common.Hash) (uint64, if _, err := state.New(newHeadBlock.Root(), bc.stateCache, bc.snaps); err != nil { log.Trace("Block state missing, rewinding further", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) if pivot == nil || newHeadBlock.NumberU64() > *pivot { - newHeadBlock = bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1) - continue + parent := bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1) + if parent != nil { + newHeadBlock = parent + continue + } + log.Error("Missing block in the middle, aiming genesis", "number", newHeadBlock.NumberU64()-1, "hash", newHeadBlock.ParentHash()) + newHeadBlock = bc.genesisBlock } else { log.Trace("Rewind passed pivot, aiming genesis", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash(), "pivot", *pivot) newHeadBlock = bc.genesisBlock From adf130def83fbf7b7902ff4bacab7bb369517dcb Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 25 Jan 2021 21:36:39 +0800 Subject: [PATCH 152/235] eth/tracers: move tracing APIs into eth/tracers (#22161) This moves the tracing RPC API implementation to package eth/tracers. By doing so, package eth no longer depends on tracing and the duktape JS engine. The change also enables tracing using the light client. All tracing methods work with the light client, but it's a lot slower compared to using a full node. --- cmd/utils/flags.go | 3 + eth/api.go | 3 +- eth/api_backend.go | 12 + eth/state_accessor.go | 230 +++++++++++ eth/{api_tracer.go => tracers/api.go} | 558 +++++++++++--------------- eth/tracers/api_test.go | 487 ++++++++++++++++++++++ les/api_backend.go | 12 + les/state_accessor.go | 88 ++++ 8 files changed, 1071 insertions(+), 322 deletions(-) create mode 100644 eth/state_accessor.go rename eth/{api_tracer.go => tracers/api.go} (55%) create mode 100644 eth/tracers/api_test.go create mode 100644 les/state_accessor.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index df036cbbf0..954b5de2c2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -45,6 +45,7 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/graphql" @@ -1724,6 +1725,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } + stack.RegisterAPIs(tracers.APIs(backend.ApiBackend)) return backend.ApiBackend } backend, err := eth.New(stack, cfg) @@ -1736,6 +1738,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { Fatalf("Failed to create the LES server: %v", err) } } + stack.RegisterAPIs(tracers.APIs(backend.APIBackend)) return backend.APIBackend } diff --git a/eth/api.go b/eth/api.go index be1dcbb52a..53ef91392b 100644 --- a/eth/api.go +++ b/eth/api.go @@ -426,10 +426,11 @@ func (api *PrivateDebugAPI) StorageRangeAt(blockHash common.Hash, txIndex int, c if block == nil { return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash) } - _, _, statedb, err := api.computeTxEnv(block, txIndex, 0) + _, _, statedb, release, err := api.eth.stateAtTransaction(block, txIndex, 0) if err != nil { return StorageRangeResult{}, err } + defer release() st := statedb.StorageTrie(contractAddress) if st == nil { return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress) diff --git a/eth/api_backend.go b/eth/api_backend.go index 2f7020475f..17de83a28f 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -326,3 +326,15 @@ func (b *EthAPIBackend) Miner() *miner.Miner { func (b *EthAPIBackend) StartMining(threads int) error { return b.eth.StartMining(threads) } + +func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { + return b.eth.stateAtBlock(block, reexec) +} + +func (b *EthAPIBackend) StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { + return b.eth.statesInRange(fromBlock, toBlock, reexec) +} + +func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { + return b.eth.stateAtTransaction(block, txIndex, reexec) +} diff --git a/eth/state_accessor.go b/eth/state_accessor.go new file mode 100644 index 0000000000..869b3d7636 --- /dev/null +++ b/eth/state_accessor.go @@ -0,0 +1,230 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie" +) + +// stateAtBlock retrieves the state database associated with a certain block. +// If no state is locally available for the given block, a number of blocks are +// attempted to be reexecuted to generate the desired state. +func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64) (statedb *state.StateDB, release func(), err error) { + // If we have the state fully available, use that + statedb, err = eth.blockchain.StateAt(block.Root()) + if err == nil { + return statedb, func() {}, nil + } + // Otherwise try to reexec blocks until we find a state or reach our limit + origin := block.NumberU64() + database := state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16, Preimages: true}) + + for i := uint64(0); i < reexec; i++ { + if block.NumberU64() == 0 { + return nil, nil, errors.New("genesis state is missing") + } + parent := eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, nil, fmt.Errorf("missing block %v %d", block.ParentHash(), block.NumberU64()-1) + } + block = parent + + statedb, err = state.New(block.Root(), database, nil) + if err == nil { + break + } + } + if err != nil { + switch err.(type) { + case *trie.MissingNodeError: + return nil, nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) + default: + return nil, nil, err + } + } + // State was available at historical point, regenerate + var ( + start = time.Now() + logged time.Time + parent common.Hash + ) + defer func() { + if err != nil && parent != (common.Hash{}) { + database.TrieDB().Dereference(parent) + } + }() + for block.NumberU64() < origin { + // Print progress logs if long enough time elapsed + if time.Since(logged) > 8*time.Second { + log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start)) + logged = time.Now() + } + // Retrieve the next block to regenerate and process it + if block = eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil { + return nil, nil, fmt.Errorf("block #%d not found", block.NumberU64()+1) + } + _, _, _, err := eth.blockchain.Processor().Process(block, statedb, vm.Config{}) + if err != nil { + return nil, nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) + } + // Finalize the state so any modifications are written to the trie + root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(block.Number())) + if err != nil { + return nil, nil, err + } + statedb, err = state.New(root, database, nil) + if err != nil { + return nil, nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) + } + database.TrieDB().Reference(root, common.Hash{}) + if parent != (common.Hash{}) { + database.TrieDB().Dereference(parent) + } + parent = root + } + nodes, imgs := database.TrieDB().Size() + log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) + return statedb, func() { database.TrieDB().Dereference(parent) }, nil +} + +// statesInRange retrieves a batch of state databases associated with the specific +// block ranges. If no state is locally available for the given range, a number of +// blocks are attempted to be reexecuted to generate the ancestor state. +func (eth *Ethereum) statesInRange(fromBlock, toBlock *types.Block, reexec uint64) (states []*state.StateDB, release func(), err error) { + statedb, err := eth.blockchain.StateAt(fromBlock.Root()) + if err != nil { + statedb, _, err = eth.stateAtBlock(fromBlock, reexec) + } + if err != nil { + return nil, nil, err + } + states = append(states, statedb.Copy()) + + var ( + logged time.Time + parent common.Hash + start = time.Now() + refs = []common.Hash{fromBlock.Root()} + database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16, Preimages: true}) + ) + // Release all resources(including the states referenced by `stateAtBlock`) + // if error is returned. + defer func() { + if err != nil { + for _, ref := range refs { + database.TrieDB().Dereference(ref) + } + } + }() + for i := fromBlock.NumberU64() + 1; i <= toBlock.NumberU64(); i++ { + // Print progress logs if long enough time elapsed + if time.Since(logged) > 8*time.Second { + logged = time.Now() + log.Info("Regenerating historical state", "block", i, "target", fromBlock.NumberU64(), "remaining", toBlock.NumberU64()-i, "elapsed", time.Since(start)) + } + // Retrieve the next block to regenerate and process it + block := eth.blockchain.GetBlockByNumber(i) + if block == nil { + return nil, nil, fmt.Errorf("block #%d not found", i) + } + _, _, _, err := eth.blockchain.Processor().Process(block, statedb, vm.Config{}) + if err != nil { + return nil, nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) + } + // Finalize the state so any modifications are written to the trie + root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(block.Number())) + if err != nil { + return nil, nil, err + } + statedb, err := eth.blockchain.StateAt(root) + if err != nil { + return nil, nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) + } + states = append(states, statedb.Copy()) + + // Reference the trie twice, once for us, once for the tracer + database.TrieDB().Reference(root, common.Hash{}) + database.TrieDB().Reference(root, common.Hash{}) + refs = append(refs, root) + + // Dereference all past tries we ourselves are done working with + if parent != (common.Hash{}) { + database.TrieDB().Dereference(parent) + } + parent = root + } + // release is handler to release all states referenced, including + // the one referenced in `stateAtBlock`. + release = func() { + for _, ref := range refs { + database.TrieDB().Dereference(ref) + } + } + return states, release, nil +} + +// stateAtTransaction returns the execution environment of a certain transaction. +func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { + // Short circuit if it's genesis block. + if block.NumberU64() == 0 { + return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis") + } + // Create the parent state database + parent := eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + } + statedb, release, err := eth.stateAtBlock(parent, reexec) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, err + } + if txIndex == 0 && len(block.Transactions()) == 0 { + return nil, vm.BlockContext{}, statedb, release, nil + } + // Recompute transactions up to the target index. + signer := types.MakeSigner(eth.blockchain.Config(), block.Number()) + for idx, tx := range block.Transactions() { + // Assemble the transaction call message and return if the requested offset + msg, _ := tx.AsMessage(signer) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) + if idx == txIndex { + return msg, context, statedb, release, nil + } + // Not yet the searched for transaction, execute on top of the current state + vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{}) + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + release() + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + // Ensure any modifications are committed to the state + // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } + release() + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) +} diff --git a/eth/api_tracer.go b/eth/tracers/api.go similarity index 55% rename from eth/api_tracer.go rename to eth/tracers/api.go index 5dffb2a464..dca990ab95 100644 --- a/eth/api_tracer.go +++ b/eth/tracers/api.go @@ -1,4 +1,4 @@ -// Copyright 2017 The go-ethereum Authors +// Copyright 2021 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package eth +package tracers import ( "bufio" @@ -30,18 +30,18 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/trie" ) const ( @@ -55,6 +55,105 @@ const ( defaultTraceReexec = uint64(128) ) +// Backend interface provides the common API services (that are provided by +// both full and light clients) with access to necessary functions. +type Backend interface { + HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) + HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) + BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) + GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) + RPCGasCap() uint64 + ChainConfig() *params.ChainConfig + Engine() consensus.Engine + ChainDb() ethdb.Database + StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) + StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) + StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) +} + +// API is the collection of tracing APIs exposed over the private debugging endpoint. +type API struct { + backend Backend +} + +// NewAPI creates a new API definition for the tracing methods of the Ethereum service. +func NewAPI(backend Backend) *API { + return &API{backend: backend} +} + +type chainContext struct { + api *API + ctx context.Context +} + +func (context *chainContext) Engine() consensus.Engine { + return context.api.backend.Engine() +} + +func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header { + header, err := context.api.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number)) + if err != nil { + return nil + } + if header.Hash() == hash { + return header + } + header, err = context.api.backend.HeaderByHash(context.ctx, hash) + if err != nil { + return nil + } + return header +} + +// chainContext construts the context reader which is used by the evm for reading +// the necessary chain context. +func (api *API) chainContext(ctx context.Context) core.ChainContext { + return &chainContext{api: api, ctx: ctx} +} + +// blockByNumber is the wrapper of the chain access function offered by the backend. +// It will return an error if the block is not found. +func (api *API) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + block, err := api.backend.BlockByNumber(ctx, number) + if err != nil { + return nil, err + } + if block == nil { + return nil, fmt.Errorf("block #%d not found", number) + } + return block, nil +} + +// blockByHash is the wrapper of the chain access function offered by the backend. +// It will return an error if the block is not found. +func (api *API) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + block, err := api.backend.BlockByHash(ctx, hash) + if err != nil { + return nil, err + } + if block == nil { + return nil, fmt.Errorf("block %s not found", hash.Hex()) + } + return block, nil +} + +// blockByNumberAndHash is the wrapper of the chain access function offered by +// the backend. It will return an error if the block is not found. +// +// Note this function is friendly for the light client which can only retrieve the +// historical(before the CHT) header/block by number. +func (api *API) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber, hash common.Hash) (*types.Block, error) { + block, err := api.blockByNumber(ctx, number) + if err != nil { + return nil, err + } + if block.Hash() == hash { + return block, nil + } + return api.blockByHash(ctx, hash) +} + // TraceConfig holds extra parameters to trace functions. type TraceConfig struct { *vm.LogConfig @@ -81,7 +180,6 @@ type txTraceResult struct { type blockTraceTask struct { statedb *state.StateDB // Intermediate state prepped for tracing block *types.Block // Block to trace the transactions from - rootref common.Hash // Trie root reference held for this task results []*txTraceResult // Trace results procudes by the task } @@ -102,32 +200,14 @@ type txTraceTask struct { // TraceChain returns the structured logs created during the execution of EVM // between two blocks (excluding start) and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { - // Fetch the block interval that we want to trace - var from, to *types.Block - - switch start { - case rpc.PendingBlockNumber: - from = api.eth.miner.PendingBlock() - case rpc.LatestBlockNumber: - from = api.eth.blockchain.CurrentBlock() - default: - from = api.eth.blockchain.GetBlockByNumber(uint64(start)) - } - switch end { - case rpc.PendingBlockNumber: - to = api.eth.miner.PendingBlock() - case rpc.LatestBlockNumber: - to = api.eth.blockchain.CurrentBlock() - default: - to = api.eth.blockchain.GetBlockByNumber(uint64(end)) - } - // Trace the chain if we've found all our blocks - if from == nil { - return nil, fmt.Errorf("starting block #%d not found", start) +func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { // Fetch the block interval that we want to trace + from, err := api.blockByNumber(ctx, start) + if err != nil { + return nil, err } - if to == nil { - return nil, fmt.Errorf("end block #%d not found", end) + to, err := api.blockByNumber(ctx, end) + if err != nil { + return nil, err } if from.Number().Cmp(to.Number()) >= 0 { return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start) @@ -138,7 +218,7 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block // traceChain configures a new tracer according to the provided configuration, and // executes all the transactions contained within. The return value will be one item // per transaction, dependent on the requested tracer. -func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) { +func (api *API) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) { // Tracing a chain is a **long** operation, only do with subscriptions notifier, supported := rpc.NotifierFromContext(ctx) if !supported { @@ -146,46 +226,25 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl } sub := notifier.CreateSubscription() - // Ensure we have a valid starting state before doing any work - origin := start.NumberU64() - database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) - - if number := start.NumberU64(); number > 0 { - start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) - if start == nil { - return nil, fmt.Errorf("parent block #%d not found", number-1) - } + // Shift the border to a block ahead in order to get the states + // before these blocks. + endBlock, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(end.NumberU64()-1), end.ParentHash()) + if err != nil { + return nil, err + } + // Prepare all the states for tracing. Note this procedure can take very + // long time. Timeout mechanism is necessary. + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec } - statedb, err := state.New(start.Root(), database, nil) + states, release, err := api.backend.StatesInRange(ctx, start, endBlock, reexec) if err != nil { - // If the starting state is missing, allow some number of blocks to be reexecuted - reexec := defaultTraceReexec - if config != nil && config.Reexec != nil { - reexec = *config.Reexec - } - // Find the most recent block that has the state available - for i := uint64(0); i < reexec; i++ { - start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) - if start == nil { - break - } - if statedb, err = state.New(start.Root(), database, nil); err == nil { - break - } - } - // If we still don't have the state available, bail out - if err != nil { - switch err.(type) { - case *trie.MissingNodeError: - return nil, errors.New("required historical state unavailable") - default: - return nil, err - } - } + return nil, err } - // Execute all the transaction contained within the chain concurrently for each block - blocks := int(end.NumberU64() - origin) + defer release() // Release all the resources in the last step. + blocks := int(end.NumberU64() - start.NumberU64()) threads := runtime.NumCPU() if threads > blocks { threads = blocks @@ -202,8 +261,8 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // Fetch and execute the next block trace tasks for task := range tasks { - signer := types.MakeSigner(api.eth.blockchain.Config(), task.block.Number()) - blockCtx := core.NewEVMBlockContext(task.block.Header(), api.eth.blockchain, nil) + signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number()) + blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil) // Trace all the transactions contained within for i, tx := range task.block.Transactions() { msg, _ := tx.AsMessage(signer) @@ -214,7 +273,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl break } // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - task.statedb.Finalise(api.eth.blockchain.Config().IsEIP158(task.block.Number())) + task.statedb.Finalise(api.backend.ChainConfig().IsEIP158(task.block.Number())) task.results[i] = &txTraceResult{Result: res} } // Stream the result back to the user or abort on teardown @@ -235,7 +294,6 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl number uint64 traced uint64 failed error - proot common.Hash ) // Ensure everything is properly cleaned up on any exit path defer func() { @@ -262,60 +320,23 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl } // Print progress logs if long enough time elapsed if time.Since(logged) > 8*time.Second { - if number > origin { - nodes, imgs := database.TrieDB().Size() - log.Info("Tracing chain segment", "start", origin, "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin), "memory", nodes+imgs) - } else { - log.Info("Preparing state for chain trace", "block", number, "start", origin, "elapsed", time.Since(begin)) - } logged = time.Now() + log.Info("Tracing chain segment", "start", start.NumberU64(), "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin)) } // Retrieve the next block to trace - block := api.eth.blockchain.GetBlockByNumber(number) - if block == nil { - failed = fmt.Errorf("block #%d not found", number) - break - } - // Send the block over to the concurrent tracers (if not in the fast-forward phase) - if number > origin { - txs := block.Transactions() - - select { - case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: block, rootref: proot, results: make([]*txTraceResult, len(txs))}: - case <-notifier.Closed(): - return - } - traced += uint64(len(txs)) - } - // Generate the next state snapshot fast without tracing - _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{}) + block, err := api.blockByNumber(ctx, rpc.BlockNumber(number)) if err != nil { failed = err break } - // Finalize the state so any modifications are written to the trie - root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number())) - if err != nil { - failed = err - break - } - statedb, err = state.New(root, database, nil) - if err != nil { - failed = err - break - } - // Reference the trie twice, once for us, once for the tracer - database.TrieDB().Reference(root, common.Hash{}) - if number >= origin { - database.TrieDB().Reference(root, common.Hash{}) - } - // Dereference all past tries we ourselves are done working with - if proot != (common.Hash{}) { - database.TrieDB().Dereference(proot) + // Send the block over to the concurrent tracers (if not in the fast-forward phase) + txs := block.Transactions() + select { + case tasks <- &blockTraceTask{statedb: states[int(number-start.NumberU64()-1)], block: block, results: make([]*txTraceResult, len(txs))}: + case <-notifier.Closed(): + return } - proot = root - - // TODO(karalabe): Do we need the preimages? Won't they accumulate too much? + traced += uint64(len(txs)) } }() @@ -323,7 +344,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl go func() { var ( done = make(map[uint64]*blockTraceResult) - next = origin + 1 + next = start.NumberU64() + 1 ) for res := range results { // Queue up next received result @@ -334,9 +355,6 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl } done[uint64(result.Block)] = result - // Dereference any paret tries held in memory by this task - database.TrieDB().Dereference(res.rootref) - // Stream completed traces to the user, aborting on the first error for result, ok := done[next]; ok; result, ok = done[next] { if len(result.Traces) > 0 || next == end.NumberU64() { @@ -352,38 +370,27 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // TraceBlockByNumber returns the structured logs created during the execution of // EVM and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { - // Fetch the block that we want to trace - var block *types.Block - - switch number { - case rpc.PendingBlockNumber: - block = api.eth.miner.PendingBlock() - case rpc.LatestBlockNumber: - block = api.eth.blockchain.CurrentBlock() - default: - block = api.eth.blockchain.GetBlockByNumber(uint64(number)) - } - // Trace the block if it was found - if block == nil { - return nil, fmt.Errorf("block #%d not found", number) +func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { + block, err := api.blockByNumber(ctx, number) + if err != nil { + return nil, err } return api.traceBlock(ctx, block, config) } // TraceBlockByHash returns the structured logs created during the execution of // EVM and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { - block := api.eth.blockchain.GetBlockByHash(hash) - if block == nil { - return nil, fmt.Errorf("block %#x not found", hash) +func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { + block, err := api.blockByHash(ctx, hash) + if err != nil { + return nil, err } return api.traceBlock(ctx, block, config) } // TraceBlock returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) { +func (api *API) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) { block := new(types.Block) if err := rlp.Decode(bytes.NewReader(blob), block); err != nil { return nil, fmt.Errorf("could not decode block: %v", err) @@ -393,7 +400,7 @@ func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config // TraceBlockFromFile returns the structured logs created during the execution of // EVM and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) { +func (api *API) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) { blob, err := ioutil.ReadFile(file) if err != nil { return nil, fmt.Errorf("could not read file: %v", err) @@ -404,8 +411,8 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, // TraceBadBlock returns the structured logs created during the execution of // EVM against a block pulled from the pool of bad ones and returns them as a JSON // object. -func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { - for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) { +func (api *API) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { + for _, block := range rawdb.ReadAllBadBlocks(api.backend.ChainDb()) { if block.Hash() == hash { return api.traceBlock(ctx, block, config) } @@ -416,10 +423,10 @@ func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, // StandardTraceBlockToFile dumps the structured logs created during the // execution of EVM to the local file system and returns a list of files // to the caller. -func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { - block := api.eth.blockchain.GetBlockByHash(hash) - if block == nil { - return nil, fmt.Errorf("block %#x not found", hash) +func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { + block, err := api.blockByHash(ctx, hash) + if err != nil { + return nil, err } return api.standardTraceBlockToFile(ctx, block, config) } @@ -427,8 +434,8 @@ func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash c // StandardTraceBadBlockToFile dumps the structured logs created during the // execution of EVM against a block pulled from the pool of bad ones to the // local file system and returns a list of files to the caller. -func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { - for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) { +func (api *API) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { + for _, block := range rawdb.ReadAllBadBlocks(api.backend.ChainDb()) { if block.Hash() == hash { return api.standardTraceBlockToFile(ctx, block, config) } @@ -439,27 +446,27 @@ func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, has // traceBlock configures a new tracer according to the provided configuration, and // executes all the transactions contained within. The return value will be one item // per transaction, dependent on the requestd tracer. -func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { - // Create the parent state database - if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil { - return nil, err +func (api *API) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { + if block.NumberU64() == 0 { + return nil, errors.New("genesis is not traceable") } - parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if parent == nil { - return nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) + if err != nil { + return nil, err } reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, err := api.computeStateDB(parent, reexec) + statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec) if err != nil { return nil, err } + defer release() + // Execute all the transaction contained within the block concurrently var ( - signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) - + signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) txs = block.Transactions() results = make([]*txTraceResult, len(txs)) @@ -470,7 +477,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, if threads > len(txs) { threads = len(txs) } - blockCtx := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) + blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) for th := 0; th < threads; th++ { pend.Add(1) go func() { @@ -497,7 +504,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, msg, _ := tx.AsMessage(signer) txContext := core.NewEVMTxContext(msg) - vmenv := vm.NewEVM(blockCtx, txContext, statedb, api.eth.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(blockCtx, txContext, statedb, api.backend.ChainConfig(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { failed = err break @@ -519,29 +526,30 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, // standardTraceBlockToFile configures a new tracer which uses standard JSON output, // and traces either a full block or an individual transaction. The return value will // be one filename per transaction traced. -func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) { +func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) { // If we're tracing a single transaction, make sure it's present if config != nil && config.TxHash != (common.Hash{}) { if !containsTx(block, config.TxHash) { return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash) } } - // Create the parent state database - if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil { - return nil, err + if block.NumberU64() == 0 { + return nil, errors.New("genesis is not traceable") } - parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if parent == nil { - return nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) + if err != nil { + return nil, err } reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, err := api.computeStateDB(parent, reexec) + statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec) if err != nil { return nil, err } + defer release() + // Retrieve the tracing configurations, or use default values var ( logConfig vm.LogConfig @@ -555,10 +563,10 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block // Execute transaction, either tracing all or just the requested one var ( - signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) dumps []string - chainConfig = api.eth.blockchain.Config() - vmctx = core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) + signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) + chainConfig = api.backend.ChainConfig() + vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) canon = true ) // Check if there are any overrides: the caller may wish to enable a future @@ -645,139 +653,73 @@ func containsTx(block *types.Block, hash common.Hash) bool { return false } -// computeStateDB retrieves the state database associated with a certain block. -// If no state is locally available for the given block, a number of blocks are -// attempted to be reexecuted to generate the desired state. -func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*state.StateDB, error) { - // If we have the state fully available, use that - statedb, err := api.eth.blockchain.StateAt(block.Root()) - if err == nil { - return statedb, nil - } - // Otherwise try to reexec blocks until we find a state or reach our limit - origin := block.NumberU64() - database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) - - for i := uint64(0); i < reexec; i++ { - block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if block == nil { - break - } - if statedb, err = state.New(block.Root(), database, nil); err == nil { - break - } - } - if err != nil { - switch err.(type) { - case *trie.MissingNodeError: - return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) - default: - return nil, err - } - } - // State was available at historical point, regenerate - var ( - start = time.Now() - logged time.Time - proot common.Hash - ) - for block.NumberU64() < origin { - // Print progress logs if long enough time elapsed - if time.Since(logged) > 8*time.Second { - log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start)) - logged = time.Now() - } - // Retrieve the next block to regenerate and process it - if block = api.eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil { - return nil, fmt.Errorf("block #%d not found", block.NumberU64()+1) - } - _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{}) - if err != nil { - return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) - } - // Finalize the state so any modifications are written to the trie - root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number())) - if err != nil { - return nil, err - } - statedb, err = state.New(root, database, nil) - if err != nil { - return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) - } - database.TrieDB().Reference(root, common.Hash{}) - if proot != (common.Hash{}) { - database.TrieDB().Dereference(proot) - } - proot = root - } - nodes, imgs := database.TrieDB().Size() - log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) - return statedb, nil -} - // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { - // Retrieve the transaction and assemble its EVM context - tx, blockHash, _, index := rawdb.ReadTransaction(api.eth.ChainDb(), hash) - if tx == nil { - return nil, fmt.Errorf("transaction %#x not found", hash) +func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { + _, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) + if err != nil { + return nil, err + } + // It shouldn't happen in practice. + if blockNumber == 0 { + return nil, errors.New("genesis is not traceable") } reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } - // Retrieve the block - block := api.eth.blockchain.GetBlockByHash(blockHash) - if block == nil { - return nil, fmt.Errorf("block %#x not found", blockHash) + block, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber), blockHash) + if err != nil { + return nil, err } - msg, vmctx, statedb, err := api.computeTxEnv(block, int(index), reexec) + msg, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec) if err != nil { return nil, err } - // Trace the transaction and return + defer release() + return api.traceTx(ctx, msg, vmctx, statedb, config) } -// TraceCall lets you trace a given eth_call. It collects the structured logs created during the execution of EVM -// if the given transaction was added on top of the provided block and returns them as a JSON object. +// TraceCall lets you trace a given eth_call. It collects the structured logs +// created during the execution of EVM if the given transaction was added on +// top of the provided block and returns them as a JSON object. // You can provide -2 as a block number to trace on top of the pending block. -func (api *PrivateDebugAPI) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (interface{}, error) { - // First try to retrieve the state - statedb, header, err := api.eth.APIBackend.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) +func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (interface{}, error) { + // Try to retrieve the specified block + var ( + err error + block *types.Block + ) + if hash, ok := blockNrOrHash.Hash(); ok { + block, err = api.blockByHash(ctx, hash) + } else if number, ok := blockNrOrHash.Number(); ok { + block, err = api.blockByNumber(ctx, number) + } if err != nil { - // Try to retrieve the specified block - var block *types.Block - if hash, ok := blockNrOrHash.Hash(); ok { - block = api.eth.blockchain.GetBlockByHash(hash) - } else if number, ok := blockNrOrHash.Number(); ok { - block = api.eth.blockchain.GetBlockByNumber(uint64(number)) - } - if block == nil { - return nil, fmt.Errorf("block %v not found: %v", blockNrOrHash, err) - } - // try to recompute the state - reexec := defaultTraceReexec - if config != nil && config.Reexec != nil { - reexec = *config.Reexec - } - _, _, statedb, err = api.computeTxEnv(block, 0, reexec) - if err != nil { - return nil, err - } + return nil, err + } + // try to recompute the state + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec } + statedb, release, err := api.backend.StateAtBlock(ctx, block, reexec) + if err != nil { + return nil, err + } + defer release() // Execute the trace - msg := args.ToMessage(api.eth.APIBackend.RPCGasCap()) - vmctx := core.NewEVMBlockContext(header, api.eth.blockchain, nil) + msg := args.ToMessage(api.backend.RPCGasCap()) + vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) return api.traceTx(ctx, msg, vmctx, statedb, config) } // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *API) traceTx(ctx context.Context, message core.Message, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( tracer vm.Tracer @@ -794,14 +736,14 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } } // Constuct the JavaScript tracer to execute with - if tracer, err = tracers.New(*config.Tracer, txContext); err != nil { + if tracer, err = New(*config.Tracer, txContext); err != nil { return nil, err } // Handle timeouts and RPC cancellations deadlineCtx, cancel := context.WithTimeout(ctx, timeout) go func() { <-deadlineCtx.Done() - tracer.(*tracers.Tracer).Stop(errors.New("execution timeout")) + tracer.(*Tracer).Stop(errors.New("execution timeout")) }() defer cancel() @@ -812,7 +754,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v tracer = vm.NewStructLogger(config.LogConfig) } // Run the transaction with tracing enabled. - vmenv := vm.NewEVM(vmctx, txContext, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) + vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer}) result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { @@ -833,7 +775,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v StructLogs: ethapi.FormatLogs(tracer.StructLogs()), }, nil - case *tracers.Tracer: + case *Tracer: return tracer.GetResult() default: @@ -841,41 +783,15 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } } -// computeTxEnv returns the execution environment of a certain transaction. -func (api *PrivateDebugAPI) computeTxEnv(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { - // Create the parent state database - parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if parent == nil { - return nil, vm.BlockContext{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) - } - statedb, err := api.computeStateDB(parent, reexec) - if err != nil { - return nil, vm.BlockContext{}, nil, err - } - - if txIndex == 0 && len(block.Transactions()) == 0 { - return nil, vm.BlockContext{}, statedb, nil - } - - // Recompute transactions up to the target index. - signer := types.MakeSigner(api.eth.blockchain.Config(), block.Number()) - - for idx, tx := range block.Transactions() { - // Assemble the transaction call message and return if the requested offset - msg, _ := tx.AsMessage(signer) - txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) - if idx == txIndex { - return msg, context, statedb, nil - } - // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, txContext, statedb, api.eth.blockchain.Config(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) - } - // Ensure any modifications are committed to the state - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) +// APIs return the collection of RPC services the tracer package offers. +func APIs(backend Backend) []rpc.API { + // Append all the local APIs and return + return []rpc.API{ + { + Namespace: "debug", + Version: "1.0", + Service: NewAPI(backend), + Public: false, + }, } - return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go new file mode 100644 index 0000000000..688b983bab --- /dev/null +++ b/eth/tracers/api_test.go @@ -0,0 +1,487 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package tracers + +import ( + "bytes" + "context" + "crypto/ecdsa" + "errors" + "fmt" + "math/big" + "reflect" + "sort" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" + "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/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + errStateNotFound = errors.New("state not found") + errBlockNotFound = errors.New("block not found") + errTransactionNotFound = errors.New("transaction not found") +) + +type testBackend struct { + chainConfig *params.ChainConfig + engine consensus.Engine + chaindb ethdb.Database + chain *core.BlockChain +} + +func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { + backend := &testBackend{ + chainConfig: params.TestChainConfig, + engine: ethash.NewFaker(), + chaindb: rawdb.NewMemoryDatabase(), + } + // Generate blocks for testing + gspec.Config = backend.chainConfig + var ( + gendb = rawdb.NewMemoryDatabase() + genesis = gspec.MustCommit(gendb) + ) + blocks, _ := core.GenerateChain(backend.chainConfig, genesis, backend.engine, gendb, n, generator) + + // Import the canonical chain + gspec.MustCommit(backend.chaindb) + cacheConfig := &core.CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, + TrieDirtyDisabled: true, // Archive mode + } + chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, backend.chainConfig, backend.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + backend.chain = chain + return backend +} + +func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + return b.chain.GetHeaderByHash(hash), nil +} + +func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber { + return b.chain.CurrentHeader(), nil + } + return b.chain.GetHeaderByNumber(uint64(number)), nil +} + +func (b *testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + return b.chain.GetBlockByHash(hash), nil +} + +func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber { + return b.chain.CurrentBlock(), nil + } + return b.chain.GetBlockByNumber(uint64(number)), nil +} + +func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { + tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash) + if tx == nil { + return nil, common.Hash{}, 0, 0, errTransactionNotFound + } + return tx, hash, blockNumber, index, nil +} + +func (b *testBackend) RPCGasCap() uint64 { + return 25000000 +} + +func (b *testBackend) ChainConfig() *params.ChainConfig { + return b.chainConfig +} + +func (b *testBackend) Engine() consensus.Engine { + return b.engine +} + +func (b *testBackend) ChainDb() ethdb.Database { + return b.chaindb +} + +func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { + statedb, err := b.chain.StateAt(block.Root()) + if err != nil { + return nil, nil, errStateNotFound + } + return statedb, func() {}, nil +} + +func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { + parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, vm.BlockContext{}, nil, nil, errBlockNotFound + } + statedb, err := b.chain.StateAt(parent.Root()) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, errStateNotFound + } + if txIndex == 0 && len(block.Transactions()) == 0 { + return nil, vm.BlockContext{}, statedb, func() {}, nil + } + // Recompute transactions up to the target index. + signer := types.MakeSigner(b.chainConfig, block.Number()) + for idx, tx := range block.Transactions() { + msg, _ := tx.AsMessage(signer) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), b.chain, nil) + if idx == txIndex { + return msg, context, statedb, func() {}, nil + } + vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{}) + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) +} + +func (b *testBackend) StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { + var result []*state.StateDB + for number := fromBlock.NumberU64(); number <= toBlock.NumberU64(); number += 1 { + block := b.chain.GetBlockByNumber(number) + if block == nil { + return nil, nil, errBlockNotFound + } + statedb, err := b.chain.StateAt(block.Root()) + if err != nil { + return nil, nil, errStateNotFound + } + result = append(result, statedb) + } + return result, func() {}, nil +} + +func TestTraceCall(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }} + genBlocks := 10 + signer := types.HomesteadSigner{} + api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key) + b.AddTx(tx) + })) + + var testSuite = []struct { + blockNumber rpc.BlockNumber + call ethapi.CallArgs + config *TraceConfig + expectErr error + expect interface{} + }{ + // Standard JSON trace upon the genesis, plain transfer. + { + blockNumber: rpc.BlockNumber(0), + call: ethapi.CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + // Standard JSON trace upon the head, plain transfer. + { + blockNumber: rpc.BlockNumber(genBlocks), + call: ethapi.CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + // Standard JSON trace upon the non-existent block, error expects + { + blockNumber: rpc.BlockNumber(genBlocks + 1), + call: ethapi.CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: fmt.Errorf("block #%d not found", genBlocks+1), + expect: nil, + }, + // Standard JSON trace upon the latest block + { + blockNumber: rpc.LatestBlockNumber, + call: ethapi.CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + // Standard JSON trace upon the pending block + { + blockNumber: rpc.PendingBlockNumber, + call: ethapi.CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + } + for _, testspec := range testSuite { + result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config) + if testspec.expectErr != nil { + if err == nil { + t.Errorf("Expect error %v, get nothing", testspec.expectErr) + continue + } + if !reflect.DeepEqual(err, testspec.expectErr) { + t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err) + } + } else { + if err != nil { + t.Errorf("Expect no error, get %v", err) + continue + } + if !reflect.DeepEqual(result, testspec.expect) { + t.Errorf("Result mismatch, want %v, get %v", testspec.expect, result) + } + } + } +} + +func TestTraceTransaction(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(2) + genesis := &core.Genesis{Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + }} + target := common.Hash{} + signer := types.HomesteadSigner{} + api := NewAPI(newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key) + b.AddTx(tx) + target = tx.Hash() + })) + result, err := api.TraceTransaction(context.Background(), target, nil) + if err != nil { + t.Errorf("Failed to trace transaction %v", err) + } + if !reflect.DeepEqual(result, ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }) { + t.Error("Transaction tracing result is different") + } +} + +func TestTraceBlock(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }} + genBlocks := 10 + signer := types.HomesteadSigner{} + api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key) + b.AddTx(tx) + })) + + var testSuite = []struct { + blockNumber rpc.BlockNumber + config *TraceConfig + expect interface{} + expectErr error + }{ + // Trace genesis block, expect error + { + blockNumber: rpc.BlockNumber(0), + config: nil, + expect: nil, + expectErr: errors.New("genesis is not traceable"), + }, + // Trace head block + { + blockNumber: rpc.BlockNumber(genBlocks), + config: nil, + expectErr: nil, + expect: []*txTraceResult{ + { + Result: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + }, + }, + // Trace non-existent block + { + blockNumber: rpc.BlockNumber(genBlocks + 1), + config: nil, + expectErr: fmt.Errorf("block #%d not found", genBlocks+1), + expect: nil, + }, + // Trace latest block + { + blockNumber: rpc.LatestBlockNumber, + config: nil, + expectErr: nil, + expect: []*txTraceResult{ + { + Result: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + }, + }, + // Trace pending block + { + blockNumber: rpc.PendingBlockNumber, + config: nil, + expectErr: nil, + expect: []*txTraceResult{ + { + Result: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + }, + }, + } + for _, testspec := range testSuite { + result, err := api.TraceBlockByNumber(context.Background(), testspec.blockNumber, testspec.config) + if testspec.expectErr != nil { + if err == nil { + t.Errorf("Expect error %v, get nothing", testspec.expectErr) + continue + } + if !reflect.DeepEqual(err, testspec.expectErr) { + t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err) + } + } else { + if err != nil { + t.Errorf("Expect no error, get %v", err) + continue + } + if !reflect.DeepEqual(result, testspec.expect) { + t.Errorf("Result mismatch, want %v, get %v", testspec.expect, result) + } + } + } +} + +type Account struct { + key *ecdsa.PrivateKey + addr common.Address +} + +type Accounts []Account + +func (a Accounts) Len() int { return len(a) } +func (a Accounts) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), a[j].addr.Bytes()) < 0 } + +func newAccounts(n int) (accounts Accounts) { + for i := 0; i < n; i++ { + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + accounts = append(accounts, Account{key: key, addr: addr}) + } + sort.Sort(accounts) + return accounts +} diff --git a/les/api_backend.go b/les/api_backend.go index 9fbc21f459..0839614901 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -292,3 +292,15 @@ func (b *LesApiBackend) Engine() consensus.Engine { func (b *LesApiBackend) CurrentHeader() *types.Header { return b.eth.blockchain.CurrentHeader() } + +func (b *LesApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { + return b.eth.stateAtBlock(ctx, block, reexec) +} + +func (b *LesApiBackend) StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { + return b.eth.statesInRange(ctx, fromBlock, toBlock, reexec) +} + +func (b *LesApiBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { + return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) +} diff --git a/les/state_accessor.go b/les/state_accessor.go new file mode 100644 index 0000000000..3c9143c875 --- /dev/null +++ b/les/state_accessor.go @@ -0,0 +1,88 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package les + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/light" +) + +// stateAtBlock retrieves the state database associated with a certain block. +func (leth *LightEthereum) stateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { + return light.NewState(ctx, block.Header(), leth.odr), func() {}, nil +} + +// statesInRange retrieves a batch of state databases associated with the specific +// block ranges. +func (leth *LightEthereum) statesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { + var states []*state.StateDB + for number := fromBlock.NumberU64(); number <= toBlock.NumberU64(); number++ { + header, err := leth.blockchain.GetHeaderByNumberOdr(ctx, number) + if err != nil { + return nil, nil, err + } + states = append(states, light.NewState(ctx, header, leth.odr)) + } + return states, nil, nil +} + +// stateAtTransaction returns the execution environment of a certain transaction. +func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { + // Short circuit if it's genesis block. + if block.NumberU64() == 0 { + return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis") + } + // Create the parent state database + parent, err := leth.blockchain.GetBlock(ctx, block.ParentHash(), block.NumberU64()-1) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, err + } + statedb, _, err := leth.stateAtBlock(ctx, parent, reexec) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, err + } + if txIndex == 0 && len(block.Transactions()) == 0 { + return nil, vm.BlockContext{}, statedb, func() {}, nil + } + // Recompute transactions up to the target index. + signer := types.MakeSigner(leth.blockchain.Config(), block.Number()) + for idx, tx := range block.Transactions() { + // Assemble the transaction call message and return if the requested offset + msg, _ := tx.AsMessage(signer) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil) + if idx == txIndex { + return msg, context, statedb, func() {}, nil + } + // Not yet the searched for transaction, execute on top of the current state + vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{}) + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + // Ensure any modifications are committed to the state + // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) +} From d2779ed7acde5d0fa3ab53fcdc11ab1697703300 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 25 Jan 2021 19:06:52 +0100 Subject: [PATCH 153/235] eth, p2p: reserve half peer slots for snap peers during snap sync (#22171) * eth, p2p: reserve half peer slots for snap peers during snap sync * eth: less logging * eth: rework the eth/snap peer reservation logic * eth: rework the eth/snap peer reservation logic (again) --- eth/handler.go | 15 +++++++++++++-- eth/peerset.go | 11 ++++++++++- eth/sync.go | 4 ++++ p2p/peer.go | 10 ++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index f6366d9af1..5ae0925bb5 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -250,9 +250,20 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { peer.Log().Debug("Ethereum handshake failed", "err", err) return err } + reject := false // reserved peer slots + if atomic.LoadUint32(&h.snapSync) == 1 && !peer.SupportsCap("snap", 1) { + // If we are running snap-sync, we want to reserve roughly half the peer + // slots for peers supporting the snap protocol. + // The logic here is; we only allow up to 5 more non-snap peers than snap-peers. + if all, snp := h.peers.Len(), h.peers.SnapLen(); all-snp > snp+5 { + reject = true + } + } // Ignore maxPeers if this is a trusted peer - if h.peers.Len() >= h.maxPeers && !peer.Peer.Info().Network.Trusted { - return p2p.DiscTooManyPeers + if !peer.Peer.Info().Network.Trusted { + if reject || h.peers.Len() >= h.maxPeers { + return p2p.DiscTooManyPeers + } } peer.Log().Debug("Ethereum peer connected", "name", peer.Name()) diff --git a/eth/peerset.go b/eth/peerset.go index bf5785ff3f..663c5ce36b 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -259,7 +259,7 @@ func (ps *peerSet) ethPeersWithoutTransaction(hash common.Hash) []*ethPeer { } // Len returns if the current number of `eth` peers in the set. Since the `snap` -// peers are tied to the existnce of an `eth` connection, that will always be a +// peers are tied to the existence of an `eth` connection, that will always be a // subset of `eth`. func (ps *peerSet) Len() int { ps.lock.RLock() @@ -268,6 +268,15 @@ func (ps *peerSet) Len() int { return len(ps.ethPeers) } +// SnapLen returns if the current number of `snap` peers in the set. Since the `snap` +// peers are tied to the existence of an `eth` connection, that will always be a +// subset of `eth`. +func (ps *peerSet) SnapLen() int { + ps.lock.RLock() + defer ps.lock.RUnlock() + return len(ps.snapPeers) +} + // ethPeerWithHighestTD retrieves the known peer with the currently highest total // difficulty. func (ps *peerSet) ethPeerWithHighestTD() *eth.Peer { diff --git a/eth/sync.go b/eth/sync.go index 03a5165245..eedb8b7476 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -329,6 +329,10 @@ func (h *handler) doSync(op *chainSyncOp) error { log.Info("Fast sync complete, auto disabling") atomic.StoreUint32(&h.fastSync, 0) } + if atomic.LoadUint32(&h.snapSync) == 1 { + log.Info("Snap sync complete, auto disabling") + atomic.StoreUint32(&h.snapSync, 0) + } // If we've successfully finished a sync cycle and passed any required checkpoint, // enable accepting transactions from the network. head := h.chain.CurrentBlock() diff --git a/p2p/peer.go b/p2p/peer.go index a9c3cf01da..43ccef5c43 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -158,6 +158,16 @@ func (p *Peer) Caps() []Cap { return p.rw.caps } +// SupportsCap returns true if the peer supports the given protocol/version +func (p *Peer) SupportsCap(protocol string, version uint) bool { + for _, cap := range p.rw.caps { + if cap.Name == protocol { + return version <= cap.Version + } + } + return false +} + // RemoteAddr returns the remote address of the network connection. func (p *Peer) RemoteAddr() net.Addr { return p.rw.fd.RemoteAddr() From 7202b410b064c17c0648c4c6c212dc4c2a787907 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 25 Jan 2021 21:40:14 +0100 Subject: [PATCH 154/235] tests/fuzzers/abi: fixed one-off panic with int.Min64 value (#22233) * tests/fuzzers/abi: fixed one-off panic with int.Min64 value * tests/fuzzers/abi: fixed one-off panic with int.Min64 value --- tests/fuzzers/abi/abifuzzer.go | 5 ++++- tests/fuzzers/abi/abifuzzer_test.go | 4 +--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/fuzzers/abi/abifuzzer.go b/tests/fuzzers/abi/abifuzzer.go index 8c083b371e..60233d158a 100644 --- a/tests/fuzzers/abi/abifuzzer.go +++ b/tests/fuzzers/abi/abifuzzer.go @@ -161,7 +161,10 @@ func getUInt(fuzzer *fuzz.Fuzzer) int { var i int fuzzer.Fuzz(&i) if i < 0 { - i *= -1 + i = -i + if i < 0 { + return 0 + } } return i } diff --git a/tests/fuzzers/abi/abifuzzer_test.go b/tests/fuzzers/abi/abifuzzer_test.go index c59c45ab1a..423a3cd232 100644 --- a/tests/fuzzers/abi/abifuzzer_test.go +++ b/tests/fuzzers/abi/abifuzzer_test.go @@ -23,9 +23,7 @@ import ( // TestReplicate can be used to replicate crashers from the fuzzing tests. // Just replace testString with the data in .quoted func TestReplicate(t *testing.T) { - testString := "N\xef\xbf0\xef\xbf99000000000000" + - "000000000000" - + testString := "\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00" data := []byte(testString) runFuzzer(data) } From 573f373d2bb264af6a2e39c3b219c999fc242122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 26 Jan 2021 13:13:55 +0200 Subject: [PATCH 155/235] internal/ethapi: print tx details when submitting (#22170) This adds more info about submitted transactions in log messages. Co-authored-by: Felix Lange --- internal/ethapi/api.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index b424435b50..9c3f6b9161 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1558,16 +1558,18 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c if err := b.SendTx(ctx, tx); err != nil { return common.Hash{}, err } + // Print a log with full tx details for manual investigations and interventions + signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) + from, err := types.Sender(signer, tx) + if err != nil { + return common.Hash{}, err + } + if tx.To() == nil { - signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) - from, err := types.Sender(signer, tx) - if err != nil { - return common.Hash{}, err - } addr := crypto.CreateAddress(from, tx.Nonce()) - log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex()) + log.Info("Submitted contract creation", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "contract", addr.Hex(), "value", tx.Value()) } else { - log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To()) + log.Info("Submitted transaction", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "recipient", tx.To(), "value", tx.Value()) } return tx.Hash(), nil } From 14d495491ddf31464135563720705fc2c1e5eb22 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 26 Jan 2021 12:15:31 +0100 Subject: [PATCH 156/235] core/state: fix panic in state dumping (#22225) --- core/state/dump.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/dump.go b/core/state/dump.go index 9bb946d14b..b25da714fd 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -138,7 +138,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, account.SecureKey = it.Key } addr := common.BytesToAddress(addrBytes) - obj := newObject(nil, addr, data) + obj := newObject(s, addr, data) if !excludeCode { account.Code = common.Bytes2Hex(obj.Code(s.db)) } From 681618275cc5a4819af446029a064d266190ae8c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 26 Jan 2021 12:17:11 +0100 Subject: [PATCH 157/235] core: speed up header import (#21967) This PR implements the following modifications - Don't shortcut check if block is present, thus avoid disk lookup - Don't check hash ancestry in early-check (it's still done in parallel checker) - Don't check time.Now for every single header Charts and background info can be found here: https://github.com/holiman/headerimport/blob/main/README.md With these changes, writing 1M headers goes down to from 80s to 62s. --- consensus/ethash/consensus.go | 36 +++++++++++++++++------------------ core/headerchain.go | 11 ++++++----- core/rawdb/freezer_table.go | 5 +++++ 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index ae0905ee3a..c58fe7a530 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -39,11 +39,11 @@ import ( // Ethash proof-of-work protocol constants. var ( - FrontierBlockReward = big.NewInt(5e+18) // Block reward in wei for successfully mining a block - ByzantiumBlockReward = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium - ConstantinopleBlockReward = big.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople - maxUncles = 2 // Maximum number of uncles allowed in a single block - allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks + FrontierBlockReward = big.NewInt(5e+18) // Block reward in wei for successfully mining a block + ByzantiumBlockReward = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium + ConstantinopleBlockReward = big.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople + maxUncles = 2 // Maximum number of uncles allowed in a single block + allowedFutureBlockTimeSeconds = int64(15) // Max seconds from current time allowed for blocks, before they're considered future blocks // calcDifficultyEip2384 is the difficulty adjustment algorithm as specified by EIP 2384. // It offsets the bomb 4M blocks from Constantinople, so in total 9M blocks. @@ -102,7 +102,7 @@ func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *ty return consensus.ErrUnknownAncestor } // Sanity checks passed, do a proper verification - return ethash.verifyHeader(chain, header, parent, false, seal) + return ethash.verifyHeader(chain, header, parent, false, seal, time.Now().Unix()) } // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers @@ -126,15 +126,16 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ // Create a task channel and spawn the verifiers var ( - inputs = make(chan int) - done = make(chan int, workers) - errors = make([]error, len(headers)) - abort = make(chan struct{}) + inputs = make(chan int) + done = make(chan int, workers) + errors = make([]error, len(headers)) + abort = make(chan struct{}) + unixNow = time.Now().Unix() ) for i := 0; i < workers; i++ { go func() { for index := range inputs { - errors[index] = ethash.verifyHeaderWorker(chain, headers, seals, index) + errors[index] = ethash.verifyHeaderWorker(chain, headers, seals, index, unixNow) done <- index } }() @@ -170,7 +171,7 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ return abort, errorsOut } -func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, index int) error { +func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, index int, unixNow int64) error { var parent *types.Header if index == 0 { parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1) @@ -180,10 +181,7 @@ func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainHeaderReader, head if parent == nil { return consensus.ErrUnknownAncestor } - if chain.GetHeader(headers[index].Hash(), headers[index].Number.Uint64()) != nil { - return nil // known block - } - return ethash.verifyHeader(chain, headers[index], parent, false, seals[index]) + return ethash.verifyHeader(chain, headers[index], parent, false, seals[index], unixNow) } // VerifyUncles verifies that the given block's uncles conform to the consensus @@ -234,7 +232,7 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == block.ParentHash() { return errDanglingUncle } - if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true); err != nil { + if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true, time.Now().Unix()); err != nil { return err } } @@ -244,14 +242,14 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo // verifyHeader checks whether a header conforms to the consensus rules of the // stock Ethereum ethash engine. // See YP section 4.3.4. "Block Header Validity" -func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool) error { +func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool, unixNow int64) error { // Ensure that the header's extra-data section is of a reasonable size if uint64(len(header.Extra)) > params.MaximumExtraDataSize { return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize) } // Verify the header's timestamp if !uncle { - if header.Time > uint64(time.Now().Add(allowedFutureBlockTime).Unix()) { + if header.Time > uint64(unixNow+allowedFutureBlockTimeSeconds) { return consensus.ErrFutureBlock } } diff --git a/core/headerchain.go b/core/headerchain.go index dc354549c0..dcd3644cd1 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -299,17 +299,18 @@ func (hc *HeaderChain) writeHeaders(headers []*types.Header) (result *headerWrit func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) (int, error) { // Do a sanity check that the provided chain is actually ordered and linked for i := 1; i < len(chain); i++ { - parentHash := chain[i-1].Hash() - if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != parentHash { + if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 { + hash := chain[i].Hash() + parentHash := chain[i-1].Hash() // Chain broke ancestry, log a message (programming error) and skip insertion - log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", chain[i].Hash(), + log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", hash, "parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", parentHash) return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, chain[i-1].Number, - parentHash.Bytes()[:4], i, chain[i].Number, chain[i].Hash().Bytes()[:4], chain[i].ParentHash[:4]) + parentHash.Bytes()[:4], i, chain[i].Number, hash.Bytes()[:4], chain[i].ParentHash[:4]) } // If the header is a banned one, straight out abort - if BadHashes[parentHash] { + if BadHashes[chain[i].ParentHash] { return i - 1, ErrBlacklistedHash } // If it's the last header in the cunk, we need to check it too diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index b9d8a274a8..cd273222b1 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -103,6 +103,11 @@ type freezerTable struct { lock sync.RWMutex // Mutex protecting the data file descriptors } +// NewFreezerTable opens the given path as a freezer table. +func NewFreezerTable(path, name string, disableSnappy bool) (*freezerTable, error) { + return newTable(path, name, metrics.NilMeter{}, metrics.NilMeter{}, metrics.NilGauge{}, disableSnappy) +} + // newTable opens a freezer table with default settings - 2G files func newTable(path string, name string, readMeter metrics.Meter, writeMeter metrics.Meter, sizeGauge metrics.Gauge, disableSnappy bool) (*freezerTable, error) { return newCustomTable(path, name, readMeter, writeMeter, sizeGauge, 2*1000*1000*1000, disableSnappy) From ad038b62899ae59160f9871573ec6786e95cb918 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 26 Jan 2021 16:01:13 +0100 Subject: [PATCH 158/235] accounts/scwallet: use go-ethereum crypto instead of go-ecdh (#22212) * accounts/scwallet: use go-ethereum crypto instead of go-ecdh github.com/wsddn/go-ecdh is a wrapper package for ECDH functionality with any elliptic curve. Since 'generic' ECDH is not required in accounts/scwallet (the curve is always secp256k1), we can just use the standard library functionality and our own crypto libraries to perform ECDH and save a dependency. * Update accounts/scwallet/securechannel.go Co-authored-by: Guillaume Ballet * Use the correct key Co-authored-by: Guillaume Ballet --- accounts/scwallet/securechannel.go | 21 +++++++-------------- go.mod | 1 - go.sum | 2 -- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/accounts/scwallet/securechannel.go b/accounts/scwallet/securechannel.go index 9b70c69dcc..10887a8b43 100644 --- a/accounts/scwallet/securechannel.go +++ b/accounts/scwallet/securechannel.go @@ -20,6 +20,7 @@ import ( "bytes" "crypto/aes" "crypto/cipher" + "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/sha512" @@ -27,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" pcsc "github.com/gballet/go-libpcsclite" - "github.com/wsddn/go-ecdh" "golang.org/x/crypto/pbkdf2" "golang.org/x/text/unicode/norm" ) @@ -63,26 +63,19 @@ type SecureChannelSession struct { // NewSecureChannelSession creates a new secure channel for the given card and public key. func NewSecureChannelSession(card *pcsc.Card, keyData []byte) (*SecureChannelSession, error) { // Generate an ECDSA keypair for ourselves - gen := ecdh.NewEllipticECDH(crypto.S256()) - private, public, err := gen.GenerateKey(rand.Reader) + key, err := crypto.GenerateKey() if err != nil { return nil, err } - - cardPublic, ok := gen.Unmarshal(keyData) - if !ok { - return nil, fmt.Errorf("could not unmarshal public key from card") - } - - secret, err := gen.GenerateSharedSecret(private, cardPublic) + cardPublic, err := crypto.UnmarshalPubkey(keyData) if err != nil { - return nil, err + return nil, fmt.Errorf("could not unmarshal public key from card: %v", err) } - + secret, _ := key.Curve.ScalarMult(cardPublic.X, cardPublic.Y, key.D.Bytes()) return &SecureChannelSession{ card: card, - secret: secret, - publicKey: gen.Marshal(public), + secret: secret.Bytes(), + publicKey: elliptic.Marshal(crypto.S256(), key.PublicKey.X, key.PublicKey.Y), }, nil } diff --git a/go.mod b/go.mod index ffea94f6cf..48c6889fbe 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,6 @@ require ( github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef - github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 diff --git a/go.sum b/go.sum index fefe5afb14..4b799d77fa 100644 --- a/go.sum +++ b/go.sum @@ -365,8 +365,6 @@ github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZ github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= -github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= From 9c5729311e89bff5acc1f33c1f4a646dfcce7dab Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 26 Jan 2021 15:43:12 +0000 Subject: [PATCH 159/235] accounts/scwallet: update documentation (#22242) --- accounts/scwallet/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/accounts/scwallet/README.md b/accounts/scwallet/README.md index cfca916b3a..4313d9c6b2 100644 --- a/accounts/scwallet/README.md +++ b/accounts/scwallet/README.md @@ -31,12 +31,16 @@ Write down the URL (`keycard://044def09` in this example). Then ask `geth` to open the wallet: ``` - > personal.openWallet("keycard://044def09") - Please enter the pairing password: + > personal.openWallet("keycard://044def09", "pairing password") ``` - Enter the pairing password that you have received during card initialization. Same with the PIN that you will subsequently be - asked for. + The pairing password has been generated during the card initialization process. + + The process needs to be repeated once more with the PIN: + + ``` + > personal.openWallet("keycard://044def09", "PIN number") + ``` If everything goes well, you should see your new account when typing `personal` on the console: From a72fa88a0d661f86a06d3d89c755a4e7dcff1e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Tue, 26 Jan 2021 21:41:35 +0100 Subject: [PATCH 160/235] les: switch to new discv5 (#21940) This PR enables running the new discv5 protocol in both LES client and server mode. In client mode it mixes discv5 and dnsdisc iterators (if both are enabled) and filters incoming ENRs for "les" tag and fork ID. The old p2p/discv5 package and all references to it are removed. Co-authored-by: Felix Lange --- cmd/bootnode/main.go | 15 +- cmd/faucet/faucet.go | 7 +- cmd/utils/flags.go | 15 +- les/client.go | 13 +- les/commons.go | 12 - les/enr_entry.go | 47 +- les/server.go | 21 - les/serverpool.go | 12 +- les/serverpool_test.go | 3 +- mobile/discover.go | 12 +- mobile/params.go | 10 +- p2p/discv5/README | 4 - p2p/discv5/database.go | 396 ---------- p2p/discv5/database_test.go | 380 --------- p2p/discv5/metrics.go | 24 - p2p/discv5/net.go | 1269 ------------------------------- p2p/discv5/net_test.go | 330 -------- p2p/discv5/node.go | 413 ---------- p2p/discv5/node_test.go | 305 -------- p2p/discv5/nodeevent_string.go | 17 - p2p/discv5/sim_run_test.go | 126 --- p2p/discv5/sim_test.go | 432 ----------- p2p/discv5/sim_testmain_test.go | 43 -- p2p/discv5/table.go | 318 -------- p2p/discv5/table_test.go | 238 ------ p2p/discv5/ticket.go | 884 --------------------- p2p/discv5/topic.go | 407 ---------- p2p/discv5/topic_test.go | 71 -- p2p/discv5/udp.go | 429 ----------- p2p/server.go | 26 +- params/bootnodes.go | 18 + 31 files changed, 113 insertions(+), 6184 deletions(-) delete mode 100644 p2p/discv5/README delete mode 100644 p2p/discv5/database.go delete mode 100644 p2p/discv5/database_test.go delete mode 100644 p2p/discv5/metrics.go delete mode 100644 p2p/discv5/net.go delete mode 100644 p2p/discv5/net_test.go delete mode 100644 p2p/discv5/node.go delete mode 100644 p2p/discv5/node_test.go delete mode 100644 p2p/discv5/nodeevent_string.go delete mode 100644 p2p/discv5/sim_run_test.go delete mode 100644 p2p/discv5/sim_test.go delete mode 100644 p2p/discv5/sim_testmain_test.go delete mode 100644 p2p/discv5/table.go delete mode 100644 p2p/discv5/table_test.go delete mode 100644 p2p/discv5/ticket.go delete mode 100644 p2p/discv5/topic.go delete mode 100644 p2p/discv5/topic_test.go delete mode 100644 p2p/discv5/udp.go diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index 6c9ff615a1..036b968ef8 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/discover" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" @@ -121,17 +120,17 @@ func main() { printNotice(&nodeKey.PublicKey, *realaddr) + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, nodeKey) + cfg := discover.Config{ + PrivateKey: nodeKey, + NetRestrict: restrictList, + } if *runv5 { - if _, err := discv5.ListenUDP(nodeKey, conn, "", restrictList); err != nil { + if _, err := discover.ListenV5(conn, ln, cfg); err != nil { utils.Fatalf("%v", err) } } else { - db, _ := enode.OpenDB("") - ln := enode.NewLocalNode(db, nodeKey) - cfg := discover.Config{ - PrivateKey: nodeKey, - NetRestrict: restrictList, - } if _, err := discover.ListenUDP(conn, ln, cfg); err != nil { utils.Fatalf("%v", err) } diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index b9c4e1819a..763b8d25f8 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -55,7 +55,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/params" @@ -154,9 +153,9 @@ func main() { log.Crit("Failed to parse genesis block json", "err", err) } // Convert the bootnodes to internal enode representations - var enodes []*discv5.Node + var enodes []*enode.Node for _, boot := range strings.Split(*bootFlag, ",") { - if url, err := discv5.ParseNode(boot); err == nil { + if url, err := enode.Parse(enode.ValidSchemes, boot); err == nil { enodes = append(enodes, url) } else { log.Error("Failed to parse bootnode URL", "url", boot, "err", err) @@ -228,7 +227,7 @@ type wsConn struct { wlock sync.Mutex } -func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { +func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { // Assemble the raw devp2p protocol stack stack, err := node.New(&node.Config{ Name: "geth", diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 954b5de2c2..1d6d2d86b6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -59,7 +59,6 @@ import ( "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" @@ -842,7 +841,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { // setBootstrapNodesV5 creates a list of bootstrap nodes from the command line // flags, reverting to pre-configured ones if none have been specified. func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { - urls := params.MainnetBootnodes + urls := params.V5Bootnodes switch { case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(LegacyBootnodesV5Flag.Name): if ctx.GlobalIsSet(LegacyBootnodesV5Flag.Name) { @@ -850,22 +849,14 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { } else { urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) } - case ctx.GlobalBool(RopstenFlag.Name): - urls = params.RopstenBootnodes - case ctx.GlobalBool(RinkebyFlag.Name): - urls = params.RinkebyBootnodes - case ctx.GlobalBool(GoerliFlag.Name): - urls = params.GoerliBootnodes - case ctx.GlobalBool(YoloV2Flag.Name): - urls = params.YoloV2Bootnodes case cfg.BootstrapNodesV5 != nil: return // already set, don't apply defaults. } - cfg.BootstrapNodesV5 = make([]*discv5.Node, 0, len(urls)) + cfg.BootstrapNodesV5 = make([]*enode.Node, 0, len(urls)) for _, url := range urls { if url != "" { - node, err := discv5.ParseNode(url) + node, err := enode.Parse(enode.ValidSchemes, url) if err != nil { log.Error("Bootstrap URL invalid", "enode", url, "err", err) continue diff --git a/les/client.go b/les/client.go index 5ee50a7c3a..d8cb6e385a 100644 --- a/les/client.go +++ b/les/client.go @@ -72,6 +72,7 @@ type LightEthereum struct { netRPCService *ethapi.PublicNetAPI p2pServer *p2p.Server + p2pConfig *p2p.Config } // New creates an instance of the light client. @@ -109,14 +110,11 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), p2pServer: stack.Server(), + p2pConfig: &stack.Config().P2P, } peers.subscribe((*vtSubscription)(leth.valueTracker)) - dnsdisc, err := leth.setupDiscovery() - if err != nil { - return nil, err - } - leth.serverPool = newServerPool(lespayDb, []byte("serverpool:"), leth.valueTracker, dnsdisc, time.Second, nil, &mclock.System{}, config.UltraLightServers) + leth.serverPool = newServerPool(lespayDb, []byte("serverpool:"), leth.valueTracker, time.Second, nil, &mclock.System{}, config.UltraLightServers) peers.subscribe(leth.serverPool) leth.dialCandidates = leth.serverPool.dialIterator @@ -299,6 +297,11 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { func (s *LightEthereum) Start() error { log.Warn("Light client mode is an experimental feature") + discovery, err := s.setupDiscovery(s.p2pConfig) + if err != nil { + return err + } + s.serverPool.addSource(discovery) s.serverPool.start() // Start bloom request workers. s.wg.Add(bloomServiceThreads) diff --git a/les/commons.go b/les/commons.go index 8de1057d26..73334497ad 100644 --- a/les/commons.go +++ b/les/commons.go @@ -33,7 +33,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" ) @@ -42,17 +41,6 @@ func errResp(code errCode, format string, v ...interface{}) error { return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...)) } -func lesTopic(genesisHash common.Hash, protocolVersion uint) discv5.Topic { - var name string - switch protocolVersion { - case lpv2: - name = "LES2" - default: - panic(nil) - } - return discv5.Topic(name + "@" + common.Bytes2Hex(genesisHash.Bytes()[0:8])) -} - type chainReader interface { CurrentHeader() *types.Header } diff --git a/les/enr_entry.go b/les/enr_entry.go index a357f689df..1e56c1f175 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -17,6 +17,8 @@ package les import ( + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" @@ -25,19 +27,46 @@ import ( // lesEntry is the "les" ENR entry. This is set for LES servers only. type lesEntry struct { // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` + _ []rlp.RawValue `rlp:"tail"` } -// ENRKey implements enr.Entry. -func (e lesEntry) ENRKey() string { - return "les" +func (lesEntry) ENRKey() string { return "les" } + +// ethEntry is the "eth" ENR entry. This is redeclared here to avoid depending on package eth. +type ethEntry struct { + ForkID forkid.ID + _ []rlp.RawValue `rlp:"tail"` } +func (ethEntry) ENRKey() string { return "eth" } + // setupDiscovery creates the node discovery source for the eth protocol. -func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) { - if len(eth.config.EthDiscoveryURLs) == 0 { - return nil, nil +func (eth *LightEthereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) { + it := enode.NewFairMix(0) + + // Enable DNS discovery. + if len(eth.config.EthDiscoveryURLs) != 0 { + client := dnsdisc.NewClient(dnsdisc.Config{}) + dns, err := client.NewIterator(eth.config.EthDiscoveryURLs...) + if err != nil { + return nil, err + } + it.AddSource(dns) + } + + // Enable DHT. + if cfg.DiscoveryV5 && eth.p2pServer.DiscV5 != nil { + it.AddSource(eth.p2pServer.DiscV5.RandomNodes()) } - client := dnsdisc.NewClient(dnsdisc.Config{}) - return client.NewIterator(eth.config.EthDiscoveryURLs...) + + forkFilter := forkid.NewFilter(eth.blockchain) + iterator := enode.Filter(it, func(n *enode.Node) bool { return nodeIsServer(forkFilter, n) }) + return iterator, nil +} + +// nodeIsServer checks whether n is an LES server node. +func nodeIsServer(forkFilter forkid.Filter, n *enode.Node) bool { + var les lesEntry + var eth ethEntry + return n.Load(&les) == nil && n.Load(ð) == nil && forkFilter(eth.ForkID) == nil } diff --git a/les/server.go b/les/server.go index cbedce136c..6b12b6f8f3 100644 --- a/les/server.go +++ b/les/server.go @@ -29,7 +29,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nodestate" @@ -58,7 +57,6 @@ type LesServer struct { archiveMode bool // Flag whether the ethereum node runs in archive mode. handler *serverHandler broadcaster *broadcaster - lesTopics []discv5.Topic privateKey *ecdsa.PrivateKey // Flow control and capacity management @@ -77,11 +75,6 @@ type LesServer struct { func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) { ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) - // Collect les protocol version information supported by local node. - lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions)) - for i, pv := range AdvertiseProtocolVersions { - lesTopics[i] = lesTopic(e.BlockChain().Genesis().Hash(), pv) - } // Calculate the number of threads used to service the light client // requests based on the user-specified value. threads := config.LightServ * 4 / 100 @@ -103,7 +96,6 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer ns: ns, archiveMode: e.ArchiveMode(), broadcaster: newBroadcaster(ns), - lesTopics: lesTopics, fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}), servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100), threadsBusy: config.LightServ/100 + 1, @@ -203,19 +195,6 @@ func (s *LesServer) Start() error { s.wg.Add(1) go s.capacityManagement() - if s.p2pSrv.DiscV5 != nil { - for _, topic := range s.lesTopics { - topic := topic - go func() { - logger := log.New("topic", topic) - logger.Info("Starting topic registration") - defer logger.Info("Terminated topic registration") - - s.p2pSrv.DiscV5.RegisterTopic(topic, s.closeCh) - }() - } - } - return nil } diff --git a/les/serverpool.go b/les/serverpool.go index 6cf4affff0..ac87acf6da 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -131,7 +131,7 @@ var ( ) // newServerPool creates a new server pool -func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, discovery enode.Iterator, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string) *serverPool { +func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string) *serverPool { s := &serverPool{ db: db, clock: clock, @@ -147,9 +147,6 @@ func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, d alwaysConnect := lpc.NewQueueIterator(s.ns, sfAlwaysConnect, sfDisableSelection, true, nil) s.mixSources = append(s.mixSources, knownSelector) s.mixSources = append(s.mixSources, alwaysConnect) - if discovery != nil { - s.mixSources = append(s.mixSources, discovery) - } iter := enode.Iterator(s.mixer) if query != nil { @@ -175,6 +172,13 @@ func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, d return s } +// addSource adds a node discovery source to the server pool (should be called before start) +func (s *serverPool) addSource(source enode.Iterator) { + if source != nil { + s.mixSources = append(s.mixSources, source) + } +} + // addPreNegFilter installs a node filter mechanism that performs a pre-negotiation query. // Nodes that are filtered out and does not appear on the output iterator are put back // into redialWait state. diff --git a/les/serverpool_test.go b/les/serverpool_test.go index 70a41b74c6..3b7ae65d5d 100644 --- a/les/serverpool_test.go +++ b/les/serverpool_test.go @@ -145,7 +145,8 @@ func (s *serverPoolTest) start() { } s.vt = lpc.NewValueTracker(s.db, s.clock, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)) - s.sp = newServerPool(s.db, []byte("serverpool:"), s.vt, s.input, 0, testQuery, s.clock, s.trusted) + s.sp = newServerPool(s.db, []byte("serverpool:"), s.vt, 0, testQuery, s.clock, s.trusted) + s.sp.addSource(s.input) s.sp.validSchemes = enode.ValidSchemesForTesting s.sp.unixTime = func() int64 { return int64(s.clock.Now()) / int64(time.Second) } s.disconnect = make(map[int][]int) diff --git a/mobile/discover.go b/mobile/discover.go index 9b3c93ccd9..2c699f08be 100644 --- a/mobile/discover.go +++ b/mobile/discover.go @@ -22,12 +22,12 @@ package geth import ( "errors" - "github.com/ethereum/go-ethereum/p2p/discv5" + "github.com/ethereum/go-ethereum/p2p/enode" ) // Enode represents a host on the network. type Enode struct { - node *discv5.Node + node *enode.Node } // NewEnode parses a node designator. @@ -53,8 +53,8 @@ type Enode struct { // and UDP discovery port 30301. // // enode://@10.3.58.6:30303?discport=30301 -func NewEnode(rawurl string) (enode *Enode, _ error) { - node, err := discv5.ParseNode(rawurl) +func NewEnode(rawurl string) (*Enode, error) { + node, err := enode.Parse(enode.ValidSchemes, rawurl) if err != nil { return nil, err } @@ -62,12 +62,12 @@ func NewEnode(rawurl string) (enode *Enode, _ error) { } // Enodes represents a slice of accounts. -type Enodes struct{ nodes []*discv5.Node } +type Enodes struct{ nodes []*enode.Node } // NewEnodes creates a slice of uninitialized enodes. func NewEnodes(size int) *Enodes { return &Enodes{ - nodes: make([]*discv5.Node, size), + nodes: make([]*enode.Node, size), } } diff --git a/mobile/params.go b/mobile/params.go index 43ac004740..0fc197c9e5 100644 --- a/mobile/params.go +++ b/mobile/params.go @@ -22,7 +22,7 @@ import ( "encoding/json" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/p2p/discv5" + "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" ) @@ -62,9 +62,13 @@ func GoerliGenesis() string { // FoundationBootnodes returns the enode URLs of the P2P bootstrap nodes operated // by the foundation running the V5 discovery protocol. func FoundationBootnodes() *Enodes { - nodes := &Enodes{nodes: make([]*discv5.Node, len(params.MainnetBootnodes))} + nodes := &Enodes{nodes: make([]*enode.Node, len(params.MainnetBootnodes))} for i, url := range params.MainnetBootnodes { - nodes.nodes[i] = discv5.MustParseNode(url) + var err error + nodes.nodes[i], err = enode.Parse(enode.ValidSchemes, url) + if err != nil { + panic("invalid node URL: " + err.Error()) + } } return nodes } diff --git a/p2p/discv5/README b/p2p/discv5/README deleted file mode 100644 index 617a473d7f..0000000000 --- a/p2p/discv5/README +++ /dev/null @@ -1,4 +0,0 @@ -This package is an early prototype of Discovery v5. Do not use this code. - -See https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md for the -current Discovery v5 specification. \ No newline at end of file diff --git a/p2p/discv5/database.go b/p2p/discv5/database.go deleted file mode 100644 index ca118e7f80..0000000000 --- a/p2p/discv5/database.go +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Contains the node database, storing previously seen nodes and any collected -// metadata about them for QoS purposes. - -package discv5 - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "fmt" - "os" - "sync" - "time" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/errors" - "github.com/syndtr/goleveldb/leveldb/iterator" - "github.com/syndtr/goleveldb/leveldb/opt" - "github.com/syndtr/goleveldb/leveldb/storage" - "github.com/syndtr/goleveldb/leveldb/util" -) - -var ( - nodeDBNilNodeID = NodeID{} // Special node ID to use as a nil element. - nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped. - nodeDBCleanupCycle = time.Hour // Time period for running the expiration task. -) - -// nodeDB stores all nodes we know about. -type nodeDB struct { - lvl *leveldb.DB // Interface to the database itself - self NodeID // Own node id to prevent adding it into the database - runner sync.Once // Ensures we can start at most one expirer - quit chan struct{} // Channel to signal the expiring thread to stop -} - -// Schema layout for the node database -var ( - nodeDBVersionKey = []byte("version") // Version of the database to flush if changes - nodeDBItemPrefix = []byte("n:") // Identifier to prefix node entries with - - nodeDBDiscoverRoot = ":discover" - nodeDBDiscoverPing = nodeDBDiscoverRoot + ":lastping" - nodeDBDiscoverPong = nodeDBDiscoverRoot + ":lastpong" - nodeDBDiscoverFindFails = nodeDBDiscoverRoot + ":findfail" - nodeDBTopicRegTickets = ":tickets" -) - -// newNodeDB creates a new node database for storing and retrieving infos about -// known peers in the network. If no path is given, an in-memory, temporary -// database is constructed. -func newNodeDB(path string, version int, self NodeID) (*nodeDB, error) { - if path == "" { - return newMemoryNodeDB(self) - } - return newPersistentNodeDB(path, version, self) -} - -// newMemoryNodeDB creates a new in-memory node database without a persistent -// backend. -func newMemoryNodeDB(self NodeID) (*nodeDB, error) { - db, err := leveldb.Open(storage.NewMemStorage(), nil) - if err != nil { - return nil, err - } - return &nodeDB{ - lvl: db, - self: self, - quit: make(chan struct{}), - }, nil -} - -// newPersistentNodeDB creates/opens a leveldb backed persistent node database, -// also flushing its contents in case of a version mismatch. -func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) { - opts := &opt.Options{OpenFilesCacheCapacity: 5} - db, err := leveldb.OpenFile(path, opts) - if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted { - db, err = leveldb.RecoverFile(path, nil) - } - if err != nil { - return nil, err - } - // The nodes contained in the cache correspond to a certain protocol version. - // Flush all nodes if the version doesn't match. - currentVer := make([]byte, binary.MaxVarintLen64) - currentVer = currentVer[:binary.PutVarint(currentVer, int64(version))] - - blob, err := db.Get(nodeDBVersionKey, nil) - switch err { - case leveldb.ErrNotFound: - // Version not found (i.e. empty cache), insert it - if err := db.Put(nodeDBVersionKey, currentVer, nil); err != nil { - db.Close() - return nil, err - } - - case nil: - // Version present, flush if different - if !bytes.Equal(blob, currentVer) { - db.Close() - if err = os.RemoveAll(path); err != nil { - return nil, err - } - return newPersistentNodeDB(path, version, self) - } - } - return &nodeDB{ - lvl: db, - self: self, - quit: make(chan struct{}), - }, nil -} - -// makeKey generates the leveldb key-blob from a node id and its particular -// field of interest. -func makeKey(id NodeID, field string) []byte { - if bytes.Equal(id[:], nodeDBNilNodeID[:]) { - return []byte(field) - } - return append(nodeDBItemPrefix, append(id[:], field...)...) -} - -// splitKey tries to split a database key into a node id and a field part. -func splitKey(key []byte) (id NodeID, field string) { - // If the key is not of a node, return it plainly - if !bytes.HasPrefix(key, nodeDBItemPrefix) { - return NodeID{}, string(key) - } - // Otherwise split the id and field - item := key[len(nodeDBItemPrefix):] - copy(id[:], item[:len(id)]) - field = string(item[len(id):]) - - return id, field -} - -// fetchInt64 retrieves an integer instance associated with a particular -// database key. -func (db *nodeDB) fetchInt64(key []byte) int64 { - blob, err := db.lvl.Get(key, nil) - if err != nil { - return 0 - } - val, read := binary.Varint(blob) - if read <= 0 { - return 0 - } - return val -} - -// storeInt64 update a specific database entry to the current time instance as a -// unix timestamp. -func (db *nodeDB) storeInt64(key []byte, n int64) error { - blob := make([]byte, binary.MaxVarintLen64) - blob = blob[:binary.PutVarint(blob, n)] - return db.lvl.Put(key, blob, nil) -} - -func (db *nodeDB) storeRLP(key []byte, val interface{}) error { - blob, err := rlp.EncodeToBytes(val) - if err != nil { - return err - } - return db.lvl.Put(key, blob, nil) -} - -func (db *nodeDB) fetchRLP(key []byte, val interface{}) error { - blob, err := db.lvl.Get(key, nil) - if err != nil { - return err - } - err = rlp.DecodeBytes(blob, val) - if err != nil { - log.Warn(fmt.Sprintf("key %x (%T) %v", key, val, err)) - } - return err -} - -// node retrieves a node with a given id from the database. -func (db *nodeDB) node(id NodeID) *Node { - var node Node - if err := db.fetchRLP(makeKey(id, nodeDBDiscoverRoot), &node); err != nil { - return nil - } - node.sha = crypto.Keccak256Hash(node.ID[:]) - return &node -} - -// updateNode inserts - potentially overwriting - a node into the peer database. -func (db *nodeDB) updateNode(node *Node) error { - return db.storeRLP(makeKey(node.ID, nodeDBDiscoverRoot), node) -} - -// deleteNode deletes all information/keys associated with a node. -func (db *nodeDB) deleteNode(id NodeID) error { - deleter := db.lvl.NewIterator(util.BytesPrefix(makeKey(id, "")), nil) - for deleter.Next() { - if err := db.lvl.Delete(deleter.Key(), nil); err != nil { - return err - } - } - return nil -} - -// ensureExpirer is a small helper method ensuring that the data expiration -// mechanism is running. If the expiration goroutine is already running, this -// method simply returns. -// -// The goal is to start the data evacuation only after the network successfully -// bootstrapped itself (to prevent dumping potentially useful seed nodes). Since -// it would require significant overhead to exactly trace the first successful -// convergence, it's simpler to "ensure" the correct state when an appropriate -// condition occurs (i.e. a successful bonding), and discard further events. -func (db *nodeDB) ensureExpirer() { - db.runner.Do(func() { go db.expirer() }) -} - -// expirer should be started in a go routine, and is responsible for looping ad -// infinitum and dropping stale data from the database. -func (db *nodeDB) expirer() { - tick := time.NewTicker(nodeDBCleanupCycle) - defer tick.Stop() - for { - select { - case <-tick.C: - if err := db.expireNodes(); err != nil { - log.Error(fmt.Sprintf("Failed to expire nodedb items: %v", err)) - } - case <-db.quit: - return - } - } -} - -// expireNodes iterates over the database and deletes all nodes that have not -// been seen (i.e. received a pong from) for some allotted time. -func (db *nodeDB) expireNodes() error { - threshold := time.Now().Add(-nodeDBNodeExpiration) - - // Find discovered nodes that are older than the allowance - it := db.lvl.NewIterator(nil, nil) - defer it.Release() - - for it.Next() { - // Skip the item if not a discovery node - id, field := splitKey(it.Key()) - if field != nodeDBDiscoverRoot { - continue - } - // Skip the node if not expired yet (and not self) - if !bytes.Equal(id[:], db.self[:]) { - if seen := db.lastPong(id); seen.After(threshold) { - continue - } - } - // Otherwise delete all associated information - db.deleteNode(id) - } - return nil -} - -// lastPing retrieves the time of the last ping packet send to a remote node, -// requesting binding. -func (db *nodeDB) lastPing(id NodeID) time.Time { - return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0) -} - -// updateLastPing updates the last time we tried contacting a remote node. -func (db *nodeDB) updateLastPing(id NodeID, instance time.Time) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix()) -} - -// lastPong retrieves the time of the last successful contact from remote node. -func (db *nodeDB) lastPong(id NodeID) time.Time { - return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0) -} - -// updateLastPong updates the last time a remote node successfully contacted. -func (db *nodeDB) updateLastPong(id NodeID, instance time.Time) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix()) -} - -// findFails retrieves the number of findnode failures since bonding. -func (db *nodeDB) findFails(id NodeID) int { - return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails))) -} - -// updateFindFails updates the number of findnode failures since bonding. -func (db *nodeDB) updateFindFails(id NodeID, fails int) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails)) -} - -// querySeeds retrieves random nodes to be used as potential seed nodes -// for bootstrapping. -func (db *nodeDB) querySeeds(n int, maxAge time.Duration) []*Node { - var ( - now = time.Now() - nodes = make([]*Node, 0, n) - it = db.lvl.NewIterator(nil, nil) - id NodeID - ) - defer it.Release() - -seek: - for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ { - // Seek to a random entry. The first byte is incremented by a - // random amount each time in order to increase the likelihood - // of hitting all existing nodes in very small databases. - ctr := id[0] - rand.Read(id[:]) - id[0] = ctr + id[0]%16 - it.Seek(makeKey(id, nodeDBDiscoverRoot)) - - n := nextNode(it) - if n == nil { - id[0] = 0 - continue seek // iterator exhausted - } - if n.ID == db.self { - continue seek - } - if now.Sub(db.lastPong(n.ID)) > maxAge { - continue seek - } - for i := range nodes { - if nodes[i].ID == n.ID { - continue seek // duplicate - } - } - nodes = append(nodes, n) - } - return nodes -} - -func (db *nodeDB) fetchTopicRegTickets(id NodeID) (issued, used uint32) { - key := makeKey(id, nodeDBTopicRegTickets) - blob, _ := db.lvl.Get(key, nil) - if len(blob) != 8 { - return 0, 0 - } - issued = binary.BigEndian.Uint32(blob[0:4]) - used = binary.BigEndian.Uint32(blob[4:8]) - return -} - -func (db *nodeDB) updateTopicRegTickets(id NodeID, issued, used uint32) error { - key := makeKey(id, nodeDBTopicRegTickets) - blob := make([]byte, 8) - binary.BigEndian.PutUint32(blob[0:4], issued) - binary.BigEndian.PutUint32(blob[4:8], used) - return db.lvl.Put(key, blob, nil) -} - -// reads the next node record from the iterator, skipping over other -// database entries. -func nextNode(it iterator.Iterator) *Node { - for end := false; !end; end = !it.Next() { - id, field := splitKey(it.Key()) - if field != nodeDBDiscoverRoot { - continue - } - var n Node - if err := rlp.DecodeBytes(it.Value(), &n); err != nil { - log.Warn(fmt.Sprintf("invalid node %x: %v", id, err)) - continue - } - return &n - } - return nil -} - -// close flushes and closes the database files. -func (db *nodeDB) close() { - close(db.quit) - db.lvl.Close() -} diff --git a/p2p/discv5/database_test.go b/p2p/discv5/database_test.go deleted file mode 100644 index 2b86dc9cec..0000000000 --- a/p2p/discv5/database_test.go +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "bytes" - "io/ioutil" - "net" - "os" - "path/filepath" - "reflect" - "testing" - "time" -) - -var nodeDBKeyTests = []struct { - id NodeID - field string - key []byte -}{ - { - id: NodeID{}, - field: "version", - key: []byte{0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e}, // field - }, - { - id: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - field: ":discover", - key: []byte{0x6e, 0x3a, // prefix - 0x1d, 0xd9, 0xd6, 0x5c, 0x45, 0x52, 0xb5, 0xeb, // node id - 0x43, 0xd5, 0xad, 0x55, 0xa2, 0xee, 0x3f, 0x56, // - 0xc6, 0xcb, 0xc1, 0xc6, 0x4a, 0x5c, 0x8d, 0x65, // - 0x9f, 0x51, 0xfc, 0xd5, 0x1b, 0xac, 0xe2, 0x43, // - 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // - 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, // - 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, // - 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // - 0x3a, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, // field - }, - }, -} - -func TestNodeDBKeys(t *testing.T) { - for i, tt := range nodeDBKeyTests { - if key := makeKey(tt.id, tt.field); !bytes.Equal(key, tt.key) { - t.Errorf("make test %d: key mismatch: have 0x%x, want 0x%x", i, key, tt.key) - } - id, field := splitKey(tt.key) - if !bytes.Equal(id[:], tt.id[:]) { - t.Errorf("split test %d: id mismatch: have 0x%x, want 0x%x", i, id, tt.id) - } - if field != tt.field { - t.Errorf("split test %d: field mismatch: have 0x%x, want 0x%x", i, field, tt.field) - } - } -} - -var nodeDBInt64Tests = []struct { - key []byte - value int64 -}{ - {key: []byte{0x01}, value: 1}, - {key: []byte{0x02}, value: 2}, - {key: []byte{0x03}, value: 3}, -} - -func TestNodeDBInt64(t *testing.T) { - db, _ := newNodeDB("", Version, NodeID{}) - defer db.close() - - tests := nodeDBInt64Tests - for i := 0; i < len(tests); i++ { - // Insert the next value - if err := db.storeInt64(tests[i].key, tests[i].value); err != nil { - t.Errorf("test %d: failed to store value: %v", i, err) - } - // Check all existing and non existing values - for j := 0; j < len(tests); j++ { - num := db.fetchInt64(tests[j].key) - switch { - case j <= i && num != tests[j].value: - t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, tests[j].value) - case j > i && num != 0: - t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, 0) - } - } - } -} - -func TestNodeDBFetchStore(t *testing.T) { - node := NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{192, 168, 0, 1}, - 30303, - 30303, - ) - inst := time.Now() - num := 314 - - db, _ := newNodeDB("", Version, NodeID{}) - defer db.close() - - // Check fetch/store operations on a node ping object - if stored := db.lastPing(node.ID); stored.Unix() != 0 { - t.Errorf("ping: non-existing object: %v", stored) - } - if err := db.updateLastPing(node.ID, inst); err != nil { - t.Errorf("ping: failed to update: %v", err) - } - if stored := db.lastPing(node.ID); stored.Unix() != inst.Unix() { - t.Errorf("ping: value mismatch: have %v, want %v", stored, inst) - } - // Check fetch/store operations on a node pong object - if stored := db.lastPong(node.ID); stored.Unix() != 0 { - t.Errorf("pong: non-existing object: %v", stored) - } - if err := db.updateLastPong(node.ID, inst); err != nil { - t.Errorf("pong: failed to update: %v", err) - } - if stored := db.lastPong(node.ID); stored.Unix() != inst.Unix() { - t.Errorf("pong: value mismatch: have %v, want %v", stored, inst) - } - // Check fetch/store operations on a node findnode-failure object - if stored := db.findFails(node.ID); stored != 0 { - t.Errorf("find-node fails: non-existing object: %v", stored) - } - if err := db.updateFindFails(node.ID, num); err != nil { - t.Errorf("find-node fails: failed to update: %v", err) - } - if stored := db.findFails(node.ID); stored != num { - t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num) - } - // Check fetch/store operations on an actual node object - if stored := db.node(node.ID); stored != nil { - t.Errorf("node: non-existing object: %v", stored) - } - if err := db.updateNode(node); err != nil { - t.Errorf("node: failed to update: %v", err) - } - if stored := db.node(node.ID); stored == nil { - t.Errorf("node: not found") - } else if !reflect.DeepEqual(stored, node) { - t.Errorf("node: data mismatch: have %v, want %v", stored, node) - } -} - -var nodeDBSeedQueryNodes = []struct { - node *Node - pong time.Time -}{ - // This one should not be in the result set because its last - // pong time is too far in the past. - { - node: NewNode( - MustHexID("0x84d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 3}, - 30303, - 30303, - ), - pong: time.Now().Add(-3 * time.Hour), - }, - // This one shouldn't be in the result set because its - // nodeID is the local node's ID. - { - node: NewNode( - MustHexID("0x57d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 3}, - 30303, - 30303, - ), - pong: time.Now().Add(-4 * time.Second), - }, - - // These should be in the result set. - { - node: NewNode( - MustHexID("0x22d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 1}, - 30303, - 30303, - ), - pong: time.Now().Add(-2 * time.Second), - }, - { - node: NewNode( - MustHexID("0x44d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 2}, - 30303, - 30303, - ), - pong: time.Now().Add(-3 * time.Second), - }, - { - node: NewNode( - MustHexID("0xe2d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 3}, - 30303, - 30303, - ), - pong: time.Now().Add(-1 * time.Second), - }, -} - -func TestNodeDBSeedQuery(t *testing.T) { - db, _ := newNodeDB("", Version, nodeDBSeedQueryNodes[1].node.ID) - defer db.close() - - // Insert a batch of nodes for querying - for i, seed := range nodeDBSeedQueryNodes { - if err := db.updateNode(seed.node); err != nil { - t.Fatalf("node %d: failed to insert: %v", i, err) - } - if err := db.updateLastPong(seed.node.ID, seed.pong); err != nil { - t.Fatalf("node %d: failed to insert lastPong: %v", i, err) - } - } - - // Retrieve the entire batch and check for duplicates - seeds := db.querySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour) - have := make(map[NodeID]struct{}) - for _, seed := range seeds { - have[seed.ID] = struct{}{} - } - want := make(map[NodeID]struct{}) - for _, seed := range nodeDBSeedQueryNodes[2:] { - want[seed.node.ID] = struct{}{} - } - if len(seeds) != len(want) { - t.Errorf("seed count mismatch: have %v, want %v", len(seeds), len(want)) - } - for id := range have { - if _, ok := want[id]; !ok { - t.Errorf("extra seed: %v", id) - } - } - for id := range want { - if _, ok := have[id]; !ok { - t.Errorf("missing seed: %v", id) - } - } -} - -func TestNodeDBPersistency(t *testing.T) { - root, err := ioutil.TempDir("", "nodedb-") - if err != nil { - t.Fatalf("failed to create temporary data folder: %v", err) - } - defer os.RemoveAll(root) - - var ( - testKey = []byte("somekey") - testInt = int64(314) - ) - - // Create a persistent database and store some values - db, err := newNodeDB(filepath.Join(root, "database"), Version, NodeID{}) - if err != nil { - t.Fatalf("failed to create persistent database: %v", err) - } - if err := db.storeInt64(testKey, testInt); err != nil { - t.Fatalf("failed to store value: %v.", err) - } - db.close() - - // Reopen the database and check the value - db, err = newNodeDB(filepath.Join(root, "database"), Version, NodeID{}) - if err != nil { - t.Fatalf("failed to open persistent database: %v", err) - } - if val := db.fetchInt64(testKey); val != testInt { - t.Fatalf("value mismatch: have %v, want %v", val, testInt) - } - db.close() - - // Change the database version and check flush - db, err = newNodeDB(filepath.Join(root, "database"), Version+1, NodeID{}) - if err != nil { - t.Fatalf("failed to open persistent database: %v", err) - } - if val := db.fetchInt64(testKey); val != 0 { - t.Fatalf("value mismatch: have %v, want %v", val, 0) - } - db.close() -} - -var nodeDBExpirationNodes = []struct { - node *Node - pong time.Time - exp bool -}{ - { - node: NewNode( - MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 1}, - 30303, - 30303, - ), - pong: time.Now().Add(-nodeDBNodeExpiration + time.Minute), - exp: false, - }, { - node: NewNode( - MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 2}, - 30303, - 30303, - ), - pong: time.Now().Add(-nodeDBNodeExpiration - time.Minute), - exp: true, - }, -} - -func TestNodeDBExpiration(t *testing.T) { - db, _ := newNodeDB("", Version, NodeID{}) - defer db.close() - - // Add all the test nodes and set their last pong time - for i, seed := range nodeDBExpirationNodes { - if err := db.updateNode(seed.node); err != nil { - t.Fatalf("node %d: failed to insert: %v", i, err) - } - if err := db.updateLastPong(seed.node.ID, seed.pong); err != nil { - t.Fatalf("node %d: failed to update pong: %v", i, err) - } - } - // Expire some of them, and check the rest - if err := db.expireNodes(); err != nil { - t.Fatalf("failed to expire nodes: %v", err) - } - for i, seed := range nodeDBExpirationNodes { - node := db.node(seed.node.ID) - if (node == nil && !seed.exp) || (node != nil && seed.exp) { - t.Errorf("node %d: expiration mismatch: have %v, want %v", i, node, seed.exp) - } - } -} - -func TestNodeDBSelfExpiration(t *testing.T) { - // Find a node in the tests that shouldn't expire, and assign it as self - var self NodeID - for _, node := range nodeDBExpirationNodes { - if !node.exp { - self = node.node.ID - break - } - } - db, _ := newNodeDB("", Version, self) - defer db.close() - - // Add all the test nodes and set their last pong time - for i, seed := range nodeDBExpirationNodes { - if err := db.updateNode(seed.node); err != nil { - t.Fatalf("node %d: failed to insert: %v", i, err) - } - if err := db.updateLastPong(seed.node.ID, seed.pong); err != nil { - t.Fatalf("node %d: failed to update pong: %v", i, err) - } - } - // Expire the nodes and make sure self has been evacuated too - if err := db.expireNodes(); err != nil { - t.Fatalf("failed to expire nodes: %v", err) - } - node := db.node(self) - if node != nil { - t.Errorf("self not evacuated") - } -} diff --git a/p2p/discv5/metrics.go b/p2p/discv5/metrics.go deleted file mode 100644 index e68d53c13c..0000000000 --- a/p2p/discv5/metrics.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import "github.com/ethereum/go-ethereum/metrics" - -var ( - ingressTrafficMeter = metrics.NewRegisteredMeter("discv5/InboundTraffic", nil) - egressTrafficMeter = metrics.NewRegisteredMeter("discv5/OutboundTraffic", nil) -) diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go deleted file mode 100644 index 53e00a3881..0000000000 --- a/p2p/discv5/net.go +++ /dev/null @@ -1,1269 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "bytes" - "crypto/ecdsa" - "errors" - "fmt" - "net" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/netutil" - "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" -) - -var ( - errInvalidEvent = errors.New("invalid in current state") - errNoQuery = errors.New("no pending query") -) - -const ( - autoRefreshInterval = 1 * time.Hour - bucketRefreshInterval = 1 * time.Minute - seedCount = 30 - seedMaxAge = 5 * 24 * time.Hour - lowPort = 1024 -) - -const testTopic = "foo" - -const ( - printTestImgLogs = false -) - -// Network manages the table and all protocol interaction. -type Network struct { - db *nodeDB // database of known nodes - conn transport - netrestrict *netutil.Netlist - - closed chan struct{} // closed when loop is done - closeReq chan struct{} // 'request to close' - refreshReq chan []*Node // lookups ask for refresh on this channel - refreshResp chan (<-chan struct{}) // ...and get the channel to block on from this one - read chan ingressPacket // ingress packets arrive here - timeout chan timeoutEvent - queryReq chan *findnodeQuery // lookups submit findnode queries on this channel - tableOpReq chan func() - tableOpResp chan struct{} - topicRegisterReq chan topicRegisterReq - topicSearchReq chan topicSearchReq - - // State of the main loop. - tab *Table - topictab *topicTable - ticketStore *ticketStore - nursery []*Node - nodes map[NodeID]*Node // tracks active nodes with state != known - timeoutTimers map[timeoutEvent]*time.Timer -} - -// transport is implemented by the UDP transport. -// it is an interface so we can test without opening lots of UDP -// sockets and without generating a private key. -type transport interface { - sendPing(remote *Node, remoteAddr *net.UDPAddr, topics []Topic) (hash []byte) - sendNeighbours(remote *Node, nodes []*Node) - sendFindnodeHash(remote *Node, target common.Hash) - sendTopicRegister(remote *Node, topics []Topic, topicIdx int, pong []byte) - sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) - - send(remote *Node, ptype nodeEvent, p interface{}) (hash []byte) - - localAddr() *net.UDPAddr - Close() -} - -type findnodeQuery struct { - remote *Node - target common.Hash - reply chan<- []*Node -} - -type topicRegisterReq struct { - add bool - topic Topic -} - -type topicSearchReq struct { - topic Topic - found chan<- *Node - lookup chan<- bool - delay time.Duration -} - -type topicSearchResult struct { - target lookupInfo - nodes []*Node -} - -type timeoutEvent struct { - ev nodeEvent - node *Node -} - -func newNetwork(conn transport, ourPubkey ecdsa.PublicKey, dbPath string, netrestrict *netutil.Netlist) (*Network, error) { - ourID := PubkeyID(&ourPubkey) - - var db *nodeDB - if dbPath != "" { - var err error - if db, err = newNodeDB(dbPath, Version, ourID); err != nil { - return nil, err - } - } - - tab := newTable(ourID, conn.localAddr()) - net := &Network{ - db: db, - conn: conn, - netrestrict: netrestrict, - tab: tab, - topictab: newTopicTable(db, tab.self), - ticketStore: newTicketStore(), - refreshReq: make(chan []*Node), - refreshResp: make(chan (<-chan struct{})), - closed: make(chan struct{}), - closeReq: make(chan struct{}), - read: make(chan ingressPacket, 100), - timeout: make(chan timeoutEvent), - timeoutTimers: make(map[timeoutEvent]*time.Timer), - tableOpReq: make(chan func()), - tableOpResp: make(chan struct{}), - queryReq: make(chan *findnodeQuery), - topicRegisterReq: make(chan topicRegisterReq), - topicSearchReq: make(chan topicSearchReq), - nodes: make(map[NodeID]*Node), - } - go net.loop() - return net, nil -} - -// Close terminates the network listener and flushes the node database. -func (net *Network) Close() { - net.conn.Close() - select { - case <-net.closed: - case net.closeReq <- struct{}{}: - <-net.closed - } -} - -// Self returns the local node. -// The returned node should not be modified by the caller. -func (net *Network) Self() *Node { - return net.tab.self -} - -// ReadRandomNodes fills the given slice with random nodes from the -// table. It will not write the same node more than once. The nodes in -// the slice are copies and can be modified by the caller. -func (net *Network) ReadRandomNodes(buf []*Node) (n int) { - net.reqTableOp(func() { n = net.tab.readRandomNodes(buf) }) - return n -} - -// SetFallbackNodes sets the initial points of contact. These nodes -// are used to connect to the network if the table is empty and there -// are no known nodes in the database. -func (net *Network) SetFallbackNodes(nodes []*Node) error { - nursery := make([]*Node, 0, len(nodes)) - for _, n := range nodes { - if err := n.validateComplete(); err != nil { - return fmt.Errorf("bad bootstrap/fallback node %q (%v)", n, err) - } - // Recompute cpy.sha because the node might not have been - // created by NewNode or ParseNode. - cpy := *n - cpy.sha = crypto.Keccak256Hash(n.ID[:]) - nursery = append(nursery, &cpy) - } - net.reqRefresh(nursery) - return nil -} - -// Resolve searches for a specific node with the given ID. -// It returns nil if the node could not be found. -func (net *Network) Resolve(targetID NodeID) *Node { - result := net.lookup(crypto.Keccak256Hash(targetID[:]), true) - for _, n := range result { - if n.ID == targetID { - return n - } - } - return nil -} - -// Lookup performs a network search for nodes close -// to the given target. It approaches the target by querying -// nodes that are closer to it on each iteration. -// The given target does not need to be an actual node -// identifier. -// -// The local node may be included in the result. -func (net *Network) Lookup(targetID NodeID) []*Node { - return net.lookup(crypto.Keccak256Hash(targetID[:]), false) -} - -func (net *Network) lookup(target common.Hash, stopOnMatch bool) []*Node { - var ( - asked = make(map[NodeID]bool) - seen = make(map[NodeID]bool) - reply = make(chan []*Node, alpha) - result = nodesByDistance{target: target} - pendingQueries = 0 - ) - // Get initial answers from the local node. - result.push(net.tab.self, bucketSize) - for { - // Ask the α closest nodes that we haven't asked yet. - for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ { - n := result.entries[i] - if !asked[n.ID] { - asked[n.ID] = true - pendingQueries++ - net.reqQueryFindnode(n, target, reply) - } - } - if pendingQueries == 0 { - // We have asked all closest nodes, stop the search. - break - } - // Wait for the next reply. - select { - case nodes := <-reply: - for _, n := range nodes { - if n != nil && !seen[n.ID] { - seen[n.ID] = true - result.push(n, bucketSize) - if stopOnMatch && n.sha == target { - return result.entries - } - } - } - pendingQueries-- - case <-time.After(respTimeout): - // forget all pending requests, start new ones - pendingQueries = 0 - reply = make(chan []*Node, alpha) - } - } - return result.entries -} - -func (net *Network) RegisterTopic(topic Topic, stop <-chan struct{}) { - select { - case net.topicRegisterReq <- topicRegisterReq{true, topic}: - case <-net.closed: - return - } - select { - case <-net.closed: - case <-stop: - select { - case net.topicRegisterReq <- topicRegisterReq{false, topic}: - case <-net.closed: - } - } -} - -func (net *Network) SearchTopic(topic Topic, setPeriod <-chan time.Duration, found chan<- *Node, lookup chan<- bool) { - for { - select { - case <-net.closed: - return - case delay, ok := <-setPeriod: - select { - case net.topicSearchReq <- topicSearchReq{topic: topic, found: found, lookup: lookup, delay: delay}: - case <-net.closed: - return - } - if !ok { - return - } - } - } -} - -func (net *Network) reqRefresh(nursery []*Node) <-chan struct{} { - select { - case net.refreshReq <- nursery: - return <-net.refreshResp - case <-net.closed: - return net.closed - } -} - -func (net *Network) reqQueryFindnode(n *Node, target common.Hash, reply chan []*Node) bool { - q := &findnodeQuery{remote: n, target: target, reply: reply} - select { - case net.queryReq <- q: - return true - case <-net.closed: - return false - } -} - -func (net *Network) reqReadPacket(pkt ingressPacket) { - select { - case net.read <- pkt: - case <-net.closed: - } -} - -func (net *Network) reqTableOp(f func()) (called bool) { - select { - case net.tableOpReq <- f: - <-net.tableOpResp - return true - case <-net.closed: - return false - } -} - -// TODO: external address handling. - -type topicSearchInfo struct { - lookupChn chan<- bool - period time.Duration -} - -const maxSearchCount = 5 - -func (net *Network) loop() { - var ( - refreshTimer = time.NewTicker(autoRefreshInterval) - bucketRefreshTimer = time.NewTimer(bucketRefreshInterval) - refreshDone chan struct{} // closed when the 'refresh' lookup has ended - ) - defer refreshTimer.Stop() - defer bucketRefreshTimer.Stop() - - // Tracking the next ticket to register. - var ( - nextTicket *ticketRef - nextRegisterTimer *time.Timer - nextRegisterTime <-chan time.Time - ) - defer func() { - if nextRegisterTimer != nil { - nextRegisterTimer.Stop() - } - }() - resetNextTicket := func() { - ticket, timeout := net.ticketStore.nextFilteredTicket() - if nextTicket != ticket { - nextTicket = ticket - if nextRegisterTimer != nil { - nextRegisterTimer.Stop() - nextRegisterTime = nil - } - if ticket != nil { - nextRegisterTimer = time.NewTimer(timeout) - nextRegisterTime = nextRegisterTimer.C - } - } - } - - // Tracking registration and search lookups. - var ( - topicRegisterLookupTarget lookupInfo - topicRegisterLookupDone chan []*Node - topicRegisterLookupTick = time.NewTimer(0) - searchReqWhenRefreshDone []topicSearchReq - searchInfo = make(map[Topic]topicSearchInfo) - activeSearchCount int - ) - defer topicRegisterLookupTick.Stop() - topicSearchLookupDone := make(chan topicSearchResult, 100) - topicSearch := make(chan Topic, 100) - <-topicRegisterLookupTick.C - - statsDump := time.NewTicker(10 * time.Second) - defer statsDump.Stop() - -loop: - for { - resetNextTicket() - - select { - case <-net.closeReq: - log.Trace("<-net.closeReq") - break loop - - // Ingress packet handling. - case pkt := <-net.read: - //fmt.Println("read", pkt.ev) - log.Trace("<-net.read") - n := net.internNode(&pkt) - prestate := n.state - status := "ok" - if err := net.handle(n, pkt.ev, &pkt); err != nil { - status = err.Error() - } - log.Trace("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprintf("<<< (%d) %v from %x@%v: %v -> %v (%v)", - net.tab.count, pkt.ev, pkt.remoteID[:8], pkt.remoteAddr, prestate, n.state, status) - }}) - // TODO: persist state if n.state goes >= known, delete if it goes <= known - - // State transition timeouts. - case timeout := <-net.timeout: - log.Trace("<-net.timeout") - if net.timeoutTimers[timeout] == nil { - // Stale timer (was aborted). - continue - } - delete(net.timeoutTimers, timeout) - prestate := timeout.node.state - status := "ok" - if err := net.handle(timeout.node, timeout.ev, nil); err != nil { - status = err.Error() - } - log.Trace("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprintf("--- (%d) %v for %x@%v: %v -> %v (%v)", - net.tab.count, timeout.ev, timeout.node.ID[:8], timeout.node.addr(), prestate, timeout.node.state, status) - }}) - - // Querying. - case q := <-net.queryReq: - log.Trace("<-net.queryReq") - if !q.start(net) { - q.remote.deferQuery(q) - } - - // Interacting with the table. - case f := <-net.tableOpReq: - log.Trace("<-net.tableOpReq") - f() - net.tableOpResp <- struct{}{} - - // Topic registration stuff. - case req := <-net.topicRegisterReq: - log.Trace("<-net.topicRegisterReq") - if !req.add { - net.ticketStore.removeRegisterTopic(req.topic) - continue - } - net.ticketStore.addTopic(req.topic, true) - // If we're currently waiting idle (nothing to look up), give the ticket store a - // chance to start it sooner. This should speed up convergence of the radius - // determination for new topics. - // if topicRegisterLookupDone == nil { - if topicRegisterLookupTarget.target == (common.Hash{}) { - log.Trace("topicRegisterLookupTarget == null") - if topicRegisterLookupTick.Stop() { - <-topicRegisterLookupTick.C - } - target, delay := net.ticketStore.nextRegisterLookup() - topicRegisterLookupTarget = target - topicRegisterLookupTick.Reset(delay) - } - - case nodes := <-topicRegisterLookupDone: - log.Trace("<-topicRegisterLookupDone") - net.ticketStore.registerLookupDone(topicRegisterLookupTarget, nodes, func(n *Node) []byte { - net.ping(n, n.addr()) - return n.pingEcho - }) - target, delay := net.ticketStore.nextRegisterLookup() - topicRegisterLookupTarget = target - topicRegisterLookupTick.Reset(delay) - topicRegisterLookupDone = nil - - case <-topicRegisterLookupTick.C: - log.Trace("<-topicRegisterLookupTick") - if (topicRegisterLookupTarget.target == common.Hash{}) { - target, delay := net.ticketStore.nextRegisterLookup() - topicRegisterLookupTarget = target - topicRegisterLookupTick.Reset(delay) - topicRegisterLookupDone = nil - } else { - topicRegisterLookupDone = make(chan []*Node) - target := topicRegisterLookupTarget.target - go func() { topicRegisterLookupDone <- net.lookup(target, false) }() - } - - case <-nextRegisterTime: - log.Trace("<-nextRegisterTime") - net.ticketStore.ticketRegistered(*nextTicket) - //fmt.Println("sendTopicRegister", nextTicket.t.node.addr().String(), nextTicket.t.topics, nextTicket.idx, nextTicket.t.pong) - net.conn.sendTopicRegister(nextTicket.t.node, nextTicket.t.topics, nextTicket.idx, nextTicket.t.pong) - - case req := <-net.topicSearchReq: - if refreshDone == nil { - log.Trace("<-net.topicSearchReq") - info, ok := searchInfo[req.topic] - if ok { - if req.delay == time.Duration(0) { - delete(searchInfo, req.topic) - net.ticketStore.removeSearchTopic(req.topic) - } else { - info.period = req.delay - searchInfo[req.topic] = info - } - continue - } - if req.delay != time.Duration(0) { - var info topicSearchInfo - info.period = req.delay - info.lookupChn = req.lookup - searchInfo[req.topic] = info - net.ticketStore.addSearchTopic(req.topic, req.found) - topicSearch <- req.topic - } - } else { - searchReqWhenRefreshDone = append(searchReqWhenRefreshDone, req) - } - - case topic := <-topicSearch: - if activeSearchCount < maxSearchCount { - activeSearchCount++ - target := net.ticketStore.nextSearchLookup(topic) - go func() { - nodes := net.lookup(target.target, false) - topicSearchLookupDone <- topicSearchResult{target: target, nodes: nodes} - }() - } - period := searchInfo[topic].period - if period != time.Duration(0) { - go func() { - time.Sleep(period) - topicSearch <- topic - }() - } - - case res := <-topicSearchLookupDone: - activeSearchCount-- - if lookupChn := searchInfo[res.target.topic].lookupChn; lookupChn != nil { - lookupChn <- net.ticketStore.radius[res.target.topic].converged - } - net.ticketStore.searchLookupDone(res.target, res.nodes, func(n *Node, topic Topic) []byte { - if n.state != nil && n.state.canQuery { - return net.conn.send(n, topicQueryPacket, topicQuery{Topic: topic}) // TODO: set expiration - } - if n.state == unknown { - net.ping(n, n.addr()) - } - return nil - }) - - case <-statsDump.C: - log.Trace("<-statsDump.C") - /*r, ok := net.ticketStore.radius[testTopic] - if !ok { - fmt.Printf("(%x) no radius @ %v\n", net.tab.self.ID[:8], time.Now()) - } else { - topics := len(net.ticketStore.tickets) - tickets := len(net.ticketStore.nodes) - rad := r.radius / (maxRadius/10000+1) - fmt.Printf("(%x) topics:%d radius:%d tickets:%d @ %v\n", net.tab.self.ID[:8], topics, rad, tickets, time.Now()) - }*/ - - tm := mclock.Now() - for topic, r := range net.ticketStore.radius { - if printTestImgLogs { - rad := r.radius / (maxRadius/1000000 + 1) - minrad := r.minRadius / (maxRadius/1000000 + 1) - fmt.Printf("*R %d %v %016x %v\n", tm/1000000, topic, net.tab.self.sha[:8], rad) - fmt.Printf("*MR %d %v %016x %v\n", tm/1000000, topic, net.tab.self.sha[:8], minrad) - } - } - for topic, t := range net.topictab.topics { - wp := t.wcl.nextWaitPeriod(tm) - if printTestImgLogs { - fmt.Printf("*W %d %v %016x %d\n", tm/1000000, topic, net.tab.self.sha[:8], wp/1000000) - } - } - - // Periodic / lookup-initiated bucket refresh. - case <-refreshTimer.C: - log.Trace("<-refreshTimer.C") - // TODO: ideally we would start the refresh timer after - // fallback nodes have been set for the first time. - if refreshDone == nil { - refreshDone = make(chan struct{}) - net.refresh(refreshDone) - } - case <-bucketRefreshTimer.C: - target := net.tab.chooseBucketRefreshTarget() - go func() { - net.lookup(target, false) - bucketRefreshTimer.Reset(bucketRefreshInterval) - }() - case newNursery := <-net.refreshReq: - log.Trace("<-net.refreshReq") - if newNursery != nil { - net.nursery = newNursery - } - if refreshDone == nil { - refreshDone = make(chan struct{}) - net.refresh(refreshDone) - } - net.refreshResp <- refreshDone - case <-refreshDone: - log.Trace("<-net.refreshDone", "table size", net.tab.count) - if net.tab.count != 0 { - refreshDone = nil - list := searchReqWhenRefreshDone - searchReqWhenRefreshDone = nil - go func() { - for _, req := range list { - net.topicSearchReq <- req - } - }() - } else { - refreshDone = make(chan struct{}) - net.refresh(refreshDone) - } - } - } - log.Trace("loop stopped") - - log.Debug("shutting down") - if net.conn != nil { - net.conn.Close() - } - // TODO: wait for pending refresh. - // if refreshDone != nil { - // <-refreshResults - // } - // Cancel all pending timeouts. - for _, timer := range net.timeoutTimers { - timer.Stop() - } - if net.db != nil { - net.db.close() - } - close(net.closed) -} - -// Everything below runs on the Network.loop goroutine -// and can modify Node, Table and Network at any time without locking. - -func (net *Network) refresh(done chan<- struct{}) { - var seeds []*Node - if net.db != nil { - seeds = net.db.querySeeds(seedCount, seedMaxAge) - } - if len(seeds) == 0 { - seeds = net.nursery - } - if len(seeds) == 0 { - log.Trace("no seed nodes found") - time.AfterFunc(time.Second*10, func() { close(done) }) - return - } - for _, n := range seeds { - log.Debug("", "msg", log.Lazy{Fn: func() string { - var age string - if net.db != nil { - age = time.Since(net.db.lastPong(n.ID)).String() - } else { - age = "unknown" - } - return fmt.Sprintf("seed node (age %s): %v", age, n) - }}) - n = net.internNodeFromDB(n) - if n.state == unknown { - net.transition(n, verifyinit) - } - // Force-add the seed node so Lookup does something. - // It will be deleted again if verification fails. - net.tab.add(n) - } - // Start self lookup to fill up the buckets. - go func() { - net.Lookup(net.tab.self.ID) - close(done) - }() -} - -// Node Interning. - -func (net *Network) internNode(pkt *ingressPacket) *Node { - if n := net.nodes[pkt.remoteID]; n != nil { - n.IP = pkt.remoteAddr.IP - n.UDP = uint16(pkt.remoteAddr.Port) - n.TCP = uint16(pkt.remoteAddr.Port) - return n - } - n := NewNode(pkt.remoteID, pkt.remoteAddr.IP, uint16(pkt.remoteAddr.Port), uint16(pkt.remoteAddr.Port)) - n.state = unknown - net.nodes[pkt.remoteID] = n - return n -} - -func (net *Network) internNodeFromDB(dbn *Node) *Node { - if n := net.nodes[dbn.ID]; n != nil { - return n - } - n := NewNode(dbn.ID, dbn.IP, dbn.UDP, dbn.TCP) - n.state = unknown - net.nodes[n.ID] = n - return n -} - -func (net *Network) internNodeFromNeighbours(sender *net.UDPAddr, rn rpcNode) (n *Node, err error) { - if rn.ID == net.tab.self.ID { - return nil, errors.New("is self") - } - if rn.UDP <= lowPort { - return nil, errors.New("low port") - } - n = net.nodes[rn.ID] - if n == nil { - // We haven't seen this node before. - n, err = nodeFromRPC(sender, rn) - if net.netrestrict != nil && !net.netrestrict.Contains(n.IP) { - return n, errors.New("not contained in netrestrict whitelist") - } - if err == nil { - n.state = unknown - net.nodes[n.ID] = n - } - return n, err - } - if !n.IP.Equal(rn.IP) || n.UDP != rn.UDP || n.TCP != rn.TCP { - if n.state == known { - // reject address change if node is known by us - err = fmt.Errorf("metadata mismatch: got %v, want %v", rn, n) - } else { - // accept otherwise; this will be handled nicer with signed ENRs - n.IP = rn.IP - n.UDP = rn.UDP - n.TCP = rn.TCP - } - } - return n, err -} - -// nodeNetGuts is embedded in Node and contains fields. -type nodeNetGuts struct { - // This is a cached copy of sha3(ID) which is used for node - // distance calculations. This is part of Node in order to make it - // possible to write tests that need a node at a certain distance. - // In those tests, the content of sha will not actually correspond - // with ID. - sha common.Hash - - // State machine fields. Access to these fields - // is restricted to the Network.loop goroutine. - state *nodeState - pingEcho []byte // hash of last ping sent by us - pingTopics []Topic // topic set sent by us in last ping - deferredQueries []*findnodeQuery // queries that can't be sent yet - pendingNeighbours *findnodeQuery // current query, waiting for reply - queryTimeouts int -} - -func (n *nodeNetGuts) deferQuery(q *findnodeQuery) { - n.deferredQueries = append(n.deferredQueries, q) -} - -func (n *nodeNetGuts) startNextQuery(net *Network) { - if len(n.deferredQueries) == 0 { - return - } - nextq := n.deferredQueries[0] - if nextq.start(net) { - n.deferredQueries = append(n.deferredQueries[:0], n.deferredQueries[1:]...) - } -} - -func (q *findnodeQuery) start(net *Network) bool { - // Satisfy queries against the local node directly. - if q.remote == net.tab.self { - closest := net.tab.closest(q.target, bucketSize) - q.reply <- closest.entries - return true - } - if q.remote.state.canQuery && q.remote.pendingNeighbours == nil { - net.conn.sendFindnodeHash(q.remote, q.target) - net.timedEvent(respTimeout, q.remote, neighboursTimeout) - q.remote.pendingNeighbours = q - return true - } - // If the node is not known yet, it won't accept queries. - // Initiate the transition to known. - // The request will be sent later when the node reaches known state. - if q.remote.state == unknown { - net.transition(q.remote, verifyinit) - } - return false -} - -// Node Events (the input to the state machine). - -type nodeEvent uint - -//go:generate stringer -type=nodeEvent - -const ( - - // Packet type events. - // These correspond to packet types in the UDP protocol. - pingPacket = iota + 1 - pongPacket - findnodePacket - neighborsPacket - findnodeHashPacket - topicRegisterPacket - topicQueryPacket - topicNodesPacket - - // Non-packet events. - // Event values in this category are allocated outside - // the packet type range (packet types are encoded as a single byte). - pongTimeout nodeEvent = iota + 256 - pingTimeout - neighboursTimeout -) - -// Node State Machine. - -type nodeState struct { - name string - handle func(*Network, *Node, nodeEvent, *ingressPacket) (next *nodeState, err error) - enter func(*Network, *Node) - canQuery bool -} - -func (s *nodeState) String() string { - return s.name -} - -var ( - unknown *nodeState - verifyinit *nodeState - verifywait *nodeState - remoteverifywait *nodeState - known *nodeState - contested *nodeState - unresponsive *nodeState -) - -func init() { - unknown = &nodeState{ - name: "unknown", - enter: func(net *Network, n *Node) { - net.tab.delete(n) - n.pingEcho = nil - // Abort active queries. - for _, q := range n.deferredQueries { - q.reply <- nil - } - n.deferredQueries = nil - if n.pendingNeighbours != nil { - n.pendingNeighbours.reply <- nil - n.pendingNeighbours = nil - } - n.queryTimeouts = 0 - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - net.ping(n, pkt.remoteAddr) - return verifywait, nil - default: - return unknown, errInvalidEvent - } - }, - } - - verifyinit = &nodeState{ - name: "verifyinit", - enter: func(net *Network, n *Node) { - net.ping(n, n.addr()) - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return verifywait, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return remoteverifywait, err - case pongTimeout: - return unknown, nil - default: - return verifyinit, errInvalidEvent - } - }, - } - - verifywait = &nodeState{ - name: "verifywait", - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return verifywait, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return known, err - case pongTimeout: - return unknown, nil - default: - return verifywait, errInvalidEvent - } - }, - } - - remoteverifywait = &nodeState{ - name: "remoteverifywait", - enter: func(net *Network, n *Node) { - net.timedEvent(respTimeout, n, pingTimeout) - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return remoteverifywait, nil - case pingTimeout: - return known, nil - default: - return remoteverifywait, errInvalidEvent - } - }, - } - - known = &nodeState{ - name: "known", - canQuery: true, - enter: func(net *Network, n *Node) { - n.queryTimeouts = 0 - n.startNextQuery(net) - // Insert into the table and start revalidation of the last node - // in the bucket if it is full. - last := net.tab.add(n) - if last != nil && last.state == known { - // TODO: do this asynchronously - net.transition(last, contested) - } - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return known, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return known, err - default: - return net.handleQueryEvent(n, ev, pkt) - } - }, - } - - contested = &nodeState{ - name: "contested", - canQuery: true, - enter: func(net *Network, n *Node) { - net.ping(n, n.addr()) - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pongPacket: - // Node is still alive. - err := net.handleKnownPong(n, pkt) - return known, err - case pongTimeout: - net.tab.deleteReplace(n) - return unresponsive, nil - case pingPacket: - net.handlePing(n, pkt) - return contested, nil - default: - return net.handleQueryEvent(n, ev, pkt) - } - }, - } - - unresponsive = &nodeState{ - name: "unresponsive", - canQuery: true, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return known, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return known, err - default: - return net.handleQueryEvent(n, ev, pkt) - } - }, - } -} - -// handle processes packets sent by n and events related to n. -func (net *Network) handle(n *Node, ev nodeEvent, pkt *ingressPacket) error { - //fmt.Println("handle", n.addr().String(), n.state, ev) - if pkt != nil { - if err := net.checkPacket(n, ev, pkt); err != nil { - //fmt.Println("check err:", err) - return err - } - // Start the background expiration goroutine after the first - // successful communication. Subsequent calls have no effect if it - // is already running. We do this here instead of somewhere else - // so that the search for seed nodes also considers older nodes - // that would otherwise be removed by the expirer. - if net.db != nil { - net.db.ensureExpirer() - } - } - if ev == pongTimeout { - n.pingEcho = nil // clean up if pongtimeout - } - if n.state == nil { - n.state = unknown //??? - } - next, err := n.state.handle(net, n, ev, pkt) - net.transition(n, next) - //fmt.Println("new state:", n.state) - return err -} - -func (net *Network) checkPacket(n *Node, ev nodeEvent, pkt *ingressPacket) error { - // Replay prevention checks. - switch ev { - case pingPacket, findnodeHashPacket, neighborsPacket: - // TODO: check date is > last date seen - // TODO: check ping version - case pongPacket: - if !bytes.Equal(pkt.data.(*pong).ReplyTok, n.pingEcho) { - // fmt.Println("pong reply token mismatch") - return fmt.Errorf("pong reply token mismatch") - } - n.pingEcho = nil - } - // Address validation. - // TODO: Ideally we would do the following: - // - reject all packets with wrong address except ping. - // - for ping with new address, transition to verifywait but keep the - // previous node (with old address) around. if the new one reaches known, - // swap it out. - return nil -} - -func (net *Network) transition(n *Node, next *nodeState) { - if n.state != next { - n.state = next - if next.enter != nil { - next.enter(net, n) - } - } - - // TODO: persist/unpersist node -} - -func (net *Network) timedEvent(d time.Duration, n *Node, ev nodeEvent) { - timeout := timeoutEvent{ev, n} - net.timeoutTimers[timeout] = time.AfterFunc(d, func() { - select { - case net.timeout <- timeout: - case <-net.closed: - } - }) -} - -func (net *Network) abortTimedEvent(n *Node, ev nodeEvent) { - timer := net.timeoutTimers[timeoutEvent{ev, n}] - if timer != nil { - timer.Stop() - delete(net.timeoutTimers, timeoutEvent{ev, n}) - } -} - -func (net *Network) ping(n *Node, addr *net.UDPAddr) { - //fmt.Println("ping", n.addr().String(), n.ID.String(), n.sha.Hex()) - if n.pingEcho != nil || n.ID == net.tab.self.ID { - //fmt.Println(" not sent") - return - } - log.Trace("Pinging remote node", "node", n.ID) - n.pingTopics = net.ticketStore.regTopicSet() - n.pingEcho = net.conn.sendPing(n, addr, n.pingTopics) - net.timedEvent(respTimeout, n, pongTimeout) -} - -func (net *Network) handlePing(n *Node, pkt *ingressPacket) { - log.Trace("Handling remote ping", "node", n.ID) - ping := pkt.data.(*ping) - n.TCP = ping.From.TCP - t := net.topictab.getTicket(n, ping.Topics) - - pong := &pong{ - To: makeEndpoint(n.addr(), n.TCP), // TODO: maybe use known TCP port from DB - ReplyTok: pkt.hash, - Expiration: uint64(time.Now().Add(expiration).Unix()), - } - ticketToPong(t, pong) - net.conn.send(n, pongPacket, pong) -} - -func (net *Network) handleKnownPong(n *Node, pkt *ingressPacket) error { - log.Trace("Handling known pong", "node", n.ID) - net.abortTimedEvent(n, pongTimeout) - now := mclock.Now() - ticket, err := pongToTicket(now, n.pingTopics, n, pkt) - if err == nil { - // fmt.Printf("(%x) ticket: %+v\n", net.tab.self.ID[:8], pkt.data) - net.ticketStore.addTicket(now, pkt.data.(*pong).ReplyTok, ticket) - } else { - log.Trace("Failed to convert pong to ticket", "err", err) - } - n.pingEcho = nil - n.pingTopics = nil - return err -} - -func (net *Network) handleQueryEvent(n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case findnodePacket: - target := crypto.Keccak256Hash(pkt.data.(*findnode).Target[:]) - results := net.tab.closest(target, bucketSize).entries - net.conn.sendNeighbours(n, results) - return n.state, nil - case neighborsPacket: - err := net.handleNeighboursPacket(n, pkt) - return n.state, err - case neighboursTimeout: - if n.pendingNeighbours != nil { - n.pendingNeighbours.reply <- nil - n.pendingNeighbours = nil - } - n.queryTimeouts++ - if n.queryTimeouts > maxFindnodeFailures && n.state == known { - return contested, errors.New("too many timeouts") - } - return n.state, nil - - // v5 - - case findnodeHashPacket: - results := net.tab.closest(pkt.data.(*findnodeHash).Target, bucketSize).entries - net.conn.sendNeighbours(n, results) - return n.state, nil - case topicRegisterPacket: - //fmt.Println("got topicRegisterPacket") - regdata := pkt.data.(*topicRegister) - pong, err := net.checkTopicRegister(regdata) - if err != nil { - //fmt.Println(err) - return n.state, fmt.Errorf("bad waiting ticket: %v", err) - } - net.topictab.useTicket(n, pong.TicketSerial, regdata.Topics, int(regdata.Idx), pong.Expiration, pong.WaitPeriods) - return n.state, nil - case topicQueryPacket: - // TODO: handle expiration - topic := pkt.data.(*topicQuery).Topic - results := net.topictab.getEntries(topic) - if _, ok := net.ticketStore.tickets[topic]; ok { - results = append(results, net.tab.self) // we're not registering in our own table but if we're advertising, return ourselves too - } - if len(results) > 10 { - results = results[:10] - } - var hash common.Hash - copy(hash[:], pkt.hash) - net.conn.sendTopicNodes(n, hash, results) - return n.state, nil - case topicNodesPacket: - p := pkt.data.(*topicNodes) - if net.ticketStore.gotTopicNodes(n, p.Echo, p.Nodes) { - n.queryTimeouts++ - if n.queryTimeouts > maxFindnodeFailures && n.state == known { - return contested, errors.New("too many timeouts") - } - } - return n.state, nil - - default: - return n.state, errInvalidEvent - } -} - -func (net *Network) checkTopicRegister(data *topicRegister) (*pong, error) { - var pongpkt ingressPacket - if err := decodePacket(data.Pong, &pongpkt); err != nil { - return nil, err - } - if pongpkt.ev != pongPacket { - return nil, errors.New("is not pong packet") - } - if pongpkt.remoteID != net.tab.self.ID { - return nil, errors.New("not signed by us") - } - // check that we previously authorised all topics - // that the other side is trying to register. - if rlpHash(data.Topics) != pongpkt.data.(*pong).TopicHash { - return nil, errors.New("topic hash mismatch") - } - if data.Idx >= uint(len(data.Topics)) { - return nil, errors.New("topic index out of range") - } - return pongpkt.data.(*pong), nil -} - -func rlpHash(x interface{}) (h common.Hash) { - hw := sha3.NewLegacyKeccak256() - rlp.Encode(hw, x) - hw.Sum(h[:0]) - return h -} - -func (net *Network) handleNeighboursPacket(n *Node, pkt *ingressPacket) error { - if n.pendingNeighbours == nil { - return errNoQuery - } - net.abortTimedEvent(n, neighboursTimeout) - - req := pkt.data.(*neighbors) - nodes := make([]*Node, len(req.Nodes)) - for i, rn := range req.Nodes { - nn, err := net.internNodeFromNeighbours(pkt.remoteAddr, rn) - if err != nil { - log.Debug(fmt.Sprintf("invalid neighbour (%v) from %x@%v: %v", rn.IP, n.ID[:8], pkt.remoteAddr, err)) - continue - } - nodes[i] = nn - // Start validation of query results immediately. - // This fills the table quickly. - // TODO: generates way too many packets, maybe do it via queue. - if nn.state == unknown { - net.transition(nn, verifyinit) - } - } - // TODO: don't ignore second packet - n.pendingNeighbours.reply <- nodes - n.pendingNeighbours = nil - // Now that this query is done, start the next one. - n.startNextQuery(net) - return nil -} diff --git a/p2p/discv5/net_test.go b/p2p/discv5/net_test.go deleted file mode 100644 index 29321bc86f..0000000000 --- a/p2p/discv5/net_test.go +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "net" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -func TestNetwork_Lookup(t *testing.T) { - key, _ := crypto.GenerateKey() - network, err := newNetwork(lookupTestnet, key.PublicKey, "", nil) - if err != nil { - t.Fatal(err) - } - lookupTestnet.net = network - defer network.Close() - - // lookup on empty table returns no nodes - // if results := network.Lookup(lookupTestnet.target, false); len(results) > 0 { - // t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) - // } - // seed table with initial node (otherwise lookup will terminate immediately) - seeds := []*Node{NewNode(lookupTestnet.dists[256][0], net.IP{10, 0, 2, 99}, lowPort+256, 999)} - if err := network.SetFallbackNodes(seeds); err != nil { - t.Fatal(err) - } - time.Sleep(3 * time.Second) - - results := network.Lookup(lookupTestnet.target) - t.Logf("results:") - for _, e := range results { - t.Logf(" ld=%d, %x", logdist(lookupTestnet.targetSha, e.sha), e.sha[:]) - } - if len(results) != bucketSize { - t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize) - } - if hasDuplicates(results) { - t.Errorf("result set contains duplicate entries") - } - if !sortedByDistanceTo(lookupTestnet.targetSha, results) { - t.Errorf("result set not sorted by distance to target") - } - // TODO: check result nodes are actually closest -} - -// This is the test network for the Lookup test. -// The nodes were obtained by running testnet.mine with a random NodeID as target. -var lookupTestnet = &preminedTestnet{ - target: MustHexID("166aea4f556532c6d34e8b740e5d314af7e9ac0ca79833bd751d6b665f12dfd38ec563c363b32f02aef4a80b44fd3def94612d497b99cb5f17fd24de454927ec"), - targetSha: common.Hash{0x5c, 0x94, 0x4e, 0xe5, 0x1c, 0x5a, 0xe9, 0xf7, 0x2a, 0x95, 0xec, 0xcb, 0x8a, 0xed, 0x3, 0x74, 0xee, 0xcb, 0x51, 0x19, 0xd7, 0x20, 0xcb, 0xea, 0x68, 0x13, 0xe8, 0xe0, 0xd6, 0xad, 0x92, 0x61}, - dists: [257][]NodeID{ - 240: { - MustHexID("2001ad5e3e80c71b952161bc0186731cf5ffe942d24a79230a0555802296238e57ea7a32f5b6f18564eadc1c65389448481f8c9338df0a3dbd18f708cbc2cbcb"), - MustHexID("6ba3f4f57d084b6bf94cc4555b8c657e4a8ac7b7baf23c6874efc21dd1e4f56b7eb2721e07f5242d2f1d8381fc8cae535e860197c69236798ba1ad231b105794"), - }, - 244: { - MustHexID("696ba1f0a9d55c59246f776600542a9e6432490f0cd78f8bb55a196918df2081a9b521c3c3ba48e465a75c10768807717f8f689b0b4adce00e1c75737552a178"), - }, - 246: { - MustHexID("d6d32178bdc38416f46ffb8b3ec9e4cb2cfff8d04dd7e4311a70e403cb62b10be1b447311b60b4f9ee221a8131fc2cbd45b96dd80deba68a949d467241facfa8"), - MustHexID("3ea3d04a43a3dfb5ac11cffc2319248cf41b6279659393c2f55b8a0a5fc9d12581a9d97ef5d8ff9b5abf3321a290e8f63a4f785f450dc8a672aba3ba2ff4fdab"), - MustHexID("2fc897f05ae585553e5c014effd3078f84f37f9333afacffb109f00ca8e7a3373de810a3946be971cbccdfd40249f9fe7f322118ea459ac71acca85a1ef8b7f4"), - }, - 247: { - MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), - MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), - MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), - MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), - MustHexID("8b58c6073dd98bbad4e310b97186c8f822d3a5c7d57af40e2136e88e315afd115edb27d2d0685a908cfe5aa49d0debdda6e6e63972691d6bd8c5af2d771dd2a9"), - MustHexID("2cbb718b7dc682da19652e7d9eb4fefaf7b7147d82c1c2b6805edf77b85e29fde9f6da195741467ff2638dc62c8d3e014ea5686693c15ed0080b6de90354c137"), - MustHexID("e84027696d3f12f2de30a9311afea8fbd313c2360daff52bb5fc8c7094d5295758bec3134e4eef24e4cdf377b40da344993284628a7a346eba94f74160998feb"), - MustHexID("f1357a4f04f9d33753a57c0b65ba20a5d8777abbffd04e906014491c9103fb08590e45548d37aa4bd70965e2e81ddba94f31860348df01469eec8c1829200a68"), - MustHexID("4ab0a75941b12892369b4490a1928c8ca52a9ad6d3dffbd1d8c0b907bc200fe74c022d011ec39b64808a39c0ca41f1d3254386c3e7733e7044c44259486461b6"), - MustHexID("d45150a72dc74388773e68e03133a3b5f51447fe91837d566706b3c035ee4b56f160c878c6273394daee7f56cc398985269052f22f75a8057df2fe6172765354"), - }, - 248: { - MustHexID("6aadfce366a189bab08ac84721567483202c86590642ea6d6a14f37ca78d82bdb6509eb7b8b2f6f63c78ae3ae1d8837c89509e41497d719b23ad53dd81574afa"), - MustHexID("a605ecfd6069a4cf4cf7f5840e5bc0ce10d23a3ac59e2aaa70c6afd5637359d2519b4524f56fc2ca180cdbebe54262f720ccaae8c1b28fd553c485675831624d"), - MustHexID("29701451cb9448ca33fc33680b44b840d815be90146eb521641efbffed0859c154e8892d3906eae9934bfacee72cd1d2fa9dd050fd18888eea49da155ab0efd2"), - MustHexID("3ed426322dee7572b08592e1e079f8b6c6b30e10e6243edd144a6a48fdbdb83df73a6e41b1143722cb82604f2203a32758610b5d9544f44a1a7921ba001528c1"), - MustHexID("b2e2a2b7fdd363572a3256e75435fab1da3b16f7891a8bd2015f30995dae665d7eabfd194d87d99d5df628b4bbc7b04e5b492c596422dd8272746c7a1b0b8e4f"), - MustHexID("0c69c9756162c593e85615b814ce57a2a8ca2df6c690b9c4e4602731b61e1531a3bbe3f7114271554427ffabea80ad8f36fa95a49fa77b675ae182c6ccac1728"), - MustHexID("8d28be21d5a97b0876442fa4f5e5387f5bf3faad0b6f13b8607b64d6e448c0991ca28dd7fe2f64eb8eadd7150bff5d5666aa6ed868b84c71311f4ba9a38569dd"), - MustHexID("2c677e1c64b9c9df6359348a7f5f33dc79e22f0177042486d125f8b6ca7f0dc756b1f672aceee5f1746bcff80aaf6f92a8dc0c9fbeb259b3fa0da060de5ab7e8"), - MustHexID("3994880f94a8678f0cd247a43f474a8af375d2a072128da1ad6cae84a244105ff85e94fc7d8496f639468de7ee998908a91c7e33ef7585fff92e984b210941a1"), - MustHexID("b45a9153c08d002a48090d15d61a7c7dad8c2af85d4ff5bd36ce23a9a11e0709bf8d56614c7b193bc028c16cbf7f20dfbcc751328b64a924995d47b41e452422"), - MustHexID("057ab3a9e53c7a84b0f3fc586117a525cdd18e313f52a67bf31798d48078e325abe5cfee3f6c2533230cb37d0549289d692a29dd400e899b8552d4b928f6f907"), - MustHexID("0ddf663d308791eb92e6bd88a2f8cb45e4f4f35bb16708a0e6ff7f1362aa6a73fedd0a1b1557fb3365e38e1b79d6918e2fae2788728b70c9ab6b51a3b94a4338"), - MustHexID("f637e07ff50cc1e3731735841c4798411059f2023abcf3885674f3e8032531b0edca50fd715df6feb489b6177c345374d64f4b07d257a7745de393a107b013a5"), - MustHexID("e24ec7c6eec094f63c7b3239f56d311ec5a3e45bc4e622a1095a65b95eea6fe13e29f3b6b7a2cbfe40906e3989f17ac834c3102dd0cadaaa26e16ee06d782b72"), - MustHexID("b76ea1a6fd6506ef6e3506a4f1f60ed6287fff8114af6141b2ff13e61242331b54082b023cfea5b3083354a4fb3f9eb8be01fb4a518f579e731a5d0707291a6b"), - MustHexID("9b53a37950ca8890ee349b325032d7b672cab7eced178d3060137b24ef6b92a43977922d5bdfb4a3409a2d80128e02f795f9dae6d7d99973ad0e23a2afb8442f"), - }, - 249: { - MustHexID("675ae65567c3c72c50c73bc0fd4f61f202ea5f93346ca57b551de3411ccc614fad61cb9035493af47615311b9d44ee7a161972ee4d77c28fe1ec029d01434e6a"), - MustHexID("8eb81408389da88536ae5800392b16ef5109d7ea132c18e9a82928047ecdb502693f6e4a4cdd18b54296caf561db937185731456c456c98bfe7de0baf0eaa495"), - MustHexID("2adba8b1612a541771cb93a726a38a4b88e97b18eced2593eb7daf82f05a5321ca94a72cc780c306ff21e551a932fc2c6d791e4681907b5ceab7f084c3fa2944"), - MustHexID("b1b4bfbda514d9b8f35b1c28961da5d5216fe50548f4066f69af3b7666a3b2e06eac646735e963e5c8f8138a2fb95af15b13b23ff00c6986eccc0efaa8ee6fb4"), - MustHexID("d2139281b289ad0e4d7b4243c4364f5c51aac8b60f4806135de06b12b5b369c9e43a6eb494eab860d115c15c6fbb8c5a1b0e382972e0e460af395b8385363de7"), - MustHexID("4a693df4b8fc5bdc7cec342c3ed2e228d7c5b4ab7321ddaa6cccbeb45b05a9f1d95766b4002e6d4791c2deacb8a667aadea6a700da28a3eea810a30395701bbc"), - MustHexID("ab41611195ec3c62bb8cd762ee19fb182d194fd141f4a66780efbef4b07ce916246c022b841237a3a6b512a93431157edd221e854ed2a259b72e9c5351f44d0c"), - MustHexID("68e8e26099030d10c3c703ae7045c0a48061fb88058d853b3e67880014c449d4311014da99d617d3150a20f1a3da5e34bf0f14f1c51fe4dd9d58afd222823176"), - MustHexID("3fbcacf546fb129cd70fc48de3b593ba99d3c473798bc309292aca280320e0eacc04442c914cad5c4cf6950345ba79b0d51302df88285d4e83ee3fe41339eee7"), - MustHexID("1d4a623659f7c8f80b6c3939596afdf42e78f892f682c768ad36eb7bfba402dbf97aea3a268f3badd8fe7636be216edf3d67ee1e08789ebbc7be625056bd7109"), - MustHexID("a283c474ab09da02bbc96b16317241d0627646fcc427d1fe790b76a7bf1989ced90f92101a973047ae9940c92720dffbac8eff21df8cae468a50f72f9e159417"), - MustHexID("dbf7e5ad7f87c3dfecae65d87c3039e14ed0bdc56caf00ce81931073e2e16719d746295512ff7937a15c3b03603e7c41a4f9df94fcd37bb200dd8f332767e9cb"), - MustHexID("caaa070a26692f64fc77f30d7b5ae980d419b4393a0f442b1c821ef58c0862898b0d22f74a4f8c5d83069493e3ec0b92f17dc1fe6e4cd437c1ec25039e7ce839"), - MustHexID("874cc8d1213beb65c4e0e1de38ef5d8165235893ac74ab5ea937c885eaab25c8d79dad0456e9fd3e9450626cac7e107b004478fb59842f067857f39a47cee695"), - MustHexID("d94193f236105010972f5df1b7818b55846592a0445b9cdc4eaed811b8c4c0f7c27dc8cc9837a4774656d6b34682d6d329d42b6ebb55da1d475c2474dc3dfdf4"), - MustHexID("edd9af6aded4094e9785637c28fccbd3980cbe28e2eb9a411048a23c2ace4bd6b0b7088a7817997b49a3dd05fc6929ca6c7abbb69438dbdabe65e971d2a794b2"), - }, - 250: { - MustHexID("53a5bd1215d4ab709ae8fdc2ced50bba320bced78bd9c5dc92947fb402250c914891786db0978c898c058493f86fc68b1c5de8a5cb36336150ac7a88655b6c39"), - MustHexID("b7f79e3ab59f79262623c9ccefc8f01d682323aee56ffbe295437487e9d5acaf556a9c92e1f1c6a9601f2b9eb6b027ae1aeaebac71d61b9b78e88676efd3e1a3"), - MustHexID("d374bf7e8d7ffff69cc00bebff38ef5bc1dcb0a8d51c1a3d70e61ac6b2e2d6617109254b0ac224354dfbf79009fe4239e09020c483cc60c071e00b9238684f30"), - MustHexID("1e1eac1c9add703eb252eb991594f8f5a173255d526a855fab24ae57dc277e055bc3c7a7ae0b45d437c4f47a72d97eb7b126f2ba344ba6c0e14b2c6f27d4b1e6"), - MustHexID("ae28953f63d4bc4e706712a59319c111f5ff8f312584f65d7436b4cd3d14b217b958f8486bad666b4481fe879019fb1f767cf15b3e3e2711efc33b56d460448a"), - MustHexID("934bb1edf9c7a318b82306aca67feb3d6b434421fa275d694f0b4927afd8b1d3935b727fd4ff6e3d012e0c82f1824385174e8c6450ade59c2a43281a4b3446b6"), - MustHexID("9eef3f28f70ce19637519a0916555bf76d26de31312ac656cf9d3e379899ea44e4dd7ffcce923b4f3563f8a00489a34bd6936db0cbb4c959d32c49f017e07d05"), - MustHexID("82200872e8f871c48f1fad13daec6478298099b591bb3dbc4ef6890aa28ebee5860d07d70be62f4c0af85085a90ae8179ee8f937cf37915c67ea73e704b03ee7"), - MustHexID("6c75a5834a08476b7fc37ff3dc2011dc3ea3b36524bad7a6d319b18878fad813c0ba76d1f4555cacd3890c865438c21f0e0aed1f80e0a157e642124c69f43a11"), - MustHexID("995b873742206cb02b736e73a88580c2aacb0bd4a3c97a647b647bcab3f5e03c0e0736520a8b3600da09edf4248991fb01091ec7ff3ec7cdc8a1beae011e7aae"), - MustHexID("c773a056594b5cdef2e850d30891ff0e927c3b1b9c35cd8e8d53a1017001e237468e1ece3ae33d612ca3e6abb0a9169aa352e9dcda358e5af2ad982b577447db"), - MustHexID("2b46a5f6923f475c6be99ec6d134437a6d11f6bb4b4ac6bcd94572fa1092639d1c08aeefcb51f0912f0a060f71d4f38ee4da70ecc16010b05dd4a674aab14c3a"), - MustHexID("af6ab501366debbaa0d22e20e9688f32ef6b3b644440580fd78de4fe0e99e2a16eb5636bbae0d1c259df8ddda77b35b9a35cbc36137473e9c68fbc9d203ba842"), - MustHexID("c9f6f2dd1a941926f03f770695bda289859e85fabaf94baaae20b93e5015dc014ba41150176a36a1884adb52f405194693e63b0c464a6891cc9cc1c80d450326"), - MustHexID("5b116f0751526868a909b61a30b0c5282c37df6925cc03ddea556ef0d0602a9595fd6c14d371f8ed7d45d89918a032dcd22be4342a8793d88fdbeb3ca3d75bd7"), - MustHexID("50f3222fb6b82481c7c813b2172e1daea43e2710a443b9c2a57a12bd160dd37e20f87aa968c82ad639af6972185609d47036c0d93b4b7269b74ebd7073221c10"), - }, - 251: { - MustHexID("9b8f702a62d1bee67bedfeb102eca7f37fa1713e310f0d6651cc0c33ea7c5477575289ccd463e5a2574a00a676a1fdce05658ba447bb9d2827f0ba47b947e894"), - MustHexID("b97532eb83054ed054b4abdf413bb30c00e4205545c93521554dbe77faa3cfaa5bd31ef466a107b0b34a71ec97214c0c83919720142cddac93aa7a3e928d4708"), - MustHexID("2f7a5e952bfb67f2f90b8441b5fadc9ee13b1dcde3afeeb3dd64bf937f86663cc5c55d1fa83952b5422763c7df1b7f2794b751c6be316ebc0beb4942e65ab8c1"), - MustHexID("42c7483781727051a0b3660f14faf39e0d33de5e643702ae933837d036508ab856ce7eec8ec89c4929a4901256e5233a3d847d5d4893f91bcf21835a9a880fee"), - MustHexID("873bae27bf1dc854408fba94046a53ab0c965cebe1e4e12290806fc62b88deb1f4a47f9e18f78fc0e7913a0c6e42ac4d0fc3a20cea6bc65f0c8a0ca90b67521e"), - MustHexID("a7e3a370bbd761d413f8d209e85886f68bf73d5c3089b2dc6fa42aab1ecb5162635497eed95dee2417f3c9c74a3e76319625c48ead2e963c7de877cd4551f347"), - MustHexID("528597534776a40df2addaaea15b6ff832ce36b9748a265768368f657e76d58569d9f30dbb91e91cf0ae7efe8f402f17aa0ae15f5c55051ba03ba830287f4c42"), - MustHexID("461d8bd4f13c3c09031fdb84f104ed737a52f630261463ce0bdb5704259bab4b737dda688285b8444dbecaecad7f50f835190b38684ced5e90c54219e5adf1bc"), - MustHexID("6ec50c0be3fd232737090fc0111caaf0bb6b18f72be453428087a11a97fd6b52db0344acbf789a689bd4f5f50f79017ea784f8fd6fe723ad6ae675b9e3b13e21"), - MustHexID("12fc5e2f77a83fdcc727b79d8ae7fe6a516881138d3011847ee136b400fed7cfba1f53fd7a9730253c7aa4f39abeacd04f138417ba7fcb0f36cccc3514e0dab6"), - MustHexID("4fdbe75914ccd0bce02101606a1ccf3657ec963e3b3c20239d5fec87673fe446d649b4f15f1fe1a40e6cfbd446dda2d31d40bb602b1093b8fcd5f139ba0eb46a"), - MustHexID("3753668a0f6281e425ea69b52cb2d17ab97afbe6eb84cf5d25425bc5e53009388857640668fadd7c110721e6047c9697803bd8a6487b43bb343bfa32ebf24039"), - MustHexID("2e81b16346637dec4410fd88e527346145b9c0a849dbf2628049ac7dae016c8f4305649d5659ec77f1e8a0fac0db457b6080547226f06283598e3740ad94849a"), - MustHexID("802c3cc27f91c89213223d758f8d2ecd41135b357b6d698f24d811cdf113033a81c38e0bdff574a5c005b00a8c193dc2531f8c1fa05fa60acf0ab6f2858af09f"), - MustHexID("fcc9a2e1ac3667026ff16192876d1813bb75abdbf39b929a92863012fe8b1d890badea7a0de36274d5c1eb1e8f975785532c50d80fd44b1a4b692f437303393f"), - MustHexID("6d8b3efb461151dd4f6de809b62726f5b89e9b38e9ba1391967f61cde844f7528fecf821b74049207cee5a527096b31f3ad623928cd3ce51d926fa345a6b2951"), - }, - 252: { - MustHexID("f1ae93157cc48c2075dd5868fbf523e79e06caf4b8198f352f6e526680b78ff4227263de92612f7d63472bd09367bb92a636fff16fe46ccf41614f7a72495c2a"), - MustHexID("587f482d111b239c27c0cb89b51dd5d574db8efd8de14a2e6a1400c54d4567e77c65f89c1da52841212080b91604104768350276b6682f2f961cdaf4039581c7"), - MustHexID("e3f88274d35cefdaabdf205afe0e80e936cc982b8e3e47a84ce664c413b29016a4fb4f3a3ebae0a2f79671f8323661ed462bf4390af94c424dc8ace0c301b90f"), - MustHexID("0ddc736077da9a12ba410dc5ea63cbcbe7659dd08596485b2bff3435221f82c10d263efd9af938e128464be64a178b7cd22e19f400d5802f4c9df54bf89f2619"), - MustHexID("784aa34d833c6ce63fcc1279630113c3272e82c4ae8c126c5a52a88ac461b6baeed4244e607b05dc14e5b2f41c70a273c3804dea237f14f7a1e546f6d1309d14"), - MustHexID("f253a2c354ee0e27cfcae786d726753d4ad24be6516b279a936195a487de4a59dbc296accf20463749ff55293263ed8c1b6365eecb248d44e75e9741c0d18205"), - MustHexID("a1910b80357b3ad9b4593e0628922939614dc9056a5fbf477279c8b2c1d0b4b31d89a0c09d0d41f795271d14d3360ef08a3f821e65e7e1f56c07a36afe49c7c5"), - MustHexID("f1168552c2efe541160f0909b0b4a9d6aeedcf595cdf0e9b165c97e3e197471a1ee6320e93389edfba28af6eaf10de98597ad56e7ab1b504ed762451996c3b98"), - MustHexID("b0c8e5d2c8634a7930e1a6fd082e448c6cf9d2d8b7293558b59238815a4df926c286bf297d2049f14e8296a6eb3256af614ec1812c4f2bbe807673b58bf14c8c"), - MustHexID("0fb346076396a38badc342df3679b55bd7f40a609ab103411fe45082c01f12ea016729e95914b2b5540e987ff5c9b133e85862648e7f36abdfd23100d248d234"), - MustHexID("f736e0cc83417feaa280d9483f5d4d72d1b036cd0c6d9cbdeb8ac35ceb2604780de46dddaa32a378474e1d5ccdf79b373331c30c7911ade2ae32f98832e5de1f"), - MustHexID("8b02991457602f42b38b342d3f2259ae4100c354b3843885f7e4e07bd644f64dab94bb7f38a3915f8b7f11d8e3f81c28e07a0078cf79d7397e38a7b7e0c857e2"), - MustHexID("9221d9f04a8a184993d12baa91116692bb685f887671302999d69300ad103eb2d2c75a09d8979404c6dd28f12362f58a1a43619c493d9108fd47588a23ce5824"), - MustHexID("652797801744dada833fff207d67484742eea6835d695925f3e618d71b68ec3c65bdd85b4302b2cdcb835ad3f94fd00d8da07e570b41bc0d2bcf69a8de1b3284"), - MustHexID("d84f06fe64debc4cd0625e36d19b99014b6218375262cc2209202bdbafd7dffcc4e34ce6398e182e02fd8faeed622c3e175545864902dfd3d1ac57647cddf4c6"), - MustHexID("d0ed87b294f38f1d741eb601020eeec30ac16331d05880fe27868f1e454446de367d7457b41c79e202eaf9525b029e4f1d7e17d85a55f83a557c005c68d7328a"), - }, - 253: { - MustHexID("ad4485e386e3cc7c7310366a7c38fb810b8896c0d52e55944bfd320ca294e7912d6c53c0a0cf85e7ce226e92491d60430e86f8f15cda0161ed71893fb4a9e3a1"), - MustHexID("36d0e7e5b7734f98c6183eeeb8ac5130a85e910a925311a19c4941b1290f945d4fc3996b12ef4966960b6fa0fb29b1604f83a0f81bd5fd6398d2e1a22e46af0c"), - MustHexID("7d307d8acb4a561afa23bdf0bd945d35c90245e26345ec3a1f9f7df354222a7cdcb81339c9ed6744526c27a1a0c8d10857e98df942fa433602facac71ac68a31"), - MustHexID("d97bf55f88c83fae36232661af115d66ca600fc4bd6d1fb35ff9bb4dad674c02cf8c8d05f317525b5522250db58bb1ecafb7157392bf5aa61b178c61f098d995"), - MustHexID("7045d678f1f9eb7a4613764d17bd5698796494d0bf977b16f2dbc272b8a0f7858a60805c022fc3d1fe4f31c37e63cdaca0416c0d053ef48a815f8b19121605e0"), - MustHexID("14e1f21418d445748de2a95cd9a8c3b15b506f86a0acabd8af44bb968ce39885b19c8822af61b3dd58a34d1f265baec30e3ae56149dc7d2aa4a538f7319f69c8"), - MustHexID("b9453d78281b66a4eac95a1546017111eaaa5f92a65d0de10b1122940e92b319728a24edf4dec6acc412321b1c95266d39c7b3a5d265c629c3e49a65fb022c09"), - MustHexID("e8a49248419e3824a00d86af422f22f7366e2d4922b304b7169937616a01d9d6fa5abf5cc01061a352dc866f48e1fa2240dbb453d872b1d7be62bdfc1d5e248c"), - MustHexID("bebcff24b52362f30e0589ee573ce2d86f073d58d18e6852a592fa86ceb1a6c9b96d7fb9ec7ed1ed98a51b6743039e780279f6bb49d0a04327ac7a182d9a56f6"), - MustHexID("d0835e5a4291db249b8d2fca9f503049988180c7d247bedaa2cf3a1bad0a76709360a85d4f9a1423b2cbc82bb4d94b47c0cde20afc430224834c49fe312a9ae3"), - MustHexID("6b087fe2a2da5e4f0b0f4777598a4a7fb66bf77dbd5bfc44e8a7eaa432ab585a6e226891f56a7d4f5ed11a7c57b90f1661bba1059590ca4267a35801c2802913"), - MustHexID("d901e5bde52d1a0f4ddf010a686a53974cdae4ebe5c6551b3c37d6b6d635d38d5b0e5f80bc0186a2c7809dbf3a42870dd09643e68d32db896c6da8ba734579e7"), - MustHexID("96419fb80efae4b674402bb969ebaab86c1274f29a83a311e24516d36cdf148fe21754d46c97688cdd7468f24c08b13e4727c29263393638a3b37b99ff60ebca"), - MustHexID("7b9c1889ae916a5d5abcdfb0aaedcc9c6f9eb1c1a4f68d0c2d034fe79ac610ce917c3abc670744150fa891bfcd8ab14fed6983fca964de920aa393fa7b326748"), - MustHexID("7a369b2b8962cc4c65900be046482fbf7c14f98a135bbbae25152c82ad168fb2097b3d1429197cf46d3ce9fdeb64808f908a489cc6019725db040060fdfe5405"), - MustHexID("47bcae48288da5ecc7f5058dfa07cf14d89d06d6e449cb946e237aa6652ea050d9f5a24a65efdc0013ccf232bf88670979eddef249b054f63f38da9d7796dbd8"), - }, - 254: { - MustHexID("099739d7abc8abd38ecc7a816c521a1168a4dbd359fa7212a5123ab583ffa1cf485a5fed219575d6475dbcdd541638b2d3631a6c7fce7474e7fe3cba1d4d5853"), - MustHexID("c2b01603b088a7182d0cf7ef29fb2b04c70acb320fccf78526bf9472e10c74ee70b3fcfa6f4b11d167bd7d3bc4d936b660f2c9bff934793d97cb21750e7c3d31"), - MustHexID("20e4d8f45f2f863e94b45548c1ef22a11f7d36f263e4f8623761e05a64c4572379b000a52211751e2561b0f14f4fc92dd4130410c8ccc71eb4f0e95a700d4ca9"), - MustHexID("27f4a16cc085e72d86e25c98bd2eca173eaaee7565c78ec5a52e9e12b2211f35de81b5b45e9195de2ebfe29106742c59112b951a04eb7ae48822911fc1f9389e"), - MustHexID("55db5ee7d98e7f0b1c3b9d5be6f2bc619a1b86c3cdd513160ad4dcf267037a5fffad527ac15d50aeb32c59c13d1d4c1e567ebbf4de0d25236130c8361f9aac63"), - MustHexID("883df308b0130fc928a8559fe50667a0fff80493bc09685d18213b2db241a3ad11310ed86b0ef662b3ce21fc3d9aa7f3fc24b8d9afe17c7407e9afd3345ae548"), - MustHexID("c7af968cc9bc8200c3ee1a387405f7563be1dce6710a3439f42ea40657d0eae9d2b3c16c42d779605351fcdece4da637b9804e60ca08cfb89aec32c197beffa6"), - MustHexID("3e66f2b788e3ff1d04106b80597915cd7afa06c405a7ae026556b6e583dca8e05cfbab5039bb9a1b5d06083ffe8de5780b1775550e7218f5e98624bf7af9a0a8"), - MustHexID("4fc7f53764de3337fdaec0a711d35d3a923e72fa65025444d12230b3552ed43d9b2d1ad08ccb11f2d50c58809e6dd74dde910e195294fca3b47ae5a3967cc479"), - MustHexID("bafdfdcf6ccaa989436752fa97c77477b6baa7deb374b16c095492c529eb133e8e2f99e1977012b64767b9d34b2cf6d2048ed489bd822b5139b523f6a423167b"), - MustHexID("7f5d78008a4312fe059104ce80202c82b8915c2eb4411c6b812b16f7642e57c00f2c9425121f5cbac4257fe0b3e81ef5dea97ea2dbaa98f6a8b6fd4d1e5980bb"), - MustHexID("598c37fe78f922751a052f463aeb0cb0bc7f52b7c2a4cf2da72ec0931c7c32175d4165d0f8998f7320e87324ac3311c03f9382a5385c55f0407b7a66b2acd864"), - MustHexID("f758c4136e1c148777a7f3275a76e2db0b2b04066fd738554ec398c1c6cc9fb47e14a3b4c87bd47deaeab3ffd2110514c3855685a374794daff87b605b27ee2e"), - MustHexID("0307bb9e4fd865a49dcf1fe4333d1b944547db650ab580af0b33e53c4fef6c789531110fac801bbcbce21fc4d6f61b6d5b24abdf5b22e3030646d579f6dca9c2"), - MustHexID("82504b6eb49bb2c0f91a7006ce9cefdbaf6df38706198502c2e06601091fc9dc91e4f15db3410d45c6af355bc270b0f268d3dff560f956985c7332d4b10bd1ed"), - MustHexID("b39b5b677b45944ceebe76e76d1f051de2f2a0ec7b0d650da52135743e66a9a5dba45f638258f9a7545d9a790c7fe6d3fdf82c25425c7887323e45d27d06c057"), - }, - 255: { - MustHexID("5c4d58d46e055dd1f093f81ee60a675e1f02f54da6206720adee4dccef9b67a31efc5c2a2949c31a04ee31beadc79aba10da31440a1f9ff2a24093c63c36d784"), - MustHexID("ea72161ffdd4b1e124c7b93b0684805f4c4b58d617ed498b37a145c670dbc2e04976f8785583d9c805ffbf343c31d492d79f841652bbbd01b61ed85640b23495"), - MustHexID("51caa1d93352d47a8e531692a3612adac1e8ac68d0a200d086c1c57ae1e1a91aa285ab242e8c52ef9d7afe374c9485b122ae815f1707b875569d0433c1c3ce85"), - MustHexID("c08397d5751b47bd3da044b908be0fb0e510d3149574dff7aeab33749b023bb171b5769990fe17469dbebc100bc150e798aeda426a2dcc766699a225fddd75c6"), - MustHexID("0222c1c194b749736e593f937fad67ee348ac57287a15c7e42877aa38a9b87732a408bca370f812efd0eedbff13e6d5b854bf3ba1dec431a796ed47f32552b09"), - MustHexID("03d859cd46ef02d9bfad5268461a6955426845eef4126de6be0fa4e8d7e0727ba2385b78f1a883a8239e95ebb814f2af8379632c7d5b100688eebc5841209582"), - MustHexID("64d5004b7e043c39ff0bd10cb20094c287721d5251715884c280a612b494b3e9e1c64ba6f67614994c7d969a0d0c0295d107d53fc225d47c44c4b82852d6f960"), - MustHexID("b0a5eefb2dab6f786670f35bf9641eefe6dd87fd3f1362bcab4aaa792903500ab23d88fae68411372e0813b057535a601d46e454323745a948017f6063a47b1f"), - MustHexID("0cc6df0a3433d448b5684d2a3ffa9d1a825388177a18f44ad0008c7bd7702f1ec0fc38b83506f7de689c3b6ecb552599927e29699eed6bb867ff08f80068b287"), - MustHexID("50772f7b8c03a4e153355fbbf79c8a80cf32af656ff0c7873c99911099d04a0dae0674706c357e0145ad017a0ade65e6052cb1b0d574fcd6f67da3eee0ace66b"), - MustHexID("1ae37829c9ef41f8b508b82259ebac76b1ed900d7a45c08b7970f25d2d48ddd1829e2f11423a18749940b6dab8598c6e416cef0efd47e46e51f29a0bc65b37cd"), - MustHexID("ba973cab31c2af091fc1644a93527d62b2394999e2b6ccbf158dd5ab9796a43d408786f1803ef4e29debfeb62fce2b6caa5ab2b24d1549c822a11c40c2856665"), - MustHexID("bc413ad270dd6ea25bddba78f3298b03b8ba6f8608ac03d06007d4116fa78ef5a0cfe8c80155089382fc7a193243ee5500082660cb5d7793f60f2d7d18650964"), - MustHexID("5a6a9ef07634d9eec3baa87c997b529b92652afa11473dfee41ef7037d5c06e0ddb9fe842364462d79dd31cff8a59a1b8d5bc2b810dea1d4cbbd3beb80ecec83"), - MustHexID("f492c6ee2696d5f682f7f537757e52744c2ae560f1090a07024609e903d334e9e174fc01609c5a229ddbcac36c9d21adaf6457dab38a25bfd44f2f0ee4277998"), - MustHexID("459e4db99298cb0467a90acee6888b08bb857450deac11015cced5104853be5adce5b69c740968bc7f931495d671a70cad9f48546d7cd203357fe9af0e8d2164"), - }, - 256: { - MustHexID("a8593af8a4aef7b806b5197612017951bac8845a1917ca9a6a15dd6086d608505144990b245785c4cd2d67a295701c7aac2aa18823fb0033987284b019656268"), - MustHexID("d2eebef914928c3aad77fc1b2a495f52d2294acf5edaa7d8a530b540f094b861a68fe8348a46a7c302f08ab609d85912a4968eacfea0740847b29421b4795d9e"), - MustHexID("b14bfcb31495f32b650b63cf7d08492e3e29071fdc73cf2da0da48d4b191a70ba1a65f42ad8c343206101f00f8a48e8db4b08bf3f622c0853e7323b250835b91"), - MustHexID("7feaee0d818c03eb30e4e0bf03ade0f3c21ca38e938a761aa1781cf70bda8cc5cd631a6cc53dd44f1d4a6d3e2dae6513c6c66ee50cb2f0e9ad6f7e319b309fd9"), - MustHexID("4ca3b657b139311db8d583c25dd5963005e46689e1317620496cc64129c7f3e52870820e0ec7941d28809311df6db8a2867bbd4f235b4248af24d7a9c22d1232"), - MustHexID("1181defb1d16851d42dd951d84424d6bd1479137f587fa184d5a8152be6b6b16ed08bcdb2c2ed8539bcde98c80c432875f9f724737c316a2bd385a39d3cab1d8"), - MustHexID("d9dd818769fa0c3ec9f553c759b92476f082817252a04a47dc1777740b1731d280058c66f982812f173a294acf4944a85ba08346e2de153ba3ba41ce8a62cb64"), - MustHexID("bd7c4f8a9e770aa915c771b15e107ca123d838762da0d3ffc53aa6b53e9cd076cffc534ec4d2e4c334c683f1f5ea72e0e123f6c261915ed5b58ac1b59f003d88"), - MustHexID("3dd5739c73649d510456a70e9d6b46a855864a4a3f744e088fd8c8da11b18e4c9b5f2d7da50b1c147b2bae5ca9609ae01f7a3cdea9dce34f80a91d29cd82f918"), - MustHexID("f0d7df1efc439b4bcc0b762118c1cfa99b2a6143a9f4b10e3c9465125f4c9fca4ab88a2504169bbcad65492cf2f50da9dd5d077c39574a944f94d8246529066b"), - MustHexID("dd598b9ba441448e5fb1a6ec6c5f5aa9605bad6e223297c729b1705d11d05f6bfd3d41988b694681ae69bb03b9a08bff4beab5596503d12a39bffb5cd6e94c7c"), - MustHexID("3fce284ac97e567aebae681b15b7a2b6df9d873945536335883e4bbc26460c064370537f323fd1ada828ea43154992d14ac0cec0940a2bd2a3f42ec156d60c83"), - MustHexID("7c8dfa8c1311cb14fb29a8ac11bca23ecc115e56d9fcf7b7ac1db9066aa4eb39f8b1dabf46e192a65be95ebfb4e839b5ab4533fef414921825e996b210dd53bd"), - MustHexID("cafa6934f82120456620573d7f801390ed5e16ed619613a37e409e44ab355ef755e83565a913b48a9466db786f8d4fbd590bfec474c2524d4a2608d4eafd6abd"), - MustHexID("9d16600d0dd310d77045769fed2cb427f32db88cd57d86e49390c2ba8a9698cfa856f775be2013237226e7bf47b248871cf865d23015937d1edeb20db5e3e760"), - MustHexID("17be6b6ba54199b1d80eff866d348ea11d8a4b341d63ad9a6681d3ef8a43853ac564d153eb2a8737f0afc9ab320f6f95c55aa11aaa13bbb1ff422fd16bdf8188"), - }, - }, -} - -type preminedTestnet struct { - target NodeID - targetSha common.Hash // sha3(target) - dists [hashBits + 1][]NodeID - net *Network -} - -func (tn *preminedTestnet) sendFindnodeHash(to *Node, target common.Hash) { - // current log distance is encoded in port number - // fmt.Println("findnode query at dist", toaddr.Port) - if to.UDP <= lowPort { - panic("query to node at or below distance 0") - } - next := to.UDP - 1 - var result []rpcNode - for i, id := range tn.dists[to.UDP-lowPort] { - result = append(result, nodeToRPC(NewNode(id, net.ParseIP("10.0.2.99"), next, uint16(i)+1+lowPort))) - } - injectResponse(tn.net, to, neighborsPacket, &neighbors{Nodes: result}) -} - -func (tn *preminedTestnet) sendPing(to *Node, addr *net.UDPAddr, topics []Topic) []byte { - injectResponse(tn.net, to, pongPacket, &pong{ReplyTok: []byte{1}}) - return []byte{1} -} - -func (tn *preminedTestnet) send(to *Node, ptype nodeEvent, data interface{}) (hash []byte) { - switch ptype { - case pingPacket: - injectResponse(tn.net, to, pongPacket, &pong{ReplyTok: []byte{1}}) - case pongPacket: - // ignored - case findnodeHashPacket: - // current log distance is encoded in port number - // fmt.Println("findnode query at dist", toaddr.Port-lowPort) - if to.UDP <= lowPort { - panic("query to node at or below distance 0") - } - next := to.UDP - 1 - var result []rpcNode - for i, id := range tn.dists[to.UDP-lowPort] { - result = append(result, nodeToRPC(NewNode(id, net.ParseIP("10.0.2.99"), next, uint16(i)+1+lowPort))) - } - injectResponse(tn.net, to, neighborsPacket, &neighbors{Nodes: result}) - default: - panic("send(" + ptype.String() + ")") - } - return []byte{2} -} - -func (tn *preminedTestnet) sendNeighbours(to *Node, nodes []*Node) { - panic("sendNeighbours called") -} - -func (tn *preminedTestnet) sendTopicNodes(to *Node, queryHash common.Hash, nodes []*Node) { - panic("sendTopicNodes called") -} - -func (tn *preminedTestnet) sendTopicRegister(to *Node, topics []Topic, idx int, pong []byte) { - panic("sendTopicRegister called") -} - -func (*preminedTestnet) Close() {} - -func (*preminedTestnet) localAddr() *net.UDPAddr { - return &net.UDPAddr{IP: net.ParseIP("10.0.1.1"), Port: 40000} -} - -func injectResponse(net *Network, from *Node, ev nodeEvent, packet interface{}) { - go net.reqReadPacket(ingressPacket{remoteID: from.ID, remoteAddr: from.addr(), ev: ev, data: packet}) -} diff --git a/p2p/discv5/node.go b/p2p/discv5/node.go deleted file mode 100644 index 44d3025b70..0000000000 --- a/p2p/discv5/node.go +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "encoding/hex" - "errors" - "fmt" - "math/big" - "math/rand" - "net" - "net/url" - "regexp" - "strconv" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -// Node represents a host on the network. -// The public fields of Node may not be modified. -type Node struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP, TCP uint16 // port numbers - ID NodeID // the node's public key - - // Network-related fields are contained in nodeNetGuts. - // These fields are not supposed to be used off the - // Network.loop goroutine. - nodeNetGuts -} - -// NewNode creates a new node. It is mostly meant to be used for -// testing purposes. -func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node { - if ipv4 := ip.To4(); ipv4 != nil { - ip = ipv4 - } - return &Node{ - IP: ip, - UDP: udpPort, - TCP: tcpPort, - ID: id, - nodeNetGuts: nodeNetGuts{sha: crypto.Keccak256Hash(id[:])}, - } -} - -func (n *Node) addr() *net.UDPAddr { - return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)} -} - -// Incomplete returns true for nodes with no IP address. -func (n *Node) Incomplete() bool { - return n.IP == nil -} - -// checks whether n is a valid complete node. -func (n *Node) validateComplete() error { - if n.Incomplete() { - return errors.New("incomplete node") - } - if n.UDP == 0 { - return errors.New("missing UDP port") - } - if n.TCP == 0 { - return errors.New("missing TCP port") - } - if n.IP.IsMulticast() || n.IP.IsUnspecified() { - return errors.New("invalid IP (multicast/unspecified)") - } - _, err := n.ID.Pubkey() // validate the key (on curve, etc.) - return err -} - -// The string representation of a Node is a URL. -// Please see ParseNode for a description of the format. -func (n *Node) String() string { - u := url.URL{Scheme: "enode"} - if n.Incomplete() { - u.Host = fmt.Sprintf("%x", n.ID[:]) - } else { - addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)} - u.User = url.User(fmt.Sprintf("%x", n.ID[:])) - u.Host = addr.String() - if n.UDP != n.TCP { - u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP)) - } - } - return u.String() -} - -var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$") - -// ParseNode parses a node designator. -// -// There are two basic forms of node designators -// - incomplete nodes, which only have the public key (node ID) -// - complete nodes, which contain the public key and IP/Port information -// -// For incomplete nodes, the designator must look like one of these -// -// enode:// -// -// -// For complete nodes, the node ID is encoded in the username portion -// of the URL, separated from the host by an @ sign. The hostname can -// only be given as an IP address, DNS domain names are not allowed. -// The port in the host name section is the TCP listening port. If the -// TCP and UDP (discovery) ports differ, the UDP port is specified as -// query parameter "discport". -// -// In the following example, the node URL describes -// a node with IP address 10.3.58.6, TCP listening port 30303 -// and UDP discovery port 30301. -// -// enode://@10.3.58.6:30303?discport=30301 -func ParseNode(rawurl string) (*Node, error) { - if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil { - id, err := HexID(m[1]) - if err != nil { - return nil, fmt.Errorf("invalid node ID (%v)", err) - } - return NewNode(id, nil, 0, 0), nil - } - return parseComplete(rawurl) -} - -func parseComplete(rawurl string) (*Node, error) { - var ( - id NodeID - ip net.IP - tcpPort, udpPort uint64 - ) - u, err := url.Parse(rawurl) - if err != nil { - return nil, err - } - if u.Scheme != "enode" { - return nil, errors.New("invalid URL scheme, want \"enode\"") - } - // Parse the Node ID from the user portion. - if u.User == nil { - return nil, errors.New("does not contain node ID") - } - if id, err = HexID(u.User.String()); err != nil { - return nil, fmt.Errorf("invalid node ID (%v)", err) - } - // Parse the IP address. - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - return nil, fmt.Errorf("invalid host: %v", err) - } - if ip = net.ParseIP(host); ip == nil { - return nil, errors.New("invalid IP address") - } - // Ensure the IP is 4 bytes long for IPv4 addresses. - if ipv4 := ip.To4(); ipv4 != nil { - ip = ipv4 - } - // Parse the port numbers. - if tcpPort, err = strconv.ParseUint(port, 10, 16); err != nil { - return nil, errors.New("invalid port") - } - udpPort = tcpPort - qv := u.Query() - if qv.Get("discport") != "" { - udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16) - if err != nil { - return nil, errors.New("invalid discport in query") - } - } - return NewNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil -} - -// MustParseNode parses a node URL. It panics if the URL is not valid. -func MustParseNode(rawurl string) *Node { - n, err := ParseNode(rawurl) - if err != nil { - panic("invalid node URL: " + err.Error()) - } - return n -} - -// MarshalText implements encoding.TextMarshaler. -func (n *Node) MarshalText() ([]byte, error) { - return []byte(n.String()), nil -} - -// UnmarshalText implements encoding.TextUnmarshaler. -func (n *Node) UnmarshalText(text []byte) error { - dec, err := ParseNode(string(text)) - if err == nil { - *n = *dec - } - return err -} - -// type nodeQueue []*Node -// -// // pushNew adds n to the end if it is not present. -// func (nl *nodeList) appendNew(n *Node) { -// for _, entry := range n { -// if entry == n { -// return -// } -// } -// *nq = append(*nq, n) -// } -// -// // popRandom removes a random node. Nodes closer to -// // to the head of the beginning of the have a slightly higher probability. -// func (nl *nodeList) popRandom() *Node { -// ix := rand.Intn(len(*nq)) -// //TODO: probability as mentioned above. -// nl.removeIndex(ix) -// } -// -// func (nl *nodeList) removeIndex(i int) *Node { -// slice = *nl -// if len(*slice) <= i { -// return nil -// } -// *nl = append(slice[:i], slice[i+1:]...) -// } - -const nodeIDBits = 512 - -// NodeID is a unique identifier for each node. -// The node identifier is a marshaled elliptic curve public key. -type NodeID [nodeIDBits / 8]byte - -// NodeID prints as a long hexadecimal number. -func (n NodeID) String() string { - return fmt.Sprintf("%x", n[:]) -} - -// The Go syntax representation of a NodeID is a call to HexID. -func (n NodeID) GoString() string { - return fmt.Sprintf("discover.HexID(\"%x\")", n[:]) -} - -// TerminalString returns a shortened hex string for terminal logging. -func (n NodeID) TerminalString() string { - return hex.EncodeToString(n[:8]) -} - -// HexID converts a hex string to a NodeID. -// The string may be prefixed with 0x. -func HexID(in string) (NodeID, error) { - var id NodeID - b, err := hex.DecodeString(strings.TrimPrefix(in, "0x")) - if err != nil { - return id, err - } else if len(b) != len(id) { - return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2) - } - copy(id[:], b) - return id, nil -} - -// MustHexID converts a hex string to a NodeID. -// It panics if the string is not a valid NodeID. -func MustHexID(in string) NodeID { - id, err := HexID(in) - if err != nil { - panic(err) - } - return id -} - -// PubkeyID returns a marshaled representation of the given public key. -func PubkeyID(pub *ecdsa.PublicKey) NodeID { - var id NodeID - pbytes := elliptic.Marshal(pub.Curve, pub.X, pub.Y) - if len(pbytes)-1 != len(id) { - panic(fmt.Errorf("need %d bit pubkey, got %d bits", (len(id)+1)*8, len(pbytes))) - } - copy(id[:], pbytes[1:]) - return id -} - -// Pubkey returns the public key represented by the node ID. -// It returns an error if the ID is not a point on the curve. -func (n NodeID) Pubkey() (*ecdsa.PublicKey, error) { - p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)} - half := len(n) / 2 - p.X.SetBytes(n[:half]) - p.Y.SetBytes(n[half:]) - if !p.Curve.IsOnCurve(p.X, p.Y) { - return nil, errors.New("id is invalid secp256k1 curve point") - } - return p, nil -} - -// recoverNodeID computes the public key used to sign the -// given hash from the signature. -func recoverNodeID(hash, sig []byte) (id NodeID, err error) { - pubkey, err := crypto.Ecrecover(hash, sig) - if err != nil { - return id, err - } - if len(pubkey)-1 != len(id) { - return id, fmt.Errorf("recovered pubkey has %d bits, want %d bits", len(pubkey)*8, (len(id)+1)*8) - } - for i := range id { - id[i] = pubkey[i+1] - } - return id, nil -} - -// distcmp compares the distances a->target and b->target. -// Returns -1 if a is closer to target, 1 if b is closer to target -// and 0 if they are equal. -func distcmp(target, a, b common.Hash) int { - for i := range target { - da := a[i] ^ target[i] - db := b[i] ^ target[i] - if da > db { - return 1 - } else if da < db { - return -1 - } - } - return 0 -} - -// table of leading zero counts for bytes [0..255] -var lzcount = [256]int{ - 8, 7, 6, 6, 5, 5, 5, 5, - 4, 4, 4, 4, 4, 4, 4, 4, - 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, - 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, -} - -// logdist returns the logarithmic distance between a and b, log2(a ^ b). -func logdist(a, b common.Hash) int { - lz := 0 - for i := range a { - x := a[i] ^ b[i] - if x == 0 { - lz += 8 - } else { - lz += lzcount[x] - break - } - } - return len(a)*8 - lz -} - -// hashAtDistance returns a random hash such that logdist(a, b) == n -func hashAtDistance(a common.Hash, n int) (b common.Hash) { - if n == 0 { - return a - } - // flip bit at position n, fill the rest with random bits - b = a - pos := len(a) - n/8 - 1 - bit := byte(0x01) << (byte(n%8) - 1) - if bit == 0 { - pos++ - bit = 0x80 - } - b[pos] = a[pos]&^bit | ^a[pos]&bit // TODO: randomize end bits - for i := pos + 1; i < len(a); i++ { - b[i] = byte(rand.Intn(255)) - } - return b -} diff --git a/p2p/discv5/node_test.go b/p2p/discv5/node_test.go deleted file mode 100644 index 4e0fdbe3db..0000000000 --- a/p2p/discv5/node_test.go +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "fmt" - "math/big" - "math/rand" - "net" - "reflect" - "strings" - "testing" - "testing/quick" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -func ExampleNewNode() { - id := MustHexID("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439") - - // Complete nodes contain UDP and TCP endpoints: - n1 := NewNode(id, net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 52150, 30303) - fmt.Println("n1:", n1) - fmt.Println("n1.Incomplete() ->", n1.Incomplete()) - - // An incomplete node can be created by passing zero values - // for all parameters except id. - n2 := NewNode(id, nil, 0, 0) - fmt.Println("n2:", n2) - fmt.Println("n2.Incomplete() ->", n2.Incomplete()) - - // Output: - // n1: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:30303?discport=52150 - // n1.Incomplete() -> false - // n2: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439 - // n2.Incomplete() -> true -} - -var parseNodeTests = []struct { - rawurl string - wantError string - wantResult *Node -}{ - { - rawurl: "http://foobar", - wantError: `invalid URL scheme, want "enode"`, - }, - { - rawurl: "enode://01010101@123.124.125.126:3", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - // Complete nodes with IP address. - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3", - wantError: `invalid IP address`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo", - wantError: `invalid port`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo", - wantError: `invalid discport in query`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{0x7f, 0x0, 0x0, 0x1}, - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.ParseIP("::"), - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{0x7f, 0x0, 0x0, 0x1}, - 22334, - 52150, - ), - }, - // Incomplete nodes with no address. - { - rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - nil, 0, 0, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - nil, 0, 0, - ), - }, - // Invalid URLs - { - rawurl: "01010101", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - { - rawurl: "enode://01010101", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - { - // This test checks that errors from url.Parse are handled. - rawurl: "://foo", - wantError: `missing protocol scheme`, - }, -} - -func TestParseNode(t *testing.T) { - for _, test := range parseNodeTests { - n, err := ParseNode(test.rawurl) - if test.wantError != "" { - if err == nil { - t.Errorf("test %q:\n got nil error, expected %#q", test.rawurl, test.wantError) - continue - } else if !strings.Contains(err.Error(), test.wantError) { - t.Errorf("test %q:\n got error %#q, expected %#q", test.rawurl, err.Error(), test.wantError) - continue - } - } else { - if err != nil { - t.Errorf("test %q:\n unexpected error: %v", test.rawurl, err) - continue - } - if !reflect.DeepEqual(n, test.wantResult) { - t.Errorf("test %q:\n result mismatch:\ngot: %#v, want: %#v", test.rawurl, n, test.wantResult) - } - } - } -} - -func TestNodeString(t *testing.T) { - for i, test := range parseNodeTests { - if test.wantError == "" && strings.HasPrefix(test.rawurl, "enode://") { - str := test.wantResult.String() - if str != test.rawurl { - t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.rawurl) - } - } - } -} - -func TestHexID(t *testing.T) { - ref := NodeID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 106, 217, 182, 31, 165, 174, 1, 67, 7, 235, 220, 150, 66, 83, 173, 205, 159, 44, 10, 57, 42, 161, 26, 188} - id1 := MustHexID("0x000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") - id2 := MustHexID("000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") - - if id1 != ref { - t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:]) - } - if id2 != ref { - t.Errorf("wrong id2\ngot %v\nwant %v", id2[:], ref[:]) - } -} - -func TestNodeID_recover(t *testing.T) { - prv := newkey() - hash := make([]byte, 32) - sig, err := crypto.Sign(hash, prv) - if err != nil { - t.Fatalf("signing error: %v", err) - } - - pub := PubkeyID(&prv.PublicKey) - recpub, err := recoverNodeID(hash, sig) - if err != nil { - t.Fatalf("recovery error: %v", err) - } - if pub != recpub { - t.Errorf("recovered wrong pubkey:\ngot: %v\nwant: %v", recpub, pub) - } - - ecdsa, err := pub.Pubkey() - if err != nil { - t.Errorf("Pubkey error: %v", err) - } - if !reflect.DeepEqual(ecdsa, &prv.PublicKey) { - t.Errorf("Pubkey mismatch:\n got: %#v\n want: %#v", ecdsa, &prv.PublicKey) - } -} - -func TestNodeID_pubkeyBad(t *testing.T) { - ecdsa, err := NodeID{}.Pubkey() - if err == nil { - t.Error("expected error for zero ID") - } - if ecdsa != nil { - t.Error("expected nil result") - } -} - -func TestNodeID_distcmp(t *testing.T) { - distcmpBig := func(target, a, b common.Hash) int { - tbig := new(big.Int).SetBytes(target[:]) - abig := new(big.Int).SetBytes(a[:]) - bbig := new(big.Int).SetBytes(b[:]) - return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig)) - } - if err := quick.CheckEqual(distcmp, distcmpBig, quickcfg()); err != nil { - t.Error(err) - } -} - -// the random tests is likely to miss the case where they're equal. -func TestNodeID_distcmpEqual(t *testing.T) { - base := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - x := common.Hash{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0} - if distcmp(base, x, x) != 0 { - t.Errorf("distcmp(base, x, x) != 0") - } -} - -func TestNodeID_logdist(t *testing.T) { - logdistBig := func(a, b common.Hash) int { - abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:]) - return new(big.Int).Xor(abig, bbig).BitLen() - } - if err := quick.CheckEqual(logdist, logdistBig, quickcfg()); err != nil { - t.Error(err) - } -} - -// the random tests is likely to miss the case where they're equal. -func TestNodeID_logdistEqual(t *testing.T) { - x := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - if logdist(x, x) != 0 { - t.Errorf("logdist(x, x) != 0") - } -} - -func TestNodeID_hashAtDistance(t *testing.T) { - // we don't use quick.Check here because its output isn't - // very helpful when the test fails. - cfg := quickcfg() - for i := 0; i < cfg.MaxCount; i++ { - a := gen(common.Hash{}, cfg.Rand).(common.Hash) - dist := cfg.Rand.Intn(len(common.Hash{}) * 8) - result := hashAtDistance(a, dist) - actualdist := logdist(result, a) - - if dist != actualdist { - t.Log("a: ", a) - t.Log("result:", result) - t.Fatalf("#%d: distance of result is %d, want %d", i, actualdist, dist) - } - } -} - -func quickcfg() *quick.Config { - return &quick.Config{ - MaxCount: 5000, - Rand: rand.New(rand.NewSource(time.Now().Unix())), - } -} - -// TODO: The Generate method can be dropped when we require Go >= 1.5 -// because testing/quick learned to generate arrays in 1.5. - -func (NodeID) Generate(rand *rand.Rand, size int) reflect.Value { - var id NodeID - m := rand.Intn(len(id)) - for i := len(id) - 1; i > m; i-- { - id[i] = byte(rand.Uint32()) - } - return reflect.ValueOf(id) -} diff --git a/p2p/discv5/nodeevent_string.go b/p2p/discv5/nodeevent_string.go deleted file mode 100644 index 38c1993bac..0000000000 --- a/p2p/discv5/nodeevent_string.go +++ /dev/null @@ -1,17 +0,0 @@ -// Code generated by "stringer -type=nodeEvent"; DO NOT EDIT. - -package discv5 - -import "strconv" - -const _nodeEvent_name = "pongTimeoutpingTimeoutneighboursTimeout" - -var _nodeEvent_index = [...]uint8{0, 11, 22, 39} - -func (i nodeEvent) String() string { - i -= 264 - if i >= nodeEvent(len(_nodeEvent_index)-1) { - return "nodeEvent(" + strconv.FormatInt(int64(i+264), 10) + ")" - } - return _nodeEvent_name[_nodeEvent_index[i]:_nodeEvent_index[i+1]] -} diff --git a/p2p/discv5/sim_run_test.go b/p2p/discv5/sim_run_test.go deleted file mode 100644 index bded0cc023..0000000000 --- a/p2p/discv5/sim_run_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "bufio" - "bytes" - "encoding/binary" - "errors" - "fmt" - "io" - "os" - "os/exec" - "runtime" - "strings" - "testing" -) - -func getnacl() (string, error) { - switch runtime.GOARCH { - case "amd64": - _, err := exec.LookPath("sel_ldr_x86_64") - return "amd64p32", err - case "i386": - _, err := exec.LookPath("sel_ldr_i386") - return "i386", err - default: - return "", errors.New("nacl is not supported on " + runtime.GOARCH) - } -} - -// runWithPlaygroundTime executes the caller -// in the NaCl sandbox with faketime enabled. -// -// This function must be called from a Test* function -// and the caller must skip the actual test when isHost is true. -func runWithPlaygroundTime(t *testing.T) (isHost bool) { - if runtime.GOOS == "nacl" { - return false - } - - // Get the caller. - callerPC, _, _, ok := runtime.Caller(1) - if !ok { - panic("can't get caller") - } - callerFunc := runtime.FuncForPC(callerPC) - if callerFunc == nil { - panic("can't get caller") - } - callerName := callerFunc.Name()[strings.LastIndexByte(callerFunc.Name(), '.')+1:] - if !strings.HasPrefix(callerName, "Test") { - panic("must be called from witin a Test* function") - } - testPattern := "^" + callerName + "$" - - // Unfortunately runtime.faketime (playground time mode) only works on NaCl. The NaCl - // SDK must be installed and linked into PATH for this to work. - arch, err := getnacl() - if err != nil { - t.Skip(err) - } - - // Compile and run the calling test using NaCl. - // The extra tag ensures that the TestMain function in sim_main_test.go is used. - cmd := exec.Command("go", "test", "-v", "-tags", "faketime_simulation", "-timeout", "100h", "-run", testPattern, ".") - cmd.Env = append([]string{"GOOS=nacl", "GOARCH=" + arch}, os.Environ()...) - stdout, _ := cmd.StdoutPipe() - stderr, _ := cmd.StderrPipe() - go skipPlaygroundOutputHeaders(os.Stdout, stdout) - go skipPlaygroundOutputHeaders(os.Stderr, stderr) - if err := cmd.Run(); err != nil { - t.Error(err) - } - - // Ensure that the test function doesn't run in the (non-NaCl) host process. - return true -} - -func skipPlaygroundOutputHeaders(out io.Writer, in io.Reader) { - // Additional output can be printed without the headers - // before the NaCl binary starts running (e.g. compiler error messages). - bufin := bufio.NewReader(in) - output, err := bufin.ReadBytes(0) - output = bytes.TrimSuffix(output, []byte{0}) - if len(output) > 0 { - out.Write(output) - } - if err != nil { - return - } - bufin.UnreadByte() - - // Playback header: 0 0 P B <8-byte time> <4-byte data length> - head := make([]byte, 4+8+4) - for { - if _, err := io.ReadFull(bufin, head); err != nil { - if err != io.EOF { - fmt.Fprintln(out, "read error:", err) - } - return - } - if !bytes.HasPrefix(head, []byte{0x00, 0x00, 'P', 'B'}) { - fmt.Fprintf(out, "expected playback header, got %q\n", head) - io.Copy(out, bufin) - return - } - // Copy data until next header. - size := binary.BigEndian.Uint32(head[12:]) - io.CopyN(out, bufin, int64(size)) - } -} diff --git a/p2p/discv5/sim_test.go b/p2p/discv5/sim_test.go deleted file mode 100644 index 3d1e610d3a..0000000000 --- a/p2p/discv5/sim_test.go +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "crypto/ecdsa" - "encoding/binary" - "fmt" - "math/rand" - "net" - "strconv" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" -) - -// In this test, nodes try to randomly resolve each other. -func TestSimRandomResolve(t *testing.T) { - t.Skip("boring") - if runWithPlaygroundTime(t) { - return - } - - sim := newSimulation() - bootnode := sim.launchNode(false) - - // A new node joins every 10s. - launcher := time.NewTicker(10 * time.Second) - defer launcher.Stop() - go func() { - for range launcher.C { - net := sim.launchNode(false) - go randomResolves(t, sim, net) - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - t.Logf("launched @ %v: %x\n", time.Now(), net.Self().ID[:16]) - } - }() - - time.Sleep(3 * time.Hour) - sim.shutdown() - sim.printStats() -} - -func TestSimTopics(t *testing.T) { - t.Skip("NaCl test") - if runWithPlaygroundTime(t) { - return - } - sim := newSimulation() - bootnode := sim.launchNode(false) - - go func() { - nets := make([]*Network, 1024) - for i := range nets { - net := sim.launchNode(false) - nets[i] = net - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - time.Sleep(time.Second * 5) - } - - for i, net := range nets { - if i < 256 { - stop := make(chan struct{}) - go net.RegisterTopic(testTopic, stop) - go func() { - //time.Sleep(time.Second * 36000) - time.Sleep(time.Second * 40000) - close(stop) - }() - time.Sleep(time.Millisecond * 100) - } - // time.Sleep(time.Second * 10) - //time.Sleep(time.Second) - /*if i%500 == 499 { - time.Sleep(time.Second * 9501) - } else { - time.Sleep(time.Second) - }*/ - } - }() - - // A new node joins every 10s. - /* launcher := time.NewTicker(5 * time.Second) - cnt := 0 - var printNet *Network - go func() { - for range launcher.C { - cnt++ - if cnt <= 1000 { - log := false //(cnt == 500) - net := sim.launchNode(log) - if log { - printNet = net - } - if cnt > 500 { - go net.RegisterTopic(testTopic, nil) - } - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - } - //fmt.Printf("launched @ %v: %x\n", time.Now(), net.Self().ID[:16]) - } - }() - */ - time.Sleep(55000 * time.Second) - //launcher.Stop() - sim.shutdown() - //sim.printStats() - //printNet.log.printLogs() -} - -/*func testHierarchicalTopics(i int) []Topic { - digits := strconv.FormatInt(int64(256+i/4), 4) - res := make([]Topic, 5) - for i, _ := range res { - res[i] = Topic("foo" + digits[1:i+1]) - } - return res -}*/ - -func testHierarchicalTopics(i int) []Topic { - digits := strconv.FormatInt(int64(128+i/8), 2) - res := make([]Topic, 8) - for i := range res { - res[i] = Topic("foo" + digits[1:i+1]) - } - return res -} - -func TestSimTopicHierarchy(t *testing.T) { - t.Skip("NaCl test") - if runWithPlaygroundTime(t) { - return - } - sim := newSimulation() - bootnode := sim.launchNode(false) - - go func() { - nets := make([]*Network, 1024) - for i := range nets { - net := sim.launchNode(false) - nets[i] = net - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - time.Sleep(time.Second * 5) - } - - stop := make(chan struct{}) - for i, net := range nets { - //if i < 256 { - for _, topic := range testHierarchicalTopics(i)[:5] { - //fmt.Println("reg", topic) - go net.RegisterTopic(topic, stop) - } - time.Sleep(time.Millisecond * 100) - //} - } - time.Sleep(time.Second * 90000) - close(stop) - }() - - time.Sleep(100000 * time.Second) - sim.shutdown() -} - -func randomResolves(t *testing.T, s *simulation, net *Network) { - randtime := func() time.Duration { - return time.Duration(rand.Intn(50)+20) * time.Second - } - lookup := func(target NodeID) bool { - result := net.Resolve(target) - return result != nil && result.ID == target - } - - timer := time.NewTimer(randtime()) - defer timer.Stop() - for { - select { - case <-timer.C: - target := s.randomNode().Self().ID - if !lookup(target) { - t.Errorf("node %x: target %x not found", net.Self().ID[:8], target[:8]) - } - timer.Reset(randtime()) - case <-net.closed: - return - } - } -} - -type simulation struct { - mu sync.RWMutex - nodes map[NodeID]*Network - nodectr uint32 -} - -func newSimulation() *simulation { - return &simulation{nodes: make(map[NodeID]*Network)} -} - -func (s *simulation) shutdown() { - s.mu.RLock() - alive := make([]*Network, 0, len(s.nodes)) - for _, n := range s.nodes { - alive = append(alive, n) - } - defer s.mu.RUnlock() - - for _, n := range alive { - n.Close() - } -} - -func (s *simulation) printStats() { - s.mu.Lock() - defer s.mu.Unlock() - fmt.Println("node counter:", s.nodectr) - fmt.Println("alive nodes:", len(s.nodes)) - - // for _, n := range s.nodes { - // fmt.Printf("%x\n", n.tab.self.ID[:8]) - // transport := n.conn.(*simTransport) - // fmt.Println(" joined:", transport.joinTime) - // fmt.Println(" sends:", transport.hashctr) - // fmt.Println(" table size:", n.tab.count) - // } - - /*for _, n := range s.nodes { - fmt.Println() - fmt.Printf("*** Node %x\n", n.tab.self.ID[:8]) - n.log.printLogs() - }*/ - -} - -func (s *simulation) randomNode() *Network { - s.mu.Lock() - defer s.mu.Unlock() - - n := rand.Intn(len(s.nodes)) - for _, net := range s.nodes { - if n == 0 { - return net - } - n-- - } - return nil -} - -func (s *simulation) launchNode(log bool) *Network { - var ( - num = s.nodectr - key = newkey() - id = PubkeyID(&key.PublicKey) - ip = make(net.IP, 4) - ) - s.nodectr++ - binary.BigEndian.PutUint32(ip, num) - ip[0] = 10 - addr := &net.UDPAddr{IP: ip, Port: 30303} - - transport := &simTransport{joinTime: time.Now(), sender: id, senderAddr: addr, sim: s, priv: key} - net, err := newNetwork(transport, key.PublicKey, "", nil) - if err != nil { - panic("cannot launch new node: " + err.Error()) - } - - s.mu.Lock() - s.nodes[id] = net - s.mu.Unlock() - - return net -} - -type simTransport struct { - joinTime time.Time - sender NodeID - senderAddr *net.UDPAddr - sim *simulation - hashctr uint64 - priv *ecdsa.PrivateKey -} - -func (st *simTransport) localAddr() *net.UDPAddr { - return st.senderAddr -} - -func (st *simTransport) Close() {} - -func (st *simTransport) send(remote *Node, ptype nodeEvent, data interface{}) (hash []byte) { - hash = st.nextHash() - var raw []byte - if ptype == pongPacket { - var err error - raw, _, err = encodePacket(st.priv, byte(ptype), data) - if err != nil { - panic(err) - } - } - - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: hash, - ev: ptype, - data: data, - rawData: raw, - }) - return hash -} - -func (st *simTransport) sendPing(remote *Node, remoteAddr *net.UDPAddr, topics []Topic) []byte { - hash := st.nextHash() - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: hash, - ev: pingPacket, - data: &ping{ - Version: 4, - From: rpcEndpoint{IP: st.senderAddr.IP, UDP: uint16(st.senderAddr.Port), TCP: 30303}, - To: rpcEndpoint{IP: remoteAddr.IP, UDP: uint16(remoteAddr.Port), TCP: 30303}, - Expiration: uint64(time.Now().Unix() + int64(expiration)), - Topics: topics, - }, - }) - return hash -} - -func (st *simTransport) sendFindnodeHash(remote *Node, target common.Hash) { - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: findnodeHashPacket, - data: &findnodeHash{ - Target: target, - Expiration: uint64(time.Now().Unix() + int64(expiration)), - }, - }) -} - -func (st *simTransport) sendTopicRegister(remote *Node, topics []Topic, idx int, pong []byte) { - //fmt.Println("send", topics, pong) - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: topicRegisterPacket, - data: &topicRegister{ - Topics: topics, - Idx: uint(idx), - Pong: pong, - }, - }) -} - -func (st *simTransport) sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) { - rnodes := make([]rpcNode, len(nodes)) - for i := range nodes { - rnodes[i] = nodeToRPC(nodes[i]) - } - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: topicNodesPacket, - data: &topicNodes{Echo: queryHash, Nodes: rnodes}, - }) -} - -func (st *simTransport) sendNeighbours(remote *Node, nodes []*Node) { - // TODO: send multiple packets - rnodes := make([]rpcNode, len(nodes)) - for i := range nodes { - rnodes[i] = nodeToRPC(nodes[i]) - } - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: neighborsPacket, - data: &neighbors{ - Nodes: rnodes, - Expiration: uint64(time.Now().Unix() + int64(expiration)), - }, - }) -} - -func (st *simTransport) nextHash() []byte { - v := atomic.AddUint64(&st.hashctr, 1) - var hash common.Hash - binary.BigEndian.PutUint64(hash[:], v) - return hash[:] -} - -const packetLoss = 0 // 1/1000 - -func (st *simTransport) sendPacket(remote NodeID, p ingressPacket) { - if rand.Int31n(1000) >= packetLoss { - st.sim.mu.RLock() - recipient := st.sim.nodes[remote] - st.sim.mu.RUnlock() - - time.AfterFunc(200*time.Millisecond, func() { - recipient.reqReadPacket(p) - }) - } -} diff --git a/p2p/discv5/sim_testmain_test.go b/p2p/discv5/sim_testmain_test.go deleted file mode 100644 index 77e751c419..0000000000 --- a/p2p/discv5/sim_testmain_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build go1.4,nacl,faketime_simulation - -package discv5 - -import ( - "os" - "runtime" - "testing" - "unsafe" -) - -// Enable fake time mode in the runtime, like on the go playground. -// There is a slight chance that this won't work because some go code -// might have executed before the variable is set. - -//go:linkname faketime runtime.faketime -var faketime = 1 - -func TestMain(m *testing.M) { - // We need to use unsafe somehow in order to get access to go:linkname. - _ = unsafe.Sizeof(0) - - // Run the actual test. runWithPlaygroundTime ensures that the only test - // that runs is the one calling it. - runtime.GOMAXPROCS(8) - os.Exit(m.Run()) -} diff --git a/p2p/discv5/table.go b/p2p/discv5/table.go deleted file mode 100644 index 64c3ecd1c7..0000000000 --- a/p2p/discv5/table.go +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package discv5 is a prototype implementation of Discvery v5. -// Deprecated: do not use this package. -package discv5 - -import ( - "crypto/rand" - "encoding/binary" - "fmt" - "net" - "sort" - - "github.com/ethereum/go-ethereum/common" -) - -const ( - alpha = 3 // Kademlia concurrency factor - bucketSize = 16 // Kademlia bucket size - hashBits = len(common.Hash{}) * 8 - nBuckets = hashBits + 1 // Number of buckets - - maxFindnodeFailures = 5 -) - -type Table struct { - count int // number of nodes - buckets [nBuckets]*bucket // index of known nodes by distance - nodeAddedHook func(*Node) // for testing - self *Node // metadata of the local node -} - -// bucket contains nodes, ordered by their last activity. the entry -// that was most recently active is the first element in entries. -type bucket struct { - entries []*Node - replacements []*Node -} - -func newTable(ourID NodeID, ourAddr *net.UDPAddr) *Table { - self := NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)) - tab := &Table{self: self} - for i := range tab.buckets { - tab.buckets[i] = new(bucket) - } - return tab -} - -const printTable = false - -// chooseBucketRefreshTarget selects random refresh targets to keep all Kademlia -// buckets filled with live connections and keep the network topology healthy. -// This requires selecting addresses closer to our own with a higher probability -// in order to refresh closer buckets too. -// -// This algorithm approximates the distance distribution of existing nodes in the -// table by selecting a random node from the table and selecting a target address -// with a distance less than twice of that of the selected node. -// This algorithm will be improved later to specifically target the least recently -// used buckets. -func (tab *Table) chooseBucketRefreshTarget() common.Hash { - entries := 0 - if printTable { - fmt.Println() - } - for i, b := range &tab.buckets { - entries += len(b.entries) - if printTable { - for _, e := range b.entries { - fmt.Println(i, e.state, e.addr().String(), e.ID.String(), e.sha.Hex()) - } - } - } - - prefix := binary.BigEndian.Uint64(tab.self.sha[0:8]) - dist := ^uint64(0) - entry := int(randUint(uint32(entries + 1))) - for _, b := range &tab.buckets { - if entry < len(b.entries) { - n := b.entries[entry] - dist = binary.BigEndian.Uint64(n.sha[0:8]) ^ prefix - break - } - entry -= len(b.entries) - } - - ddist := ^uint64(0) - if dist+dist > dist { - ddist = dist - } - targetPrefix := prefix ^ randUint64n(ddist) - - var target common.Hash - binary.BigEndian.PutUint64(target[0:8], targetPrefix) - rand.Read(target[8:]) - return target -} - -// readRandomNodes fills the given slice with random nodes from the -// table. It will not write the same node more than once. The nodes in -// the slice are copies and can be modified by the caller. -func (tab *Table) readRandomNodes(buf []*Node) (n int) { - // TODO: tree-based buckets would help here - // Find all non-empty buckets and get a fresh slice of their entries. - var buckets [][]*Node - for _, b := range &tab.buckets { - if len(b.entries) > 0 { - buckets = append(buckets, b.entries) - } - } - if len(buckets) == 0 { - return 0 - } - // Shuffle the buckets. - for i := uint32(len(buckets)) - 1; i > 0; i-- { - j := randUint(i) - buckets[i], buckets[j] = buckets[j], buckets[i] - } - // Move head of each bucket into buf, removing buckets that become empty. - var i, j int - for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) { - b := buckets[j] - buf[i] = &(*b[0]) - buckets[j] = b[1:] - if len(b) == 1 { - buckets = append(buckets[:j], buckets[j+1:]...) - } - if len(buckets) == 0 { - break - } - } - return i + 1 -} - -func randUint(max uint32) uint32 { - if max < 2 { - return 0 - } - var b [4]byte - rand.Read(b[:]) - return binary.BigEndian.Uint32(b[:]) % max -} - -func randUint64n(max uint64) uint64 { - if max < 2 { - return 0 - } - var b [8]byte - rand.Read(b[:]) - return binary.BigEndian.Uint64(b[:]) % max -} - -// closest returns the n nodes in the table that are closest to the -// given id. The caller must hold tab.mutex. -func (tab *Table) closest(target common.Hash, nresults int) *nodesByDistance { - // This is a very wasteful way to find the closest nodes but - // obviously correct. I believe that tree-based buckets would make - // this easier to implement efficiently. - close := &nodesByDistance{target: target} - for _, b := range &tab.buckets { - for _, n := range b.entries { - close.push(n, nresults) - } - } - return close -} - -// add attempts to add the given node its corresponding bucket. If the -// bucket has space available, adding the node succeeds immediately. -// Otherwise, the node is added to the replacement cache for the bucket. -func (tab *Table) add(n *Node) (contested *Node) { - //fmt.Println("add", n.addr().String(), n.ID.String(), n.sha.Hex()) - if n.ID == tab.self.ID { - return - } - b := tab.buckets[logdist(tab.self.sha, n.sha)] - switch { - case b.bump(n): - // n exists in b. - return nil - case len(b.entries) < bucketSize: - // b has space available. - b.addFront(n) - tab.count++ - if tab.nodeAddedHook != nil { - tab.nodeAddedHook(n) - } - return nil - default: - // b has no space left, add to replacement cache - // and revalidate the last entry. - // TODO: drop previous node - b.replacements = append(b.replacements, n) - if len(b.replacements) > bucketSize { - copy(b.replacements, b.replacements[1:]) - b.replacements = b.replacements[:len(b.replacements)-1] - } - return b.entries[len(b.entries)-1] - } -} - -// stuff adds nodes the table to the end of their corresponding bucket -// if the bucket is not full. -func (tab *Table) stuff(nodes []*Node) { -outer: - for _, n := range nodes { - if n.ID == tab.self.ID { - continue // don't add self - } - bucket := tab.buckets[logdist(tab.self.sha, n.sha)] - for i := range bucket.entries { - if bucket.entries[i].ID == n.ID { - continue outer // already in bucket - } - } - if len(bucket.entries) < bucketSize { - bucket.entries = append(bucket.entries, n) - tab.count++ - if tab.nodeAddedHook != nil { - tab.nodeAddedHook(n) - } - } - } -} - -// delete removes an entry from the node table (used to evacuate -// failed/non-bonded discovery peers). -func (tab *Table) delete(node *Node) { - //fmt.Println("delete", node.addr().String(), node.ID.String(), node.sha.Hex()) - bucket := tab.buckets[logdist(tab.self.sha, node.sha)] - for i := range bucket.entries { - if bucket.entries[i].ID == node.ID { - bucket.entries = append(bucket.entries[:i], bucket.entries[i+1:]...) - tab.count-- - return - } - } -} - -func (tab *Table) deleteReplace(node *Node) { - b := tab.buckets[logdist(tab.self.sha, node.sha)] - i := 0 - for i < len(b.entries) { - if b.entries[i].ID == node.ID { - b.entries = append(b.entries[:i], b.entries[i+1:]...) - tab.count-- - } else { - i++ - } - } - // refill from replacement cache - // TODO: maybe use random index - if len(b.entries) < bucketSize && len(b.replacements) > 0 { - ri := len(b.replacements) - 1 - b.addFront(b.replacements[ri]) - tab.count++ - b.replacements[ri] = nil - b.replacements = b.replacements[:ri] - } -} - -func (b *bucket) addFront(n *Node) { - b.entries = append(b.entries, nil) - copy(b.entries[1:], b.entries) - b.entries[0] = n -} - -func (b *bucket) bump(n *Node) bool { - for i := range b.entries { - if b.entries[i].ID == n.ID { - // move it to the front - copy(b.entries[1:], b.entries[:i]) - b.entries[0] = n - return true - } - } - return false -} - -// nodesByDistance is a list of nodes, ordered by -// distance to target. -type nodesByDistance struct { - entries []*Node - target common.Hash -} - -// push adds the given node to the list, keeping the total size below maxElems. -func (h *nodesByDistance) push(n *Node, maxElems int) { - ix := sort.Search(len(h.entries), func(i int) bool { - return distcmp(h.target, h.entries[i].sha, n.sha) > 0 - }) - if len(h.entries) < maxElems { - h.entries = append(h.entries, n) - } - if ix == len(h.entries) { - // farther away than all nodes we already have. - // if there was room for it, the node is now the last element. - } else { - // slide existing entries down to make room - // this will overwrite the entry we just appended. - copy(h.entries[ix+1:], h.entries[ix:]) - h.entries[ix] = n - } -} diff --git a/p2p/discv5/table_test.go b/p2p/discv5/table_test.go deleted file mode 100644 index 872a4f6836..0000000000 --- a/p2p/discv5/table_test.go +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "crypto/ecdsa" - "fmt" - "math/rand" - - "net" - "reflect" - "testing" - "testing/quick" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -func TestBucket_bumpNoDuplicates(t *testing.T) { - t.Parallel() - cfg := &quick.Config{ - MaxCount: 1000, - Rand: rand.New(rand.NewSource(time.Now().Unix())), - Values: func(args []reflect.Value, rand *rand.Rand) { - // generate a random list of nodes. this will be the content of the bucket. - n := rand.Intn(bucketSize-1) + 1 - nodes := make([]*Node, n) - for i := range nodes { - nodes[i] = nodeAtDistance(common.Hash{}, 200) - } - args[0] = reflect.ValueOf(nodes) - // generate random bump positions. - bumps := make([]int, rand.Intn(100)) - for i := range bumps { - bumps[i] = rand.Intn(len(nodes)) - } - args[1] = reflect.ValueOf(bumps) - }, - } - - prop := func(nodes []*Node, bumps []int) (ok bool) { - b := &bucket{entries: make([]*Node, len(nodes))} - copy(b.entries, nodes) - for i, pos := range bumps { - b.bump(b.entries[pos]) - if hasDuplicates(b.entries) { - t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps)) - for _, n := range b.entries { - t.Logf(" %p", n) - } - return false - } - } - return true - } - if err := quick.Check(prop, cfg); err != nil { - t.Error(err) - } -} - -// nodeAtDistance creates a node for which logdist(base, n.sha) == ld. -// The node's ID does not correspond to n.sha. -func nodeAtDistance(base common.Hash, ld int) (n *Node) { - n = new(Node) - n.sha = hashAtDistance(base, ld) - copy(n.ID[:], n.sha[:]) // ensure the node still has a unique ID - return n -} - -func TestTable_closest(t *testing.T) { - t.Parallel() - - test := func(test *closeTest) bool { - // for any node table, Target and N - tab := newTable(test.Self, &net.UDPAddr{}) - tab.stuff(test.All) - - // check that doClosest(Target, N) returns nodes - result := tab.closest(test.Target, test.N).entries - if hasDuplicates(result) { - t.Errorf("result contains duplicates") - return false - } - if !sortedByDistanceTo(test.Target, result) { - t.Errorf("result is not sorted by distance to target") - return false - } - - // check that the number of results is min(N, tablen) - wantN := test.N - if tab.count < test.N { - wantN = tab.count - } - if len(result) != wantN { - t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN) - return false - } else if len(result) == 0 { - return true // no need to check distance - } - - // check that the result nodes have minimum distance to target. - for _, b := range tab.buckets { - for _, n := range b.entries { - if contains(result, n.ID) { - continue // don't run the check below for nodes in result - } - farthestResult := result[len(result)-1].sha - if distcmp(test.Target, n.sha, farthestResult) < 0 { - t.Errorf("table contains node that is closer to target but it's not in result") - t.Logf(" Target: %v", test.Target) - t.Logf(" Farthest Result: %v", farthestResult) - t.Logf(" ID: %v", n.ID) - return false - } - } - } - return true - } - if err := quick.Check(test, quickcfg()); err != nil { - t.Error(err) - } -} - -func TestTable_ReadRandomNodesGetAll(t *testing.T) { - cfg := &quick.Config{ - MaxCount: 200, - Rand: rand.New(rand.NewSource(time.Now().Unix())), - Values: func(args []reflect.Value, rand *rand.Rand) { - args[0] = reflect.ValueOf(make([]*Node, rand.Intn(1000))) - }, - } - test := func(buf []*Node) bool { - tab := newTable(NodeID{}, &net.UDPAddr{}) - for i := 0; i < len(buf); i++ { - ld := cfg.Rand.Intn(len(tab.buckets)) - tab.stuff([]*Node{nodeAtDistance(tab.self.sha, ld)}) - } - gotN := tab.readRandomNodes(buf) - if gotN != tab.count { - t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.count) - return false - } - if hasDuplicates(buf[:gotN]) { - t.Errorf("result contains duplicates") - return false - } - return true - } - if err := quick.Check(test, cfg); err != nil { - t.Error(err) - } -} - -type closeTest struct { - Self NodeID - Target common.Hash - All []*Node - N int -} - -func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { - t := &closeTest{ - Self: gen(NodeID{}, rand).(NodeID), - Target: gen(common.Hash{}, rand).(common.Hash), - N: rand.Intn(bucketSize), - } - for _, id := range gen([]NodeID{}, rand).([]NodeID) { - t.All = append(t.All, &Node{ID: id}) - } - return reflect.ValueOf(t) -} - -func hasDuplicates(slice []*Node) bool { - seen := make(map[NodeID]bool) - for i, e := range slice { - if e == nil { - panic(fmt.Sprintf("nil *Node at %d", i)) - } - if seen[e.ID] { - return true - } - seen[e.ID] = true - } - return false -} - -func sortedByDistanceTo(distbase common.Hash, slice []*Node) bool { - var last common.Hash - for i, e := range slice { - if i > 0 && distcmp(distbase, e.sha, last) < 0 { - return false - } - last = e.sha - } - return true -} - -func contains(ns []*Node, id NodeID) bool { - for _, n := range ns { - if n.ID == id { - return true - } - } - return false -} - -// gen wraps quick.Value so it's easier to use. -// it generates a random value of the given value's type. -func gen(typ interface{}, rand *rand.Rand) interface{} { - v, ok := quick.Value(reflect.TypeOf(typ), rand) - if !ok { - panic(fmt.Sprintf("couldn't generate random value of type %T", typ)) - } - return v.Interface() -} - -func newkey() *ecdsa.PrivateKey { - key, err := crypto.GenerateKey() - if err != nil { - panic("couldn't generate key: " + err.Error()) - } - return key -} diff --git a/p2p/discv5/ticket.go b/p2p/discv5/ticket.go deleted file mode 100644 index c5e3d6c08f..0000000000 --- a/p2p/discv5/ticket.go +++ /dev/null @@ -1,884 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "bytes" - "encoding/binary" - "fmt" - "math" - "math/rand" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" -) - -const ( - ticketTimeBucketLen = time.Minute - collectFrequency = time.Second * 30 - registerFrequency = time.Second * 60 - maxCollectDebt = 10 - maxRegisterDebt = 5 - keepTicketConst = time.Minute * 10 - keepTicketExp = time.Minute * 5 - targetWaitTime = time.Minute * 10 - topicQueryTimeout = time.Second * 5 - topicQueryResend = time.Minute - // topic radius detection - maxRadius = 0xffffffffffffffff - radiusTC = time.Minute * 20 - radiusBucketsPerBit = 8 - minSlope = 1 - minPeakSize = 40 - maxNoAdjust = 20 - lookupWidth = 8 - minRightSum = 20 - searchForceQuery = 4 -) - -// timeBucket represents absolute monotonic time in minutes. -// It is used as the index into the per-topic ticket buckets. -type timeBucket int - -type ticket struct { - topics []Topic - regTime []mclock.AbsTime // Per-topic local absolute time when the ticket can be used. - - // The serial number that was issued by the server. - serial uint32 - // Used by registrar, tracks absolute time when the ticket was created. - issueTime mclock.AbsTime - - // Fields used only by registrants - node *Node // the registrar node that signed this ticket - refCnt int // tracks number of topics that will be registered using this ticket - pong []byte // encoded pong packet signed by the registrar -} - -// ticketRef refers to a single topic in a ticket. -type ticketRef struct { - t *ticket - idx int // index of the topic in t.topics and t.regTime -} - -func (ref ticketRef) topic() Topic { - return ref.t.topics[ref.idx] -} - -func (ref ticketRef) topicRegTime() mclock.AbsTime { - return ref.t.regTime[ref.idx] -} - -func pongToTicket(localTime mclock.AbsTime, topics []Topic, node *Node, p *ingressPacket) (*ticket, error) { - wps := p.data.(*pong).WaitPeriods - if len(topics) != len(wps) { - return nil, fmt.Errorf("bad wait period list: got %d values, want %d", len(topics), len(wps)) - } - if rlpHash(topics) != p.data.(*pong).TopicHash { - return nil, fmt.Errorf("bad topic hash") - } - t := &ticket{ - issueTime: localTime, - node: node, - topics: topics, - pong: p.rawData, - regTime: make([]mclock.AbsTime, len(wps)), - } - // Convert wait periods to local absolute time. - for i, wp := range wps { - t.regTime[i] = localTime + mclock.AbsTime(time.Second*time.Duration(wp)) - } - return t, nil -} - -func ticketToPong(t *ticket, pong *pong) { - pong.Expiration = uint64(t.issueTime / mclock.AbsTime(time.Second)) - pong.TopicHash = rlpHash(t.topics) - pong.TicketSerial = t.serial - pong.WaitPeriods = make([]uint32, len(t.regTime)) - for i, regTime := range t.regTime { - pong.WaitPeriods[i] = uint32(time.Duration(regTime-t.issueTime) / time.Second) - } -} - -type ticketStore struct { - // radius detector and target address generator - // exists for both searched and registered topics - radius map[Topic]*topicRadius - - // Contains buckets (for each absolute minute) of tickets - // that can be used in that minute. - // This is only set if the topic is being registered. - tickets map[Topic]*topicTickets - - regQueue []Topic // Topic registration queue for round robin attempts - regSet map[Topic]struct{} // Topic registration queue contents for fast filling - - nodes map[*Node]*ticket - nodeLastReq map[*Node]reqInfo - - lastBucketFetched timeBucket - nextTicketCached *ticketRef - - searchTopicMap map[Topic]searchTopic - nextTopicQueryCleanup mclock.AbsTime - queriesSent map[*Node]map[common.Hash]sentQuery -} - -type searchTopic struct { - foundChn chan<- *Node -} - -type sentQuery struct { - sent mclock.AbsTime - lookup lookupInfo -} - -type topicTickets struct { - buckets map[timeBucket][]ticketRef - nextLookup mclock.AbsTime - nextReg mclock.AbsTime -} - -func newTicketStore() *ticketStore { - return &ticketStore{ - radius: make(map[Topic]*topicRadius), - tickets: make(map[Topic]*topicTickets), - regSet: make(map[Topic]struct{}), - nodes: make(map[*Node]*ticket), - nodeLastReq: make(map[*Node]reqInfo), - searchTopicMap: make(map[Topic]searchTopic), - queriesSent: make(map[*Node]map[common.Hash]sentQuery), - } -} - -// addTopic starts tracking a topic. If register is true, -// the local node will register the topic and tickets will be collected. -func (s *ticketStore) addTopic(topic Topic, register bool) { - log.Trace("Adding discovery topic", "topic", topic, "register", register) - if s.radius[topic] == nil { - s.radius[topic] = newTopicRadius(topic) - } - if register && s.tickets[topic] == nil { - s.tickets[topic] = &topicTickets{buckets: make(map[timeBucket][]ticketRef)} - } -} - -func (s *ticketStore) addSearchTopic(t Topic, foundChn chan<- *Node) { - s.addTopic(t, false) - if s.searchTopicMap[t].foundChn == nil { - s.searchTopicMap[t] = searchTopic{foundChn: foundChn} - } -} - -func (s *ticketStore) removeSearchTopic(t Topic) { - if st := s.searchTopicMap[t]; st.foundChn != nil { - delete(s.searchTopicMap, t) - } -} - -// removeRegisterTopic deletes all tickets for the given topic. -func (s *ticketStore) removeRegisterTopic(topic Topic) { - log.Trace("Removing discovery topic", "topic", topic) - if s.tickets[topic] == nil { - log.Warn("Removing non-existent discovery topic", "topic", topic) - return - } - for _, list := range s.tickets[topic].buckets { - for _, ref := range list { - ref.t.refCnt-- - if ref.t.refCnt == 0 { - delete(s.nodes, ref.t.node) - delete(s.nodeLastReq, ref.t.node) - } - } - } - delete(s.tickets, topic) -} - -func (s *ticketStore) regTopicSet() []Topic { - topics := make([]Topic, 0, len(s.tickets)) - for topic := range s.tickets { - topics = append(topics, topic) - } - return topics -} - -// nextRegisterLookup returns the target of the next lookup for ticket collection. -func (s *ticketStore) nextRegisterLookup() (lookupInfo, time.Duration) { - // Queue up any new topics (or discarded ones), preserving iteration order - for topic := range s.tickets { - if _, ok := s.regSet[topic]; !ok { - s.regQueue = append(s.regQueue, topic) - s.regSet[topic] = struct{}{} - } - } - // Iterate over the set of all topics and look up the next suitable one - for len(s.regQueue) > 0 { - // Fetch the next topic from the queue, and ensure it still exists - topic := s.regQueue[0] - s.regQueue = s.regQueue[1:] - delete(s.regSet, topic) - - if s.tickets[topic] == nil { - continue - } - // If the topic needs more tickets, return it - if s.tickets[topic].nextLookup < mclock.Now() { - next, delay := s.radius[topic].nextTarget(false), 100*time.Millisecond - log.Trace("Found discovery topic to register", "topic", topic, "target", next.target, "delay", delay) - return next, delay - } - } - // No registration topics found or all exhausted, sleep - delay := 40 * time.Second - log.Trace("No topic found to register", "delay", delay) - return lookupInfo{}, delay -} - -func (s *ticketStore) nextSearchLookup(topic Topic) lookupInfo { - tr := s.radius[topic] - target := tr.nextTarget(tr.radiusLookupCnt >= searchForceQuery) - if target.radiusLookup { - tr.radiusLookupCnt++ - } else { - tr.radiusLookupCnt = 0 - } - return target -} - -func (s *ticketStore) addTicketRef(r ticketRef) { - topic := r.t.topics[r.idx] - tickets := s.tickets[topic] - if tickets == nil { - log.Warn("Adding ticket to non-existent topic", "topic", topic) - return - } - bucket := timeBucket(r.t.regTime[r.idx] / mclock.AbsTime(ticketTimeBucketLen)) - tickets.buckets[bucket] = append(tickets.buckets[bucket], r) - r.t.refCnt++ - - min := mclock.Now() - mclock.AbsTime(collectFrequency)*maxCollectDebt - if tickets.nextLookup < min { - tickets.nextLookup = min - } - tickets.nextLookup += mclock.AbsTime(collectFrequency) - - //s.removeExcessTickets(topic) -} - -func (s *ticketStore) nextFilteredTicket() (*ticketRef, time.Duration) { - now := mclock.Now() - for { - ticket, wait := s.nextRegisterableTicket() - if ticket == nil { - return ticket, wait - } - log.Trace("Found discovery ticket to register", "node", ticket.t.node, "serial", ticket.t.serial, "wait", wait) - - regTime := now + mclock.AbsTime(wait) - topic := ticket.t.topics[ticket.idx] - if s.tickets[topic] != nil && regTime >= s.tickets[topic].nextReg { - return ticket, wait - } - s.removeTicketRef(*ticket) - } -} - -func (s *ticketStore) ticketRegistered(ref ticketRef) { - now := mclock.Now() - - topic := ref.t.topics[ref.idx] - tickets := s.tickets[topic] - min := now - mclock.AbsTime(registerFrequency)*maxRegisterDebt - if min > tickets.nextReg { - tickets.nextReg = min - } - tickets.nextReg += mclock.AbsTime(registerFrequency) - s.tickets[topic] = tickets - - s.removeTicketRef(ref) -} - -// nextRegisterableTicket returns the next ticket that can be used -// to register. -// -// If the returned wait time <= zero the ticket can be used. For a positive -// wait time, the caller should requery the next ticket later. -// -// A ticket can be returned more than once with <= zero wait time in case -// the ticket contains multiple topics. -func (s *ticketStore) nextRegisterableTicket() (*ticketRef, time.Duration) { - now := mclock.Now() - if s.nextTicketCached != nil { - return s.nextTicketCached, time.Duration(s.nextTicketCached.topicRegTime() - now) - } - - for bucket := s.lastBucketFetched; ; bucket++ { - var ( - empty = true // true if there are no tickets - nextTicket ticketRef // uninitialized if this bucket is empty - ) - for _, tickets := range s.tickets { - //s.removeExcessTickets(topic) - if len(tickets.buckets) != 0 { - empty = false - - list := tickets.buckets[bucket] - for _, ref := range list { - //debugLog(fmt.Sprintf(" nrt bucket = %d node = %x sn = %v wait = %v", bucket, ref.t.node.ID[:8], ref.t.serial, time.Duration(ref.topicRegTime()-now))) - if nextTicket.t == nil || ref.topicRegTime() < nextTicket.topicRegTime() { - nextTicket = ref - } - } - } - } - if empty { - return nil, 0 - } - if nextTicket.t != nil { - s.nextTicketCached = &nextTicket - return &nextTicket, time.Duration(nextTicket.topicRegTime() - now) - } - s.lastBucketFetched = bucket - } -} - -// removeTicket removes a ticket from the ticket store -func (s *ticketStore) removeTicketRef(ref ticketRef) { - log.Trace("Removing discovery ticket reference", "node", ref.t.node.ID, "serial", ref.t.serial) - - // Make nextRegisterableTicket return the next available ticket. - s.nextTicketCached = nil - - topic := ref.topic() - tickets := s.tickets[topic] - - if tickets == nil { - log.Trace("Removing tickets from unknown topic", "topic", topic) - return - } - bucket := timeBucket(ref.t.regTime[ref.idx] / mclock.AbsTime(ticketTimeBucketLen)) - list := tickets.buckets[bucket] - idx := -1 - for i, bt := range list { - if bt.t == ref.t { - idx = i - break - } - } - if idx == -1 { - panic(nil) - } - list = append(list[:idx], list[idx+1:]...) - if len(list) != 0 { - tickets.buckets[bucket] = list - } else { - delete(tickets.buckets, bucket) - } - ref.t.refCnt-- - if ref.t.refCnt == 0 { - delete(s.nodes, ref.t.node) - delete(s.nodeLastReq, ref.t.node) - } -} - -type lookupInfo struct { - target common.Hash - topic Topic - radiusLookup bool -} - -type reqInfo struct { - pingHash []byte - lookup lookupInfo - time mclock.AbsTime -} - -// returns -1 if not found -func (t *ticket) findIdx(topic Topic) int { - for i, tt := range t.topics { - if tt == topic { - return i - } - } - return -1 -} - -func (s *ticketStore) registerLookupDone(lookup lookupInfo, nodes []*Node, ping func(n *Node) []byte) { - now := mclock.Now() - for i, n := range nodes { - if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius { - if lookup.radiusLookup { - if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC { - s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now} - } - } else { - if s.nodes[n] == nil { - s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now} - } - } - } - } -} - -func (s *ticketStore) searchLookupDone(lookup lookupInfo, nodes []*Node, query func(n *Node, topic Topic) []byte) { - now := mclock.Now() - for i, n := range nodes { - if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius { - if lookup.radiusLookup { - if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC { - s.nodeLastReq[n] = reqInfo{pingHash: nil, lookup: lookup, time: now} - } - } // else { - if s.canQueryTopic(n, lookup.topic) { - hash := query(n, lookup.topic) - if hash != nil { - s.addTopicQuery(common.BytesToHash(hash), n, lookup) - } - } - //} - } - } -} - -func (s *ticketStore) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t *ticket) { - for i, topic := range t.topics { - if tt, ok := s.radius[topic]; ok { - tt.adjustWithTicket(now, targetHash, ticketRef{t, i}) - } - } -} - -func (s *ticketStore) addTicket(localTime mclock.AbsTime, pingHash []byte, ticket *ticket) { - log.Trace("Adding discovery ticket", "node", ticket.node.ID, "serial", ticket.serial) - - lastReq, ok := s.nodeLastReq[ticket.node] - if !(ok && bytes.Equal(pingHash, lastReq.pingHash)) { - return - } - s.adjustWithTicket(localTime, lastReq.lookup.target, ticket) - - if lastReq.lookup.radiusLookup || s.nodes[ticket.node] != nil { - return - } - - topic := lastReq.lookup.topic - topicIdx := ticket.findIdx(topic) - if topicIdx == -1 { - return - } - - bucket := timeBucket(localTime / mclock.AbsTime(ticketTimeBucketLen)) - if s.lastBucketFetched == 0 || bucket < s.lastBucketFetched { - s.lastBucketFetched = bucket - } - - if _, ok := s.tickets[topic]; ok { - wait := ticket.regTime[topicIdx] - localTime - rnd := rand.ExpFloat64() - if rnd > 10 { - rnd = 10 - } - if float64(wait) < float64(keepTicketConst)+float64(keepTicketExp)*rnd { - // use the ticket to register this topic - //fmt.Println("addTicket", ticket.node.ID[:8], ticket.node.addr().String(), ticket.serial, ticket.pong) - s.addTicketRef(ticketRef{ticket, topicIdx}) - } - } - - if ticket.refCnt > 0 { - s.nextTicketCached = nil - s.nodes[ticket.node] = ticket - } -} - -func (s *ticketStore) canQueryTopic(node *Node, topic Topic) bool { - qq := s.queriesSent[node] - if qq != nil { - now := mclock.Now() - for _, sq := range qq { - if sq.lookup.topic == topic && sq.sent > now-mclock.AbsTime(topicQueryResend) { - return false - } - } - } - return true -} - -func (s *ticketStore) addTopicQuery(hash common.Hash, node *Node, lookup lookupInfo) { - now := mclock.Now() - qq := s.queriesSent[node] - if qq == nil { - qq = make(map[common.Hash]sentQuery) - s.queriesSent[node] = qq - } - qq[hash] = sentQuery{sent: now, lookup: lookup} - s.cleanupTopicQueries(now) -} - -func (s *ticketStore) cleanupTopicQueries(now mclock.AbsTime) { - if s.nextTopicQueryCleanup > now { - return - } - exp := now - mclock.AbsTime(topicQueryResend) - for n, qq := range s.queriesSent { - for h, q := range qq { - if q.sent < exp { - delete(qq, h) - } - } - if len(qq) == 0 { - delete(s.queriesSent, n) - } - } - s.nextTopicQueryCleanup = now + mclock.AbsTime(topicQueryTimeout) -} - -func (s *ticketStore) gotTopicNodes(from *Node, hash common.Hash, nodes []rpcNode) (timeout bool) { - now := mclock.Now() - //fmt.Println("got", from.addr().String(), hash, len(nodes)) - qq := s.queriesSent[from] - if qq == nil { - return true - } - q, ok := qq[hash] - if !ok || now > q.sent+mclock.AbsTime(topicQueryTimeout) { - return true - } - inside := float64(0) - if len(nodes) > 0 { - inside = 1 - } - s.radius[q.lookup.topic].adjust(now, q.lookup.target, from.sha, inside) - chn := s.searchTopicMap[q.lookup.topic].foundChn - if chn == nil { - //fmt.Println("no channel") - return false - } - for _, node := range nodes { - ip := node.IP - if ip.IsUnspecified() || ip.IsLoopback() { - ip = from.IP - } - n := NewNode(node.ID, ip, node.UDP, node.TCP) - select { - case chn <- n: - default: - return false - } - } - return false -} - -type topicRadius struct { - topic Topic - topicHashPrefix uint64 - radius, minRadius uint64 - buckets []topicRadiusBucket - converged bool - radiusLookupCnt int -} - -type topicRadiusEvent int - -const ( - trOutside topicRadiusEvent = iota - trInside - trNoAdjust - trCount -) - -type topicRadiusBucket struct { - weights [trCount]float64 - lastTime mclock.AbsTime - value float64 - lookupSent map[common.Hash]mclock.AbsTime -} - -func (b *topicRadiusBucket) update(now mclock.AbsTime) { - if now == b.lastTime { - return - } - exp := math.Exp(-float64(now-b.lastTime) / float64(radiusTC)) - for i, w := range b.weights { - b.weights[i] = w * exp - } - b.lastTime = now - - for target, tm := range b.lookupSent { - if now-tm > mclock.AbsTime(respTimeout) { - b.weights[trNoAdjust] += 1 - delete(b.lookupSent, target) - } - } -} - -func (b *topicRadiusBucket) adjust(now mclock.AbsTime, inside float64) { - b.update(now) - if inside <= 0 { - b.weights[trOutside] += 1 - } else { - if inside >= 1 { - b.weights[trInside] += 1 - } else { - b.weights[trInside] += inside - b.weights[trOutside] += 1 - inside - } - } -} - -func newTopicRadius(t Topic) *topicRadius { - topicHash := crypto.Keccak256Hash([]byte(t)) - topicHashPrefix := binary.BigEndian.Uint64(topicHash[0:8]) - - return &topicRadius{ - topic: t, - topicHashPrefix: topicHashPrefix, - radius: maxRadius, - minRadius: maxRadius, - } -} - -func (r *topicRadius) getBucketIdx(addrHash common.Hash) int { - prefix := binary.BigEndian.Uint64(addrHash[0:8]) - var log2 float64 - if prefix != r.topicHashPrefix { - log2 = math.Log2(float64(prefix ^ r.topicHashPrefix)) - } - bucket := int((64 - log2) * radiusBucketsPerBit) - max := 64*radiusBucketsPerBit - 1 - if bucket > max { - return max - } - if bucket < 0 { - return 0 - } - return bucket -} - -func (r *topicRadius) targetForBucket(bucket int) common.Hash { - min := math.Pow(2, 64-float64(bucket+1)/radiusBucketsPerBit) - max := math.Pow(2, 64-float64(bucket)/radiusBucketsPerBit) - a := uint64(min) - b := randUint64n(uint64(max - min)) - xor := a + b - if xor < a { - xor = ^uint64(0) - } - prefix := r.topicHashPrefix ^ xor - var target common.Hash - binary.BigEndian.PutUint64(target[0:8], prefix) - globalRandRead(target[8:]) - return target -} - -// package rand provides a Read function in Go 1.6 and later, but -// we can't use it yet because we still support Go 1.5. -func globalRandRead(b []byte) { - pos := 0 - val := 0 - for n := 0; n < len(b); n++ { - if pos == 0 { - val = rand.Int() - pos = 7 - } - b[n] = byte(val) - val >>= 8 - pos-- - } -} - -func (r *topicRadius) chooseLookupBucket(a, b int) int { - if a < 0 { - a = 0 - } - if a > b { - return -1 - } - c := 0 - for i := a; i <= b; i++ { - if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust { - c++ - } - } - if c == 0 { - return -1 - } - rnd := randUint(uint32(c)) - for i := a; i <= b; i++ { - if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust { - if rnd == 0 { - return i - } - rnd-- - } - } - panic(nil) // should never happen -} - -func (r *topicRadius) needMoreLookups(a, b int, maxValue float64) bool { - var max float64 - if a < 0 { - a = 0 - } - if b >= len(r.buckets) { - b = len(r.buckets) - 1 - if r.buckets[b].value > max { - max = r.buckets[b].value - } - } - if b >= a { - for i := a; i <= b; i++ { - if r.buckets[i].value > max { - max = r.buckets[i].value - } - } - } - return maxValue-max < minPeakSize -} - -func (r *topicRadius) recalcRadius() (radius uint64, radiusLookup int) { - maxBucket := 0 - maxValue := float64(0) - now := mclock.Now() - v := float64(0) - for i := range r.buckets { - r.buckets[i].update(now) - v += r.buckets[i].weights[trOutside] - r.buckets[i].weights[trInside] - r.buckets[i].value = v - //fmt.Printf("%v %v | ", v, r.buckets[i].weights[trNoAdjust]) - } - //fmt.Println() - slopeCross := -1 - for i, b := range r.buckets { - v := b.value - if v < float64(i)*minSlope { - slopeCross = i - break - } - if v > maxValue { - maxValue = v - maxBucket = i + 1 - } - } - - minRadBucket := len(r.buckets) - sum := float64(0) - for minRadBucket > 0 && sum < minRightSum { - minRadBucket-- - b := r.buckets[minRadBucket] - sum += b.weights[trInside] + b.weights[trOutside] - } - r.minRadius = uint64(math.Pow(2, 64-float64(minRadBucket)/radiusBucketsPerBit)) - - lookupLeft := -1 - if r.needMoreLookups(0, maxBucket-lookupWidth-1, maxValue) { - lookupLeft = r.chooseLookupBucket(maxBucket-lookupWidth, maxBucket-1) - } - lookupRight := -1 - if slopeCross != maxBucket && (minRadBucket <= maxBucket || r.needMoreLookups(maxBucket+lookupWidth, len(r.buckets)-1, maxValue)) { - for len(r.buckets) <= maxBucket+lookupWidth { - r.buckets = append(r.buckets, topicRadiusBucket{lookupSent: make(map[common.Hash]mclock.AbsTime)}) - } - lookupRight = r.chooseLookupBucket(maxBucket, maxBucket+lookupWidth-1) - } - if lookupLeft == -1 { - radiusLookup = lookupRight - } else { - if lookupRight == -1 { - radiusLookup = lookupLeft - } else { - if randUint(2) == 0 { - radiusLookup = lookupLeft - } else { - radiusLookup = lookupRight - } - } - } - - //fmt.Println("mb", maxBucket, "sc", slopeCross, "mrb", minRadBucket, "ll", lookupLeft, "lr", lookupRight, "mv", maxValue) - - if radiusLookup == -1 { - // no more radius lookups needed at the moment, return a radius - r.converged = true - rad := maxBucket - if minRadBucket < rad { - rad = minRadBucket - } - radius = ^uint64(0) - if rad > 0 { - radius = uint64(math.Pow(2, 64-float64(rad)/radiusBucketsPerBit)) - } - r.radius = radius - } - - return -} - -func (r *topicRadius) nextTarget(forceRegular bool) lookupInfo { - if !forceRegular { - _, radiusLookup := r.recalcRadius() - if radiusLookup != -1 { - target := r.targetForBucket(radiusLookup) - r.buckets[radiusLookup].lookupSent[target] = mclock.Now() - return lookupInfo{target: target, topic: r.topic, radiusLookup: true} - } - } - - radExt := r.radius / 2 - if radExt > maxRadius-r.radius { - radExt = maxRadius - r.radius - } - rnd := randUint64n(r.radius) + randUint64n(2*radExt) - if rnd > radExt { - rnd -= radExt - } else { - rnd = radExt - rnd - } - - prefix := r.topicHashPrefix ^ rnd - var target common.Hash - binary.BigEndian.PutUint64(target[0:8], prefix) - globalRandRead(target[8:]) - return lookupInfo{target: target, topic: r.topic, radiusLookup: false} -} - -func (r *topicRadius) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t ticketRef) { - wait := t.t.regTime[t.idx] - t.t.issueTime - inside := float64(wait)/float64(targetWaitTime) - 0.5 - if inside > 1 { - inside = 1 - } - if inside < 0 { - inside = 0 - } - r.adjust(now, targetHash, t.t.node.sha, inside) -} - -func (r *topicRadius) adjust(now mclock.AbsTime, targetHash, addrHash common.Hash, inside float64) { - bucket := r.getBucketIdx(addrHash) - //fmt.Println("adjust", bucket, len(r.buckets), inside) - if bucket >= len(r.buckets) { - return - } - r.buckets[bucket].adjust(now, inside) - delete(r.buckets[bucket].lookupSent, targetHash) -} diff --git a/p2p/discv5/topic.go b/p2p/discv5/topic.go deleted file mode 100644 index 609a41297f..0000000000 --- a/p2p/discv5/topic.go +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "container/heap" - "fmt" - "math" - "math/rand" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/log" -) - -const ( - maxEntries = 10000 - maxEntriesPerTopic = 50 - - fallbackRegistrationExpiry = 1 * time.Hour -) - -type Topic string - -type topicEntry struct { - topic Topic - fifoIdx uint64 - node *Node - expire mclock.AbsTime -} - -type topicInfo struct { - entries map[uint64]*topicEntry - fifoHead, fifoTail uint64 - rqItem *topicRequestQueueItem - wcl waitControlLoop -} - -// removes tail element from the fifo -func (t *topicInfo) getFifoTail() *topicEntry { - for t.entries[t.fifoTail] == nil { - t.fifoTail++ - } - tail := t.entries[t.fifoTail] - t.fifoTail++ - return tail -} - -type nodeInfo struct { - entries map[Topic]*topicEntry - lastIssuedTicket, lastUsedTicket uint32 - // you can't register a ticket newer than lastUsedTicket before noRegUntil (absolute time) - noRegUntil mclock.AbsTime -} - -type topicTable struct { - db *nodeDB - self *Node - nodes map[*Node]*nodeInfo - topics map[Topic]*topicInfo - globalEntries uint64 - requested topicRequestQueue - requestCnt uint64 - lastGarbageCollection mclock.AbsTime -} - -func newTopicTable(db *nodeDB, self *Node) *topicTable { - if printTestImgLogs { - fmt.Printf("*N %016x\n", self.sha[:8]) - } - return &topicTable{ - db: db, - nodes: make(map[*Node]*nodeInfo), - topics: make(map[Topic]*topicInfo), - self: self, - } -} - -func (t *topicTable) getOrNewTopic(topic Topic) *topicInfo { - ti := t.topics[topic] - if ti == nil { - rqItem := &topicRequestQueueItem{ - topic: topic, - priority: t.requestCnt, - } - ti = &topicInfo{ - entries: make(map[uint64]*topicEntry), - rqItem: rqItem, - } - t.topics[topic] = ti - heap.Push(&t.requested, rqItem) - } - return ti -} - -func (t *topicTable) checkDeleteTopic(topic Topic) { - ti := t.topics[topic] - if ti == nil { - return - } - if len(ti.entries) == 0 && ti.wcl.hasMinimumWaitPeriod() { - delete(t.topics, topic) - heap.Remove(&t.requested, ti.rqItem.index) - } -} - -func (t *topicTable) getOrNewNode(node *Node) *nodeInfo { - n := t.nodes[node] - if n == nil { - //fmt.Printf("newNode %016x %016x\n", t.self.sha[:8], node.sha[:8]) - var issued, used uint32 - if t.db != nil { - issued, used = t.db.fetchTopicRegTickets(node.ID) - } - n = &nodeInfo{ - entries: make(map[Topic]*topicEntry), - lastIssuedTicket: issued, - lastUsedTicket: used, - } - t.nodes[node] = n - } - return n -} - -func (t *topicTable) checkDeleteNode(node *Node) { - if n, ok := t.nodes[node]; ok && len(n.entries) == 0 && n.noRegUntil < mclock.Now() { - //fmt.Printf("deleteNode %016x %016x\n", t.self.sha[:8], node.sha[:8]) - delete(t.nodes, node) - } -} - -func (t *topicTable) storeTicketCounters(node *Node) { - n := t.getOrNewNode(node) - if t.db != nil { - t.db.updateTopicRegTickets(node.ID, n.lastIssuedTicket, n.lastUsedTicket) - } -} - -func (t *topicTable) getEntries(topic Topic) []*Node { - t.collectGarbage() - - te := t.topics[topic] - if te == nil { - return nil - } - nodes := make([]*Node, len(te.entries)) - i := 0 - for _, e := range te.entries { - nodes[i] = e.node - i++ - } - t.requestCnt++ - t.requested.update(te.rqItem, t.requestCnt) - return nodes -} - -func (t *topicTable) addEntry(node *Node, topic Topic) { - n := t.getOrNewNode(node) - // clear previous entries by the same node - for _, e := range n.entries { - t.deleteEntry(e) - } - // *** - n = t.getOrNewNode(node) - - tm := mclock.Now() - te := t.getOrNewTopic(topic) - - if len(te.entries) == maxEntriesPerTopic { - t.deleteEntry(te.getFifoTail()) - } - - if t.globalEntries == maxEntries { - t.deleteEntry(t.leastRequested()) // not empty, no need to check for nil - } - - fifoIdx := te.fifoHead - te.fifoHead++ - entry := &topicEntry{ - topic: topic, - fifoIdx: fifoIdx, - node: node, - expire: tm + mclock.AbsTime(fallbackRegistrationExpiry), - } - if printTestImgLogs { - fmt.Printf("*+ %d %v %016x %016x\n", tm/1000000, topic, t.self.sha[:8], node.sha[:8]) - } - te.entries[fifoIdx] = entry - n.entries[topic] = entry - t.globalEntries++ - te.wcl.registered(tm) -} - -// removes least requested element from the fifo -func (t *topicTable) leastRequested() *topicEntry { - for t.requested.Len() > 0 && t.topics[t.requested[0].topic] == nil { - heap.Pop(&t.requested) - } - if t.requested.Len() == 0 { - return nil - } - return t.topics[t.requested[0].topic].getFifoTail() -} - -// entry should exist -func (t *topicTable) deleteEntry(e *topicEntry) { - if printTestImgLogs { - fmt.Printf("*- %d %v %016x %016x\n", mclock.Now()/1000000, e.topic, t.self.sha[:8], e.node.sha[:8]) - } - ne := t.nodes[e.node].entries - delete(ne, e.topic) - if len(ne) == 0 { - t.checkDeleteNode(e.node) - } - te := t.topics[e.topic] - delete(te.entries, e.fifoIdx) - if len(te.entries) == 0 { - t.checkDeleteTopic(e.topic) - } - t.globalEntries-- -} - -// It is assumed that topics and waitPeriods have the same length. -func (t *topicTable) useTicket(node *Node, serialNo uint32, topics []Topic, idx int, issueTime uint64, waitPeriods []uint32) (registered bool) { - log.Trace("Using discovery ticket", "serial", serialNo, "topics", topics, "waits", waitPeriods) - //fmt.Println("useTicket", serialNo, topics, waitPeriods) - t.collectGarbage() - - n := t.getOrNewNode(node) - if serialNo < n.lastUsedTicket { - return false - } - - tm := mclock.Now() - if serialNo > n.lastUsedTicket && tm < n.noRegUntil { - return false - } - if serialNo != n.lastUsedTicket { - n.lastUsedTicket = serialNo - n.noRegUntil = tm + mclock.AbsTime(noRegTimeout()) - t.storeTicketCounters(node) - } - - currTime := uint64(tm / mclock.AbsTime(time.Second)) - regTime := issueTime + uint64(waitPeriods[idx]) - relTime := int64(currTime - regTime) - if relTime >= -1 && relTime <= regTimeWindow+1 { // give clients a little security margin on both ends - if e := n.entries[topics[idx]]; e == nil { - t.addEntry(node, topics[idx]) - } else { - // if there is an active entry, don't move to the front of the FIFO but prolong expire time - e.expire = tm + mclock.AbsTime(fallbackRegistrationExpiry) - } - return true - } - - return false -} - -func (t *topicTable) getTicket(node *Node, topics []Topic) *ticket { - t.collectGarbage() - - now := mclock.Now() - n := t.getOrNewNode(node) - n.lastIssuedTicket++ - t.storeTicketCounters(node) - - tic := &ticket{ - issueTime: now, - topics: topics, - serial: n.lastIssuedTicket, - regTime: make([]mclock.AbsTime, len(topics)), - } - for i, topic := range topics { - var waitPeriod time.Duration - if topic := t.topics[topic]; topic != nil { - waitPeriod = topic.wcl.waitPeriod - } else { - waitPeriod = minWaitPeriod - } - - tic.regTime[i] = now + mclock.AbsTime(waitPeriod) - } - return tic -} - -const gcInterval = time.Minute - -func (t *topicTable) collectGarbage() { - tm := mclock.Now() - if time.Duration(tm-t.lastGarbageCollection) < gcInterval { - return - } - t.lastGarbageCollection = tm - - for node, n := range t.nodes { - for _, e := range n.entries { - if e.expire <= tm { - t.deleteEntry(e) - } - } - - t.checkDeleteNode(node) - } - - for topic := range t.topics { - t.checkDeleteTopic(topic) - } -} - -const ( - minWaitPeriod = time.Minute - regTimeWindow = 10 // seconds - avgnoRegTimeout = time.Minute * 10 - // target average interval between two incoming ad requests - wcTargetRegInterval = time.Minute * 10 / maxEntriesPerTopic - // - wcTimeConst = time.Minute * 10 -) - -// initialization is not required, will set to minWaitPeriod at first registration -type waitControlLoop struct { - lastIncoming mclock.AbsTime - waitPeriod time.Duration -} - -func (w *waitControlLoop) registered(tm mclock.AbsTime) { - w.waitPeriod = w.nextWaitPeriod(tm) - w.lastIncoming = tm -} - -func (w *waitControlLoop) nextWaitPeriod(tm mclock.AbsTime) time.Duration { - period := tm - w.lastIncoming - wp := time.Duration(float64(w.waitPeriod) * math.Exp((float64(wcTargetRegInterval)-float64(period))/float64(wcTimeConst))) - if wp < minWaitPeriod { - wp = minWaitPeriod - } - return wp -} - -func (w *waitControlLoop) hasMinimumWaitPeriod() bool { - return w.nextWaitPeriod(mclock.Now()) == minWaitPeriod -} - -func noRegTimeout() time.Duration { - e := rand.ExpFloat64() - if e > 100 { - e = 100 - } - return time.Duration(float64(avgnoRegTimeout) * e) -} - -type topicRequestQueueItem struct { - topic Topic - priority uint64 - index int -} - -// A topicRequestQueue implements heap.Interface and holds topicRequestQueueItems. -type topicRequestQueue []*topicRequestQueueItem - -func (tq topicRequestQueue) Len() int { return len(tq) } - -func (tq topicRequestQueue) Less(i, j int) bool { - return tq[i].priority < tq[j].priority -} - -func (tq topicRequestQueue) Swap(i, j int) { - tq[i], tq[j] = tq[j], tq[i] - tq[i].index = i - tq[j].index = j -} - -func (tq *topicRequestQueue) Push(x interface{}) { - n := len(*tq) - item := x.(*topicRequestQueueItem) - item.index = n - *tq = append(*tq, item) -} - -func (tq *topicRequestQueue) Pop() interface{} { - old := *tq - n := len(old) - item := old[n-1] - item.index = -1 - *tq = old[0 : n-1] - return item -} - -func (tq *topicRequestQueue) update(item *topicRequestQueueItem, priority uint64) { - item.priority = priority - heap.Fix(tq, item.index) -} diff --git a/p2p/discv5/topic_test.go b/p2p/discv5/topic_test.go deleted file mode 100644 index ba79993f29..0000000000 --- a/p2p/discv5/topic_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "encoding/binary" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/mclock" -) - -func TestTopicRadius(t *testing.T) { - now := mclock.Now() - topic := Topic("qwerty") - rad := newTopicRadius(topic) - targetRad := (^uint64(0)) / 100 - - waitFn := func(addr common.Hash) time.Duration { - prefix := binary.BigEndian.Uint64(addr[0:8]) - dist := prefix ^ rad.topicHashPrefix - relDist := float64(dist) / float64(targetRad) - relTime := (1 - relDist/2) * 2 - if relTime < 0 { - relTime = 0 - } - return time.Duration(float64(targetWaitTime) * relTime) - } - - bcnt := 0 - cnt := 0 - var sum float64 - for cnt < 100 { - addr := rad.nextTarget(false).target - wait := waitFn(addr) - ticket := &ticket{ - topics: []Topic{topic}, - regTime: []mclock.AbsTime{mclock.AbsTime(wait)}, - node: &Node{nodeNetGuts: nodeNetGuts{sha: addr}}, - } - rad.adjustWithTicket(now, addr, ticketRef{ticket, 0}) - if rad.radius != maxRadius { - cnt++ - sum += float64(rad.radius) - } else { - bcnt++ - if bcnt > 500 { - t.Errorf("Radius did not converge in 500 iterations") - } - } - } - avgRel := sum / float64(cnt) / float64(targetRad) - if avgRel > 1.05 || avgRel < 0.95 { - t.Errorf("Average/target ratio is too far from 1 (%v)", avgRel) - } -} diff --git a/p2p/discv5/udp.go b/p2p/discv5/udp.go deleted file mode 100644 index 088f95cac6..0000000000 --- a/p2p/discv5/udp.go +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "bytes" - "crypto/ecdsa" - "errors" - "fmt" - "net" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/netutil" - "github.com/ethereum/go-ethereum/rlp" -) - -const Version = 4 - -// Errors -var ( - errPacketTooSmall = errors.New("too small") - errBadPrefix = errors.New("bad prefix") -) - -// Timeouts -const ( - respTimeout = 500 * time.Millisecond - expiration = 20 * time.Second -) - -// RPC request structures -type ( - ping struct { - Version uint - From, To rpcEndpoint - Expiration uint64 - - // v5 - Topics []Topic - - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // pong is the reply to ping. - pong struct { - // This field should mirror the UDP envelope address - // of the ping packet, which provides a way to discover the - // the external address (after NAT). - To rpcEndpoint - - ReplyTok []byte // This contains the hash of the ping packet. - Expiration uint64 // Absolute timestamp at which the packet becomes invalid. - - // v5 - TopicHash common.Hash - TicketSerial uint32 - WaitPeriods []uint32 - - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // findnode is a query for nodes close to the given target. - findnode struct { - Target NodeID // doesn't need to be an actual public key - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // findnode is a query for nodes close to the given target. - findnodeHash struct { - Target common.Hash - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // reply to findnode - neighbors struct { - Nodes []rpcNode - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - topicRegister struct { - Topics []Topic - Idx uint - Pong []byte - } - - topicQuery struct { - Topic Topic - Expiration uint64 - } - - // reply to topicQuery - topicNodes struct { - Echo common.Hash - Nodes []rpcNode - } - - rpcNode struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP uint16 // for discovery protocol - TCP uint16 // for RLPx protocol - ID NodeID - } - - rpcEndpoint struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP uint16 // for discovery protocol - TCP uint16 // for RLPx protocol - } -) - -var ( - versionPrefix = []byte("temporary discovery v5") - versionPrefixSize = len(versionPrefix) - sigSize = 520 / 8 - headSize = versionPrefixSize + sigSize // space of packet frame data -) - -// Neighbors replies are sent across multiple packets to -// stay below the 1280 byte limit. We compute the maximum number -// of entries by stuffing a packet until it grows too large. -var maxNeighbors = func() int { - p := neighbors{Expiration: ^uint64(0)} - maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} - for n := 0; ; n++ { - p.Nodes = append(p.Nodes, maxSizeNode) - size, _, err := rlp.EncodeToReader(p) - if err != nil { - // If this ever happens, it will be caught by the unit tests. - panic("cannot encode: " + err.Error()) - } - if headSize+size+1 >= 1280 { - return n - } - } -}() - -var maxTopicNodes = func() int { - p := topicNodes{} - maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} - for n := 0; ; n++ { - p.Nodes = append(p.Nodes, maxSizeNode) - size, _, err := rlp.EncodeToReader(p) - if err != nil { - // If this ever happens, it will be caught by the unit tests. - panic("cannot encode: " + err.Error()) - } - if headSize+size+1 >= 1280 { - return n - } - } -}() - -func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { - ip := addr.IP.To4() - if ip == nil { - ip = addr.IP.To16() - } - return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} -} - -func nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) { - if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil { - return nil, err - } - n := NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP) - err := n.validateComplete() - return n, err -} - -func nodeToRPC(n *Node) rpcNode { - return rpcNode{ID: n.ID, IP: n.IP, UDP: n.UDP, TCP: n.TCP} -} - -type ingressPacket struct { - remoteID NodeID - remoteAddr *net.UDPAddr - ev nodeEvent - hash []byte - data interface{} // one of the RPC structs - rawData []byte -} - -type conn interface { - ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) - WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) - Close() error - LocalAddr() net.Addr -} - -// udp implements the RPC protocol. -type udp struct { - conn conn - priv *ecdsa.PrivateKey - ourEndpoint rpcEndpoint - net *Network -} - -// ListenUDP returns a new table that listens for UDP packets on laddr. -func ListenUDP(priv *ecdsa.PrivateKey, conn conn, nodeDBPath string, netrestrict *netutil.Netlist) (*Network, error) { - realaddr := conn.LocalAddr().(*net.UDPAddr) - transport, err := listenUDP(priv, conn, realaddr) - if err != nil { - return nil, err - } - net, err := newNetwork(transport, priv.PublicKey, nodeDBPath, netrestrict) - if err != nil { - return nil, err - } - log.Info("UDP listener up", "net", net.tab.self) - transport.net = net - go transport.readLoop() - return net, nil -} - -func listenUDP(priv *ecdsa.PrivateKey, conn conn, realaddr *net.UDPAddr) (*udp, error) { - return &udp{conn: conn, priv: priv, ourEndpoint: makeEndpoint(realaddr, uint16(realaddr.Port))}, nil -} - -func (t *udp) localAddr() *net.UDPAddr { - return t.conn.LocalAddr().(*net.UDPAddr) -} - -func (t *udp) Close() { - t.conn.Close() -} - -func (t *udp) send(remote *Node, ptype nodeEvent, data interface{}) (hash []byte) { - hash, _ = t.sendPacket(remote.ID, remote.addr(), byte(ptype), data) - return hash -} - -func (t *udp) sendPing(remote *Node, toaddr *net.UDPAddr, topics []Topic) (hash []byte) { - hash, _ = t.sendPacket(remote.ID, toaddr, byte(pingPacket), ping{ - Version: Version, - From: t.ourEndpoint, - To: makeEndpoint(toaddr, uint16(toaddr.Port)), // TODO: maybe use known TCP port from DB - Expiration: uint64(time.Now().Add(expiration).Unix()), - Topics: topics, - }) - return hash -} - -func (t *udp) sendNeighbours(remote *Node, results []*Node) { - // Send neighbors in chunks with at most maxNeighbors per packet - // to stay below the 1280 byte limit. - p := neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())} - for i, result := range results { - p.Nodes = append(p.Nodes, nodeToRPC(result)) - if len(p.Nodes) == maxNeighbors || i == len(results)-1 { - t.sendPacket(remote.ID, remote.addr(), byte(neighborsPacket), p) - p.Nodes = p.Nodes[:0] - } - } -} - -func (t *udp) sendFindnodeHash(remote *Node, target common.Hash) { - t.sendPacket(remote.ID, remote.addr(), byte(findnodeHashPacket), findnodeHash{ - Target: target, - Expiration: uint64(time.Now().Add(expiration).Unix()), - }) -} - -func (t *udp) sendTopicRegister(remote *Node, topics []Topic, idx int, pong []byte) { - t.sendPacket(remote.ID, remote.addr(), byte(topicRegisterPacket), topicRegister{ - Topics: topics, - Idx: uint(idx), - Pong: pong, - }) -} - -func (t *udp) sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) { - p := topicNodes{Echo: queryHash} - var sent bool - for _, result := range nodes { - if result.IP.Equal(t.net.tab.self.IP) || netutil.CheckRelayIP(remote.IP, result.IP) == nil { - p.Nodes = append(p.Nodes, nodeToRPC(result)) - } - if len(p.Nodes) == maxTopicNodes { - t.sendPacket(remote.ID, remote.addr(), byte(topicNodesPacket), p) - p.Nodes = p.Nodes[:0] - sent = true - } - } - if !sent || len(p.Nodes) > 0 { - t.sendPacket(remote.ID, remote.addr(), byte(topicNodesPacket), p) - } -} - -func (t *udp) sendPacket(toid NodeID, toaddr *net.UDPAddr, ptype byte, req interface{}) (hash []byte, err error) { - //fmt.Println("sendPacket", nodeEvent(ptype), toaddr.String(), toid.String()) - packet, hash, err := encodePacket(t.priv, ptype, req) - if err != nil { - //fmt.Println(err) - return hash, err - } - log.Trace(fmt.Sprintf(">>> %v to %x@%v", nodeEvent(ptype), toid[:8], toaddr)) - if nbytes, err := t.conn.WriteToUDP(packet, toaddr); err != nil { - log.Trace(fmt.Sprint("UDP send failed:", err)) - } else { - egressTrafficMeter.Mark(int64(nbytes)) - } - //fmt.Println(err) - return hash, err -} - -// zeroed padding space for encodePacket. -var headSpace = make([]byte, headSize) - -func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (p, hash []byte, err error) { - b := new(bytes.Buffer) - b.Write(headSpace) - b.WriteByte(ptype) - if err := rlp.Encode(b, req); err != nil { - log.Error(fmt.Sprint("error encoding packet:", err)) - return nil, nil, err - } - packet := b.Bytes() - sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv) - if err != nil { - log.Error(fmt.Sprint("could not sign packet:", err)) - return nil, nil, err - } - copy(packet, versionPrefix) - copy(packet[versionPrefixSize:], sig) - hash = crypto.Keccak256(packet[versionPrefixSize:]) - return packet, hash, nil -} - -// readLoop runs in its own goroutine. it injects ingress UDP packets -// into the network loop. -func (t *udp) readLoop() { - defer t.conn.Close() - // Discovery packets are defined to be no larger than 1280 bytes. - // Packets larger than this size will be cut at the end and treated - // as invalid because their hash won't match. - buf := make([]byte, 1280) - for { - nbytes, from, err := t.conn.ReadFromUDP(buf) - ingressTrafficMeter.Mark(int64(nbytes)) - if netutil.IsTemporaryError(err) { - // Ignore temporary read errors. - log.Debug(fmt.Sprintf("Temporary read error: %v", err)) - continue - } else if err != nil { - // Shut down the loop for permament errors. - log.Debug(fmt.Sprintf("Read error: %v", err)) - return - } - t.handlePacket(from, buf[:nbytes]) - } -} - -func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error { - pkt := ingressPacket{remoteAddr: from} - if err := decodePacket(buf, &pkt); err != nil { - log.Debug(fmt.Sprintf("Bad packet from %v: %v", from, err)) - //fmt.Println("bad packet", err) - return err - } - t.net.reqReadPacket(pkt) - return nil -} - -func decodePacket(buffer []byte, pkt *ingressPacket) error { - if len(buffer) < headSize+1 { - return errPacketTooSmall - } - buf := make([]byte, len(buffer)) - copy(buf, buffer) - prefix, sig, sigdata := buf[:versionPrefixSize], buf[versionPrefixSize:headSize], buf[headSize:] - if !bytes.Equal(prefix, versionPrefix) { - return errBadPrefix - } - fromID, err := recoverNodeID(crypto.Keccak256(buf[headSize:]), sig) - if err != nil { - return err - } - pkt.rawData = buf - pkt.hash = crypto.Keccak256(buf[versionPrefixSize:]) - pkt.remoteID = fromID - switch pkt.ev = nodeEvent(sigdata[0]); pkt.ev { - case pingPacket: - pkt.data = new(ping) - case pongPacket: - pkt.data = new(pong) - case findnodePacket: - pkt.data = new(findnode) - case neighborsPacket: - pkt.data = new(neighbors) - case findnodeHashPacket: - pkt.data = new(findnodeHash) - case topicRegisterPacket: - pkt.data = new(topicRegister) - case topicQueryPacket: - pkt.data = new(topicQuery) - case topicNodesPacket: - pkt.data = new(topicNodes) - default: - return fmt.Errorf("unknown packet type: %d", sigdata[0]) - } - s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) - err = s.Decode(pkt.data) - return err -} diff --git a/p2p/server.go b/p2p/server.go index 275cb5ea5c..fc71548554 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -35,7 +35,6 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/discover" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nat" @@ -105,7 +104,7 @@ type Config struct { // BootstrapNodesV5 are used to establish connectivity // with the rest of the network using the V5 discovery // protocol. - BootstrapNodesV5 []*discv5.Node `toml:",omitempty"` + BootstrapNodesV5 []*enode.Node `toml:",omitempty"` // Static nodes are used as pre-configured connections which are always // maintained and re-connected on disconnects. @@ -182,7 +181,7 @@ type Server struct { nodedb *enode.DB localnode *enode.LocalNode ntab *discover.UDPv4 - DiscV5 *discv5.Network + DiscV5 *discover.UDPv5 discmix *enode.FairMix dialsched *dialScheduler @@ -413,7 +412,7 @@ type sharedUDPConn struct { unhandled chan discover.ReadPacket } -// ReadFromUDP implements discv5.conn +// ReadFromUDP implements discover.UDPConn func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) { packet, ok := <-s.unhandled if !ok { @@ -427,7 +426,7 @@ func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err err return l, packet.Addr, nil } -// Close implements discv5.conn +// Close implements discover.UDPConn func (s *sharedUDPConn) Close() error { return nil } @@ -586,7 +585,7 @@ func (srv *Server) setupDiscovery() error { Unhandled: unhandled, Log: srv.log, } - ntab, err := discover.ListenUDP(conn, srv.localnode, cfg) + ntab, err := discover.ListenV4(conn, srv.localnode, cfg) if err != nil { return err } @@ -596,20 +595,21 @@ func (srv *Server) setupDiscovery() error { // Discovery V5 if srv.DiscoveryV5 { - var ntab *discv5.Network + cfg := discover.Config{ + PrivateKey: srv.PrivateKey, + NetRestrict: srv.NetRestrict, + Bootnodes: srv.BootstrapNodesV5, + Log: srv.log, + } var err error if sconn != nil { - ntab, err = discv5.ListenUDP(srv.PrivateKey, sconn, "", srv.NetRestrict) + srv.DiscV5, err = discover.ListenV5(sconn, srv.localnode, cfg) } else { - ntab, err = discv5.ListenUDP(srv.PrivateKey, conn, "", srv.NetRestrict) + srv.DiscV5, err = discover.ListenV5(conn, srv.localnode, cfg) } if err != nil { return err } - if err := ntab.SetFallbackNodes(srv.BootstrapNodesV5); err != nil { - return err - } - srv.DiscV5 = ntab } return nil } diff --git a/params/bootnodes.go b/params/bootnodes.go index d4512bf789..66f917015e 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -73,6 +73,24 @@ var YoloV2Bootnodes = []string{ "enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@3.9.20.133:30303", } +var V5Bootnodes = []string{ + // Teku team's bootnode + "enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2Gxb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNlY3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA", + "enr:-KG4QDyytgmE4f7AnvW-ZaUOIi9i79qX4JwjRAiXBZCU65wOfBu-3Nb5I7b_Rmg3KCOcZM_C3y5pg7EBU5XGrcLTduQEhGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQ2_DUbiXNlY3AyNTZrMaEDKnz_-ps3UUOfHWVYaskI5kWYO_vtYMGYCQRAR3gHDouDdGNwgiMog3VkcIIjKA", + // Prylab team's bootnodes + "enr:-Ku4QImhMc1z8yCiNJ1TyUxdcfNucje3BGwEHzodEZUan8PherEo4sF7pPHPSIB1NNuSg5fZy7qFsjmUKs2ea1Whi0EBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQOVphkDqal4QzPMksc5wnpuC3gvSC8AfbFOnZY_On34wIN1ZHCCIyg", + "enr:-Ku4QP2xDnEtUXIjzJ_DhlCRN9SN99RYQPJL92TMlSv7U5C1YnYLjwOQHgZIUXw6c-BvRg2Yc2QsZxxoS_pPRVe0yK8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMeFF5GrS7UZpAH2Ly84aLK-TyvH-dRo0JM1i8yygH50YN1ZHCCJxA", + "enr:-Ku4QPp9z1W4tAO8Ber_NQierYaOStqhDqQdOPY3bB3jDgkjcbk6YrEnVYIiCBbTxuar3CzS528d2iE7TdJsrL-dEKoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMw5fqqkw2hHC4F5HZZDPsNmPdB1Gi8JPQK7pRc9XHh-oN1ZHCCKvg", + // Lighthouse team's bootnodes + "enr:-IS4QLkKqDMy_ExrpOEWa59NiClemOnor-krjp4qoeZwIw2QduPC-q7Kz4u1IOWf3DDbdxqQIgC4fejavBOuUPy-HE4BgmlkgnY0gmlwhCLzAHqJc2VjcDI1NmsxoQLQSJfEAHZApkm5edTCZ_4qps_1k_ub2CxHFxi-gr2JMIN1ZHCCIyg", + "enr:-IS4QDAyibHCzYZmIYZCjXwU9BqpotWmv2BsFlIq1V31BwDDMJPFEbox1ijT5c2Ou3kvieOKejxuaCqIcjxBjJ_3j_cBgmlkgnY0gmlwhAMaHiCJc2VjcDI1NmsxoQJIdpj_foZ02MXz4It8xKD7yUHTBx7lVFn3oeRP21KRV4N1ZHCCIyg", + // EF bootnodes + "enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg", + "enr:-Ku4QG-2_Md3sZIAUebGYT6g0SMskIml77l6yR-M_JXc-UdNHCmHQeOiMLbylPejyJsdAPsTHJyjJB2sYGDLe0dn8uYBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhBLY-NyJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyg", + "enr:-Ku4QPn5eVhcoF1opaFEvg1b6JNFD2rqVkHQ8HApOKK61OIcIXD127bKWgAtbwI7pnxx6cDyk_nI88TrZKQaGMZj0q0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDayLMaJc2VjcDI1NmsxoQK2sBOLGcUb4AwuYzFuAVCaNHA-dy24UuEKkeFNgCVCsIN1ZHCCIyg", + "enr:-Ku4QEWzdnVtXc2Q0ZVigfCGggOVB2Vc1ZCPEc6j21NIFLODSJbvNaef1g4PxhPwl_3kax86YPheFUSLXPRs98vvYsoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDZBrP2Jc2VjcDI1NmsxoQM6jr8Rb1ktLEsVcKAPa08wCsKUmvoQ8khiOl_SLozf9IN1ZHCCIyg", +} + const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@" // KnownDNSNetwork returns the address of a public DNS-based node list for the given From 2e5d14170846ae72adc47467a1129e41d6800349 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Wed, 27 Jan 2021 09:20:34 +0000 Subject: [PATCH 161/235] rpc: deprecate Client.ShhSubscribe (#22239) It never worked, whisper uses polling. Co-authored-by: Felix Lange --- rpc/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rpc/client.go b/rpc/client.go index 9393adb779..198ce63573 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -414,6 +414,7 @@ func (c *Client) EthSubscribe(ctx context.Context, channel interface{}, args ... } // ShhSubscribe registers a subscripion under the "shh" namespace. +// Deprecated: use Subscribe(ctx, "shh", ...). func (c *Client) ShhSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*ClientSubscription, error) { return c.Subscribe(ctx, "shh", channel, args...) } From eb21c652c0a9d8f651efc0251cc5797a3328d863 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Thu, 28 Jan 2021 21:19:07 +0100 Subject: [PATCH 162/235] cmd,core,eth,params,tests: define yolov3 + enable EIP-2565 (#22213) Removes the yolov2 definition, adds yolov3, including EIP-2565. This PR also disables some of the erroneously generated blockchain and statetests, and adds the new genesis hash + alloc for yolov3. This PR disables the CLI switches for yolo, since it's not complete until we merge support for 2930. --- cmd/evm/internal/t8ntool/execution.go | 2 +- cmd/geth/chaincmd.go | 4 +-- cmd/geth/consolecmd.go | 4 +-- cmd/geth/main.go | 4 ++- cmd/geth/usage.go | 3 ++- cmd/puppeth/wizard_genesis.go | 4 +-- cmd/utils/flags.go | 28 ++++++++++----------- core/genesis.go | 14 +++++------ core/genesis_alloc.go | 3 ++- core/state_processor.go | 2 +- core/vm/contracts.go | 35 +++++++++++++++------------ core/vm/evm.go | 10 ++++---- core/vm/interpreter.go | 4 +-- core/vm/jump_table.go | 6 ++--- core/vm/runtime/runtime.go | 8 +++--- eth/tracers/api.go | 4 +-- params/bootnodes.go | 7 +++--- params/config.go | 31 ++++++++++++------------ tests/block_test.go | 7 ++++++ tests/fuzzers/bls12381/bls_fuzzer.go | 2 +- tests/init.go | 8 +++--- tests/state_test.go | 8 ++++++ tests/state_test_util.go | 2 +- 23 files changed, 112 insertions(+), 88 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 171443e156..95e6de37cb 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -145,7 +145,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, txContext := core.NewEVMTxContext(msg) evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) - if chainConfig.IsYoloV2(vmContext.BlockNumber) { + if chainConfig.IsYoloV3(vmContext.BlockNumber) { statedb.AddAddressToAccessList(msg.From()) if dst := msg.To(); dst != nil { statedb.AddAddressToAccessList(*dst) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index f539322654..9eec30f813 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -164,7 +164,7 @@ The export-preimages command export hash preimages to an RLP encoded stream`, utils.RinkebyFlag, utils.TxLookupLimitFlag, utils.GoerliFlag, - utils.YoloV2Flag, + utils.YoloV3Flag, utils.LegacyTestnetFlag, }, Category: "BLOCKCHAIN COMMANDS", @@ -215,7 +215,7 @@ Use "ethereum dump 0" to dump the genesis block.`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV2Flag, + utils.YoloV3Flag, utils.LegacyTestnetFlag, utils.SyncModeFlag, }, diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 7822c73b31..5369612e87 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -136,8 +136,8 @@ func remoteConsole(ctx *cli.Context) error { path = filepath.Join(path, "rinkeby") } else if ctx.GlobalBool(utils.GoerliFlag.Name) { path = filepath.Join(path, "goerli") - } else if ctx.GlobalBool(utils.YoloV2Flag.Name) { - path = filepath.Join(path, "yolo-v2") + } else if ctx.GlobalBool(utils.YoloV3Flag.Name) { + path = filepath.Join(path, "yolo-v3") } } endpoint = fmt.Sprintf("%s/geth.ipc", path) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index e577ab370d..86dc6f40fe 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -148,7 +148,9 @@ var ( utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV2Flag, + // YOLOv3 is not yet complete! + // TODO: enable this once 2718/2930 is added + //utils.YoloV3Flag, utils.VMEnableDebugFlag, utils.NetworkIdFlag, utils.EthStatsURLFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 25accc9b79..ba311bf7f6 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -44,7 +44,8 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.MainnetFlag, utils.GoerliFlag, utils.RinkebyFlag, - utils.YoloV2Flag, + // TODO: Re-enable this when 2718/2930 is added + //utils.YoloV3Flag, utils.RopstenFlag, utils.SyncModeFlag, utils.ExitWhenSyncedFlag, diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index 2d014e83bc..52093975cb 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -236,8 +236,8 @@ func (w *wizard) manageGenesis() { w.conf.Genesis.Config.IstanbulBlock = w.readDefaultBigInt(w.conf.Genesis.Config.IstanbulBlock) fmt.Println() - fmt.Printf("Which block should YOLOv2 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV2Block) - w.conf.Genesis.Config.YoloV2Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV2Block) + fmt.Printf("Which block should YOLOv3 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV3Block) + w.conf.Genesis.Config.YoloV3Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV3Block) out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", " ") fmt.Printf("Chain configuration updated:\n\n%s\n", out) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1d6d2d86b6..8f9f68ba68 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -147,9 +147,9 @@ var ( Name: "goerli", Usage: "Görli network: pre-configured proof-of-authority test network", } - YoloV2Flag = cli.BoolFlag{ - Name: "yolov2", - Usage: "YOLOv2 network: pre-configured proof-of-authority shortlived test network.", + YoloV3Flag = cli.BoolFlag{ + Name: "yolov3", + Usage: "YOLOv3 network: pre-configured proof-of-authority shortlived test network.", } RinkebyFlag = cli.BoolFlag{ Name: "rinkeby", @@ -760,8 +760,8 @@ func MakeDataDir(ctx *cli.Context) string { if ctx.GlobalBool(GoerliFlag.Name) { return filepath.Join(path, "goerli") } - if ctx.GlobalBool(YoloV2Flag.Name) { - return filepath.Join(path, "yolo-v2") + if ctx.GlobalBool(YoloV3Flag.Name) { + return filepath.Join(path, "yolo-v3") } return path } @@ -819,8 +819,8 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { urls = params.RinkebyBootnodes case ctx.GlobalBool(GoerliFlag.Name): urls = params.GoerliBootnodes - case ctx.GlobalBool(YoloV2Flag.Name): - urls = params.YoloV2Bootnodes + case ctx.GlobalBool(YoloV3Flag.Name): + urls = params.YoloV3Bootnodes case cfg.BootstrapNodes != nil: return // already set, don't apply defaults. } @@ -1280,7 +1280,7 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby") case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli") - case ctx.GlobalBool(YoloV2Flag.Name) && cfg.DataDir == node.DefaultDataDir(): + case ctx.GlobalBool(YoloV3Flag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v2") } } @@ -1494,7 +1494,7 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, MainnetFlag, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV2Flag) + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag) CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) @@ -1631,11 +1631,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { } cfg.Genesis = core.DefaultGoerliGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) - case ctx.GlobalBool(YoloV2Flag.Name): + case ctx.GlobalBool(YoloV3Flag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { - cfg.NetworkId = 133519467574834 // "yolov2" + cfg.NetworkId = new(big.Int).SetBytes([]byte("yolov3")).Uint64() // "yolov3" } - cfg.Genesis = core.DefaultYoloV2GenesisBlock() + cfg.Genesis = core.DefaultYoloV3GenesisBlock() case ctx.GlobalBool(DeveloperFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1337 @@ -1824,8 +1824,8 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { genesis = core.DefaultRinkebyGenesisBlock() case ctx.GlobalBool(GoerliFlag.Name): genesis = core.DefaultGoerliGenesisBlock() - case ctx.GlobalBool(YoloV2Flag.Name): - genesis = core.DefaultYoloV2GenesisBlock() + case ctx.GlobalBool(YoloV3Flag.Name): + genesis = core.DefaultYoloV3GenesisBlock() case ctx.GlobalBool(DeveloperFlag.Name): Fatalf("Developer chains are ephemeral") } diff --git a/core/genesis.go b/core/genesis.go index 908a969afd..f678a3bbca 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -243,8 +243,8 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { return params.RinkebyChainConfig case ghash == params.GoerliGenesisHash: return params.GoerliChainConfig - case ghash == params.YoloV2GenesisHash: - return params.YoloV2ChainConfig + case ghash == params.YoloV3GenesisHash: + return params.YoloV3ChainConfig default: return params.AllEthashProtocolChanges } @@ -380,15 +380,15 @@ func DefaultGoerliGenesisBlock() *Genesis { } } -func DefaultYoloV2GenesisBlock() *Genesis { - // TODO: Update with yolov2 values + regenerate alloc data +func DefaultYoloV3GenesisBlock() *Genesis { + // Full genesis: https://gist.github.com/holiman/b2c32a05ff2e2712e11c0787d987d46f return &Genesis{ - Config: params.YoloV2ChainConfig, - Timestamp: 0x5f91b932, + Config: params.YoloV3ChainConfig, + Timestamp: 0x60117f8b, ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000008a37866fd3627c9205a37c8685666f32ec07bb1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), GasLimit: 0x47b760, Difficulty: big.NewInt(1), - Alloc: decodePrealloc(yoloV1AllocData), + Alloc: decodePrealloc(yoloV3AllocData), } } diff --git a/core/genesis_alloc.go b/core/genesis_alloc.go index 3e03d16407..6eecbbf0e8 100644 --- a/core/genesis_alloc.go +++ b/core/genesis_alloc.go @@ -25,4 +25,5 @@ const mainnetAllocData = "\xfa\x04]X\u0793\r\x83b\x011\x8e\u0189\x9agT\x06\x908' const ropstenAllocData = "\xf9\x03\xa4\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x80\xc2\v\x80\xc2\f\x80\xc2\r\x80\xc2\x0e\x80\xc2\x0f\x80\xc2\x10\x80\xc2\x11\x80\xc2\x12\x80\xc2\x13\x80\xc2\x14\x80\xc2\x15\x80\xc2\x16\x80\xc2\x17\x80\xc2\x18\x80\xc2\x19\x80\xc2\x1a\x80\xc2\x1b\x80\xc2\x1c\x80\xc2\x1d\x80\xc2\x1e\x80\xc2\x1f\x80\xc2 \x80\xc2!\x80\xc2\"\x80\xc2#\x80\xc2$\x80\xc2%\x80\xc2&\x80\xc2'\x80\xc2(\x80\xc2)\x80\xc2*\x80\xc2+\x80\xc2,\x80\xc2-\x80\xc2.\x80\xc2/\x80\xc20\x80\xc21\x80\xc22\x80\xc23\x80\xc24\x80\xc25\x80\xc26\x80\xc27\x80\xc28\x80\xc29\x80\xc2:\x80\xc2;\x80\xc2<\x80\xc2=\x80\xc2>\x80\xc2?\x80\xc2@\x80\xc2A\x80\xc2B\x80\xc2C\x80\xc2D\x80\xc2E\x80\xc2F\x80\xc2G\x80\xc2H\x80\xc2I\x80\xc2J\x80\xc2K\x80\xc2L\x80\xc2M\x80\xc2N\x80\xc2O\x80\xc2P\x80\xc2Q\x80\xc2R\x80\xc2S\x80\xc2T\x80\xc2U\x80\xc2V\x80\xc2W\x80\xc2X\x80\xc2Y\x80\xc2Z\x80\xc2[\x80\xc2\\\x80\xc2]\x80\xc2^\x80\xc2_\x80\xc2`\x80\xc2a\x80\xc2b\x80\xc2c\x80\xc2d\x80\xc2e\x80\xc2f\x80\xc2g\x80\xc2h\x80\xc2i\x80\xc2j\x80\xc2k\x80\xc2l\x80\xc2m\x80\xc2n\x80\xc2o\x80\xc2p\x80\xc2q\x80\xc2r\x80\xc2s\x80\xc2t\x80\xc2u\x80\xc2v\x80\xc2w\x80\xc2x\x80\xc2y\x80\xc2z\x80\xc2{\x80\xc2|\x80\xc2}\x80\xc2~\x80\xc2\u007f\x80\u00c1\x80\x80\u00c1\x81\x80\u00c1\x82\x80\u00c1\x83\x80\u00c1\x84\x80\u00c1\x85\x80\u00c1\x86\x80\u00c1\x87\x80\u00c1\x88\x80\u00c1\x89\x80\u00c1\x8a\x80\u00c1\x8b\x80\u00c1\x8c\x80\u00c1\x8d\x80\u00c1\x8e\x80\u00c1\x8f\x80\u00c1\x90\x80\u00c1\x91\x80\u00c1\x92\x80\u00c1\x93\x80\u00c1\x94\x80\u00c1\x95\x80\u00c1\x96\x80\u00c1\x97\x80\u00c1\x98\x80\u00c1\x99\x80\u00c1\x9a\x80\u00c1\x9b\x80\u00c1\x9c\x80\u00c1\x9d\x80\u00c1\x9e\x80\u00c1\x9f\x80\u00c1\xa0\x80\u00c1\xa1\x80\u00c1\xa2\x80\u00c1\xa3\x80\u00c1\xa4\x80\u00c1\xa5\x80\u00c1\xa6\x80\u00c1\xa7\x80\u00c1\xa8\x80\u00c1\xa9\x80\u00c1\xaa\x80\u00c1\xab\x80\u00c1\xac\x80\u00c1\xad\x80\u00c1\xae\x80\u00c1\xaf\x80\u00c1\xb0\x80\u00c1\xb1\x80\u00c1\xb2\x80\u00c1\xb3\x80\u00c1\xb4\x80\u00c1\xb5\x80\u00c1\xb6\x80\u00c1\xb7\x80\u00c1\xb8\x80\u00c1\xb9\x80\u00c1\xba\x80\u00c1\xbb\x80\u00c1\xbc\x80\u00c1\xbd\x80\u00c1\xbe\x80\u00c1\xbf\x80\u00c1\xc0\x80\u00c1\xc1\x80\u00c1\u0080\u00c1\u00c0\u00c1\u0100\u00c1\u0140\u00c1\u0180\u00c1\u01c0\u00c1\u0200\u00c1\u0240\u00c1\u0280\u00c1\u02c0\u00c1\u0300\u00c1\u0340\u00c1\u0380\u00c1\u03c0\u00c1\u0400\u00c1\u0440\u00c1\u0480\u00c1\u04c0\u00c1\u0500\u00c1\u0540\u00c1\u0580\u00c1\u05c0\u00c1\u0600\u00c1\u0640\u00c1\u0680\u00c1\u06c0\u00c1\u0700\u00c1\u0740\u00c1\u0780\u00c1\u07c0\u00c1\xe0\x80\u00c1\xe1\x80\u00c1\xe2\x80\u00c1\xe3\x80\u00c1\xe4\x80\u00c1\xe5\x80\u00c1\xe6\x80\u00c1\xe7\x80\u00c1\xe8\x80\u00c1\xe9\x80\u00c1\xea\x80\u00c1\xeb\x80\u00c1\xec\x80\u00c1\xed\x80\u00c1\xee\x80\u00c1\xef\x80\u00c1\xf0\x80\u00c1\xf1\x80\u00c1\xf2\x80\u00c1\xf3\x80\u00c1\xf4\x80\u00c1\xf5\x80\u00c1\xf6\x80\u00c1\xf7\x80\u00c1\xf8\x80\u00c1\xf9\x80\u00c1\xfa\x80\u00c1\xfb\x80\u00c1\xfc\x80\u00c1\xfd\x80\u00c1\xfe\x80\u00c1\xff\x80\u3507KT\xa8\xbd\x15)f\xd6?pk\xae\x1f\xfe\xb0A\x19!\xe5\x8d\f\x9f,\x9c\xd0Ft\xed\xea@\x00\x00\x00" const rinkebyAllocData = "\xf9\x03\xb7\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x941\xb9\x8d\x14\x00{\xde\xe67)\x80\x86\x98\x8a\v\xbd1\x18E#\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" const goerliAllocData = "\xf9\x04\x06\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xe0\x94L*\xe4\x82Y5\x05\xf0\x16<\xde\xfc\a>\x81\xc6<\xdaA\a\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xa8\xe8\xf1G2e\x8eKQ\xe8q\x191\x05:\x8ai\xba\xf2\xb1\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe1\x94\u0665\x17\x9f\t\x1d\x85\x05\x1d<\x98'\x85\xef\xd1E\\\uc199\x8b\bE\x95\x16\x14\x01HJ\x00\x00\x00\xe1\x94\u08bdBX\xd2v\x887\xba\xa2j(\xfeq\xdc\a\x9f\x84\u01cbJG\xe3\xc1$H\xf4\xad\x00\x00\x00" -const yoloV1AllocData = "\xf9\x03\xb7\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x94\x8a7\x86o\xd3b|\x92\x05\xa3|\x86\x85fo2\xec\a\xbb\x1b\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +const yoloV3AllocData = "\xf9\x05\x01\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x94\x0e\x89\xe2\xae\xdb\x1c\xfc\u06d4$\xd4\x1a\x1f!\x8fA2s\x81r\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94`\xad\xc0\xf8\x9aA\xaf#|\xe75T\xed\xe1p\xd73\xec\x14\xe0\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94y\x9d2\x9e_X4\x19\x16|\xd7\"\x96$\x85\x92n3\x8fJ\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94|\xf5\xb7\x9b\xfe)\x1ag\xab\x02\xb3\x93\xe4V\xcc\xc4\xc2f\xf7S\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8a\x8e\xaf\xb1\xcfb\xbf\xbe\xb1t\x17i\xda\xe1\xa9\xddG\x99a\x92\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8b\xa1\xf1\tU\x1b\xd42\x800\x12dZ\xc16\xdd\xd6M\xbar\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xb0*.\xda\x1b1\u007f\xbd\x16v\x01(\x83k\n\u015bV\x0e\x9d\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + diff --git a/core/state_processor.go b/core/state_processor.go index cdc86a694e..c5b0bb1609 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -93,7 +93,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon // Create a new context to be used in the EVM environment txContext := NewEVMTxContext(msg) // Add addresses to access list if applicable - if config.IsYoloV2(header.Number) { + if config.IsYoloV3(header.Number) { statedb.AddAddressToAccessList(msg.From()) if dst := msg.To(); dst != nil { statedb.AddAddressToAccessList(*dst) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 0c828b1cd9..9ea19d38a7 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -78,18 +78,23 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{9}): &blake2F{}, } -// PrecompiledContractsYoloV2 contains the default set of pre-compiled Ethereum -// contracts used in the Yolo v2 test release. -var PrecompiledContractsYoloV2 = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, +// PrecompiledContractsYoloV3 contains the default set of pre-compiled Ethereum +// contracts used in the Yolo v3 test release. +var PrecompiledContractsYoloV3 = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, +} + +// PrecompiledContractsBLS contains the set of pre-compiled Ethereum +// contracts specified in EIP-2537. These are exported for testing purposes. +var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{10}): &bls12381G1Add{}, common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, @@ -102,7 +107,7 @@ var PrecompiledContractsYoloV2 = map[common.Address]PrecompiledContract{ } var ( - PrecompiledAddressesYoloV2 []common.Address + PrecompiledAddressesYoloV3 []common.Address PrecompiledAddressesIstanbul []common.Address PrecompiledAddressesByzantium []common.Address PrecompiledAddressesHomestead []common.Address @@ -118,8 +123,8 @@ func init() { for k := range PrecompiledContractsIstanbul { PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k) } - for k := range PrecompiledContractsYoloV2 { - PrecompiledAddressesYoloV2 = append(PrecompiledAddressesYoloV2, k) + for k := range PrecompiledContractsYoloV3 { + PrecompiledAddressesYoloV3 = append(PrecompiledAddressesYoloV3, k) } } diff --git a/core/vm/evm.go b/core/vm/evm.go index 9afef76643..3617d77b12 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -46,8 +46,8 @@ type ( // configuration func (evm *EVM) ActivePrecompiles() []common.Address { switch { - case evm.chainRules.IsYoloV2: - return PrecompiledAddressesYoloV2 + case evm.chainRules.IsYoloV3: + return PrecompiledAddressesYoloV3 case evm.chainRules.IsIstanbul: return PrecompiledAddressesIstanbul case evm.chainRules.IsByzantium: @@ -60,8 +60,8 @@ func (evm *EVM) ActivePrecompiles() []common.Address { func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { - case evm.chainRules.IsYoloV2: - precompiles = PrecompiledContractsYoloV2 + case evm.chainRules.IsYoloV3: + precompiles = PrecompiledContractsYoloV3 case evm.chainRules.IsIstanbul: precompiles = PrecompiledContractsIstanbul case evm.chainRules.IsByzantium: @@ -446,7 +446,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, evm.StateDB.SetNonce(caller.Address(), nonce+1) // We add this to the access list _before_ taking a snapshot. Even if the creation fails, // the access-list change should not be rolled back - if evm.chainRules.IsYoloV2 { + if evm.chainRules.IsYoloV3 { evm.StateDB.AddAddressToAccessList(address) } // Ensure there's no existing contract already at the designated address diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index bffc5013a6..967db32780 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -99,8 +99,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { if cfg.JumpTable[STOP] == nil { var jt JumpTable switch { - case evm.chainRules.IsYoloV2: - jt = yoloV2InstructionSet + case evm.chainRules.IsYoloV3: + jt = yoloV3InstructionSet case evm.chainRules.IsIstanbul: jt = istanbulInstructionSet case evm.chainRules.IsConstantinople: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 83fb2c1ed6..954f657a94 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -56,16 +56,16 @@ var ( byzantiumInstructionSet = newByzantiumInstructionSet() constantinopleInstructionSet = newConstantinopleInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet() - yoloV2InstructionSet = newYoloV2InstructionSet() + yoloV3InstructionSet = newYoloV3InstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. type JumpTable [256]*operation -// newYoloV2InstructionSet creates an instructionset containing +// newYoloV3InstructionSet creates an instructionset containing // - "EIP-2315: Simple Subroutines" // - "EIP-2929: Gas cost increases for state access opcodes" -func newYoloV2InstructionSet() JumpTable { +func newYoloV3InstructionSet() JumpTable { instructionSet := newIstanbulInstructionSet() enable2315(&instructionSet) // Subroutines - https://eips.ethereum.org/EIPS/eip-2315 enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929 diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 2586535f9d..7cdb4ebd22 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -65,7 +65,7 @@ func setDefaults(cfg *Config) { PetersburgBlock: new(big.Int), IstanbulBlock: new(big.Int), MuirGlacierBlock: new(big.Int), - YoloV2Block: nil, + YoloV3Block: nil, } } @@ -113,7 +113,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) cfg.State.AddAddressToAccessList(address) for _, addr := range vmenv.ActivePrecompiles() { @@ -149,7 +149,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) for _, addr := range vmenv.ActivePrecompiles() { cfg.State.AddAddressToAccessList(addr) @@ -177,7 +177,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er vmenv := NewEnv(cfg) sender := cfg.State.GetOrNewStateObject(cfg.Origin) - if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) cfg.State.AddAddressToAccessList(address) for _, addr := range vmenv.ActivePrecompiles() { diff --git a/eth/tracers/api.go b/eth/tracers/api.go index dca990ab95..1bf59552c9 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -581,8 +581,8 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block chainConfigCopy := new(params.ChainConfig) *chainConfigCopy = *chainConfig chainConfig = chainConfigCopy - if yolov2 := config.LogConfig.Overrides.YoloV2Block; yolov2 != nil { - chainConfig.YoloV2Block = yolov2 + if yolov3 := config.LogConfig.Overrides.YoloV3Block; yolov3 != nil { + chainConfig.YoloV3Block = yolov3 canon = false } } diff --git a/params/bootnodes.go b/params/bootnodes.go index 66f917015e..f36ad61729 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -67,9 +67,10 @@ var GoerliBootnodes = []string{ "enode://a59e33ccd2b3e52d578f1fbd70c6f9babda2650f0760d6ff3b37742fdcdfdb3defba5d56d315b40c46b70198c7621e63ffa3f987389c7118634b0fefbbdfa7fd@51.15.119.157:40303", } -// YoloV2Bootnodes are the enode URLs of the P2P bootstrap nodes running on the -// YOLOv2 ephemeral test network. -var YoloV2Bootnodes = []string{ +// YoloV3Bootnodes are the enode URLs of the P2P bootstrap nodes running on the +// YOLOv3 ephemeral test network. +// TODO: Set Yolov3 bootnodes +var YoloV3Bootnodes = []string{ "enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@3.9.20.133:30303", } diff --git a/params/config.go b/params/config.go index 588d848f1c..0dbf592d19 100644 --- a/params/config.go +++ b/params/config.go @@ -31,8 +31,7 @@ var ( RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") - // TODO: update with yolov2 values - YoloV2GenesisHash = common.HexToHash("0x498a7239036dd2cd09e2bb8a80922b78632017958c332b42044c250d603a8a3e") + YoloV3GenesisHash = common.HexToHash("0x374f07cc7fa7c251fc5f36849f574b43db43600526410349efdca2bcea14101a") ) // TrustedCheckpoints associates each known checkpoint with the genesis hash of @@ -214,8 +213,8 @@ var ( Threshold: 2, } - // YoloV2ChainConfig contains the chain parameters to run a node on the YOLOv2 test network. - YoloV2ChainConfig = &ChainConfig{ + // YoloV3ChainConfig contains the chain parameters to run a node on the YOLOv3 test network. + YoloV3ChainConfig = &ChainConfig{ ChainID: big.NewInt(133519467574834), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -228,7 +227,7 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: nil, - YoloV2Block: big.NewInt(0), + YoloV3Block: big.NewInt(0), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -321,7 +320,7 @@ type ChainConfig struct { IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) - YoloV2Block *big.Int `json:"yoloV2Block,omitempty"` // YOLO v2: Gas repricings TODO @holiman add EIP references + YoloV3Block *big.Int `json:"yoloV3Block,omitempty"` // YOLO v3: Gas repricings TODO @holiman add EIP references EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) // Various consensus engines @@ -359,7 +358,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v2: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v3: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -372,7 +371,7 @@ func (c *ChainConfig) String() string { c.PetersburgBlock, c.IstanbulBlock, c.MuirGlacierBlock, - c.YoloV2Block, + c.YoloV3Block, engine, ) } @@ -429,9 +428,9 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool { return isForked(c.IstanbulBlock, num) } -// IsYoloV2 returns whether num is either equal to the YoloV1 fork block or greater. -func (c *ChainConfig) IsYoloV2(num *big.Int) bool { - return isForked(c.YoloV2Block, num) +// IsYoloV3 returns whether num is either equal to the YoloV3 fork block or greater. +func (c *ChainConfig) IsYoloV3(num *big.Int) bool { + return isForked(c.YoloV3Block, num) } // IsEWASM returns whether num represents a block number after the EWASM fork @@ -477,7 +476,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "petersburgBlock", block: c.PetersburgBlock}, {name: "istanbulBlock", block: c.IstanbulBlock}, {name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true}, - {name: "yoloV2Block", block: c.YoloV2Block}, + {name: "yoloV3Block", block: c.YoloV3Block}, } { if lastFork.name != "" { // Next one must be higher number @@ -541,8 +540,8 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) { return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock) } - if isForkIncompatible(c.YoloV2Block, newcfg.YoloV2Block, head) { - return newCompatError("YOLOv2 fork block", c.YoloV2Block, newcfg.YoloV2Block) + if isForkIncompatible(c.YoloV3Block, newcfg.YoloV3Block, head) { + return newCompatError("YOLOv3 fork block", c.YoloV3Block, newcfg.YoloV3Block) } if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock) @@ -614,7 +613,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool - IsYoloV2 bool + IsYoloV3 bool } // Rules ensures c's ChainID is not nil. @@ -633,6 +632,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsConstantinople: c.IsConstantinople(num), IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), - IsYoloV2: c.IsYoloV2(num), + IsYoloV3: c.IsYoloV3(num), } } diff --git a/tests/block_test.go b/tests/block_test.go index 8fa90e3e39..84618fd0be 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -45,6 +45,13 @@ func TestBlockchain(t *testing.T) { bt.skipLoad(`.*randomStatetest94.json.*`) bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { + if test.json.Network == "Berlin" { + // Our current berlin-tests were generated using YOLOv2 rules, hence a lot of them + // fail when berlin is defined as YOLOv3. We skip those, until they've been + // regenerated and re-imported + // TODO (@holiman) + return + } if err := bt.checkFailure(t, name+"/trie", test.Run(false)); err != nil { t.Errorf("test without snapshotter failed: %v", err) } diff --git a/tests/fuzzers/bls12381/bls_fuzzer.go b/tests/fuzzers/bls12381/bls_fuzzer.go index 7e3f94c2aa..bc3c456526 100644 --- a/tests/fuzzers/bls12381/bls_fuzzer.go +++ b/tests/fuzzers/bls12381/bls_fuzzer.go @@ -79,7 +79,7 @@ func checkInput(id byte, inputLen int) bool { // other values are reserved for future use. func fuzz(id byte, data []byte) int { // Even on bad input, it should not crash, so we still test the gas calc - precompile := vm.PrecompiledContractsYoloV2[common.BytesToAddress([]byte{id})] + precompile := vm.PrecompiledContractsBLS[common.BytesToAddress([]byte{id})] gas := precompile.RequiredGas(data) if !checkInput(id, len(data)) { return 0 diff --git a/tests/init.go b/tests/init.go index 607c69ddb3..a2ef040786 100644 --- a/tests/init.go +++ b/tests/init.go @@ -141,7 +141,7 @@ var Forks = map[string]*params.ChainConfig{ PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(5), }, - "YOLOv2": { + "YOLOv3": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), EIP150Block: big.NewInt(0), @@ -151,9 +151,9 @@ var Forks = map[string]*params.ChainConfig{ ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), - YoloV2Block: big.NewInt(0), + YoloV3Block: big.NewInt(0), }, - // This specification is subject to change, but is for now identical to YOLOv2 + // This specification is subject to change, but is for now identical to YOLOv3 // for cross-client testing purposes "Berlin": { ChainID: big.NewInt(1), @@ -165,7 +165,7 @@ var Forks = map[string]*params.ChainConfig{ ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), - YoloV2Block: big.NewInt(0), + YoloV3Block: big.NewInt(0), }, } diff --git a/tests/state_test.go b/tests/state_test.go index b77a898c21..0f2bd38532 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -62,6 +62,14 @@ func TestState(t *testing.T) { } { st.walk(t, dir, func(t *testing.T, name string, test *StateTest) { for _, subtest := range test.Subtests() { + if subtest.Fork == "Berlin" { + // Our current berlin-tests were generated using YOLOv2 rules, hence a lot of them + // fail when berlin is defined as YOLOv3. We skip those, until they've been + // regenerated and re-imported + // TODO (@holiman) + continue + } + subtest := subtest key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) name := name + "/" + key diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 96c7316969..7bd2e801e4 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -187,7 +187,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh context.GetHash = vmTestBlockHash evm := vm.NewEVM(context, txContext, statedb, config, vmconfig) - if config.IsYoloV2(context.BlockNumber) { + if config.IsYoloV3(context.BlockNumber) { statedb.AddAddressToAccessList(msg.From()) if dst := msg.To(); dst != nil { statedb.AddAddressToAccessList(*dst) From 7a800f98f66103d934f24231ffec1a656d25b496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Thu, 28 Jan 2021 22:47:15 +0100 Subject: [PATCH 163/235] les/utils: UDP rate limiter (#21930) * les/utils: Limiter * les/utils: dropped prior weight vs variable cost logic, using fixed weights * les/utils: always create node selector in addressGroup * les/utils: renamed request weight to request cost * les/utils: simplified and improved the DoS penalty mechanism * les/utils: minor fixes * les/utils: made selection weight calculation nicer * les/utils: fixed linter warning * les/utils: more precise and reliable probabilistic test * les/utils: fixed linter warning --- les/utils/limiter.go | 405 +++++++++++++++++++++++++++++++++++ les/utils/limiter_test.go | 206 ++++++++++++++++++ les/utils/weighted_select.go | 28 +-- 3 files changed, 625 insertions(+), 14 deletions(-) create mode 100644 les/utils/limiter.go create mode 100644 les/utils/limiter_test.go diff --git a/les/utils/limiter.go b/les/utils/limiter.go new file mode 100644 index 0000000000..0cc2d7b262 --- /dev/null +++ b/les/utils/limiter.go @@ -0,0 +1,405 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utils + +import ( + "sort" + "sync" + + "github.com/ethereum/go-ethereum/p2p/enode" +) + +const maxSelectionWeight = 1000000000 // maximum selection weight of each individual node/address group + +// Limiter protects a network request serving mechanism from denial-of-service attacks. +// It limits the total amount of resources used for serving requests while ensuring that +// the most valuable connections always have a reasonable chance of being served. +type Limiter struct { + lock sync.Mutex + cond *sync.Cond + quit bool + + nodes map[enode.ID]*nodeQueue + addresses map[string]*addressGroup + addressSelect, valueSelect *WeightedRandomSelect + maxValue float64 + maxCost, sumCost, sumCostLimit uint + selectAddressNext bool +} + +// nodeQueue represents queued requests coming from a single node ID +type nodeQueue struct { + queue []request // always nil if penaltyCost != 0 + id enode.ID + address string + value float64 + flatWeight, valueWeight uint64 // current selection weights in the address/value selectors + sumCost uint // summed cost of requests queued by the node + penaltyCost uint // cumulative cost of dropped requests since last processed request + groupIndex int +} + +// addressGroup is a group of node IDs that have sent their last requests from the same +// network address +type addressGroup struct { + nodes []*nodeQueue + nodeSelect *WeightedRandomSelect + sumFlatWeight, groupWeight uint64 +} + +// request represents an incoming request scheduled for processing +type request struct { + process chan chan struct{} + cost uint +} + +// flatWeight distributes weights equally between each active network address +func flatWeight(item interface{}) uint64 { return item.(*nodeQueue).flatWeight } + +// add adds the node queue to the address group. It is the caller's responsibility to +// add the address group to the address map and the address selector if it wasn't +// there before. +func (ag *addressGroup) add(nq *nodeQueue) { + if nq.groupIndex != -1 { + panic("added node queue is already in an address group") + } + l := len(ag.nodes) + nq.groupIndex = l + ag.nodes = append(ag.nodes, nq) + ag.sumFlatWeight += nq.flatWeight + ag.groupWeight = ag.sumFlatWeight / uint64(l+1) + ag.nodeSelect.Update(ag.nodes[l]) +} + +// update updates the selection weight of the node queue inside the address group. +// It is the caller's responsibility to update the group's selection weight in the +// address selector. +func (ag *addressGroup) update(nq *nodeQueue, weight uint64) { + if nq.groupIndex == -1 || nq.groupIndex >= len(ag.nodes) || ag.nodes[nq.groupIndex] != nq { + panic("updated node queue is not in this address group") + } + ag.sumFlatWeight += weight - nq.flatWeight + nq.flatWeight = weight + ag.groupWeight = ag.sumFlatWeight / uint64(len(ag.nodes)) + ag.nodeSelect.Update(nq) +} + +// remove removes the node queue from the address group. It is the caller's responsibility +// to remove the address group from the address map if it is empty. +func (ag *addressGroup) remove(nq *nodeQueue) { + if nq.groupIndex == -1 || nq.groupIndex >= len(ag.nodes) || ag.nodes[nq.groupIndex] != nq { + panic("removed node queue is not in this address group") + } + + l := len(ag.nodes) - 1 + if nq.groupIndex != l { + ag.nodes[nq.groupIndex] = ag.nodes[l] + ag.nodes[nq.groupIndex].groupIndex = nq.groupIndex + } + nq.groupIndex = -1 + ag.nodes = ag.nodes[:l] + ag.sumFlatWeight -= nq.flatWeight + if l >= 1 { + ag.groupWeight = ag.sumFlatWeight / uint64(l) + } else { + ag.groupWeight = 0 + } + ag.nodeSelect.Remove(nq) +} + +// choose selects one of the node queues belonging to the address group +func (ag *addressGroup) choose() *nodeQueue { + return ag.nodeSelect.Choose().(*nodeQueue) +} + +// NewLimiter creates a new Limiter +func NewLimiter(sumCostLimit uint) *Limiter { + l := &Limiter{ + addressSelect: NewWeightedRandomSelect(func(item interface{}) uint64 { return item.(*addressGroup).groupWeight }), + valueSelect: NewWeightedRandomSelect(func(item interface{}) uint64 { return item.(*nodeQueue).valueWeight }), + nodes: make(map[enode.ID]*nodeQueue), + addresses: make(map[string]*addressGroup), + sumCostLimit: sumCostLimit, + } + l.cond = sync.NewCond(&l.lock) + go l.processLoop() + return l +} + +// selectionWeights calculates the selection weights of a node for both the address and +// the value selector. The selection weight depends on the next request cost or the +// summed cost of recently dropped requests. +func (l *Limiter) selectionWeights(reqCost uint, value float64) (flatWeight, valueWeight uint64) { + if value > l.maxValue { + l.maxValue = value + } + if value > 0 { + // normalize value to <= 1 + value /= l.maxValue + } + if reqCost > l.maxCost { + l.maxCost = reqCost + } + relCost := float64(reqCost) / float64(l.maxCost) + var f float64 + if relCost <= 0.001 { + f = 1 + } else { + f = 0.001 / relCost + } + f *= maxSelectionWeight + flatWeight, valueWeight = uint64(f), uint64(f*value) + if flatWeight == 0 { + flatWeight = 1 + } + return +} + +// Add adds a new request to the node queue belonging to the given id. Value belongs +// to the requesting node. A higher value gives the request a higher chance of being +// served quickly in case of heavy load or a DDoS attack. Cost is a rough estimate +// of the serving cost of the request. A lower cost also gives the request a +// better chance. +func (l *Limiter) Add(id enode.ID, address string, value float64, reqCost uint) chan chan struct{} { + l.lock.Lock() + defer l.lock.Unlock() + + process := make(chan chan struct{}, 1) + if l.quit { + close(process) + return process + } + if reqCost == 0 { + reqCost = 1 + } + if nq, ok := l.nodes[id]; ok { + if nq.queue != nil { + nq.queue = append(nq.queue, request{process, reqCost}) + nq.sumCost += reqCost + nq.value = value + if address != nq.address { + // known id sending request from a new address, move to different address group + l.removeFromGroup(nq) + l.addToGroup(nq, address) + } + } else { + // already waiting on a penalty, just add to the penalty cost and drop the request + nq.penaltyCost += reqCost + l.update(nq) + close(process) + return process + } + } else { + nq := &nodeQueue{ + queue: []request{{process, reqCost}}, + id: id, + value: value, + sumCost: reqCost, + groupIndex: -1, + } + nq.flatWeight, nq.valueWeight = l.selectionWeights(reqCost, value) + if len(l.nodes) == 0 { + l.cond.Signal() + } + l.nodes[id] = nq + if nq.valueWeight != 0 { + l.valueSelect.Update(nq) + } + l.addToGroup(nq, address) + } + l.sumCost += reqCost + if l.sumCost > l.sumCostLimit { + l.dropRequests() + } + return process +} + +// update updates the selection weights of the node queue +func (l *Limiter) update(nq *nodeQueue) { + var cost uint + if nq.queue != nil { + cost = nq.queue[0].cost + } else { + cost = nq.penaltyCost + } + flatWeight, valueWeight := l.selectionWeights(cost, nq.value) + ag := l.addresses[nq.address] + ag.update(nq, flatWeight) + l.addressSelect.Update(ag) + nq.valueWeight = valueWeight + l.valueSelect.Update(nq) +} + +// addToGroup adds the node queue to the given address group. The group is created if +// it does not exist yet. +func (l *Limiter) addToGroup(nq *nodeQueue, address string) { + nq.address = address + ag := l.addresses[address] + if ag == nil { + ag = &addressGroup{nodeSelect: NewWeightedRandomSelect(flatWeight)} + l.addresses[address] = ag + } + ag.add(nq) + l.addressSelect.Update(ag) +} + +// removeFromGroup removes the node queue from its address group +func (l *Limiter) removeFromGroup(nq *nodeQueue) { + ag := l.addresses[nq.address] + ag.remove(nq) + if len(ag.nodes) == 0 { + delete(l.addresses, nq.address) + } + l.addressSelect.Update(ag) +} + +// remove removes the node queue from its address group, the nodes map and the value +// selector +func (l *Limiter) remove(nq *nodeQueue) { + l.removeFromGroup(nq) + if nq.valueWeight != 0 { + l.valueSelect.Remove(nq) + } + delete(l.nodes, nq.id) +} + +// choose selects the next node queue to process. +func (l *Limiter) choose() *nodeQueue { + if l.valueSelect.IsEmpty() || l.selectAddressNext { + if ag, ok := l.addressSelect.Choose().(*addressGroup); ok { + l.selectAddressNext = false + return ag.choose() + } + } + nq, _ := l.valueSelect.Choose().(*nodeQueue) + l.selectAddressNext = true + return nq +} + +// processLoop processes requests sequentially +func (l *Limiter) processLoop() { + l.lock.Lock() + defer l.lock.Unlock() + + for { + if l.quit { + for _, nq := range l.nodes { + for _, request := range nq.queue { + close(request.process) + } + } + return + } + nq := l.choose() + if nq == nil { + l.cond.Wait() + continue + } + if nq.queue != nil { + request := nq.queue[0] + nq.queue = nq.queue[1:] + nq.sumCost -= request.cost + l.sumCost -= request.cost + l.lock.Unlock() + ch := make(chan struct{}) + request.process <- ch + <-ch + l.lock.Lock() + if len(nq.queue) > 0 { + l.update(nq) + } else { + l.remove(nq) + } + } else { + // penalized queue removed, next request will be added to a clean queue + l.remove(nq) + } + } +} + +// Stop stops the processing loop. All queued and future requests are rejected. +func (l *Limiter) Stop() { + l.lock.Lock() + defer l.lock.Unlock() + + l.quit = true + l.cond.Signal() +} + +type ( + dropList []dropListItem + dropListItem struct { + nq *nodeQueue + priority float64 + } +) + +func (l dropList) Len() int { + return len(l) +} + +func (l dropList) Less(i, j int) bool { + return l[i].priority < l[j].priority +} + +func (l dropList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +// dropRequests selects the nodes with the highest queued request cost to selection +// weight ratio and drops their queued request. The empty node queues stay in the +// selectors with a low selection weight in order to penalize these nodes. +func (l *Limiter) dropRequests() { + var ( + sumValue float64 + list dropList + ) + for _, nq := range l.nodes { + sumValue += nq.value + } + for _, nq := range l.nodes { + if nq.sumCost == 0 { + continue + } + w := 1 / float64(len(l.addresses)*len(l.addresses[nq.address].nodes)) + if sumValue > 0 { + w += nq.value / sumValue + } + list = append(list, dropListItem{ + nq: nq, + priority: w / float64(nq.sumCost), + }) + } + sort.Sort(list) + for _, item := range list { + for _, request := range item.nq.queue { + close(request.process) + } + // make the queue penalized; no more requests are accepted until the node is + // selected based on the penalty cost which is the cumulative cost of all dropped + // requests. This ensures that sending excess requests is always penalized + // and incentivizes the sender to stop for a while if no replies are received. + item.nq.queue = nil + item.nq.penaltyCost = item.nq.sumCost + l.sumCost -= item.nq.sumCost // penalty costs are not counted in sumCost + item.nq.sumCost = 0 + l.update(item.nq) + if l.sumCost <= l.sumCostLimit/2 { + return + } + } +} diff --git a/les/utils/limiter_test.go b/les/utils/limiter_test.go new file mode 100644 index 0000000000..43af3309ab --- /dev/null +++ b/les/utils/limiter_test.go @@ -0,0 +1,206 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utils + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/p2p/enode" +) + +const ( + ltTolerance = 0.03 + ltRounds = 7 +) + +type ( + ltNode struct { + addr, id int + value, exp float64 + cost uint + reqRate float64 + reqMax, runCount int + lastTotalCost uint + + served, dropped int + } + + ltResult struct { + node *ltNode + ch chan struct{} + } + + limTest struct { + limiter *Limiter + results chan ltResult + runCount int + expCost, totalCost uint + } +) + +func (lt *limTest) request(n *ltNode) { + var ( + address string + id enode.ID + ) + if n.addr >= 0 { + address = string([]byte{byte(n.addr)}) + } else { + var b [32]byte + rand.Read(b[:]) + address = string(b[:]) + } + if n.id >= 0 { + id = enode.ID{byte(n.id)} + } else { + rand.Read(id[:]) + } + lt.runCount++ + n.runCount++ + cch := lt.limiter.Add(id, address, n.value, n.cost) + go func() { + lt.results <- ltResult{n, <-cch} + }() +} + +func (lt *limTest) moreRequests(n *ltNode) { + maxStart := int(float64(lt.totalCost-n.lastTotalCost) * n.reqRate) + if maxStart != 0 { + n.lastTotalCost = lt.totalCost + } + for n.reqMax > n.runCount && maxStart > 0 { + lt.request(n) + maxStart-- + } +} + +func (lt *limTest) process() { + res := <-lt.results + lt.runCount-- + res.node.runCount-- + if res.ch != nil { + res.node.served++ + if res.node.exp != 0 { + lt.expCost += res.node.cost + } + lt.totalCost += res.node.cost + close(res.ch) + } else { + res.node.dropped++ + } +} + +func TestLimiter(t *testing.T) { + limTests := [][]*ltNode{ + { // one id from an individual address and two ids from a shared address + {addr: 0, id: 0, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.5}, + {addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, + {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, + }, + { // varying request costs + {addr: 0, id: 0, value: 0, cost: 10, reqRate: 0.2, reqMax: 1, exp: 0.5}, + {addr: 1, id: 1, value: 0, cost: 3, reqRate: 0.5, reqMax: 1, exp: 0.25}, + {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, + }, + { // different request rate + {addr: 0, id: 0, value: 0, cost: 1, reqRate: 2, reqMax: 2, exp: 0.5}, + {addr: 1, id: 1, value: 0, cost: 1, reqRate: 10, reqMax: 10, exp: 0.25}, + {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, + }, + { // adding value + {addr: 0, id: 0, value: 3, cost: 1, reqRate: 1, reqMax: 1, exp: (0.5 + 0.3) / 2}, + {addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25 / 2}, + {addr: 1, id: 2, value: 7, cost: 1, reqRate: 1, reqMax: 1, exp: (0.25 + 0.7) / 2}, + }, + { // DoS attack from a single address with a single id + {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 3, id: 3, value: 0, cost: 1, reqRate: 10, reqMax: 1000000000, exp: 0}, + }, + { // DoS attack from a single address with different ids + {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 3, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, + }, + { // DDoS attack from different addresses with a single id + {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: -1, id: 3, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, + }, + { // DDoS attack from different addresses with different ids + {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: -1, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, + }, + } + + lt := &limTest{ + limiter: NewLimiter(100), + results: make(chan ltResult), + } + for _, test := range limTests { + lt.expCost, lt.totalCost = 0, 0 + iterCount := 10000 + for j := 0; j < ltRounds; j++ { + // try to reach expected target range in multiple rounds with increasing iteration counts + last := j == ltRounds-1 + for _, n := range test { + lt.request(n) + } + for i := 0; i < iterCount; i++ { + lt.process() + for _, n := range test { + lt.moreRequests(n) + } + } + for lt.runCount > 0 { + lt.process() + } + if spamRatio := 1 - float64(lt.expCost)/float64(lt.totalCost); spamRatio > 0.5*(1+ltTolerance) { + t.Errorf("Spam ratio too high (%f)", spamRatio) + } + fail, success := false, true + for _, n := range test { + if n.exp != 0 { + if n.dropped > 0 { + t.Errorf("Dropped %d requests of non-spam node", n.dropped) + fail = true + } + r := float64(n.served) * float64(n.cost) / float64(lt.expCost) + if r < n.exp*(1-ltTolerance) || r > n.exp*(1+ltTolerance) { + if last { + // print error only if the target is still not reached in the last round + t.Errorf("Request ratio (%f) does not match expected value (%f)", r, n.exp) + } + success = false + } + } + } + if fail || success { + break + } + // neither failed nor succeeded; try more iterations to reach probability targets + iterCount *= 2 + } + } + lt.limiter.Stop() +} diff --git a/les/utils/weighted_select.go b/les/utils/weighted_select.go index 9b4413afb5..486b00820a 100644 --- a/les/utils/weighted_select.go +++ b/les/utils/weighted_select.go @@ -52,17 +52,17 @@ func (w *WeightedRandomSelect) Remove(item WrsItem) { // IsEmpty returns true if the set is empty func (w *WeightedRandomSelect) IsEmpty() bool { - return w.root.sumWeight == 0 + return w.root.sumCost == 0 } // setWeight sets an item's weight to a specific value (removes it if zero) func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) { - if weight > math.MaxInt64-w.root.sumWeight { - // old weight is still included in sumWeight, remove and check again + if weight > math.MaxInt64-w.root.sumCost { + // old weight is still included in sumCost, remove and check again w.setWeight(item, 0) - if weight > math.MaxInt64-w.root.sumWeight { - log.Error("WeightedRandomSelect overflow", "sumWeight", w.root.sumWeight, "new weight", weight) - weight = math.MaxInt64 - w.root.sumWeight + if weight > math.MaxInt64-w.root.sumCost { + log.Error("WeightedRandomSelect overflow", "sumCost", w.root.sumCost, "new weight", weight) + weight = math.MaxInt64 - w.root.sumCost } } idx, ok := w.idx[item] @@ -75,9 +75,9 @@ func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) { if weight != 0 { if w.root.itemCnt == w.root.maxItems { // add a new level - newRoot := &wrsNode{sumWeight: w.root.sumWeight, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches} + newRoot := &wrsNode{sumCost: w.root.sumCost, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches} newRoot.items[0] = w.root - newRoot.weights[0] = w.root.sumWeight + newRoot.weights[0] = w.root.sumCost w.root = newRoot } w.idx[item] = w.root.insert(item, weight) @@ -91,10 +91,10 @@ func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) { // updates its weight and selects another one func (w *WeightedRandomSelect) Choose() WrsItem { for { - if w.root.sumWeight == 0 { + if w.root.sumCost == 0 { return nil } - val := uint64(rand.Int63n(int64(w.root.sumWeight))) + val := uint64(rand.Int63n(int64(w.root.sumCost))) choice, lastWeight := w.root.choose(val) weight := w.wfn(choice) if weight != lastWeight { @@ -112,7 +112,7 @@ const wrsBranches = 8 // max number of branches in the wrsNode tree type wrsNode struct { items [wrsBranches]interface{} weights [wrsBranches]uint64 - sumWeight uint64 + sumCost uint64 level, itemCnt, maxItems int } @@ -126,7 +126,7 @@ func (n *wrsNode) insert(item WrsItem, weight uint64) int { } } n.itemCnt++ - n.sumWeight += weight + n.sumCost += weight n.weights[branch] += weight if n.level == 0 { n.items[branch] = item @@ -150,7 +150,7 @@ func (n *wrsNode) setWeight(idx int, weight uint64) uint64 { oldWeight := n.weights[idx] n.weights[idx] = weight diff := weight - oldWeight - n.sumWeight += diff + n.sumCost += diff if weight == 0 { n.items[idx] = nil n.itemCnt-- @@ -161,7 +161,7 @@ func (n *wrsNode) setWeight(idx int, weight uint64) uint64 { branch := idx / branchItems diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight) n.weights[branch] += diff - n.sumWeight += diff + n.sumCost += diff if weight == 0 { n.itemCnt-- } From f25b437b70dd8be8ab7677fae607e01cd86c1ef4 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 29 Jan 2021 16:53:44 +0100 Subject: [PATCH 164/235] cmd/clef: don't check file permissions on windows (#22251) Fixes #20123 --- cmd/clef/main.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 273919cb41..090edd4507 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -805,14 +805,16 @@ func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { // checkFile is a convenience function to check if a file // * exists -// * is mode 0400 +// * is mode 0400 (unix only) func checkFile(filename string) error { info, err := os.Stat(filename) if err != nil { return fmt.Errorf("failed stat on %s: %v", filename, err) } // Check the unix permission bits - if info.Mode().Perm()&0377 != 0 { + // However, on windows, we cannot use the unix perm-bits, see + // https://github.com/ethereum/go-ethereum/issues/20123 + if runtime.GOOS != "windows" && info.Mode().Perm()&0377 != 0 { return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String()) } return nil From 3c728fb129210c044ae71aad55e4c059d70a318d Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Mon, 1 Feb 2021 14:41:43 +0100 Subject: [PATCH 165/235] eth/tracers: fix unigram tracer (#22248) --- eth/tracers/internal/tracers/assets.go | 37 ++++++++----------- .../internal/tracers/unigram_tracer.js | 4 +- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/eth/tracers/internal/tracers/assets.go b/eth/tracers/internal/tracers/assets.go index 8b89ad4182..7f45ab286e 100644 --- a/eth/tracers/internal/tracers/assets.go +++ b/eth/tracers/internal/tracers/assets.go @@ -8,7 +8,7 @@ // opcount_tracer.js (1.372kB) // prestate_tracer.js (4.287kB) // trigram_tracer.js (1.788kB) -// unigram_tracer.js (1.51kB) +// unigram_tracer.js (1.469kB) package tracers @@ -28,7 +28,7 @@ import ( func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %w", name, err) } var buf bytes.Buffer @@ -36,7 +36,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %w", name, err) } if clErr != nil { return nil, err @@ -237,7 +237,7 @@ func trigram_tracerJs() (*asset, error) { return a, nil } -var _unigram_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x54\x4d\x6f\xdb\x46\x10\xbd\xeb\x57\xbc\xa3\x8c\xa8\xa4\xd3\x5e\x0a\xa5\x09\xc0\x1a\x76\x22\xc0\x91\x0d\x89\x6e\x60\x14\x3d\x2c\xc9\x21\xb9\xe8\x6a\x87\xd8\x9d\x95\x42\x04\xfa\xef\xc5\x92\xa2\xe5\x1a\x6e\x13\x9e\x04\xcd\xbc\x8f\x79\x33\x64\x9a\xe2\x8a\xbb\xde\xe9\xa6\x15\xfc\x7c\xf9\xf6\x57\xe4\x2d\xa1\xe1\x9f\x48\x5a\x72\x14\x76\xc8\x82\xb4\xec\xfc\x2c\x4d\x91\xb7\xda\xa3\xd6\x86\xa0\x3d\x3a\xe5\x04\x5c\x43\x5e\xf4\x1b\x5d\x38\xe5\xfa\x64\x96\xa6\x23\xe6\xd5\x72\x64\xa8\x1d\x11\x3c\xd7\x72\x50\x8e\x96\xe8\x39\xa0\x54\x16\x8e\x2a\xed\xc5\xe9\x22\x08\x41\x0b\x94\xad\x52\x76\xd8\x71\xa5\xeb\x3e\x52\x6a\x41\xb0\x15\xb9\x41\x5a\xc8\xed\xfc\xe4\xe3\xe3\xfa\x01\xb7\xe4\x3d\x39\x7c\x24\x4b\x4e\x19\xdc\x87\xc2\xe8\x12\xb7\xba\x24\xeb\x09\xca\xa3\x8b\xff\xf8\x96\x2a\x14\x03\x5d\x04\xde\x44\x2b\xdb\x93\x15\xdc\x70\xb0\x95\x12\xcd\x76\x01\xd2\xd1\x39\xf6\xe4\xbc\x66\x8b\x5f\x26\xa9\x13\xe1\x02\xec\x22\xc9\x5c\x49\x1c\xc0\x81\xbb\x88\xbb\x80\xb2\x3d\x8c\x92\x33\xf4\x07\x02\x39\xcf\x5d\x41\xdb\x41\xa6\xe5\x8e\x20\xad\x92\x38\xf5\x41\x1b\x83\x82\x10\x3c\xd5\xc1\x2c\x22\x5b\x11\x04\x5f\x56\xf9\xa7\xbb\x87\x1c\xd9\xfa\x11\x5f\xb2\xcd\x26\x5b\xe7\x8f\xef\x70\xd0\xd2\x72\x10\xd0\x9e\x46\x2a\xbd\xeb\x8c\xa6\x0a\x07\xe5\x9c\xb2\xd2\x83\xeb\xc8\xf0\xf9\x7a\x73\xf5\x29\x5b\xe7\xd9\xef\xab\xdb\x55\xfe\x08\x76\xb8\x59\xe5\xeb\xeb\xed\x16\x37\x77\x1b\x64\xb8\xcf\x36\xf9\xea\xea\xe1\x36\xdb\xe0\xfe\x61\x73\x7f\xb7\xbd\x4e\xb0\xa5\xe8\x8a\x22\xfe\xfb\x99\xd7\xc3\xf6\x1c\xa1\x22\x51\xda\xf8\x29\x89\x47\x0e\xf0\x2d\x07\x53\xa1\x55\x7b\x82\xa3\x92\xf4\x9e\x2a\x28\x94\xdc\xf5\x3f\xbc\xd4\xc8\xa5\x0c\xdb\x66\x98\xf9\x3f\x0f\x12\xab\x1a\x96\x65\x01\x4f\x84\xdf\x5a\x91\x6e\x99\xa6\x87\xc3\x21\x69\x6c\x48\xd8\x35\xa9\x19\xe9\x7c\xfa\x21\x99\xcd\xbe\xcd\x00\x20\x4d\xd1\x6a\x2f\x71\x39\x91\x76\xa7\xba\xe8\x8a\xbb\x92\x2b\xf2\x10\x46\xc9\xc1\x0a\x39\x3f\x74\xc7\xd6\x25\xbe\x1d\x17\x13\xd6\x72\xe7\xc7\x16\x0f\x1b\x76\x05\xb9\x11\x3e\xb6\xc7\xea\x12\x97\x4f\xdd\x5e\xa8\x8b\x4a\xda\xee\xf9\x6f\xaa\x86\xdc\x68\x4f\xae\x3f\x09\x8e\x77\x10\x7d\xfc\xf1\x19\xf4\x95\xca\x20\xe4\x93\x01\x1d\xa1\x4b\xd4\xc1\x96\xf1\xfa\xe6\x86\x9b\x05\xaa\xe2\x02\xe3\x14\xf1\xd9\xab\x78\x9b\x78\x0f\xc3\x4d\xc2\x5d\x22\xbc\x15\xa7\x6d\x33\xbf\x78\xf7\xd4\xa3\x6b\xcc\xa5\xd5\x3e\x89\x83\xfc\xc9\xdd\x5f\x17\x67\x7c\x7c\xfe\x55\x7b\xf3\xe6\x0c\x3c\x3e\xfd\x22\xe3\x09\xff\x83\xc2\x7b\xbc\x7d\x0d\x37\x34\xc5\x40\x26\xda\x73\x88\xb5\x0a\x46\x9e\xe7\x72\x68\x4f\x17\xad\x4a\x09\xca\x9c\xa2\x88\x6f\x27\xd7\x50\x76\x4a\xab\x1e\x6f\x2d\xb2\x0c\x14\xaf\xe6\x73\x5c\xcc\x26\x1d\x47\xfe\x35\x21\x65\xcc\x20\x36\x2d\x7d\x38\xd5\x82\xc8\x42\x0b\x39\x15\xdf\x55\xde\x93\x8b\x9f\x29\x38\x92\xe0\xac\x9f\x18\x23\xac\xd6\x56\x99\x89\xfb\x74\xd1\xe2\x54\xa9\x6d\x33\x7a\x1b\x4b\xcf\xcc\x95\xf2\xf5\xf9\xe2\x74\x3d\x7f\x0a\x07\x1f\x70\xf9\x62\x27\xa3\xe4\x39\xe4\x97\xe1\x1e\x17\xb3\xe3\xec\x9f\x00\x00\x00\xff\xff\x8d\xba\x8d\xa8\xe6\x05\x00\x00") +var _unigram_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x94\x41\x6f\xdb\xc6\x13\xc5\xef\xfa\x14\xef\x68\x23\xfa\x8b\xc9\xbf\x97\x42\x69\x0a\xb0\x86\x9d\x08\x70\x64\x43\xa2\x1b\x18\x45\x0f\x4b\x72\x48\x2e\xba\xda\x21\x76\x67\xa5\x08\x81\xbf\x7b\x31\xa4\x68\xb9\x85\xdb\x86\x27\x41\x3b\xef\x37\x6f\xde\x0e\x99\x65\xb8\xe2\xfe\x18\x6c\xdb\x09\xfe\xff\xf6\xdd\x8f\x28\x3a\x42\xcb\xff\x23\xe9\x28\x50\xda\x21\x4f\xd2\x71\x88\xb3\x2c\x43\xd1\xd9\x88\xc6\x3a\x82\x8d\xe8\x4d\x10\x70\x03\xf9\x5b\xbd\xb3\x65\x30\xe1\xb8\x98\x65\xd9\xa8\x79\xf5\x58\x09\x4d\x20\x42\xe4\x46\x0e\x26\xd0\x12\x47\x4e\xa8\x8c\x47\xa0\xda\x46\x09\xb6\x4c\x42\xb0\x02\xe3\xeb\x8c\x03\x76\x5c\xdb\xe6\xa8\x48\x2b\x48\xbe\xa6\x30\xb4\x16\x0a\xbb\x38\xf9\xf8\xb8\x7e\xc0\x2d\xc5\x48\x01\x1f\xc9\x53\x30\x0e\xf7\xa9\x74\xb6\xc2\xad\xad\xc8\x47\x82\x89\xe8\xf5\x9f\xd8\x51\x8d\x72\xc0\xa9\xf0\x46\xad\x6c\x4f\x56\x70\xc3\xc9\xd7\x46\x2c\xfb\x39\xc8\xaa\x73\xec\x29\x44\xcb\x1e\x3f\x4c\xad\x4e\xc0\x39\x38\x28\xe4\xc2\x88\x0e\x10\xc0\xbd\xea\x2e\x61\xfc\x11\xce\xc8\x59\xfa\x1d\x81\x9c\xe7\xae\x61\xfd\xd0\xa6\xe3\x9e\x20\x9d\x11\x9d\xfa\x60\x9d\x43\x49\x48\x91\x9a\xe4\xe6\x4a\x2b\x93\xe0\xcb\xaa\xf8\x74\xf7\x50\x20\x5f\x3f\xe2\x4b\xbe\xd9\xe4\xeb\xe2\xf1\x3d\x0e\x56\x3a\x4e\x02\xda\xd3\x88\xb2\xbb\xde\x59\xaa\x71\x30\x21\x18\x2f\x47\x70\xa3\x84\xcf\xd7\x9b\xab\x4f\xf9\xba\xc8\x7f\x59\xdd\xae\x8a\x47\x70\xc0\xcd\xaa\x58\x5f\x6f\xb7\xb8\xb9\xdb\x20\xc7\x7d\xbe\x29\x56\x57\x0f\xb7\xf9\x06\xf7\x0f\x9b\xfb\xbb\xed\xf5\x02\x5b\x52\x57\xa4\xfa\xff\xce\xbc\x19\x6e\x2f\x10\x6a\x12\x63\x5d\x9c\x92\x78\xe4\x84\xd8\x71\x72\x35\x3a\xb3\x27\x04\xaa\xc8\xee\xa9\x86\x41\xc5\xfd\xf1\xbb\x2f\x55\x59\xc6\xb1\x6f\x87\x99\xff\x71\x21\xb1\x6a\xe0\x59\xe6\x88\x44\xf8\xa9\x13\xe9\x97\x59\x76\x38\x1c\x16\xad\x4f\x0b\x0e\x6d\xe6\x46\x5c\xcc\x7e\x5e\xcc\x66\xdf\x66\x00\x90\x65\xe8\x6c\x14\xbd\x1c\xc5\xee\x4c\xaf\xae\xb8\xaf\xb8\xa6\x08\x61\x54\x9c\xbc\x50\x88\x43\xb5\x96\x2e\xf1\xed\x69\x3e\x69\x3d\xf7\x71\x2c\x89\xf0\x69\x57\x52\x18\xe5\x63\xb9\x9e\x2e\xf1\xf6\xb9\x3a\x0a\xf5\xda\xc9\xfa\x3d\xff\x41\xf5\x90\x1b\xed\x29\x1c\x4f\x0d\xc7\x3d\x50\x1f\xbf\x7e\x06\x7d\xa5\x2a\x09\xc5\xc5\xa0\x56\xe9\x12\x4d\xf2\x95\x6e\xdf\x85\xe3\x76\x8e\xba\xbc\xc4\x38\x85\x3e\x7b\xa3\xbb\x89\x0f\x70\xdc\x2e\xb8\x5f\x08\x6f\x25\x58\xdf\x5e\x5c\xbe\x7f\xae\xb1\x0d\x2e\xa4\xb3\x71\xa1\x83\xfc\xc6\xfd\xef\x97\x67\xbd\x3e\x7f\x39\x7b\xf3\xe6\x2c\x7c\x7a\xfe\x45\x2e\x12\xfe\x45\x85\x0f\x78\xf7\x9a\x6e\x28\xd2\x40\x26\xec\x39\xc4\xc6\x24\x27\x2f\x73\x39\x74\xa7\x8d\x36\x95\x24\xe3\x4e\x51\xe8\xdb\xc9\x0d\x8c\x9f\xd2\x6a\xc6\x5d\x53\xca\x80\x78\x35\x9f\xa7\xf9\x6c\xea\x13\x28\xbe\xd6\xc8\x38\x37\x34\x9b\x2e\x7d\x58\xd5\x92\xc8\xc3\x0a\x05\xa3\xef\x2a\xef\x29\xe8\x67\x0a\x81\x24\x05\x1f\x27\xa2\xca\x1a\xeb\x8d\x9b\xd8\xa7\x8d\x96\x60\x2a\xeb\xdb\xd1\xdb\x78\xf4\xc2\x5c\x25\x5f\x5f\x5e\xdc\xc8\x3c\xa7\xf8\x1c\xcf\xd3\xec\xcf\x00\x00\x00\xff\xff\xf1\x91\x30\xae\xbd\x05\x00\x00") func unigram_tracerJsBytes() ([]byte, error) { return bindataRead( @@ -253,7 +253,7 @@ func unigram_tracerJs() (*asset, error) { } info := bindataFileInfo{name: "unigram_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2f, 0x36, 0x14, 0xc2, 0xf6, 0xc3, 0x80, 0x2b, 0x4a, 0x11, 0x7d, 0xd5, 0x3e, 0xef, 0x23, 0xb5, 0xd6, 0xe6, 0xe6, 0x5, 0x41, 0xf6, 0x14, 0x7a, 0x39, 0xf7, 0xf8, 0xac, 0x89, 0x8e, 0x43, 0xe6}} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc, 0xe6, 0x5c, 0x88, 0x18, 0xa7, 0x85, 0x61, 0x18, 0xc6, 0xec, 0x17, 0xfc, 0xdf, 0x9d, 0xc0, 0x1b, 0x49, 0xf8, 0x8d, 0xf1, 0xeb, 0x35, 0xf3, 0xd, 0x3e, 0xf6, 0xa3, 0xac, 0x8c, 0xba, 0x74}} return a, nil } @@ -348,25 +348,20 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "4byte_tracer.js": _4byte_tracerJs, - - "bigram_tracer.js": bigram_tracerJs, - - "call_tracer.js": call_tracerJs, - - "evmdis_tracer.js": evmdis_tracerJs, - - "noop_tracer.js": noop_tracerJs, - - "opcount_tracer.js": opcount_tracerJs, - + "4byte_tracer.js": _4byte_tracerJs, + "bigram_tracer.js": bigram_tracerJs, + "call_tracer.js": call_tracerJs, + "evmdis_tracer.js": evmdis_tracerJs, + "noop_tracer.js": noop_tracerJs, + "opcount_tracer.js": opcount_tracerJs, "prestate_tracer.js": prestate_tracerJs, - - "trigram_tracer.js": trigram_tracerJs, - - "unigram_tracer.js": unigram_tracerJs, + "trigram_tracer.js": trigram_tracerJs, + "unigram_tracer.js": unigram_tracerJs, } +// AssetDebug is true if the assets were built with the debug flag enabled. +const AssetDebug = false + // AssetDir returns the file names below a certain // directory embedded in the file by go-bindata. // For example if you run go-bindata on data/... and data contains the diff --git a/eth/tracers/internal/tracers/unigram_tracer.js b/eth/tracers/internal/tracers/unigram_tracer.js index 000fb13b1e..51107d8f3d 100644 --- a/eth/tracers/internal/tracers/unigram_tracer.js +++ b/eth/tracers/internal/tracers/unigram_tracer.js @@ -36,8 +36,6 @@ // result is invoked when all the opcodes have been iterated over and returns // the final result of the tracing. result: function(ctx) { - if(this.nops > 0){ - return this.hist; - } + return this.hist; }, } From e3430ac7df8a7c232f6374120eb1297c7fdfe5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 2 Feb 2021 10:44:36 +0200 Subject: [PATCH 166/235] eth: check snap satelliteness, delegate drop to eth (#22235) * eth: check snap satelliteness, delegate drop to eth * eth: better handle eth/snap satellite relation, merge reg/unreg paths --- eth/handler.go | 106 +++++++------ eth/handler_eth.go | 4 +- eth/handler_eth_test.go | 4 +- eth/handler_snap.go | 8 +- eth/peer.go | 9 +- eth/peerset.go | 261 +++++++++++++-------------------- eth/protocols/eth/handler.go | 6 +- eth/protocols/eth/protocol.go | 8 +- eth/protocols/snap/handler.go | 6 +- eth/protocols/snap/protocol.go | 8 +- eth/sync.go | 4 +- eth/sync_test.go | 2 +- p2p/peer.go | 11 +- 13 files changed, 201 insertions(+), 236 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index 5ae0925bb5..a5a62b894d 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -218,7 +218,7 @@ func newHandler(config *handlerConfig) (*handler, error) { h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, h.removePeer) fetchTx := func(peer string, hashes []common.Hash) error { - p := h.peers.ethPeer(peer) + p := h.peers.peer(peer) if p == nil { return errors.New("unknown peer") } @@ -229,8 +229,17 @@ func newHandler(config *handlerConfig) (*handler, error) { return h, nil } -// runEthPeer +// runEthPeer registers an eth peer into the joint eth/snap peerset, adds it to +// various subsistems and starts handling messages. func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { + // If the peer has a `snap` extension, wait for it to connect so we can have + // a uniform initialization/teardown mechanism + snap, err := h.peers.waitSnapExtension(peer) + if err != nil { + peer.Log().Error("Snapshot extension barrier failed", "err", err) + return err + } + // TODO(karalabe): Not sure why this is needed if !h.chainSync.handlePeerEvent(peer) { return p2p.DiscQuitting } @@ -251,37 +260,46 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { return err } reject := false // reserved peer slots - if atomic.LoadUint32(&h.snapSync) == 1 && !peer.SupportsCap("snap", 1) { - // If we are running snap-sync, we want to reserve roughly half the peer - // slots for peers supporting the snap protocol. - // The logic here is; we only allow up to 5 more non-snap peers than snap-peers. - if all, snp := h.peers.Len(), h.peers.SnapLen(); all-snp > snp+5 { - reject = true + if atomic.LoadUint32(&h.snapSync) == 1 { + if snap == nil { + // If we are running snap-sync, we want to reserve roughly half the peer + // slots for peers supporting the snap protocol. + // The logic here is; we only allow up to 5 more non-snap peers than snap-peers. + if all, snp := h.peers.len(), h.peers.snapLen(); all-snp > snp+5 { + reject = true + } } } // Ignore maxPeers if this is a trusted peer if !peer.Peer.Info().Network.Trusted { - if reject || h.peers.Len() >= h.maxPeers { + if reject || h.peers.len() >= h.maxPeers { return p2p.DiscTooManyPeers } } peer.Log().Debug("Ethereum peer connected", "name", peer.Name()) // Register the peer locally - if err := h.peers.registerEthPeer(peer); err != nil { + if err := h.peers.registerPeer(peer, snap); err != nil { peer.Log().Error("Ethereum peer registration failed", "err", err) return err } defer h.removePeer(peer.ID()) - p := h.peers.ethPeer(peer.ID()) + p := h.peers.peer(peer.ID()) if p == nil { return errors.New("peer dropped during handling") } // Register the peer in the downloader. If the downloader considers it banned, we disconnect if err := h.downloader.RegisterPeer(peer.ID(), peer.Version(), peer); err != nil { + peer.Log().Error("Failed to register peer in eth syncer", "err", err) return err } + if snap != nil { + if err := h.downloader.SnapSyncer.Register(snap); err != nil { + peer.Log().Error("Failed to register peer in snap syncer", "err", err) + return err + } + } h.chainSync.handlePeerEvent(peer) // Propagate existing transactions. new transactions appearing @@ -317,25 +335,23 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { return handler(peer) } -// runSnapPeer -func (h *handler) runSnapPeer(peer *snap.Peer, handler snap.Handler) error { +// runSnapExtension registers a `snap` peer into the joint eth/snap peerset and +// starts handling inbound messages. As `snap` is only a satellite protocol to +// `eth`, all subsystem registrations and lifecycle management will be done by +// the main `eth` handler to prevent strange races. +func (h *handler) runSnapExtension(peer *snap.Peer, handler snap.Handler) error { h.peerWG.Add(1) defer h.peerWG.Done() - // Register the peer locally - if err := h.peers.registerSnapPeer(peer); err != nil { - peer.Log().Error("Snapshot peer registration failed", "err", err) - return err - } - defer h.removePeer(peer.ID()) - - if err := h.downloader.SnapSyncer.Register(peer); err != nil { + if err := h.peers.registerSnapExtension(peer); err != nil { + peer.Log().Error("Snapshot extension registration failed", "err", err) return err } - // Handle incoming messages until the connection is torn down return handler(peer) } +// removePeer unregisters a peer from the downloader and fetchers, removes it from +// the set of tracked peers and closes the network connection to it. func (h *handler) removePeer(id string) { // Create a custom logger to avoid printing the entire id var logger log.Logger @@ -345,33 +361,27 @@ func (h *handler) removePeer(id string) { } else { logger = log.New("peer", id[:8]) } - // Remove the eth peer if it exists - eth := h.peers.ethPeer(id) - if eth != nil { - logger.Debug("Removing Ethereum peer") - h.downloader.UnregisterPeer(id) - h.txFetcher.Drop(id) - - if err := h.peers.unregisterEthPeer(id); err != nil { - logger.Error("Ethereum peer removal failed", "err", err) - } + // Abort if the peer does not exist + peer := h.peers.peer(id) + if peer == nil { + logger.Error("Ethereum peer removal failed", "err", errPeerNotRegistered) + return } - // Remove the snap peer if it exists - snap := h.peers.snapPeer(id) - if snap != nil { - logger.Debug("Removing Snapshot peer") + // Remove the `eth` peer if it exists + logger.Debug("Removing Ethereum peer", "snap", peer.snapExt != nil) + + // Remove the `snap` extension if it exists + if peer.snapExt != nil { h.downloader.SnapSyncer.Unregister(id) - if err := h.peers.unregisterSnapPeer(id); err != nil { - logger.Error("Snapshot peer removel failed", "err", err) - } - } - // Hard disconnect at the networking layer - if eth != nil { - eth.Peer.Disconnect(p2p.DiscUselessPeer) } - if snap != nil { - snap.Peer.Disconnect(p2p.DiscUselessPeer) + h.downloader.UnregisterPeer(id) + h.txFetcher.Drop(id) + + if err := h.peers.unregisterPeer(id); err != nil { + logger.Error("Ethereum peer removal failed", "err", err) } + // Hard disconnect at the networking layer + peer.Peer.Disconnect(p2p.DiscUselessPeer) } func (h *handler) Start(maxPeers int) { @@ -417,7 +427,7 @@ func (h *handler) Stop() { // will only announce its availability (depending what's requested). func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { hash := block.Hash() - peers := h.peers.ethPeersWithoutBlock(hash) + peers := h.peers.peersWithoutBlock(hash) // If propagation is requested, send to a subset of the peer if propagate { @@ -456,7 +466,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) // Broadcast transactions to a batch of peers not knowing about it if propagate { for _, tx := range txs { - peers := h.peers.ethPeersWithoutTransaction(tx.Hash()) + peers := h.peers.peersWithoutTransaction(tx.Hash()) // Send the block to a subset of our peers transfer := peers[:int(math.Sqrt(float64(len(peers))))] @@ -472,7 +482,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) } // Otherwise only broadcast the announcement to peers for _, tx := range txs { - peers := h.peers.ethPeersWithoutTransaction(tx.Hash()) + peers := h.peers.peersWithoutTransaction(tx.Hash()) for _, peer := range peers { annos[peer] = append(annos[peer], tx.Hash()) } diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 84bdac659a..3ff9f2245b 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -47,7 +47,7 @@ func (h *ethHandler) RunPeer(peer *eth.Peer, hand eth.Handler) error { // PeerInfo retrieves all known `eth` information about a peer. func (h *ethHandler) PeerInfo(id enode.ID) interface{} { - if p := h.peers.ethPeer(id.String()); p != nil { + if p := h.peers.peer(id.String()); p != nil { return p.info() } return nil @@ -107,7 +107,7 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error { // handleHeaders is invoked from a peer's message handler when it transmits a batch // of headers for the local node to process. func (h *ethHandler) handleHeaders(peer *eth.Peer, headers []*types.Header) error { - p := h.peers.ethPeer(peer.ID()) + p := h.peers.peer(peer.ID()) if p == nil { return errors.New("unregistered during callback") } diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index 0e5c0c90ee..5f5d4e9e82 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -574,11 +574,11 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo // Verify that the remote peer is maintained or dropped if drop { - if peers := handler.handler.peers.Len(); peers != 0 { + if peers := handler.handler.peers.len(); peers != 0 { t.Fatalf("peer count mismatch: have %d, want %d", peers, 0) } } else { - if peers := handler.handler.peers.Len(); peers != 1 { + if peers := handler.handler.peers.len(); peers != 1 { t.Fatalf("peer count mismatch: have %d, want %d", peers, 1) } } diff --git a/eth/handler_snap.go b/eth/handler_snap.go index 25975bf60b..767416ffd6 100644 --- a/eth/handler_snap.go +++ b/eth/handler_snap.go @@ -30,13 +30,15 @@ func (h *snapHandler) Chain() *core.BlockChain { return h.chain } // RunPeer is invoked when a peer joins on the `snap` protocol. func (h *snapHandler) RunPeer(peer *snap.Peer, hand snap.Handler) error { - return (*handler)(h).runSnapPeer(peer, hand) + return (*handler)(h).runSnapExtension(peer, hand) } // PeerInfo retrieves all known `snap` information about a peer. func (h *snapHandler) PeerInfo(id enode.ID) interface{} { - if p := h.peers.snapPeer(id.String()); p != nil { - return p.info() + if p := h.peers.peer(id.String()); p != nil { + if p.snapExt != nil { + return p.snapExt.info() + } } return nil } diff --git a/eth/peer.go b/eth/peer.go index 6970c8afd3..1cea9c640e 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -36,9 +36,11 @@ type ethPeerInfo struct { // ethPeer is a wrapper around eth.Peer to maintain a few extra metadata. type ethPeer struct { *eth.Peer + snapExt *snapPeer // Satellite `snap` connection - syncDrop *time.Timer // Connection dropper if `eth` sync progress isn't validated in time - lock sync.RWMutex // Mutex protecting the internal fields + syncDrop *time.Timer // Connection dropper if `eth` sync progress isn't validated in time + snapWait chan struct{} // Notification channel for snap connections + lock sync.RWMutex // Mutex protecting the internal fields } // info gathers and returns some `eth` protocol metadata known about a peer. @@ -61,9 +63,6 @@ type snapPeerInfo struct { // snapPeer is a wrapper around snap.Peer to maintain a few extra metadata. type snapPeer struct { *snap.Peer - - ethDrop *time.Timer // Connection dropper if `eth` doesn't connect in time - lock sync.RWMutex // Mutex protecting the internal fields } // info gathers and returns some `snap` protocol metadata known about a peer. diff --git a/eth/peerset.go b/eth/peerset.go index 663c5ce36b..f0657e140b 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -20,12 +20,10 @@ import ( "errors" "math/big" "sync" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" - "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/p2p" ) @@ -42,22 +40,19 @@ var ( // a peer set, but no peer with the given id exists. errPeerNotRegistered = errors.New("peer not registered") - // ethConnectTimeout is the `snap` timeout for `eth` to connect too. - ethConnectTimeout = 3 * time.Second + // errSnapWithoutEth is returned if a peer attempts to connect only on the + // snap protocol without advertizing the eth main protocol. + errSnapWithoutEth = errors.New("peer connected on snap without compatible eth support") ) // peerSet represents the collection of active peers currently participating in -// the `eth` or `snap` protocols. +// the `eth` protocol, with or without the `snap` extension. type peerSet struct { - ethPeers map[string]*ethPeer // Peers connected on the `eth` protocol - snapPeers map[string]*snapPeer // Peers connected on the `snap` protocol + peers map[string]*ethPeer // Peers connected on the `eth` protocol + snapPeers int // Number of `snap` compatible peers for connection prioritization - ethJoinFeed event.Feed // Events when an `eth` peer successfully joins - ethDropFeed event.Feed // Events when an `eth` peer gets dropped - snapJoinFeed event.Feed // Events when a `snap` peer joins on both `eth` and `snap` - snapDropFeed event.Feed // Events when a `snap` peer gets dropped (only if fully joined) - - scope event.SubscriptionScope // Subscription group to unsubscribe everyone at once + snapWait map[string]chan *snap.Peer // Peers connected on `eth` waiting for their snap extension + snapPend map[string]*snap.Peer // Peers connected on the `snap` protocol, but not yet on `eth` lock sync.RWMutex closed bool @@ -66,176 +61,134 @@ type peerSet struct { // newPeerSet creates a new peer set to track the active participants. func newPeerSet() *peerSet { return &peerSet{ - ethPeers: make(map[string]*ethPeer), - snapPeers: make(map[string]*snapPeer), + peers: make(map[string]*ethPeer), + snapWait: make(map[string]chan *snap.Peer), + snapPend: make(map[string]*snap.Peer), } } -// subscribeEthJoin registers a subscription for peers joining (and completing -// the handshake) on the `eth` protocol. -func (ps *peerSet) subscribeEthJoin(ch chan<- *eth.Peer) event.Subscription { - return ps.scope.Track(ps.ethJoinFeed.Subscribe(ch)) -} - -// subscribeEthDrop registers a subscription for peers being dropped from the -// `eth` protocol. -func (ps *peerSet) subscribeEthDrop(ch chan<- *eth.Peer) event.Subscription { - return ps.scope.Track(ps.ethDropFeed.Subscribe(ch)) -} - -// subscribeSnapJoin registers a subscription for peers joining (and completing -// the `eth` join) on the `snap` protocol. -func (ps *peerSet) subscribeSnapJoin(ch chan<- *snap.Peer) event.Subscription { - return ps.scope.Track(ps.snapJoinFeed.Subscribe(ch)) -} - -// subscribeSnapDrop registers a subscription for peers being dropped from the -// `snap` protocol. -func (ps *peerSet) subscribeSnapDrop(ch chan<- *snap.Peer) event.Subscription { - return ps.scope.Track(ps.snapDropFeed.Subscribe(ch)) -} - -// registerEthPeer injects a new `eth` peer into the working set, or returns an -// error if the peer is already known. The peer is announced on the `eth` join -// feed and if it completes a pending `snap` peer, also on that feed. -func (ps *peerSet) registerEthPeer(peer *eth.Peer) error { - ps.lock.Lock() - if ps.closed { - ps.lock.Unlock() - return errPeerSetClosed +// registerSnapExtension unblocks an already connected `eth` peer waiting for its +// `snap` extension, or if no such peer exists, tracks the extension for the time +// being until the `eth` main protocol starts looking for it. +func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error { + // Reject the peer if it advertises `snap` without `eth` as `snap` is only a + // satellite protocol meaningful with the chain selection of `eth` + if !peer.SupportsCap(eth.ProtocolName, eth.ProtocolVersions) { + return errSnapWithoutEth } + // Ensure nobody can double connect + ps.lock.Lock() + defer ps.lock.Unlock() + id := peer.ID() - if _, ok := ps.ethPeers[id]; ok { - ps.lock.Unlock() - return errPeerAlreadyRegistered + if _, ok := ps.peers[id]; ok { + return errPeerAlreadyRegistered // avoid connections with the same id as existing ones } - ps.ethPeers[id] = ðPeer{Peer: peer} - - snap, ok := ps.snapPeers[id] - ps.lock.Unlock() - - if ok { - // Previously dangling `snap` peer, stop it's timer since `eth` connected - snap.lock.Lock() - if snap.ethDrop != nil { - snap.ethDrop.Stop() - snap.ethDrop = nil - } - snap.lock.Unlock() + if _, ok := ps.snapPend[id]; ok { + return errPeerAlreadyRegistered // avoid connections with the same id as pending ones } - ps.ethJoinFeed.Send(peer) - if ok { - ps.snapJoinFeed.Send(snap.Peer) + // Inject the peer into an `eth` counterpart is available, otherwise save for later + if wait, ok := ps.snapWait[id]; ok { + delete(ps.snapWait, id) + wait <- peer + return nil } + ps.snapPend[id] = peer return nil } -// unregisterEthPeer removes a remote peer from the active set, disabling any further -// actions to/from that particular entity. The drop is announced on the `eth` drop -// feed and also on the `snap` feed if the eth/snap duality was broken just now. -func (ps *peerSet) unregisterEthPeer(id string) error { +// waitExtensions blocks until all satellite protocols are connected and tracked +// by the peerset. +func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) { + // If the peer does not support a compatible `snap`, don't wait + if !peer.SupportsCap(snap.ProtocolName, snap.ProtocolVersions) { + return nil, nil + } + // Ensure nobody can double connect ps.lock.Lock() - eth, ok := ps.ethPeers[id] - if !ok { + + id := peer.ID() + if _, ok := ps.peers[id]; ok { ps.lock.Unlock() - return errPeerNotRegistered + return nil, errPeerAlreadyRegistered // avoid connections with the same id as existing ones + } + if _, ok := ps.snapWait[id]; ok { + ps.lock.Unlock() + return nil, errPeerAlreadyRegistered // avoid connections with the same id as pending ones } - delete(ps.ethPeers, id) + // If `snap` already connected, retrieve the peer from the pending set + if snap, ok := ps.snapPend[id]; ok { + delete(ps.snapPend, id) - snap, ok := ps.snapPeers[id] + ps.lock.Unlock() + return snap, nil + } + // Otherwise wait for `snap` to connect concurrently + wait := make(chan *snap.Peer) + ps.snapWait[id] = wait ps.lock.Unlock() - ps.ethDropFeed.Send(eth) - if ok { - ps.snapDropFeed.Send(snap) - } - return nil + return <-wait, nil } -// registerSnapPeer injects a new `snap` peer into the working set, or returns -// an error if the peer is already known. The peer is announced on the `snap` -// join feed if it completes an existing `eth` peer. -// -// If the peer isn't yet connected on `eth` and fails to do so within a given -// amount of time, it is dropped. This enforces that `snap` is an extension to -// `eth`, not a standalone leeching protocol. -func (ps *peerSet) registerSnapPeer(peer *snap.Peer) error { +// registerPeer injects a new `eth` peer into the working set, or returns an error +// if the peer is already known. +func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer) error { + // Start tracking the new peer ps.lock.Lock() + defer ps.lock.Unlock() + if ps.closed { - ps.lock.Unlock() return errPeerSetClosed } id := peer.ID() - if _, ok := ps.snapPeers[id]; ok { - ps.lock.Unlock() + if _, ok := ps.peers[id]; ok { return errPeerAlreadyRegistered } - ps.snapPeers[id] = &snapPeer{Peer: peer} - - _, ok := ps.ethPeers[id] - if !ok { - // Dangling `snap` peer, start a timer to drop if `eth` doesn't connect - ps.snapPeers[id].ethDrop = time.AfterFunc(ethConnectTimeout, func() { - peer.Log().Warn("Snapshot peer missing eth, dropping", "addr", peer.RemoteAddr(), "type", peer.Name()) - peer.Disconnect(p2p.DiscUselessPeer) - }) + eth := ðPeer{ + Peer: peer, } - ps.lock.Unlock() - - if ok { - ps.snapJoinFeed.Send(peer) + if ext != nil { + eth.snapExt = &snapPeer{ext} + ps.snapPeers++ } + ps.peers[id] = eth return nil } -// unregisterSnapPeer removes a remote peer from the active set, disabling any -// further actions to/from that particular entity. The drop is announced on the -// `snap` drop feed. -func (ps *peerSet) unregisterSnapPeer(id string) error { +// unregisterPeer removes a remote peer from the active set, disabling any further +// actions to/from that particular entity. +func (ps *peerSet) unregisterPeer(id string) error { ps.lock.Lock() - peer, ok := ps.snapPeers[id] + defer ps.lock.Unlock() + + peer, ok := ps.peers[id] if !ok { - ps.lock.Unlock() return errPeerNotRegistered } - delete(ps.snapPeers, id) - ps.lock.Unlock() - - peer.lock.Lock() - if peer.ethDrop != nil { - peer.ethDrop.Stop() - peer.ethDrop = nil + delete(ps.peers, id) + if peer.snapExt != nil { + ps.snapPeers-- } - peer.lock.Unlock() - - ps.snapDropFeed.Send(peer) return nil } -// ethPeer retrieves the registered `eth` peer with the given id. -func (ps *peerSet) ethPeer(id string) *ethPeer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return ps.ethPeers[id] -} - -// snapPeer retrieves the registered `snap` peer with the given id. -func (ps *peerSet) snapPeer(id string) *snapPeer { +// peer retrieves the registered peer with the given id. +func (ps *peerSet) peer(id string) *ethPeer { ps.lock.RLock() defer ps.lock.RUnlock() - return ps.snapPeers[id] + return ps.peers[id] } -// ethPeersWithoutBlock retrieves a list of `eth` peers that do not have a given -// block in their set of known hashes so it might be propagated to them. -func (ps *peerSet) ethPeersWithoutBlock(hash common.Hash) []*ethPeer { +// peersWithoutBlock retrieves a list of peers that do not have a given block in +// their set of known hashes so it might be propagated to them. +func (ps *peerSet) peersWithoutBlock(hash common.Hash) []*ethPeer { ps.lock.RLock() defer ps.lock.RUnlock() - list := make([]*ethPeer, 0, len(ps.ethPeers)) - for _, p := range ps.ethPeers { + list := make([]*ethPeer, 0, len(ps.peers)) + for _, p := range ps.peers { if !p.KnownBlock(hash) { list = append(list, p) } @@ -243,14 +196,14 @@ func (ps *peerSet) ethPeersWithoutBlock(hash common.Hash) []*ethPeer { return list } -// ethPeersWithoutTransaction retrieves a list of `eth` peers that do not have a -// given transaction in their set of known hashes. -func (ps *peerSet) ethPeersWithoutTransaction(hash common.Hash) []*ethPeer { +// peersWithoutTransaction retrieves a list of peers that do not have a given +// transaction in their set of known hashes. +func (ps *peerSet) peersWithoutTransaction(hash common.Hash) []*ethPeer { ps.lock.RLock() defer ps.lock.RUnlock() - list := make([]*ethPeer, 0, len(ps.ethPeers)) - for _, p := range ps.ethPeers { + list := make([]*ethPeer, 0, len(ps.peers)) + for _, p := range ps.peers { if !p.KnownTransaction(hash) { list = append(list, p) } @@ -258,28 +211,27 @@ func (ps *peerSet) ethPeersWithoutTransaction(hash common.Hash) []*ethPeer { return list } -// Len returns if the current number of `eth` peers in the set. Since the `snap` +// len returns if the current number of `eth` peers in the set. Since the `snap` // peers are tied to the existence of an `eth` connection, that will always be a // subset of `eth`. -func (ps *peerSet) Len() int { +func (ps *peerSet) len() int { ps.lock.RLock() defer ps.lock.RUnlock() - return len(ps.ethPeers) + return len(ps.peers) } -// SnapLen returns if the current number of `snap` peers in the set. Since the `snap` -// peers are tied to the existence of an `eth` connection, that will always be a -// subset of `eth`. -func (ps *peerSet) SnapLen() int { +// snapLen returns if the current number of `snap` peers in the set. +func (ps *peerSet) snapLen() int { ps.lock.RLock() defer ps.lock.RUnlock() - return len(ps.snapPeers) + + return ps.snapPeers } -// ethPeerWithHighestTD retrieves the known peer with the currently highest total +// peerWithHighestTD retrieves the known peer with the currently highest total // difficulty. -func (ps *peerSet) ethPeerWithHighestTD() *eth.Peer { +func (ps *peerSet) peerWithHighestTD() *eth.Peer { ps.lock.RLock() defer ps.lock.RUnlock() @@ -287,7 +239,7 @@ func (ps *peerSet) ethPeerWithHighestTD() *eth.Peer { bestPeer *eth.Peer bestTd *big.Int ) - for _, p := range ps.ethPeers { + for _, p := range ps.peers { if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 { bestPeer, bestTd = p.Peer, td } @@ -300,10 +252,7 @@ func (ps *peerSet) close() { ps.lock.Lock() defer ps.lock.Unlock() - for _, p := range ps.ethPeers { - p.Disconnect(p2p.DiscQuitting) - } - for _, p := range ps.snapPeers { + for _, p := range ps.peers { p.Disconnect(p2p.DiscQuitting) } ps.closed = true diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 25ddcd93ec..e32008fb41 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -103,12 +103,12 @@ type TxPool interface { // MakeProtocols constructs the P2P protocol definitions for `eth`. func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol { - protocols := make([]p2p.Protocol, len(protocolVersions)) - for i, version := range protocolVersions { + protocols := make([]p2p.Protocol, len(ProtocolVersions)) + for i, version := range ProtocolVersions { version := version // Closure protocols[i] = p2p.Protocol{ - Name: protocolName, + Name: ProtocolName, Version: version, Length: protocolLengths[version], Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 63d3494ec4..9fff64b72a 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -34,13 +34,13 @@ const ( ETH65 = 65 ) -// protocolName is the official short name of the `eth` protocol used during +// ProtocolName is the official short name of the `eth` protocol used during // devp2p capability negotiation. -const protocolName = "eth" +const ProtocolName = "eth" -// protocolVersions are the supported versions of the `eth` protocol (first +// ProtocolVersions are the supported versions of the `eth` protocol (first // is primary). -var protocolVersions = []uint{ETH65, ETH64} +var ProtocolVersions = []uint{ETH65, ETH64} // protocolLengths are the number of implemented message corresponding to // different protocol versions. diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 36322e648b..24c8599552 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -77,12 +77,12 @@ type Backend interface { // MakeProtocols constructs the P2P protocol definitions for `snap`. func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { - protocols := make([]p2p.Protocol, len(protocolVersions)) - for i, version := range protocolVersions { + protocols := make([]p2p.Protocol, len(ProtocolVersions)) + for i, version := range ProtocolVersions { version := version // Closure protocols[i] = p2p.Protocol{ - Name: protocolName, + Name: ProtocolName, Version: version, Length: protocolLengths[version], Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go index f1a25a2066..a74142cafb 100644 --- a/eth/protocols/snap/protocol.go +++ b/eth/protocols/snap/protocol.go @@ -30,13 +30,13 @@ const ( snap1 = 1 ) -// protocolName is the official short name of the `snap` protocol used during +// ProtocolName is the official short name of the `snap` protocol used during // devp2p capability negotiation. -const protocolName = "snap" +const ProtocolName = "snap" -// protocolVersions are the supported versions of the `snap` protocol (first +// ProtocolVersions are the supported versions of the `snap` protocol (first // is primary). -var protocolVersions = []uint{snap1} +var ProtocolVersions = []uint{snap1} // protocolLengths are the number of implemented message corresponding to // different protocol versions. diff --git a/eth/sync.go b/eth/sync.go index eedb8b7476..dc72e88388 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -247,11 +247,11 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { } else if minPeers > cs.handler.maxPeers { minPeers = cs.handler.maxPeers } - if cs.handler.peers.Len() < minPeers { + if cs.handler.peers.len() < minPeers { return nil } // We have enough peers, check TD - peer := cs.handler.peers.ethPeerWithHighestTD() + peer := cs.handler.peers.peerWithHighestTD() if peer == nil { return nil } diff --git a/eth/sync_test.go b/eth/sync_test.go index 473e19518f..9cc806b18a 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -70,7 +70,7 @@ func testFastSyncDisabling(t *testing.T, protocol uint) { time.Sleep(250 * time.Millisecond) // Check that fast sync was disabled - op := peerToSyncOp(downloader.FastSync, empty.handler.peers.ethPeerWithHighestTD()) + op := peerToSyncOp(downloader.FastSync, empty.handler.peers.peerWithHighestTD()) if err := empty.handler.doSync(op); err != nil { t.Fatal("sync failed:", err) } diff --git a/p2p/peer.go b/p2p/peer.go index 43ccef5c43..08881e2583 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -158,11 +158,16 @@ func (p *Peer) Caps() []Cap { return p.rw.caps } -// SupportsCap returns true if the peer supports the given protocol/version -func (p *Peer) SupportsCap(protocol string, version uint) bool { +// SupportsCap returns true if the peer supports any of the enumerated versions +// of a specific protocol. +func (p *Peer) SupportsCap(protocol string, versions []uint) bool { for _, cap := range p.rw.caps { if cap.Name == protocol { - return version <= cap.Version + for _, ver := range versions { + if cap.Version == ver { + return true + } + } } } return false From 4eae0c6b6f2bcaa450df50077bb991e3bbbb106f Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 2 Feb 2021 10:05:46 +0100 Subject: [PATCH 167/235] cmd/geth, node: allow configuring JSON-RPC on custom path prefix (#22184) This change allows users to set a custom path prefix on which to mount the http-rpc or ws-rpc handlers via the new flags --http.rpcprefix and --ws.rpcprefix. Fixes #21826 Co-authored-by: Felix Lange --- cmd/geth/main.go | 2 + cmd/geth/usage.go | 2 + cmd/utils/flags.go | 18 ++++++ go.sum | 5 -- graphql/graphql_test.go | 14 +--- node/api_test.go | 3 + node/config.go | 6 ++ node/node.go | 20 ++++-- node/node_test.go | 107 ++++++++++++++++++++++++++++++- node/rpcstack.go | 71 +++++++++++++++++---- node/rpcstack_test.go | 137 +++++++++++++++++++++++++++++++--------- 11 files changed, 320 insertions(+), 65 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 86dc6f40fe..11829cbad9 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -181,6 +181,7 @@ var ( utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, utils.HTTPApiFlag, + utils.HTTPPathPrefixFlag, utils.LegacyRPCApiFlag, utils.WSEnabledFlag, utils.WSListenAddrFlag, @@ -190,6 +191,7 @@ var ( utils.WSApiFlag, utils.LegacyWSApiFlag, utils.WSAllowedOriginsFlag, + utils.WSPathPrefixFlag, utils.LegacyWSAllowedOriginsFlag, utils.IPCDisabledFlag, utils.IPCPathFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index ba311bf7f6..0ed31d7da6 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -138,12 +138,14 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.HTTPListenAddrFlag, utils.HTTPPortFlag, utils.HTTPApiFlag, + utils.HTTPPathPrefixFlag, utils.HTTPCORSDomainFlag, utils.HTTPVirtualHostsFlag, utils.WSEnabledFlag, utils.WSListenAddrFlag, utils.WSPortFlag, utils.WSApiFlag, + utils.WSPathPrefixFlag, utils.WSAllowedOriginsFlag, utils.GraphQLEnabledFlag, utils.GraphQLCORSDomainFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8f9f68ba68..aa5180dd9f 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -531,6 +531,11 @@ var ( Usage: "API's offered over the HTTP-RPC interface", Value: "", } + HTTPPathPrefixFlag = cli.StringFlag{ + Name: "http.rpcprefix", + Usage: "HTTP path path prefix on which JSON-RPC is served. Use '/' to serve on all paths.", + Value: "", + } GraphQLEnabledFlag = cli.BoolFlag{ Name: "graphql", Usage: "Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.", @@ -569,6 +574,11 @@ var ( Usage: "Origins from which to accept websockets requests", Value: "", } + WSPathPrefixFlag = cli.StringFlag{ + Name: "ws.rpcprefix", + Usage: "HTTP path prefix on which JSON-RPC is served. Use '/' to serve on all paths.", + Value: "", + } ExecFlag = cli.StringFlag{ Name: "exec", Usage: "Execute JavaScript statement", @@ -946,6 +956,10 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(HTTPVirtualHostsFlag.Name) { cfg.HTTPVirtualHosts = SplitAndTrim(ctx.GlobalString(HTTPVirtualHostsFlag.Name)) } + + if ctx.GlobalIsSet(HTTPPathPrefixFlag.Name) { + cfg.HTTPPathPrefix = ctx.GlobalString(HTTPPathPrefixFlag.Name) + } } // setGraphQL creates the GraphQL listener interface string from the set @@ -995,6 +1009,10 @@ func setWS(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(WSApiFlag.Name) { cfg.WSModules = SplitAndTrim(ctx.GlobalString(WSApiFlag.Name)) } + + if ctx.GlobalIsSet(WSPathPrefixFlag.Name) { + cfg.WSPathPrefix = ctx.GlobalString(WSPathPrefixFlag.Name) + } } // setIPC creates an IPC path configuration from the set command line flags, diff --git a/go.sum b/go.sum index 4b799d77fa..2c16d81f04 100644 --- a/go.sum +++ b/go.sum @@ -433,7 +433,6 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -501,7 +500,6 @@ golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapK golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -549,15 +547,12 @@ google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyz google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 71320012d5..a88c9b30b1 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -30,6 +30,8 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + + "github.com/stretchr/testify/assert" ) func TestBuildSchema(t *testing.T) { @@ -166,18 +168,8 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { if err != nil { t.Fatalf("could not post: %v", err) } - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("could not read from response body: %v", err) - } - resp.Body.Close() // make sure the request is not handled successfully - if want, have := "404 page not found\n", string(bodyBytes); have != want { - t.Errorf("have:\n%v\nwant:\n%v", have, want) - } - if want, have := 404, resp.StatusCode; want != have { - t.Errorf("wrong statuscode, have:\n%v\nwant:%v", have, want) - } + assert.Equal(t, http.StatusNotFound, resp.StatusCode) } func createNode(t *testing.T, gqlEnabled bool) *node.Node { diff --git a/node/api_test.go b/node/api_test.go index a07ce833c4..9c3fa3a31d 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -244,7 +244,10 @@ func TestStartRPC(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { + t.Parallel() + // Apply some sane defaults. config := test.cfg // config.Logger = testlog.Logger(t, log.LvlDebug) diff --git a/node/config.go b/node/config.go index 61e41cd7d0..447a69505c 100644 --- a/node/config.go +++ b/node/config.go @@ -139,6 +139,9 @@ type Config struct { // interface. HTTPTimeouts rpc.HTTPTimeouts + // HTTPPathPrefix specifies a path prefix on which http-rpc is to be served. + HTTPPathPrefix string `toml:",omitempty"` + // WSHost is the host interface on which to start the websocket RPC server. If // this field is empty, no websocket API endpoint will be started. WSHost string @@ -148,6 +151,9 @@ type Config struct { // ephemeral nodes). WSPort int `toml:",omitempty"` + // WSPathPrefix specifies a path prefix on which ws-rpc is to be served. + WSPathPrefix string `toml:",omitempty"` + // WSOrigins is the list of domain to accept websocket requests from. Please be // aware that the server can only act upon the HTTP request the client sends and // cannot verify the validity of the request header. diff --git a/node/node.go b/node/node.go index b58594ef1d..2ed4c31f60 100644 --- a/node/node.go +++ b/node/node.go @@ -135,6 +135,14 @@ func New(conf *Config) (*Node, error) { node.server.Config.NodeDatabase = node.config.NodeDB() } + // Check HTTP/WS prefixes are valid. + if err := validatePrefix("HTTP", conf.HTTPPathPrefix); err != nil { + return nil, err + } + if err := validatePrefix("WebSocket", conf.WSPathPrefix); err != nil { + return nil, err + } + // Configure RPC servers. node.http = newHTTPServer(node.log, conf.HTTPTimeouts) node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts) @@ -346,6 +354,7 @@ func (n *Node) startRPC() error { CorsAllowedOrigins: n.config.HTTPCors, Vhosts: n.config.HTTPVirtualHosts, Modules: n.config.HTTPModules, + prefix: n.config.HTTPPathPrefix, } if err := n.http.setListenAddr(n.config.HTTPHost, n.config.HTTPPort); err != nil { return err @@ -361,6 +370,7 @@ func (n *Node) startRPC() error { config := wsConfig{ Modules: n.config.WSModules, Origins: n.config.WSOrigins, + prefix: n.config.WSPathPrefix, } if err := server.setListenAddr(n.config.WSHost, n.config.WSPort); err != nil { return err @@ -457,6 +467,7 @@ func (n *Node) RegisterHandler(name, path string, handler http.Handler) { if n.state != initializingState { panic("can't register HTTP handler on running/stopped node") } + n.http.mux.Handle(path, handler) n.http.handlerNames[path] = name } @@ -513,17 +524,18 @@ func (n *Node) IPCEndpoint() string { return n.ipc.endpoint } -// HTTPEndpoint returns the URL of the HTTP server. +// HTTPEndpoint returns the URL of the HTTP server. Note that this URL does not +// contain the JSON-RPC path prefix set by HTTPPathPrefix. func (n *Node) HTTPEndpoint() string { return "http://" + n.http.listenAddr() } -// WSEndpoint retrieves the current WS endpoint used by the protocol stack. +// WSEndpoint returns the current JSON-RPC over WebSocket endpoint. func (n *Node) WSEndpoint() string { if n.http.wsAllowed() { - return "ws://" + n.http.listenAddr() + return "ws://" + n.http.listenAddr() + n.http.wsConfig.prefix } - return "ws://" + n.ws.listenAddr() + return "ws://" + n.ws.listenAddr() + n.ws.wsConfig.prefix } // EventMux retrieves the event multiplexer used by all the network services in diff --git a/node/node_test.go b/node/node_test.go index 8f306ef021..6731dbac1f 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -390,7 +390,7 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { } // Tests whether a handler can be successfully mounted on the canonical HTTP server -// on the given path +// on the given prefix func TestRegisterHandler_Successful(t *testing.T) { node := createNode(t, 7878, 7979) @@ -483,7 +483,112 @@ func TestWebsocketHTTPOnSeparatePort_WSRequest(t *testing.T) { if !checkRPC(node.HTTPEndpoint()) { t.Fatalf("http request failed") } +} + +type rpcPrefixTest struct { + httpPrefix, wsPrefix string + // These lists paths on which JSON-RPC should be served / not served. + wantHTTP []string + wantNoHTTP []string + wantWS []string + wantNoWS []string +} + +func TestNodeRPCPrefix(t *testing.T) { + t.Parallel() + + tests := []rpcPrefixTest{ + // both off + { + httpPrefix: "", wsPrefix: "", + wantHTTP: []string{"/", "/?p=1"}, + wantNoHTTP: []string{"/test", "/test?p=1"}, + wantWS: []string{"/", "/?p=1"}, + wantNoWS: []string{"/test", "/test?p=1"}, + }, + // only http prefix + { + httpPrefix: "/testprefix", wsPrefix: "", + wantHTTP: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"}, + wantNoHTTP: []string{"/", "/?p=1", "/test", "/test?p=1"}, + wantWS: []string{"/", "/?p=1"}, + wantNoWS: []string{"/testprefix", "/testprefix?p=1", "/test", "/test?p=1"}, + }, + // only ws prefix + { + httpPrefix: "", wsPrefix: "/testprefix", + wantHTTP: []string{"/", "/?p=1"}, + wantNoHTTP: []string{"/testprefix", "/testprefix?p=1", "/test", "/test?p=1"}, + wantWS: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"}, + wantNoWS: []string{"/", "/?p=1", "/test", "/test?p=1"}, + }, + // both set + { + httpPrefix: "/testprefix", wsPrefix: "/testprefix", + wantHTTP: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"}, + wantNoHTTP: []string{"/", "/?p=1", "/test", "/test?p=1"}, + wantWS: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"}, + wantNoWS: []string{"/", "/?p=1", "/test", "/test?p=1"}, + }, + } + + for _, test := range tests { + test := test + name := fmt.Sprintf("http=%s ws=%s", test.httpPrefix, test.wsPrefix) + t.Run(name, func(t *testing.T) { + cfg := &Config{ + HTTPHost: "127.0.0.1", + HTTPPathPrefix: test.httpPrefix, + WSHost: "127.0.0.1", + WSPathPrefix: test.wsPrefix, + } + node, err := New(cfg) + if err != nil { + t.Fatal("can't create node:", err) + } + defer node.Close() + if err := node.Start(); err != nil { + t.Fatal("can't start node:", err) + } + test.check(t, node) + }) + } +} + +func (test rpcPrefixTest) check(t *testing.T, node *Node) { + t.Helper() + httpBase := "http://" + node.http.listenAddr() + wsBase := "ws://" + node.http.listenAddr() + + if node.WSEndpoint() != wsBase+test.wsPrefix { + t.Errorf("Error: node has wrong WSEndpoint %q", node.WSEndpoint()) + } + + for _, path := range test.wantHTTP { + resp := rpcRequest(t, httpBase+path) + if resp.StatusCode != 200 { + t.Errorf("Error: %s: bad status code %d, want 200", path, resp.StatusCode) + } + } + for _, path := range test.wantNoHTTP { + resp := rpcRequest(t, httpBase+path) + if resp.StatusCode != 404 { + t.Errorf("Error: %s: bad status code %d, want 404", path, resp.StatusCode) + } + } + for _, path := range test.wantWS { + err := wsRequest(t, wsBase+path, "") + if err != nil { + t.Errorf("Error: %s: WebSocket connection failed: %v", path, err) + } + } + for _, path := range test.wantNoWS { + err := wsRequest(t, wsBase+path, "") + if err == nil { + t.Errorf("Error: %s: WebSocket connection succeeded for path in wantNoWS", path) + } + } } func createNode(t *testing.T, httpPort, wsPort int) *Node { diff --git a/node/rpcstack.go b/node/rpcstack.go index 81e054ec99..d693bb0bbd 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -39,12 +39,14 @@ type httpConfig struct { Modules []string CorsAllowedOrigins []string Vhosts []string + prefix string // path prefix on which to mount http handler } // wsConfig is the JSON-RPC/Websocket configuration type wsConfig struct { Origins []string Modules []string + prefix string // path prefix on which to mount ws handler } type rpcHandler struct { @@ -62,6 +64,7 @@ type httpServer struct { listener net.Listener // non-nil when server is running // HTTP RPC handler things. + httpConfig httpConfig httpHandler atomic.Value // *rpcHandler @@ -79,6 +82,7 @@ type httpServer struct { func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer { h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)} + h.httpHandler.Store((*rpcHandler)(nil)) h.wsHandler.Store((*rpcHandler)(nil)) return h @@ -142,12 +146,17 @@ func (h *httpServer) start() error { // if server is websocket only, return after logging if h.wsAllowed() && !h.rpcAllowed() { - h.log.Info("WebSocket enabled", "url", fmt.Sprintf("ws://%v", listener.Addr())) + url := fmt.Sprintf("ws://%v", listener.Addr()) + if h.wsConfig.prefix != "" { + url += h.wsConfig.prefix + } + h.log.Info("WebSocket enabled", "url", url) return nil } // Log http endpoint. h.log.Info("HTTP server started", "endpoint", listener.Addr(), + "prefix", h.httpConfig.prefix, "cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","), "vhosts", strings.Join(h.httpConfig.Vhosts, ","), ) @@ -170,26 +179,60 @@ func (h *httpServer) start() error { } func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - rpc := h.httpHandler.Load().(*rpcHandler) - if r.RequestURI == "/" { - // Serve JSON-RPC on the root path. - ws := h.wsHandler.Load().(*rpcHandler) - if ws != nil && isWebsocket(r) { + // check if ws request and serve if ws enabled + ws := h.wsHandler.Load().(*rpcHandler) + if ws != nil && isWebsocket(r) { + if checkPath(r, h.wsConfig.prefix) { ws.ServeHTTP(w, r) + } + return + } + // if http-rpc is enabled, try to serve request + rpc := h.httpHandler.Load().(*rpcHandler) + if rpc != nil { + // First try to route in the mux. + // Requests to a path below root are handled by the mux, + // which has all the handlers registered via Node.RegisterHandler. + // These are made available when RPC is enabled. + muxHandler, pattern := h.mux.Handler(r) + if pattern != "" { + muxHandler.ServeHTTP(w, r) return } - if rpc != nil { + + if checkPath(r, h.httpConfig.prefix) { rpc.ServeHTTP(w, r) return } - } else if rpc != nil { - // Requests to a path below root are handled by the mux, - // which has all the handlers registered via Node.RegisterHandler. - // These are made available when RPC is enabled. - h.mux.ServeHTTP(w, r) - return } - w.WriteHeader(404) + w.WriteHeader(http.StatusNotFound) +} + +// checkPath checks whether a given request URL matches a given path prefix. +func checkPath(r *http.Request, path string) bool { + // if no prefix has been specified, request URL must be on root + if path == "" { + return r.URL.Path == "/" + } + // otherwise, check to make sure prefix matches + return len(r.URL.Path) >= len(path) && r.URL.Path[:len(path)] == path +} + +// validatePrefix checks if 'path' is a valid configuration value for the RPC prefix option. +func validatePrefix(what, path string) error { + if path == "" { + return nil + } + if path[0] != '/' { + return fmt.Errorf(`%s RPC path prefix %q does not contain leading "/"`, what, path) + } + if strings.ContainsAny(path, "?#") { + // This is just to avoid confusion. While these would match correctly (i.e. they'd + // match if URL-escaped into path), it's not easy to understand for users when + // setting that on the command line. + return fmt.Errorf("%s RPC path prefix %q contains URL meta-characters", what, path) + } + return nil } // stop shuts down the HTTP server. diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 8267fb2f1d..f92f0ba396 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -18,7 +18,10 @@ package node import ( "bytes" + "fmt" "net/http" + "net/url" + "strconv" "strings" "testing" @@ -31,25 +34,27 @@ import ( // TestCorsHandler makes sure CORS are properly handled on the http server. func TestCorsHandler(t *testing.T) { - srv := createAndStartServer(t, httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, wsConfig{}) + srv := createAndStartServer(t, &httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, &wsConfig{}) defer srv.stop() + url := "http://" + srv.listenAddr() - resp := testRequest(t, "origin", "test.com", "", srv) + resp := rpcRequest(t, url, "origin", "test.com") assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin")) - resp2 := testRequest(t, "origin", "bad", "", srv) + resp2 := rpcRequest(t, url, "origin", "bad") assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin")) } // TestVhosts makes sure vhosts are properly handled on the http server. func TestVhosts(t *testing.T) { - srv := createAndStartServer(t, httpConfig{Vhosts: []string{"test"}}, false, wsConfig{}) + srv := createAndStartServer(t, &httpConfig{Vhosts: []string{"test"}}, false, &wsConfig{}) defer srv.stop() + url := "http://" + srv.listenAddr() - resp := testRequest(t, "", "", "test", srv) + resp := rpcRequest(t, url, "host", "test") assert.Equal(t, resp.StatusCode, http.StatusOK) - resp2 := testRequest(t, "", "", "bad", srv) + resp2 := rpcRequest(t, url, "host", "bad") assert.Equal(t, resp2.StatusCode, http.StatusForbidden) } @@ -138,14 +143,15 @@ func TestWebsocketOrigins(t *testing.T) { }, } for _, tc := range tests { - srv := createAndStartServer(t, httpConfig{}, true, wsConfig{Origins: splitAndTrim(tc.spec)}) + srv := createAndStartServer(t, &httpConfig{}, true, &wsConfig{Origins: splitAndTrim(tc.spec)}) + url := fmt.Sprintf("ws://%v", srv.listenAddr()) for _, origin := range tc.expOk { - if err := attemptWebsocketConnectionFromOrigin(t, srv, origin); err != nil { + if err := wsRequest(t, url, origin); err != nil { t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err) } } for _, origin := range tc.expFail { - if err := attemptWebsocketConnectionFromOrigin(t, srv, origin); err == nil { + if err := wsRequest(t, url, origin); err == nil { t.Errorf("spec '%v', origin '%v': expected not to allow, got ok", tc.spec, origin) } } @@ -168,47 +174,118 @@ func TestIsWebsocket(t *testing.T) { assert.True(t, isWebsocket(r)) } -func createAndStartServer(t *testing.T, conf httpConfig, ws bool, wsConf wsConfig) *httpServer { +func Test_checkPath(t *testing.T) { + tests := []struct { + req *http.Request + prefix string + expected bool + }{ + { + req: &http.Request{URL: &url.URL{Path: "/test"}}, + prefix: "/test", + expected: true, + }, + { + req: &http.Request{URL: &url.URL{Path: "/testing"}}, + prefix: "/test", + expected: true, + }, + { + req: &http.Request{URL: &url.URL{Path: "/"}}, + prefix: "/test", + expected: false, + }, + { + req: &http.Request{URL: &url.URL{Path: "/fail"}}, + prefix: "/test", + expected: false, + }, + { + req: &http.Request{URL: &url.URL{Path: "/"}}, + prefix: "", + expected: true, + }, + { + req: &http.Request{URL: &url.URL{Path: "/fail"}}, + prefix: "", + expected: false, + }, + { + req: &http.Request{URL: &url.URL{Path: "/"}}, + prefix: "/", + expected: true, + }, + { + req: &http.Request{URL: &url.URL{Path: "/testing"}}, + prefix: "/", + expected: true, + }, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + assert.Equal(t, tt.expected, checkPath(tt.req, tt.prefix)) + }) + } +} + +func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsConfig) *httpServer { t.Helper() srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts) - - assert.NoError(t, srv.enableRPC(nil, conf)) + assert.NoError(t, srv.enableRPC(nil, *conf)) if ws { - assert.NoError(t, srv.enableWS(nil, wsConf)) + assert.NoError(t, srv.enableWS(nil, *wsConf)) } assert.NoError(t, srv.setListenAddr("localhost", 0)) assert.NoError(t, srv.start()) - return srv } -func attemptWebsocketConnectionFromOrigin(t *testing.T, srv *httpServer, browserOrigin string) error { +// wsRequest attempts to open a WebSocket connection to the given URL. +func wsRequest(t *testing.T, url, browserOrigin string) error { t.Helper() - dialer := websocket.DefaultDialer - _, _, err := dialer.Dial("ws://"+srv.listenAddr(), http.Header{ - "Content-type": []string{"application/json"}, - "Sec-WebSocket-Version": []string{"13"}, - "Origin": []string{browserOrigin}, - }) + t.Logf("checking WebSocket on %s (origin %q)", url, browserOrigin) + + headers := make(http.Header) + if browserOrigin != "" { + headers.Set("Origin", browserOrigin) + } + conn, _, err := websocket.DefaultDialer.Dial(url, headers) + if conn != nil { + conn.Close() + } return err } -func testRequest(t *testing.T, key, value, host string, srv *httpServer) *http.Response { +// rpcRequest performs a JSON-RPC request to the given URL. +func rpcRequest(t *testing.T, url string, extraHeaders ...string) *http.Response { t.Helper() - body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,method":"rpc_modules"}`)) - req, _ := http.NewRequest("POST", "http://"+srv.listenAddr(), body) + // Create the request. + body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,"method":"rpc_modules","params":[]}`)) + req, err := http.NewRequest("POST", url, body) + if err != nil { + t.Fatal("could not create http request:", err) + } req.Header.Set("content-type", "application/json") - if key != "" && value != "" { - req.Header.Set(key, value) + + // Apply extra headers. + if len(extraHeaders)%2 != 0 { + panic("odd extraHeaders length") } - if host != "" { - req.Host = host + for i := 0; i < len(extraHeaders); i += 2 { + key, value := extraHeaders[i], extraHeaders[i+1] + if strings.ToLower(key) == "host" { + req.Host = value + } else { + req.Header.Set(key, value) + } } - client := http.DefaultClient - resp, err := client.Do(req) + // Perform the request. + t.Logf("checking RPC/HTTP on %s %v", url, extraHeaders) + resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } From ef84da8481feedce8616c43ec48e15b7a4838290 Mon Sep 17 00:00:00 2001 From: Alex Prut <1648497+alexprut@users.noreply.github.com> Date: Tue, 2 Feb 2021 10:32:44 +0100 Subject: [PATCH 168/235] all: remove unneeded parentheses (#21921) * remove uneeded convertion type * remove redundant type in composite literal * omit explicit type where implicit * remove unused redundant parenthesis * remove redundant import alias duktape --- accounts/keystore/account_cache.go | 2 +- cmd/puppeth/genesis.go | 2 +- cmd/puppeth/wizard_genesis.go | 2 +- core/state/snapshot/conversion.go | 4 ++-- core/vm/runtime/runtime_test.go | 4 ++-- crypto/bls12381/bls12_381_test.go | 2 +- crypto/signify/signify.go | 2 +- eth/protocols/snap/sync.go | 2 +- eth/tracers/tracer.go | 2 +- metrics/cpu_syscall.go | 2 +- metrics/exp/exp.go | 2 +- metrics/gauge_float64_test.go | 12 ++++++------ node/utils_test.go | 4 ++-- signer/core/api.go | 2 +- trie/trie_test.go | 2 +- 15 files changed, 23 insertions(+), 23 deletions(-) diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go index 8f660e282f..a3ec6e9c56 100644 --- a/accounts/keystore/account_cache.go +++ b/accounts/keystore/account_cache.go @@ -262,7 +262,7 @@ func (ac *accountCache) scanAccounts() error { switch { case err != nil: log.Debug("Failed to decode keystore key", "path", path, "err", err) - case (addr == common.Address{}): + case addr == common.Address{}: log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") default: return &accounts.Account{ diff --git a/cmd/puppeth/genesis.go b/cmd/puppeth/genesis.go index 46268ec11b..ef1f977bf0 100644 --- a/cmd/puppeth/genesis.go +++ b/cmd/puppeth/genesis.go @@ -425,7 +425,7 @@ func newParityChainSpec(network string, genesis *core.Genesis, bootnodes []strin spec.Params.EIP98Transition = math.MaxInt64 spec.Genesis.Seal.Ethereum.Nonce = types.EncodeNonce(genesis.Nonce) - spec.Genesis.Seal.Ethereum.MixHash = (genesis.Mixhash[:]) + spec.Genesis.Seal.Ethereum.MixHash = genesis.Mixhash[:] spec.Genesis.Difficulty = (*hexutil.Big)(genesis.Difficulty) spec.Genesis.Author = genesis.Coinbase spec.Genesis.Timestamp = (hexutil.Uint64)(genesis.Timestamp) diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index 52093975cb..78f63e1c7a 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -259,7 +259,7 @@ func (w *wizard) manageGenesis() { // Export the native genesis spec used by puppeth and Geth gethJson := filepath.Join(folder, fmt.Sprintf("%s.json", w.network)) - if err := ioutil.WriteFile((gethJson), out, 0644); err != nil { + if err := ioutil.WriteFile(gethJson, out, 0644); err != nil { log.Error("Failed to save genesis file", "err", err) return } diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index 9832225345..4ec229b7ac 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -38,7 +38,7 @@ type trieKV struct { type ( // trieGeneratorFn is the interface of trie generation which can // be implemented by different trie algorithm. - trieGeneratorFn func(in chan (trieKV), out chan (common.Hash)) + trieGeneratorFn func(in chan trieKV, out chan common.Hash) // leafCallbackFn is the callback invoked at the leaves of the trie, // returns the subtrie root with the specified subtrie identifier. @@ -266,7 +266,7 @@ func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGenerato // stdGenerate is a very basic hexary trie builder which uses the same Trie // as the rest of geth, with no enhancements or optimizations -func stdGenerate(in chan (trieKV), out chan (common.Hash)) { +func stdGenerate(in chan trieKV, out chan common.Hash) { t, _ := trie.New(common.Hash{}, trie.NewDatabase(memorydb.New())) for leaf := range in { t.TryUpdate(leaf.key[:], leaf.value) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index b185258dad..af69e3333f 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -500,7 +500,7 @@ func DisabledTestEipExampleCases(t *testing.T) { { code := []byte{ - byte(vm.PUSH9), 0x00, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, (4 + 8), + byte(vm.PUSH9), 0x00, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 4 + 8, byte(vm.JUMPSUB), byte(vm.STOP), byte(vm.BEGINSUB), @@ -516,7 +516,7 @@ func DisabledTestEipExampleCases(t *testing.T) { // out the trace. { code := []byte{ - byte(vm.PUSH9), 0x01, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, (4 + 8), + byte(vm.PUSH9), 0x01, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 4 + 8, byte(vm.JUMPSUB), byte(vm.STOP), byte(vm.BEGINSUB), diff --git a/crypto/bls12381/bls12_381_test.go b/crypto/bls12381/bls12_381_test.go index 51523c9ee7..6bf5834105 100644 --- a/crypto/bls12381/bls12_381_test.go +++ b/crypto/bls12381/bls12_381_test.go @@ -5,7 +5,7 @@ import ( "math/big" ) -var fuz int = 10 +var fuz = 10 func randScalar(max *big.Int) *big.Int { a, _ := rand.Int(rand.Reader, max) diff --git a/crypto/signify/signify.go b/crypto/signify/signify.go index 7ba9705491..e280f87268 100644 --- a/crypto/signify/signify.go +++ b/crypto/signify/signify.go @@ -46,7 +46,7 @@ func parsePrivateKey(key string) (k ed25519.PrivateKey, header []byte, keyNum [] if string(keydata[:2]) != "Ed" { return nil, nil, nil, errInvalidKeyHeader } - return ed25519.PrivateKey(keydata[40:]), keydata[:2], keydata[32:40], nil + return keydata[40:], keydata[:2], keydata[32:40], nil } // SignFile creates a signature of the input file. diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index e7720026bf..422cdf8f72 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -1494,7 +1494,7 @@ func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) { // retrievals as not-pending, ready for resheduling req.timeout.Stop() for i, hash := range req.hashes { - req.task.trieTasks[hash] = [][]byte(req.paths[i]) + req.task.trieTasks[hash] = req.paths[i] } } diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index c9f00d7371..dba7ce87cf 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -31,7 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - duktape "gopkg.in/olebedev/go-duktape.v3" + "gopkg.in/olebedev/go-duktape.v3" ) // bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js. diff --git a/metrics/cpu_syscall.go b/metrics/cpu_syscall.go index e245453e82..106637af5a 100644 --- a/metrics/cpu_syscall.go +++ b/metrics/cpu_syscall.go @@ -31,5 +31,5 @@ func getProcessCPUTime() int64 { log.Warn("Failed to retrieve CPU time", "err", err) return 0 } - return int64(usage.Utime.Sec+usage.Stime.Sec)*100 + int64(usage.Utime.Usec+usage.Stime.Usec)/10000 //nolint:unconvert + return (usage.Utime.Sec+usage.Stime.Sec)*100 + int64(usage.Utime.Usec+usage.Stime.Usec)/10000 //nolint:unconvert } diff --git a/metrics/exp/exp.go b/metrics/exp/exp.go index f510b8381e..3ebe8cc68a 100644 --- a/metrics/exp/exp.go +++ b/metrics/exp/exp.go @@ -128,7 +128,7 @@ func (exp *exp) publishMeter(name string, metric metrics.Meter) { exp.getInt(name + ".count").Set(m.Count()) exp.getFloat(name + ".one-minute").Set(m.Rate1()) exp.getFloat(name + ".five-minute").Set(m.Rate5()) - exp.getFloat(name + ".fifteen-minute").Set((m.Rate15())) + exp.getFloat(name + ".fifteen-minute").Set(m.Rate15()) exp.getFloat(name + ".mean").Set(m.RateMean()) } diff --git a/metrics/gauge_float64_test.go b/metrics/gauge_float64_test.go index 3ee568e7ba..02b75580c4 100644 --- a/metrics/gauge_float64_test.go +++ b/metrics/gauge_float64_test.go @@ -12,27 +12,27 @@ func BenchmarkGuageFloat64(b *testing.B) { func TestGaugeFloat64(t *testing.T) { g := NewGaugeFloat64() - g.Update(float64(47.0)) - if v := g.Value(); float64(47.0) != v { + g.Update(47.0) + if v := g.Value(); 47.0 != v { t.Errorf("g.Value(): 47.0 != %v\n", v) } } func TestGaugeFloat64Snapshot(t *testing.T) { g := NewGaugeFloat64() - g.Update(float64(47.0)) + g.Update(47.0) snapshot := g.Snapshot() g.Update(float64(0)) - if v := snapshot.Value(); float64(47.0) != v { + if v := snapshot.Value(); 47.0 != v { t.Errorf("g.Value(): 47.0 != %v\n", v) } } func TestGetOrRegisterGaugeFloat64(t *testing.T) { r := NewRegistry() - NewRegisteredGaugeFloat64("foo", r).Update(float64(47.0)) + NewRegisteredGaugeFloat64("foo", r).Update(47.0) t.Logf("registry: %v", r) - if g := GetOrRegisterGaugeFloat64("foo", r); float64(47.0) != g.Value() { + if g := GetOrRegisterGaugeFloat64("foo", r); 47.0 != g.Value() { t.Fatal(g) } } diff --git a/node/utils_test.go b/node/utils_test.go index 44c83e22da..b7474bb706 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -82,11 +82,11 @@ func (f *FullService) Stop() error { return nil } func (f *FullService) Protocols() []p2p.Protocol { return []p2p.Protocol{ - p2p.Protocol{ + { Name: "test1", Version: uint(1), }, - p2p.Protocol{ + { Name: "test2", Version: uint(2), }, diff --git a/signer/core/api.go b/signer/core/api.go index 7595d2d484..07e206a74c 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -439,7 +439,7 @@ func (api *SignerAPI) newAccount() (common.Address, error) { continue } if pwErr := ValidatePasswordFormat(resp.Text); pwErr != nil { - api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", (i + 1), pwErr)) + api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", i+1, pwErr)) } else { // No error acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Text) diff --git a/trie/trie_test.go b/trie/trie_test.go index ddbdcbbd5b..87bce9abca 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -322,7 +322,7 @@ func TestLargeValue(t *testing.T) { // TestRandomCases tests som cases that were found via random fuzzing func TestRandomCases(t *testing.T) { - var rt []randTestStep = []randTestStep{ + var rt = []randTestStep{ {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 0 {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 1 {op: 0, key: common.Hex2Bytes("d51b182b95d677e5f1c82508c0228de96b73092d78ce78b2230cd948674f66fd1483bd"), value: common.Hex2Bytes("0000000000000002")}, // step 2 From 83e4c49e2b8d3fa83675cf5647e45a6ade7b8b4f Mon Sep 17 00:00:00 2001 From: ucwong Date: Tue, 2 Feb 2021 20:09:23 +0800 Subject: [PATCH 169/235] trie : use trie.NewStackTrie instead of new(trie.Trie) (#22246) The PR makes use of the stacktrie, which is is more lenient on resource consumption, than the regular trie, in cases where we only need it for DeriveSha --- cmd/evm/internal/t8ntool/execution.go | 4 ++-- consensus/clique/clique.go | 2 +- consensus/ethash/consensus.go | 2 +- core/blockchain_test.go | 4 ++-- core/genesis.go | 2 +- core/state_processor_test.go | 2 +- core/tx_pool_test.go | 2 +- eth/fetcher/block_fetcher.go | 2 +- eth/fetcher/block_fetcher_test.go | 2 +- les/odr_requests.go | 4 ++-- miner/miner_test.go | 2 +- miner/worker.go | 2 +- 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 95e6de37cb..525723e3cb 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -229,8 +229,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } execRs := &ExecutionResult{ StateRoot: root, - TxRoot: types.DeriveSha(includedTxs, new(trie.Trie)), - ReceiptRoot: types.DeriveSha(receipts, new(trie.Trie)), + TxRoot: types.DeriveSha(includedTxs, trie.NewStackTrie(nil)), + ReceiptRoot: types.DeriveSha(receipts, trie.NewStackTrie(nil)), Bloom: types.CreateBloom(receipts), LogsHash: rlpHash(statedb.Logs()), Receipts: receipts, diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 6c667804d8..61d4358b4e 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -565,7 +565,7 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header * c.Finalize(chain, header, state, txs, uncles) // Assemble and return the final block for sealing - return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)), nil + return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), nil } // Authorize injects a private key into the consensus engine to mint new blocks diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index c58fe7a530..1b801b253f 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -586,7 +586,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea ethash.Finalize(chain, header, state, txs, uncles) // Header seems complete, assemble into a block and return - return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie)), nil + return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), nil } // SealHash returns the hash of a block prior to it being sealed. diff --git a/core/blockchain_test.go b/core/blockchain_test.go index d60a235981..c33e7321ec 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -683,12 +683,12 @@ func TestFastVsFullChains(t *testing.T) { } if fblock, arblock, anblock := fast.GetBlockByHash(hash), archive.GetBlockByHash(hash), ancient.GetBlockByHash(hash); fblock.Hash() != arblock.Hash() || anblock.Hash() != arblock.Hash() { t.Errorf("block #%d [%x]: block mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock, anblock, arblock) - } else if types.DeriveSha(fblock.Transactions(), new(trie.Trie)) != types.DeriveSha(arblock.Transactions(), new(trie.Trie)) || types.DeriveSha(anblock.Transactions(), new(trie.Trie)) != types.DeriveSha(arblock.Transactions(), new(trie.Trie)) { + } else if types.DeriveSha(fblock.Transactions(), trie.NewStackTrie(nil)) != types.DeriveSha(arblock.Transactions(), trie.NewStackTrie(nil)) || types.DeriveSha(anblock.Transactions(), trie.NewStackTrie(nil)) != types.DeriveSha(arblock.Transactions(), trie.NewStackTrie(nil)) { t.Errorf("block #%d [%x]: transactions mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock.Transactions(), anblock.Transactions(), arblock.Transactions()) } else if types.CalcUncleHash(fblock.Uncles()) != types.CalcUncleHash(arblock.Uncles()) || types.CalcUncleHash(anblock.Uncles()) != types.CalcUncleHash(arblock.Uncles()) { t.Errorf("block #%d [%x]: uncles mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock.Uncles(), anblock, arblock.Uncles()) } - if freceipts, anreceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash), fast.Config()), rawdb.ReadReceipts(ancientDb, hash, *rawdb.ReadHeaderNumber(ancientDb, hash), fast.Config()), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash), fast.Config()); types.DeriveSha(freceipts, new(trie.Trie)) != types.DeriveSha(areceipts, new(trie.Trie)) { + if freceipts, anreceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash), fast.Config()), rawdb.ReadReceipts(ancientDb, hash, *rawdb.ReadHeaderNumber(ancientDb, hash), fast.Config()), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash), fast.Config()); types.DeriveSha(freceipts, trie.NewStackTrie(nil)) != types.DeriveSha(areceipts, trie.NewStackTrie(nil)) { t.Errorf("block #%d [%x]: receipts mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, freceipts, anreceipts, areceipts) } } diff --git a/core/genesis.go b/core/genesis.go index f678a3bbca..a01eee9eb2 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -288,7 +288,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { statedb.Commit(false) statedb.Database().TrieDB().Commit(root, true, nil) - return types.NewBlock(head, nil, nil, nil, new(trie.Trie)) + return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)) } // Commit writes the block and state of a genesis specification to the database. diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 6e63975ac1..5976ecc3d4 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -148,5 +148,5 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr } header.Root = common.BytesToHash(hasher.Sum(nil)) // Assemble and return the final block for sealing - return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)) + return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)) } diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 47d3830b06..5d555f5a9c 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -55,7 +55,7 @@ type testBlockChain struct { func (bc *testBlockChain) CurrentBlock() *types.Block { return types.NewBlock(&types.Header{ GasLimit: bc.gasLimit, - }, nil, nil, nil, new(trie.Trie)) + }, nil, nil, nil, trie.NewStackTrie(nil)) } func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index 270aaf5918..5ea8a128d9 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -620,7 +620,7 @@ func (f *BlockFetcher) loop() { continue } if txnHash == (common.Hash{}) { - txnHash = types.DeriveSha(types.Transactions(task.transactions[i]), new(trie.Trie)) + txnHash = types.DeriveSha(types.Transactions(task.transactions[i]), trie.NewStackTrie(nil)) } if txnHash != announce.header.TxHash { continue diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index 3220002a99..a6eef71da0 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -39,7 +39,7 @@ var ( testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") testAddress = crypto.PubkeyToAddress(testKey.PublicKey) genesis = core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000)) - unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit}, nil, nil, nil, new(trie.Trie)) + unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit}, nil, nil, nil, trie.NewStackTrie(nil)) ) // makeChain creates a chain of n blocks starting at and including parent. diff --git a/les/odr_requests.go b/les/odr_requests.go index 962b88a322..711c54b40f 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -116,7 +116,7 @@ func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error { if r.Header == nil { return errHeaderUnavailable } - if r.Header.TxHash != types.DeriveSha(types.Transactions(body.Transactions), new(trie.Trie)) { + if r.Header.TxHash != types.DeriveSha(types.Transactions(body.Transactions), trie.NewStackTrie(nil)) { return errTxHashMismatch } if r.Header.UncleHash != types.CalcUncleHash(body.Uncles) { @@ -174,7 +174,7 @@ func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error { if r.Header == nil { return errHeaderUnavailable } - if r.Header.ReceiptHash != types.DeriveSha(receipt, new(trie.Trie)) { + if r.Header.ReceiptHash != types.DeriveSha(receipt, trie.NewStackTrie(nil)) { return errReceiptHashMismatch } // Validations passed, store and return diff --git a/miner/miner_test.go b/miner/miner_test.go index 127b4c7687..da1e472dbd 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -63,7 +63,7 @@ type testBlockChain struct { func (bc *testBlockChain) CurrentBlock() *types.Block { return types.NewBlock(&types.Header{ GasLimit: bc.gasLimit, - }, nil, nil, nil, new(trie.Trie)) + }, nil, nil, nil, trie.NewStackTrie(nil)) } func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { diff --git a/miner/worker.go b/miner/worker.go index 82d08d4c7e..e81d50e46e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -728,7 +728,7 @@ func (w *worker) updateSnapshot() { w.current.txs, uncles, w.current.receipts, - new(trie.Trie), + trie.NewStackTrie(nil), ) w.snapshotState = w.current.state.Copy() } From 3512b41c5cd024421a2048c70688c1b82f122dff Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 3 Feb 2021 11:02:35 +0100 Subject: [PATCH 170/235] core: reset txpool state on sethead (#22247) fixes an issue where local transactions that were included in the chain before a SetHead were rejected if resubmitted, since the txpool had not reset the state to the current (older) state. --- core/tx_pool.go | 59 +++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index 4a17c31ca8..36546cde75 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -1137,44 +1137,45 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // head from the chain. // If that is the case, we don't have the lost transactions any more, and // there's nothing to add - if newNum < oldNum { - // If the reorg ended up on a lower number, it's indicative of setHead being the cause - log.Debug("Skipping transaction reset caused by setHead", - "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) - } else { + if newNum >= oldNum { // If we reorged to a same or higher number, then it's not a case of setHead log.Warn("Transaction pool reset with missing oldhead", "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) - } - return - } - for rem.NumberU64() > add.NumberU64() { - discarded = append(discarded, rem.Transactions()...) - if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { - log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) return } - } - for add.NumberU64() > rem.NumberU64() { - included = append(included, add.Transactions()...) - if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { - log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) - return + // If the reorg ended up on a lower number, it's indicative of setHead being the cause + log.Debug("Skipping transaction reset caused by setHead", + "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) + // We still need to update the current state s.th. the lost transactions can be readded by the user + } else { + for rem.NumberU64() > add.NumberU64() { + discarded = append(discarded, rem.Transactions()...) + if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) + return + } } - } - for rem.Hash() != add.Hash() { - discarded = append(discarded, rem.Transactions()...) - if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { - log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) - return + for add.NumberU64() > rem.NumberU64() { + included = append(included, add.Transactions()...) + if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) + return + } } - included = append(included, add.Transactions()...) - if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { - log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) - return + for rem.Hash() != add.Hash() { + discarded = append(discarded, rem.Transactions()...) + if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) + return + } + included = append(included, add.Transactions()...) + if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) + return + } } + reinject = types.TxDifference(discarded, included) } - reinject = types.TxDifference(discarded, included) } } // Initialize the internal state to the current head From 54735a67239d19d3f29e3f8ebd348a6c88ed31fa Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 3 Feb 2021 15:04:28 +0100 Subject: [PATCH 171/235] fuzzers: added consensys/gurvy library to bn256 differential fuzzer (#21812) This pr adds consensys' gurvy bn256 variant into the code for differential fuzzing. --- go.mod | 1 + go.sum | 124 ++++++++++++++++++++++++++++++ tests/fuzzers/bn256/bn256_fuzz.go | 73 +++++++++++++----- 3 files changed, 180 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 48c6889fbe..5fd3f772a4 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/btcsuite/btcd v0.20.1-beta github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 + github.com/consensys/gurvy v0.3.8 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea github.com/dlclark/regexp2 v1.2.0 // indirect diff --git a/go.sum b/go.sum index 2c16d81f04..1ac0cf81f2 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,7 @@ cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbf cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= @@ -56,12 +57,19 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= @@ -92,7 +100,17 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 h1:J82+/8rub3qSy0HxEnoYD8cs+HDlHWYrqYXe2Vqxluk= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= +github.com/consensys/bavard v0.1.8-0.20210105233146-c16790d2aa8b/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= +github.com/consensys/goff v0.3.10/go.mod h1:xTldOBEHmFiYS0gPXd3NsaEqZWlnmeWcRLWgD3ba3xc= +github.com/consensys/gurvy v0.3.8 h1:H2hvjvT2OFMgdMn5ZbhXqHt+F8DJ2clZW7Vmc0kFFxc= +github.com/consensys/gurvy v0.3.8/go.mod h1:sN75xnsiD593XnhbhvG2PkOy194pZBzqShWF/kwuW/g= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -110,13 +128,19 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 h1:Y9vTBSsV4hSwPSj4bacAU/eSnV3dAxVpepaghAdhGoQ= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM= github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -126,6 +150,7 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -145,10 +170,12 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -190,26 +217,52 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 h1:sezaKhEfPFg8W0Enm61B9Gs911H8iesGY5R8NDPtd1M= github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 h1:HarGZ5h9HD9LgEg1yRVMXyfiw4wlXiLiYM2oMjeA/SE= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= @@ -226,6 +279,7 @@ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89 h1:12K8AlpT0/6QU github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -233,12 +287,15 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kilic/bls12-381 v0.0.0-20201226121925-69dacb279461/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= @@ -257,13 +314,18 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leanovate/gopter v0.2.8/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d h1:oNAwILwmgWKFpuU+dXvI6dl9jG2mAWAZLX3r9s0PPiw= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 h1:USWjF42jDCSEeikX/G1g40ZWnsPXN5WkZ4jMHZWyBK4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -273,6 +335,15 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= @@ -300,9 +371,11 @@ github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 h1:goeTyGkArOZIVOMA0dQbyuPWGNQJZGPwPu/QS9GlpnA= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= @@ -314,25 +387,36 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 h1:ZeU+auZj1iNzN8iVhff6M38Mfu73FQiJve/GEXYJBjE= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -342,12 +426,20 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= +github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -358,22 +450,30 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D69SiV4JoN7kkfvJdOWlPpfxrzxpLMoUk= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -387,6 +487,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= @@ -404,14 +505,19 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -440,9 +546,11 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -464,6 +572,10 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -472,6 +584,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -491,15 +604,19 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -547,18 +664,25 @@ google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyz google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/tests/fuzzers/bn256/bn256_fuzz.go b/tests/fuzzers/bn256/bn256_fuzz.go index 477fe0a160..c98fbc33ae 100644 --- a/tests/fuzzers/bn256/bn256_fuzz.go +++ b/tests/fuzzers/bn256/bn256_fuzz.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. +// +build gofuzz + package bn256 import ( @@ -10,44 +12,53 @@ import ( "io" "math/big" + gurvy "github.com/consensys/gurvy/bn256" cloudflare "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" google "github.com/ethereum/go-ethereum/crypto/bn256/google" ) -func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1) { +func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1, *gurvy.G1Affine) { _, xc, err := cloudflare.RandomG1(input) if err != nil { // insufficient input - return nil, nil + return nil, nil, nil } xg := new(google.G1) if _, err := xg.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) + } + xs := new(gurvy.G1Affine) + if err := xs.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> consensys:", err)) } - return xc, xg + return xc, xg, xs } -func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2) { +func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2, *gurvy.G2Affine) { _, xc, err := cloudflare.RandomG2(input) if err != nil { // insufficient input - return nil, nil + return nil, nil, nil } xg := new(google.G2) if _, err := xg.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) } - return xc, xg + xs := new(gurvy.G2Affine) + if err := xs.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> consensys:", err)) + } + return xc, xg, xs } // FuzzAdd fuzzez bn256 addition between the Google and Cloudflare libraries. func FuzzAdd(data []byte) int { input := bytes.NewReader(data) - xc, xg := getG1Points(input) + xc, xg, xs := getG1Points(input) if xc == nil { return 0 } - yc, yg := getG1Points(input) + yc, yg, ys := getG1Points(input) if yc == nil { return 0 } @@ -59,8 +70,16 @@ func FuzzAdd(data []byte) int { rg := new(google.G1) rg.Add(xg, yg) + tmpX := new(gurvy.G1Jac).FromAffine(xs) + tmpY := new(gurvy.G1Jac).FromAffine(ys) + rs := new(gurvy.G1Affine).FromJacobian(tmpX.AddAssign(tmpY)) + if !bytes.Equal(rc.Marshal(), rg.Marshal()) { - panic("add mismatch") + panic("add mismatch: cloudflare/google") + } + + if !bytes.Equal(rc.Marshal(), rs.Marshal()) { + panic("add mismatch: cloudflare/consensys") } return 1 } @@ -69,7 +88,7 @@ func FuzzAdd(data []byte) int { // libraries. func FuzzMul(data []byte) int { input := bytes.NewReader(data) - pc, pg := getG1Points(input) + pc, pg, ps := getG1Points(input) if pc == nil { return 0 } @@ -93,25 +112,43 @@ func FuzzMul(data []byte) int { rg := new(google.G1) rg.ScalarMult(pg, new(big.Int).SetBytes(buf)) + rs := new(gurvy.G1Jac) + psJac := new(gurvy.G1Jac).FromAffine(ps) + rs.ScalarMultiplication(psJac, new(big.Int).SetBytes(buf)) + rsAffine := new(gurvy.G1Affine).FromJacobian(rs) + if !bytes.Equal(rc.Marshal(), rg.Marshal()) { - panic("scalar mul mismatch") + panic("scalar mul mismatch: cloudflare/google") + } + if !bytes.Equal(rc.Marshal(), rsAffine.Marshal()) { + panic("scalar mul mismatch: cloudflare/consensys") } return 1 } func FuzzPair(data []byte) int { input := bytes.NewReader(data) - pc, pg := getG1Points(input) + pc, pg, ps := getG1Points(input) if pc == nil { return 0 } - tc, tg := getG2Points(input) + tc, tg, ts := getG2Points(input) if tc == nil { return 0 } - // Pair the two points and ensure thet result in the same output - if cloudflare.PairingCheck([]*cloudflare.G1{pc}, []*cloudflare.G2{tc}) != google.PairingCheck([]*google.G1{pg}, []*google.G2{tg}) { - panic("pair mismatch") + // Pair the two points and ensure they result in the same output + clPair := cloudflare.PairingCheck([]*cloudflare.G1{pc}, []*cloudflare.G2{tc}) + if clPair != google.PairingCheck([]*google.G1{pg}, []*google.G2{tg}) { + panic("pairing mismatch: cloudflare/google") + } + + coPair, err := gurvy.PairingCheck([]gurvy.G1Affine{*ps}, []gurvy.G2Affine{*ts}) + if err != nil { + panic(fmt.Sprintf("gurvy encountered error: %v", err)) + } + if clPair != coPair { + panic("pairing mismatch: cloudflare/consensys") } + return 1 } From 28121324ac42ad88b911da514ae2c092f5718f5d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 5 Feb 2021 11:35:55 +0100 Subject: [PATCH 172/235] internal/ethapi: comment nitpick (#22270) --- internal/ethapi/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9c3f6b9161..ed109cd201 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -368,7 +368,7 @@ func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArg } // SendTransaction will create a transaction from the given arguments and -// tries to sign it with the key associated with args.To. If the given passwd isn't +// tries to sign it with the key associated with args.From. If the given passwd isn't // able to decrypt the key it fails. func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) { if args.Nonce == nil { From 098a2b6e26e59ce09c72869f7919724ba7fd97ee Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 5 Feb 2021 20:51:15 +0800 Subject: [PATCH 173/235] eth: move eth.Config to a common package (#22205) This moves the eth config definition into a separate package, eth/ethconfig. Packages eth and les can now import this common package instead of importing eth from les, reducing dependencies. Co-authored-by: Felix Lange --- cmd/faucet/faucet.go | 4 +- cmd/geth/config.go | 6 +- cmd/utils/cmd.go | 4 +- cmd/utils/flags.go | 109 +++++++++++++++--------------- cmd/utils/flags_legacy.go | 14 ++-- console/console_test.go | 5 +- core/bloom_indexer.go | 92 +++++++++++++++++++++++++ eth/backend.go | 51 +++----------- eth/bloombits.go | 69 ------------------- eth/{ => ethconfig}/config.go | 64 ++++++++++++++---- eth/{ => ethconfig}/gen_config.go | 2 +- ethclient/ethclient_test.go | 3 +- graphql/graphql_test.go | 3 +- les/api_test.go | 5 +- les/client.go | 8 +-- les/commons.go | 6 +- les/costtracker.go | 4 +- les/server.go | 15 +++- les/test_helper.go | 8 +-- miner/stress_clique.go | 2 +- miner/stress_ethash.go | 2 +- mobile/geth.go | 4 +- 22 files changed, 265 insertions(+), 215 deletions(-) create mode 100644 core/bloom_indexer.go rename eth/{ => ethconfig}/config.go (72%) rename eth/{ => ethconfig}/gen_config.go (99%) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 763b8d25f8..e839f1c886 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -47,8 +47,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/les" @@ -247,7 +247,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network ui } // Assemble the Ethereum light client protocol - cfg := eth.DefaultConfig + cfg := ethconfig.Defaults cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis diff --git a/cmd/geth/config.go b/cmd/geth/config.go index c5b330b2d8..c71d5eb653 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -27,7 +27,7 @@ import ( "gopkg.in/urfave/cli.v1" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -85,7 +85,7 @@ type whisperDeprecatedConfig struct { } type gethConfig struct { - Eth eth.Config + Eth ethconfig.Config Shh whisperDeprecatedConfig Node node.Config Ethstats ethstatsConfig @@ -121,7 +121,7 @@ func defaultNodeConfig() node.Config { func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // Load defaults. cfg := gethConfig{ - Eth: eth.DefaultConfig, + Eth: ethconfig.Defaults, Node: defaultNodeConfig(), Metrics: metrics.DefaultConfig, } diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 4306216892..d4051e59ef 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/log" @@ -75,7 +75,7 @@ func StartNode(ctx *cli.Context, stack *node.Node) { signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) defer signal.Stop(sigc) - minFreeDiskSpace := eth.DefaultConfig.TrieDirtyCache + minFreeDiskSpace := ethconfig.Defaults.TrieDirtyCache if ctx.GlobalIsSet(MinFreeDiskSpaceFlag.Name) { minFreeDiskSpace = ctx.GlobalInt(MinFreeDiskSpaceFlag.Name) } else if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index aa5180dd9f..1ea26be457 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -44,6 +44,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/ethdb" @@ -137,7 +138,7 @@ var ( NetworkIdFlag = cli.Uint64Flag{ Name: "networkid", Usage: "Explicitly set network id (integer)(For testnets: use --ropsten, --rinkeby, --goerli instead)", - Value: eth.DefaultConfig.NetworkId, + Value: ethconfig.Defaults.NetworkId, } MainnetFlag = cli.BoolFlag{ Name: "mainnet", @@ -196,7 +197,7 @@ var ( Name: "nocode", Usage: "Exclude contract code (save db lookups)", } - defaultSyncMode = eth.DefaultConfig.SyncMode + defaultSyncMode = ethconfig.Defaults.SyncMode SyncModeFlag = TextMarshalerFlag{ Name: "syncmode", Usage: `Blockchain sync mode ("fast", "full", "snap" or "light")`, @@ -228,32 +229,32 @@ var ( LightServeFlag = cli.IntFlag{ Name: "light.serve", Usage: "Maximum percentage of time allowed for serving LES requests (multi-threaded processing allows values over 100)", - Value: eth.DefaultConfig.LightServ, + Value: ethconfig.Defaults.LightServ, } LightIngressFlag = cli.IntFlag{ Name: "light.ingress", Usage: "Incoming bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)", - Value: eth.DefaultConfig.LightIngress, + Value: ethconfig.Defaults.LightIngress, } LightEgressFlag = cli.IntFlag{ Name: "light.egress", Usage: "Outgoing bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)", - Value: eth.DefaultConfig.LightEgress, + Value: ethconfig.Defaults.LightEgress, } LightMaxPeersFlag = cli.IntFlag{ Name: "light.maxpeers", Usage: "Maximum number of light clients to serve, or light servers to attach to", - Value: eth.DefaultConfig.LightPeers, + Value: ethconfig.Defaults.LightPeers, } UltraLightServersFlag = cli.StringFlag{ Name: "ulc.servers", Usage: "List of trusted ultra-light servers", - Value: strings.Join(eth.DefaultConfig.UltraLightServers, ","), + Value: strings.Join(ethconfig.Defaults.UltraLightServers, ","), } UltraLightFractionFlag = cli.IntFlag{ Name: "ulc.fraction", Usage: "Minimum % of trusted ultra-light servers required to announce a new head", - Value: eth.DefaultConfig.UltraLightFraction, + Value: ethconfig.Defaults.UltraLightFraction, } UltraLightOnlyAnnounceFlag = cli.BoolFlag{ Name: "ulc.onlyannounce", @@ -271,12 +272,12 @@ var ( EthashCachesInMemoryFlag = cli.IntFlag{ Name: "ethash.cachesinmem", Usage: "Number of recent ethash caches to keep in memory (16MB each)", - Value: eth.DefaultConfig.Ethash.CachesInMem, + Value: ethconfig.Defaults.Ethash.CachesInMem, } EthashCachesOnDiskFlag = cli.IntFlag{ Name: "ethash.cachesondisk", Usage: "Number of recent ethash caches to keep on disk (16MB each)", - Value: eth.DefaultConfig.Ethash.CachesOnDisk, + Value: ethconfig.Defaults.Ethash.CachesOnDisk, } EthashCachesLockMmapFlag = cli.BoolFlag{ Name: "ethash.cacheslockmmap", @@ -285,17 +286,17 @@ var ( EthashDatasetDirFlag = DirectoryFlag{ Name: "ethash.dagdir", Usage: "Directory to store the ethash mining DAGs", - Value: DirectoryString(eth.DefaultConfig.Ethash.DatasetDir), + Value: DirectoryString(ethconfig.Defaults.Ethash.DatasetDir), } EthashDatasetsInMemoryFlag = cli.IntFlag{ Name: "ethash.dagsinmem", Usage: "Number of recent ethash mining DAGs to keep in memory (1+GB each)", - Value: eth.DefaultConfig.Ethash.DatasetsInMem, + Value: ethconfig.Defaults.Ethash.DatasetsInMem, } EthashDatasetsOnDiskFlag = cli.IntFlag{ Name: "ethash.dagsondisk", Usage: "Number of recent ethash mining DAGs to keep on disk (1+GB each)", - Value: eth.DefaultConfig.Ethash.DatasetsOnDisk, + Value: ethconfig.Defaults.Ethash.DatasetsOnDisk, } EthashDatasetsLockMmapFlag = cli.BoolFlag{ Name: "ethash.dagslockmmap", @@ -323,37 +324,37 @@ var ( TxPoolPriceLimitFlag = cli.Uint64Flag{ Name: "txpool.pricelimit", Usage: "Minimum gas price limit to enforce for acceptance into the pool", - Value: eth.DefaultConfig.TxPool.PriceLimit, + Value: ethconfig.Defaults.TxPool.PriceLimit, } TxPoolPriceBumpFlag = cli.Uint64Flag{ Name: "txpool.pricebump", Usage: "Price bump percentage to replace an already existing transaction", - Value: eth.DefaultConfig.TxPool.PriceBump, + Value: ethconfig.Defaults.TxPool.PriceBump, } TxPoolAccountSlotsFlag = cli.Uint64Flag{ Name: "txpool.accountslots", Usage: "Minimum number of executable transaction slots guaranteed per account", - Value: eth.DefaultConfig.TxPool.AccountSlots, + Value: ethconfig.Defaults.TxPool.AccountSlots, } TxPoolGlobalSlotsFlag = cli.Uint64Flag{ Name: "txpool.globalslots", Usage: "Maximum number of executable transaction slots for all accounts", - Value: eth.DefaultConfig.TxPool.GlobalSlots, + Value: ethconfig.Defaults.TxPool.GlobalSlots, } TxPoolAccountQueueFlag = cli.Uint64Flag{ Name: "txpool.accountqueue", Usage: "Maximum number of non-executable transaction slots permitted per account", - Value: eth.DefaultConfig.TxPool.AccountQueue, + Value: ethconfig.Defaults.TxPool.AccountQueue, } TxPoolGlobalQueueFlag = cli.Uint64Flag{ Name: "txpool.globalqueue", Usage: "Maximum number of non-executable transaction slots for all accounts", - Value: eth.DefaultConfig.TxPool.GlobalQueue, + Value: ethconfig.Defaults.TxPool.GlobalQueue, } TxPoolLifetimeFlag = cli.DurationFlag{ Name: "txpool.lifetime", Usage: "Maximum amount of time non-executable transaction are queued", - Value: eth.DefaultConfig.TxPool.Lifetime, + Value: ethconfig.Defaults.TxPool.Lifetime, } // Performance tuning settings CacheFlag = cli.IntFlag{ @@ -374,12 +375,12 @@ var ( CacheTrieJournalFlag = cli.StringFlag{ Name: "cache.trie.journal", Usage: "Disk journal directory for trie cache to survive node restarts", - Value: eth.DefaultConfig.TrieCleanCacheJournal, + Value: ethconfig.Defaults.TrieCleanCacheJournal, } CacheTrieRejournalFlag = cli.DurationFlag{ Name: "cache.trie.rejournal", Usage: "Time interval to regenerate the trie cache journal", - Value: eth.DefaultConfig.TrieCleanCacheRejournal, + Value: ethconfig.Defaults.TrieCleanCacheRejournal, } CacheGCFlag = cli.IntFlag{ Name: "cache.gc", @@ -416,17 +417,17 @@ var ( MinerGasTargetFlag = cli.Uint64Flag{ Name: "miner.gastarget", Usage: "Target gas floor for mined blocks", - Value: eth.DefaultConfig.Miner.GasFloor, + Value: ethconfig.Defaults.Miner.GasFloor, } MinerGasLimitFlag = cli.Uint64Flag{ Name: "miner.gaslimit", Usage: "Target gas ceiling for mined blocks", - Value: eth.DefaultConfig.Miner.GasCeil, + Value: ethconfig.Defaults.Miner.GasCeil, } MinerGasPriceFlag = BigFlag{ Name: "miner.gasprice", Usage: "Minimum gas price for mining a transaction", - Value: eth.DefaultConfig.Miner.GasPrice, + Value: ethconfig.Defaults.Miner.GasPrice, } MinerEtherbaseFlag = cli.StringFlag{ Name: "miner.etherbase", @@ -440,7 +441,7 @@ var ( MinerRecommitIntervalFlag = cli.DurationFlag{ Name: "miner.recommit", Usage: "Time interval to recreate the block being mined", - Value: eth.DefaultConfig.Miner.Recommit, + Value: ethconfig.Defaults.Miner.Recommit, } MinerNoVerfiyFlag = cli.BoolFlag{ Name: "miner.noverify", @@ -473,12 +474,12 @@ var ( RPCGlobalGasCapFlag = cli.Uint64Flag{ Name: "rpc.gascap", Usage: "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)", - Value: eth.DefaultConfig.RPCGasCap, + Value: ethconfig.Defaults.RPCGasCap, } RPCGlobalTxFeeCapFlag = cli.Float64Flag{ Name: "rpc.txfeecap", Usage: "Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)", - Value: eth.DefaultConfig.RPCTxFeeCap, + Value: ethconfig.Defaults.RPCTxFeeCap, } // Logging and debug settings EthStatsURLFlag = cli.StringFlag{ @@ -650,17 +651,17 @@ var ( GpoBlocksFlag = cli.IntFlag{ Name: "gpo.blocks", Usage: "Number of recent blocks to check for gas prices", - Value: eth.DefaultConfig.GPO.Blocks, + Value: ethconfig.Defaults.GPO.Blocks, } GpoPercentileFlag = cli.IntFlag{ Name: "gpo.percentile", Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices", - Value: eth.DefaultConfig.GPO.Percentile, + Value: ethconfig.Defaults.GPO.Percentile, } GpoMaxGasPriceFlag = cli.Int64Flag{ Name: "gpo.maxprice", Usage: "Maximum gas price will be recommended by gpo", - Value: eth.DefaultConfig.GPO.MaxPrice.Int64(), + Value: ethconfig.Defaults.GPO.MaxPrice.Int64(), } WhisperEnabledFlag = cli.BoolFlag{ Name: "shh", @@ -1028,7 +1029,7 @@ func setIPC(ctx *cli.Context, cfg *node.Config) { } // setLes configures the les server and ultra light client settings from the command line flags. -func setLes(ctx *cli.Context, cfg *eth.Config) { +func setLes(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.GlobalIsSet(LegacyLightServFlag.Name) { cfg.LightServ = ctx.GlobalInt(LegacyLightServFlag.Name) log.Warn("The flag --lightserv is deprecated and will be removed in the future, please use --light.serve") @@ -1056,8 +1057,8 @@ func setLes(ctx *cli.Context, cfg *eth.Config) { cfg.UltraLightFraction = ctx.GlobalInt(UltraLightFractionFlag.Name) } if cfg.UltraLightFraction <= 0 && cfg.UltraLightFraction > 100 { - log.Error("Ultra light fraction is invalid", "had", cfg.UltraLightFraction, "updated", eth.DefaultConfig.UltraLightFraction) - cfg.UltraLightFraction = eth.DefaultConfig.UltraLightFraction + log.Error("Ultra light fraction is invalid", "had", cfg.UltraLightFraction, "updated", ethconfig.Defaults.UltraLightFraction) + cfg.UltraLightFraction = ethconfig.Defaults.UltraLightFraction } if ctx.GlobalIsSet(UltraLightOnlyAnnounceFlag.Name) { cfg.UltraLightOnlyAnnounce = ctx.GlobalBool(UltraLightOnlyAnnounceFlag.Name) @@ -1108,7 +1109,7 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error // setEtherbase retrieves the etherbase either from the directly specified // command line flags or from the keystore if CLI indexed. -func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) { +func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *ethconfig.Config) { // Extract the current etherbase, new flag overriding legacy one var etherbase string if ctx.GlobalIsSet(LegacyMinerEtherbaseFlag.Name) { @@ -1307,8 +1308,8 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) { // If we are running the light client, apply another group // settings for gas oracle. if light { - cfg.Blocks = eth.DefaultLightGPOConfig.Blocks - cfg.Percentile = eth.DefaultLightGPOConfig.Percentile + cfg.Blocks = ethconfig.LightClientGPO.Blocks + cfg.Percentile = ethconfig.LightClientGPO.Percentile } if ctx.GlobalIsSet(LegacyGpoBlocksFlag.Name) { cfg.Blocks = ctx.GlobalInt(LegacyGpoBlocksFlag.Name) @@ -1372,7 +1373,7 @@ func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) { } } -func setEthash(ctx *cli.Context, cfg *eth.Config) { +func setEthash(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.GlobalIsSet(EthashCacheDirFlag.Name) { cfg.Ethash.CacheDir = ctx.GlobalString(EthashCacheDirFlag.Name) } @@ -1435,7 +1436,7 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) { } } -func setWhitelist(ctx *cli.Context, cfg *eth.Config) { +func setWhitelist(ctx *cli.Context, cfg *ethconfig.Config) { whitelist := ctx.GlobalString(WhitelistFlag.Name) if whitelist == "" { return @@ -1510,7 +1511,7 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node) { } // SetEthConfig applies eth-related command line flags to the config. -func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { +func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags CheckExclusive(ctx, MainnetFlag, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag) CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light") @@ -1709,7 +1710,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // SetDNSDiscoveryDefaults configures DNS discovery with the given URL if // no URLs are set. -func SetDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { +func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) { if cfg.EthDiscoveryURLs != nil { return // already set through flags/config } @@ -1728,7 +1729,7 @@ func SetDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { } // RegisterEthService adds an Ethereum client to the stack. -func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { +func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) ethapi.Backend { if cfg.SyncMode == downloader.LightSync { backend, err := les.New(stack, cfg) if err != nil { @@ -1865,14 +1866,14 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.B engine = ethash.NewFaker() if !ctx.GlobalBool(FakePoWFlag.Name) { engine = ethash.New(ethash.Config{ - CacheDir: stack.ResolvePath(eth.DefaultConfig.Ethash.CacheDir), - CachesInMem: eth.DefaultConfig.Ethash.CachesInMem, - CachesOnDisk: eth.DefaultConfig.Ethash.CachesOnDisk, - CachesLockMmap: eth.DefaultConfig.Ethash.CachesLockMmap, - DatasetDir: stack.ResolvePath(eth.DefaultConfig.Ethash.DatasetDir), - DatasetsInMem: eth.DefaultConfig.Ethash.DatasetsInMem, - DatasetsOnDisk: eth.DefaultConfig.Ethash.DatasetsOnDisk, - DatasetsLockMmap: eth.DefaultConfig.Ethash.DatasetsLockMmap, + CacheDir: stack.ResolvePath(ethconfig.Defaults.Ethash.CacheDir), + CachesInMem: ethconfig.Defaults.Ethash.CachesInMem, + CachesOnDisk: ethconfig.Defaults.Ethash.CachesOnDisk, + CachesLockMmap: ethconfig.Defaults.Ethash.CachesLockMmap, + DatasetDir: stack.ResolvePath(ethconfig.Defaults.Ethash.DatasetDir), + DatasetsInMem: ethconfig.Defaults.Ethash.DatasetsInMem, + DatasetsOnDisk: ethconfig.Defaults.Ethash.DatasetsOnDisk, + DatasetsLockMmap: ethconfig.Defaults.Ethash.DatasetsLockMmap, }, nil, false) } } @@ -1880,12 +1881,12 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.B Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name) } cache := &core.CacheConfig{ - TrieCleanLimit: eth.DefaultConfig.TrieCleanCache, + TrieCleanLimit: ethconfig.Defaults.TrieCleanCache, TrieCleanNoPrefetch: ctx.GlobalBool(CacheNoPrefetchFlag.Name), - TrieDirtyLimit: eth.DefaultConfig.TrieDirtyCache, + TrieDirtyLimit: ethconfig.Defaults.TrieDirtyCache, TrieDirtyDisabled: ctx.GlobalString(GCModeFlag.Name) == "archive", - TrieTimeLimit: eth.DefaultConfig.TrieTimeout, - SnapshotLimit: eth.DefaultConfig.SnapshotCache, + TrieTimeLimit: ethconfig.Defaults.TrieTimeout, + SnapshotLimit: ethconfig.Defaults.SnapshotCache, Preimages: ctx.GlobalBool(CachePreimagesFlag.Name), } if cache.TrieDirtyDisabled && !cache.Preimages { diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go index 1376d47c05..ff45ab9094 100644 --- a/cmd/utils/flags_legacy.go +++ b/cmd/utils/flags_legacy.go @@ -20,7 +20,7 @@ import ( "fmt" "strings" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/node" "gopkg.in/urfave/cli.v1" ) @@ -55,12 +55,12 @@ var ( LegacyMinerGasTargetFlag = cli.Uint64Flag{ Name: "targetgaslimit", Usage: "Target gas floor for mined blocks (deprecated, use --miner.gastarget)", - Value: eth.DefaultConfig.Miner.GasFloor, + Value: ethconfig.Defaults.Miner.GasFloor, } LegacyMinerGasPriceFlag = BigFlag{ Name: "gasprice", Usage: "Minimum gas price for mining a transaction (deprecated, use --miner.gasprice)", - Value: eth.DefaultConfig.Miner.GasPrice, + Value: ethconfig.Defaults.Miner.GasPrice, } LegacyMinerEtherbaseFlag = cli.StringFlag{ Name: "etherbase", @@ -76,12 +76,12 @@ var ( LegacyLightServFlag = cli.IntFlag{ Name: "lightserv", Usage: "Maximum percentage of time allowed for serving LES requests (deprecated, use --light.serve)", - Value: eth.DefaultConfig.LightServ, + Value: ethconfig.Defaults.LightServ, } LegacyLightPeersFlag = cli.IntFlag{ Name: "lightpeers", Usage: "Maximum number of light clients to serve, or light servers to attach to (deprecated, use --light.maxpeers)", - Value: eth.DefaultConfig.LightPeers, + Value: ethconfig.Defaults.LightPeers, } // (Deprecated April 2020) @@ -143,12 +143,12 @@ var ( LegacyGpoBlocksFlag = cli.IntFlag{ Name: "gpoblocks", Usage: "Number of recent blocks to check for gas prices (deprecated, use --gpo.blocks)", - Value: eth.DefaultConfig.GPO.Blocks, + Value: ethconfig.Defaults.GPO.Blocks, } LegacyGpoPercentileFlag = cli.IntFlag{ Name: "gpopercentile", Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices (deprecated, use --gpo.percentile)", - Value: eth.DefaultConfig.GPO.Percentile, + Value: ethconfig.Defaults.GPO.Percentile, } LegacyBootnodesV4Flag = cli.StringFlag{ Name: "bootnodesv4", diff --git a/console/console_test.go b/console/console_test.go index 68c03d108d..f6ab781410 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/console/prompt" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/jsre" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" @@ -85,7 +86,7 @@ type tester struct { // newTester creates a test environment based on which the console can operate. // Please ensure you call Close() on the returned tester to avoid leaks. -func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { +func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester { // Create a temporary storage for the node keys and initialize it workspace, err := ioutil.TempDir("", "console-tester-") if err != nil { @@ -97,7 +98,7 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { if err != nil { t.Fatalf("failed to create node: %v", err) } - ethConf := ð.Config{ + ethConf := ðconfig.Config{ Genesis: core.DeveloperGenesisBlock(15, common.Address{}), Miner: miner.Config{ Etherbase: common.HexToAddress(testAddress), diff --git a/core/bloom_indexer.go b/core/bloom_indexer.go new file mode 100644 index 0000000000..856746a1c0 --- /dev/null +++ b/core/bloom_indexer.go @@ -0,0 +1,92 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "context" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/bitutil" + "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" +) + +const ( + // bloomThrottling is the time to wait between processing two consecutive index + // sections. It's useful during chain upgrades to prevent disk overload. + bloomThrottling = 100 * time.Millisecond +) + +// BloomIndexer implements a core.ChainIndexer, building up a rotated bloom bits index +// for the Ethereum header bloom filters, permitting blazing fast filtering. +type BloomIndexer struct { + size uint64 // section size to generate bloombits for + db ethdb.Database // database instance to write index data and metadata into + gen *bloombits.Generator // generator to rotate the bloom bits crating the bloom index + section uint64 // Section is the section number being processed currently + head common.Hash // Head is the hash of the last header processed +} + +// NewBloomIndexer returns a chain indexer that generates bloom bits data for the +// canonical chain for fast logs filtering. +func NewBloomIndexer(db ethdb.Database, size, confirms uint64) *ChainIndexer { + backend := &BloomIndexer{ + db: db, + size: size, + } + table := rawdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix)) + + return NewChainIndexer(db, table, backend, size, confirms, bloomThrottling, "bloombits") +} + +// Reset implements core.ChainIndexerBackend, starting a new bloombits index +// section. +func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { + gen, err := bloombits.NewGenerator(uint(b.size)) + b.gen, b.section, b.head = gen, section, common.Hash{} + return err +} + +// Process implements core.ChainIndexerBackend, adding a new header's bloom into +// the index. +func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error { + b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom) + b.head = header.Hash() + return nil +} + +// Commit implements core.ChainIndexerBackend, finalizing the bloom section and +// writing it out into the database. +func (b *BloomIndexer) Commit() error { + batch := b.db.NewBatch() + for i := 0; i < types.BloomBitLength; i++ { + bits, err := b.gen.Bitset(uint(i)) + if err != nil { + return err + } + rawdb.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits)) + } + return batch.Write() +} + +// Prune returns an empty error since we don't support pruning here. +func (b *BloomIndexer) Prune(threshold uint64) error { + return nil +} diff --git a/eth/backend.go b/eth/backend.go index a6390facb3..51ad0ccf3f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -31,13 +31,13 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/clique" - "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/protocols/eth" @@ -55,9 +55,13 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +// Config contains the configuration options of the ETH protocol. +// Deprecated: use ethconfig.Config instead. +type Config = ethconfig.Config + // Ethereum implements the Ethereum full node service. type Ethereum struct { - config *Config + config *ethconfig.Config // Handlers txPool *core.TxPool @@ -93,7 +97,7 @@ type Ethereum struct { // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(stack *node.Node, config *Config) (*Ethereum, error) { +func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { // Ensure configuration values are compatible and sane if config.SyncMode == downloader.LightSync { return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") @@ -102,8 +106,8 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode) } if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 { - log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", DefaultConfig.Miner.GasPrice) - config.Miner.GasPrice = new(big.Int).Set(DefaultConfig.Miner.GasPrice) + log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice) + config.Miner.GasPrice = new(big.Int).Set(ethconfig.Defaults.Miner.GasPrice) } if config.NoPruning && config.TrieDirtyCache > 0 { if config.SnapshotCache > 0 { @@ -132,13 +136,13 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { chainDb: chainDb, eventMux: stack.EventMux(), accountManager: stack.AccountManager(), - engine: CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), + engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), closeBloomHandler: make(chan struct{}), networkID: config.NetworkId, gasPrice: config.Miner.GasPrice, etherbase: config.Miner.Etherbase, bloomRequests: make(chan chan *bloombits.Retrieval), - bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), + bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), p2pServer: stack.Server(), } @@ -269,39 +273,6 @@ func makeExtraData(extra []byte) []byte { return extra } -// CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service -func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine { - // If proof-of-authority is requested, set it up - if chainConfig.Clique != nil { - return clique.New(chainConfig.Clique, db) - } - // Otherwise assume proof-of-work - switch config.PowMode { - case ethash.ModeFake: - log.Warn("Ethash used in fake mode") - return ethash.NewFaker() - case ethash.ModeTest: - log.Warn("Ethash used in test mode") - return ethash.NewTester(nil, noverify) - case ethash.ModeShared: - log.Warn("Ethash used in shared mode") - return ethash.NewShared() - default: - engine := ethash.New(ethash.Config{ - CacheDir: stack.ResolvePath(config.CacheDir), - CachesInMem: config.CachesInMem, - CachesOnDisk: config.CachesOnDisk, - CachesLockMmap: config.CachesLockMmap, - DatasetDir: config.DatasetDir, - DatasetsInMem: config.DatasetsInMem, - DatasetsOnDisk: config.DatasetsOnDisk, - DatasetsLockMmap: config.DatasetsLockMmap, - }, notify, noverify) - engine.SetThreads(-1) // Disable CPU mining - return engine - } -} - // APIs return the collection of RPC services the ethereum package offers. // NOTE, some of these services probably need to be moved to somewhere else. func (s *Ethereum) APIs() []rpc.API { diff --git a/eth/bloombits.go b/eth/bloombits.go index bd34bd7b69..0cb7050d23 100644 --- a/eth/bloombits.go +++ b/eth/bloombits.go @@ -17,16 +17,10 @@ package eth import ( - "context" "time" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/bitutil" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" ) const ( @@ -78,66 +72,3 @@ func (eth *Ethereum) startBloomHandlers(sectionSize uint64) { }() } } - -const ( - // bloomThrottling is the time to wait between processing two consecutive index - // sections. It's useful during chain upgrades to prevent disk overload. - bloomThrottling = 100 * time.Millisecond -) - -// BloomIndexer implements a core.ChainIndexer, building up a rotated bloom bits index -// for the Ethereum header bloom filters, permitting blazing fast filtering. -type BloomIndexer struct { - size uint64 // section size to generate bloombits for - db ethdb.Database // database instance to write index data and metadata into - gen *bloombits.Generator // generator to rotate the bloom bits crating the bloom index - section uint64 // Section is the section number being processed currently - head common.Hash // Head is the hash of the last header processed -} - -// NewBloomIndexer returns a chain indexer that generates bloom bits data for the -// canonical chain for fast logs filtering. -func NewBloomIndexer(db ethdb.Database, size, confirms uint64) *core.ChainIndexer { - backend := &BloomIndexer{ - db: db, - size: size, - } - table := rawdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix)) - - return core.NewChainIndexer(db, table, backend, size, confirms, bloomThrottling, "bloombits") -} - -// Reset implements core.ChainIndexerBackend, starting a new bloombits index -// section. -func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { - gen, err := bloombits.NewGenerator(uint(b.size)) - b.gen, b.section, b.head = gen, section, common.Hash{} - return err -} - -// Process implements core.ChainIndexerBackend, adding a new header's bloom into -// the index. -func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error { - b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom) - b.head = header.Hash() - return nil -} - -// Commit implements core.ChainIndexerBackend, finalizing the bloom section and -// writing it out into the database. -func (b *BloomIndexer) Commit() error { - batch := b.db.NewBatch() - for i := 0; i < types.BloomBitLength; i++ { - bits, err := b.gen.Bitset(uint(i)) - if err != nil { - return err - } - rawdb.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits)) - } - return batch.Write() -} - -// Prune returns an empty error since we don't support pruning here. -func (b *BloomIndexer) Prune(threshold uint64) error { - return nil -} diff --git a/eth/config.go b/eth/ethconfig/config.go similarity index 72% rename from eth/config.go rename to eth/ethconfig/config.go index 446467d364..9147a602d5 100644 --- a/eth/config.go +++ b/eth/ethconfig/config.go @@ -14,7 +14,8 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package eth +// Package ethconfig contains the configuration of the ETH and LES protocols. +package ethconfig import ( "math/big" @@ -25,30 +26,35 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" ) -// DefaultFullGPOConfig contains default gasprice oracle settings for full node. -var DefaultFullGPOConfig = gasprice.Config{ +// FullNodeGPO contains default gasprice oracle settings for full node. +var FullNodeGPO = gasprice.Config{ Blocks: 20, Percentile: 60, MaxPrice: gasprice.DefaultMaxPrice, } -// DefaultLightGPOConfig contains default gasprice oracle settings for light client. -var DefaultLightGPOConfig = gasprice.Config{ +// LightClientGPO contains default gasprice oracle settings for light client. +var LightClientGPO = gasprice.Config{ Blocks: 2, Percentile: 60, MaxPrice: gasprice.DefaultMaxPrice, } -// DefaultConfig contains default settings for use on the Ethereum main net. -var DefaultConfig = Config{ +// Defaults contains default settings for use on the Ethereum main net. +var Defaults = Config{ SyncMode: downloader.FastSync, Ethash: ethash.Config{ CacheDir: "ethash", @@ -77,7 +83,7 @@ var DefaultConfig = Config{ }, TxPool: core.DefaultTxPoolConfig, RPCGasCap: 25000000, - GPO: DefaultFullGPOConfig, + GPO: FullNodeGPO, RPCTxFeeCap: 1, // 1 ether } @@ -89,21 +95,22 @@ func init() { } } if runtime.GOOS == "darwin" { - DefaultConfig.Ethash.DatasetDir = filepath.Join(home, "Library", "Ethash") + Defaults.Ethash.DatasetDir = filepath.Join(home, "Library", "Ethash") } else if runtime.GOOS == "windows" { localappdata := os.Getenv("LOCALAPPDATA") if localappdata != "" { - DefaultConfig.Ethash.DatasetDir = filepath.Join(localappdata, "Ethash") + Defaults.Ethash.DatasetDir = filepath.Join(localappdata, "Ethash") } else { - DefaultConfig.Ethash.DatasetDir = filepath.Join(home, "AppData", "Local", "Ethash") + Defaults.Ethash.DatasetDir = filepath.Join(home, "AppData", "Local", "Ethash") } } else { - DefaultConfig.Ethash.DatasetDir = filepath.Join(home, ".ethash") + Defaults.Ethash.DatasetDir = filepath.Join(home, ".ethash") } } //go:generate gencodec -type Config -formats toml -out gen_config.go +// Config contains configuration options for of the ETH and LES protocols. type Config struct { // The genesis block, which is inserted if the database is empty. // If nil, the Ethereum main net block is used. @@ -190,3 +197,36 @@ type Config struct { // CheckpointOracle is the configuration for checkpoint oracle. CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` } + +// CreateConsensusEngine creates a consensus engine for the given chain configuration. +func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine { + // If proof-of-authority is requested, set it up + if chainConfig.Clique != nil { + return clique.New(chainConfig.Clique, db) + } + // Otherwise assume proof-of-work + switch config.PowMode { + case ethash.ModeFake: + log.Warn("Ethash used in fake mode") + return ethash.NewFaker() + case ethash.ModeTest: + log.Warn("Ethash used in test mode") + return ethash.NewTester(nil, noverify) + case ethash.ModeShared: + log.Warn("Ethash used in shared mode") + return ethash.NewShared() + default: + engine := ethash.New(ethash.Config{ + CacheDir: stack.ResolvePath(config.CacheDir), + CachesInMem: config.CachesInMem, + CachesOnDisk: config.CachesOnDisk, + CachesLockMmap: config.CachesLockMmap, + DatasetDir: config.DatasetDir, + DatasetsInMem: config.DatasetsInMem, + DatasetsOnDisk: config.DatasetsOnDisk, + DatasetsLockMmap: config.DatasetsLockMmap, + }, notify, noverify) + engine.SetThreads(-1) // Disable CPU mining + return engine + } +} diff --git a/eth/gen_config.go b/eth/ethconfig/gen_config.go similarity index 99% rename from eth/gen_config.go rename to eth/ethconfig/gen_config.go index e68b29ce5e..5814b81b09 100644 --- a/eth/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -1,6 +1,6 @@ // Code generated by github.com/fjl/gencodec. DO NOT EDIT. -package eth +package ethconfig import ( "time" diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index d700022e8f..8b175ee066 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -195,7 +196,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { t.Fatalf("can't create new node: %v", err) } // Create Ethereum Service - config := ð.Config{Genesis: genesis} + config := ðconfig.Config{Genesis: genesis} config.Ethash.PowMode = ethash.ModeFake ethservice, err := eth.New(n, config) if err != nil { diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index a88c9b30b1..2f3b230329 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" @@ -191,7 +192,7 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { func createGQLService(t *testing.T, stack *node.Node) { // create backend - ethConf := ð.Config{ + ethConf := ðconfig.Config{ Genesis: &core.Genesis{ Config: params.AllEthashProtocolChanges, GasLimit: 11500000, diff --git a/les/api_test.go b/les/api_test.go index 2895264f67..f7017c5d98 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -492,14 +493,14 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] } func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { - config := eth.DefaultConfig + config := ethconfig.Defaults config.SyncMode = downloader.LightSync config.Ethash.PowMode = ethash.ModeFake return New(stack, &config) } func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { - config := eth.DefaultConfig + config := ethconfig.Defaults config.SyncMode = downloader.FullSync config.LightServ = testServerCapacity config.LightPeers = testMaxClients diff --git a/les/client.go b/les/client.go index d8cb6e385a..1b26e9a9b5 100644 --- a/les/client.go +++ b/les/client.go @@ -30,8 +30,8 @@ import ( "github.com/ethereum/go-ethereum/core/bloombits" "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/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/event" @@ -76,7 +76,7 @@ type LightEthereum struct { } // New creates an instance of the light client. -func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { +func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") if err != nil { return nil, err @@ -105,9 +105,9 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { eventMux: stack.EventMux(), reqDist: newRequestDistributor(peers, &mclock.System{}), accountManager: stack.AccountManager(), - engine: eth.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), + engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), - bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), + bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), p2pServer: stack.Server(), p2pConfig: &stack.Config().P2P, diff --git a/les/commons.go b/les/commons.go index 73334497ad..a2fce1dc97 100644 --- a/les/commons.go +++ b/les/commons.go @@ -25,7 +25,7 @@ import ( "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/eth/ethconfig" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/checkpointoracle" @@ -48,7 +48,7 @@ type chainReader interface { // lesCommons contains fields needed by both server and client. type lesCommons struct { genesis common.Hash - config *eth.Config + config *ethconfig.Config chainConfig *params.ChainConfig iConfig *light.IndexerConfig chainDb ethdb.Database @@ -138,7 +138,7 @@ func (c *lesCommons) localCheckpoint(index uint64) params.TrustedCheckpoint { } // setupOracle sets up the checkpoint oracle contract client. -func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, ethconfig *eth.Config) *checkpointoracle.CheckpointOracle { +func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, ethconfig *ethconfig.Config) *checkpointoracle.CheckpointOracle { config := ethconfig.CheckpointOracle if config == nil { // Try loading default config. diff --git a/les/costtracker.go b/les/costtracker.go index 0558779bc5..43e32a5b2d 100644 --- a/les/costtracker.go +++ b/les/costtracker.go @@ -24,7 +24,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/log" @@ -137,7 +137,7 @@ type costTracker struct { // newCostTracker creates a cost tracker and loads the cost factor statistics from the database. // It also returns the minimum capacity that can be assigned to any peer. -func newCostTracker(db ethdb.Database, config *eth.Config) (*costTracker, uint64) { +func newCostTracker(db ethdb.Database, config *ethconfig.Config) (*costTracker, uint64) { utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100 ct := &costTracker{ db: db, diff --git a/les/server.go b/les/server.go index 6b12b6f8f3..44495eb311 100644 --- a/les/server.go +++ b/les/server.go @@ -22,7 +22,9 @@ import ( "time" "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/flowcontrol" lps "github.com/ethereum/go-ethereum/les/lespay/server" "github.com/ethereum/go-ethereum/light" @@ -50,6 +52,15 @@ func init() { priorityPoolSetup.Connect(balanceTrackerSetup.BalanceField, balanceTrackerSetup.UpdateFlag) // NodeBalance implements nodePriority } +type ethBackend interface { + ArchiveMode() bool + BlockChain() *core.BlockChain + BloomIndexer() *core.ChainIndexer + ChainDb() ethdb.Database + Synced() bool + TxPool() *core.TxPool +} + type LesServer struct { lesCommons @@ -73,7 +84,7 @@ type LesServer struct { p2pSrv *p2p.Server } -func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) { +func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*LesServer, error) { ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) // Calculate the number of threads used to service the light client // requests based on the user-specified value. diff --git a/les/test_helper.go b/les/test_helper.go index 04482ba68e..e3f0616a88 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -38,7 +38,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/les/checkpointoracle" @@ -163,7 +163,7 @@ func prepare(n int, backend *backends.SimulatedBackend) { func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.IndexerConfig, disablePruning bool) []*core.ChainIndexer { var indexers [3]*core.ChainIndexer indexers[0] = light.NewChtIndexer(db, odr, config.ChtSize, config.ChtConfirms, disablePruning) - indexers[1] = eth.NewBloomIndexer(db, config.BloomSize, config.BloomConfirms) + indexers[1] = core.NewBloomIndexer(db, config.BloomSize, config.BloomConfirms) indexers[2] = light.NewBloomTrieIndexer(db, odr, config.BloomSize, config.BloomTrieSize, disablePruning) // make bloomTrieIndexer as a child indexer of bloom indexer. indexers[1].AddChildIndexer(indexers[2]) @@ -204,7 +204,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index client := &LightEthereum{ lesCommons: lesCommons{ genesis: genesis.Hash(), - config: ð.Config{LightPeers: 100, NetworkId: NetworkId}, + config: ðconfig.Config{LightPeers: 100, NetworkId: NetworkId}, chainConfig: params.AllEthashProtocolChanges, iConfig: light.TestClientIndexerConfig, chainDb: db, @@ -269,7 +269,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da server := &LesServer{ lesCommons: lesCommons{ genesis: genesis.Hash(), - config: ð.Config{LightPeers: 100, NetworkId: NetworkId}, + config: ðconfig.Config{LightPeers: 100, NetworkId: NetworkId}, chainConfig: params.AllEthashProtocolChanges, iConfig: light.TestServerIndexerConfig, chainDb: db, diff --git a/miner/stress_clique.go b/miner/stress_clique.go index a0fd596098..c585e0b1f6 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -185,7 +185,7 @@ func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { return nil, nil, err } // Create and register the backend - ethBackend, err := eth.New(stack, ð.Config{ + ethBackend, err := eth.New(stack, ðconfig.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), SyncMode: downloader.FullSync, diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 1713af9d23..0b838d48b9 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -162,7 +162,7 @@ func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { if err != nil { return nil, nil, err } - ethBackend, err := eth.New(stack, ð.Config{ + ethBackend, err := eth.New(stack, ðconfig.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), SyncMode: downloader.FullSync, diff --git a/mobile/geth.go b/mobile/geth.go index b561e33675..704d432e04 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -25,8 +25,8 @@ import ( "path/filepath" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/internal/debug" @@ -182,7 +182,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { } // Register the Ethereum protocol if requested if config.EthereumEnabled { - ethConf := eth.DefaultConfig + ethConf := ethconfig.Defaults ethConf.Genesis = genesis ethConf.SyncMode = downloader.LightSync ethConf.NetworkId = uint64(config.EthereumNetworkID) From fba5a63afe0993da70896c763b4fdfa953e066ff Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 5 Feb 2021 13:51:53 +0100 Subject: [PATCH 174/235] internal/ethapi: fix typo in comment (#22271) --- internal/ethapi/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ed109cd201..d3c007b9bf 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -386,7 +386,7 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs } // SignTransaction will create a transaction from the given arguments and -// tries to sign it with the key associated with args.To. If the given passwd isn't +// tries to sign it with the key associated with args.From. If the given passwd isn't // able to decrypt the key it fails. The transaction is returned in RLP-form, not broadcast // to other nodes func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs, passwd string) (*SignTransactionResult, error) { From 7ed860d4f17884ac9f1f6b927244a70e2e92eb94 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 5 Feb 2021 14:15:22 +0100 Subject: [PATCH 175/235] eth: don't wait for snap registration if we're not running snap (#22272) Prevents a situation where we (not running snap) connects with a peer running snap, and get stalled waiting for snap registration to succeed (which will never happen), which cause a waitgroup wait to halt shutdown --- eth/peerset.go | 4 ++-- p2p/peer.go | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/eth/peerset.go b/eth/peerset.go index f0657e140b..1e864a8e46 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -73,7 +73,7 @@ func newPeerSet() *peerSet { func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error { // Reject the peer if it advertises `snap` without `eth` as `snap` is only a // satellite protocol meaningful with the chain selection of `eth` - if !peer.SupportsCap(eth.ProtocolName, eth.ProtocolVersions) { + if !peer.RunningCap(eth.ProtocolName, eth.ProtocolVersions) { return errSnapWithoutEth } // Ensure nobody can double connect @@ -101,7 +101,7 @@ func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error { // by the peerset. func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) { // If the peer does not support a compatible `snap`, don't wait - if !peer.SupportsCap(snap.ProtocolName, snap.ProtocolVersions) { + if !peer.RunningCap(snap.ProtocolName, snap.ProtocolVersions) { return nil, nil } // Ensure nobody can double connect diff --git a/p2p/peer.go b/p2p/peer.go index 08881e2583..8ebc858392 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -158,15 +158,14 @@ func (p *Peer) Caps() []Cap { return p.rw.caps } -// SupportsCap returns true if the peer supports any of the enumerated versions -// of a specific protocol. -func (p *Peer) SupportsCap(protocol string, versions []uint) bool { - for _, cap := range p.rw.caps { - if cap.Name == protocol { - for _, ver := range versions { - if cap.Version == ver { - return true - } +// RunningCap returns true if the peer is actively connected using any of the +// enumerated versions of a specific protocol, meaning that at least one of the +// versions is supported by both this node and the peer p. +func (p *Peer) RunningCap(protocol string, versions []uint) bool { + if proto, ok := p.running[protocol]; ok { + for _, ver := range versions { + if proto.Version == ver { + return true } } } From e74bd587f730fcdb5a9b625390da8aa85a2cbbc8 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Fri, 5 Feb 2021 19:44:34 +0100 Subject: [PATCH 176/235] consensus: remove seal verification from the consensus engine interface (#22274) --- consensus/clique/clique.go | 6 ------ consensus/consensus.go | 4 ---- consensus/ethash/algorithm_test.go | 2 +- consensus/ethash/consensus.go | 8 +------- consensus/ethash/ethash_test.go | 4 ++-- 5 files changed, 4 insertions(+), 20 deletions(-) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 61d4358b4e..c62e180faa 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -434,12 +434,6 @@ func (c *Clique) VerifyUncles(chain consensus.ChainReader, block *types.Block) e return nil } -// VerifySeal implements consensus.Engine, checking whether the signature contained -// in the header satisfies the consensus protocol requirements. -func (c *Clique) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error { - return c.verifySeal(chain, header, nil) -} - // verifySeal checks whether the signature contained in the header satisfies the // consensus protocol requirements. The method accepts an optional list of parent // headers that aren't yet part of the local blockchain to generate the snapshots diff --git a/consensus/consensus.go b/consensus/consensus.go index f7a4d0ff0b..2a5aac945d 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -77,10 +77,6 @@ type Engine interface { // rules of a given engine. VerifyUncles(chain ChainReader, block *types.Block) error - // VerifySeal checks whether the crypto seal on a header is valid according to - // the consensus rules of the given engine. - VerifySeal(chain ChainHeaderReader, header *types.Header) error - // Prepare initializes the consensus fields of a block header according to the // rules of a particular engine. The changes are executed inline. Prepare(chain ChainHeaderReader, header *types.Header) error diff --git a/consensus/ethash/algorithm_test.go b/consensus/ethash/algorithm_test.go index 51fb6b124d..663687b81c 100644 --- a/consensus/ethash/algorithm_test.go +++ b/consensus/ethash/algorithm_test.go @@ -731,7 +731,7 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) { defer pend.Done() ethash := New(Config{cachedir, 0, 1, false, "", 0, 0, false, ModeNormal, nil}, nil, false) defer ethash.Close() - if err := ethash.VerifySeal(nil, block.Header()); err != nil { + if err := ethash.verifySeal(nil, block.Header(), false); err != nil { t.Errorf("proc %d: block verification failed: %v", idx, err) } }(i) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 1b801b253f..011a5688ef 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -288,7 +288,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa } // Verify the engine specific seal securing the block if seal { - if err := ethash.VerifySeal(chain, header); err != nil { + if err := ethash.verifySeal(chain, header, false); err != nil { return err } } @@ -488,12 +488,6 @@ var FrontierDifficultyCalulator = calcDifficultyFrontier var HomesteadDifficultyCalulator = calcDifficultyHomestead var DynamicDifficultyCalculator = makeDifficultyCalculator -// VerifySeal implements consensus.Engine, checking whether the given block satisfies -// the PoW difficulty requirements. -func (ethash *Ethash) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error { - return ethash.verifySeal(chain, header, false) -} - // verifySeal checks whether a block satisfies the PoW difficulty requirements, // either using the usual ethash cache for it, or alternatively using a full DAG // to make remote mining fast. diff --git a/consensus/ethash/ethash_test.go b/consensus/ethash/ethash_test.go index adbf1ccfeb..2639707eb2 100644 --- a/consensus/ethash/ethash_test.go +++ b/consensus/ethash/ethash_test.go @@ -46,7 +46,7 @@ func TestTestMode(t *testing.T) { case block := <-results: header.Nonce = types.EncodeNonce(block.Nonce()) header.MixDigest = block.MixDigest() - if err := ethash.VerifySeal(nil, header); err != nil { + if err := ethash.verifySeal(nil, header, false); err != nil { t.Fatalf("unexpected verification error: %v", err) } case <-time.NewTimer(4 * time.Second).C: @@ -86,7 +86,7 @@ func verifyTest(wg *sync.WaitGroup, e *Ethash, workerIndex, epochs int) { block = 0 } header := &types.Header{Number: big.NewInt(block), Difficulty: big.NewInt(100)} - e.VerifySeal(nil, header) + e.verifySeal(nil, header, false) } } From 994cdc69c8ab79b97f490c64721f2908df2070d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Sun, 7 Feb 2021 20:13:59 +0200 Subject: [PATCH 177/235] cmd/utils: enable snapshots by default --- cmd/utils/flags.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1ea26be457..87295fb2f4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -208,9 +208,9 @@ var ( Usage: `Blockchain garbage collection mode ("full", "archive")`, Value: "full", } - SnapshotFlag = cli.BoolFlag{ + SnapshotFlag = cli.BoolTFlag{ Name: "snapshot", - Usage: `Enables snapshot-database mode -- experimental work in progress feature`, + Usage: `Enables snapshot-database mode (default = enable)`, } TxLookupLimitFlag = cli.Int64Flag{ Name: "txlookuplimit", @@ -1579,7 +1579,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheSnapshotFlag.Name) { cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100 } - if !ctx.GlobalIsSet(SnapshotFlag.Name) { + if !ctx.GlobalBool(SnapshotFlag.Name) { // If snap-sync is requested, this flag is also required if cfg.SyncMode == downloader.SnapSync { log.Info("Snap sync requested, enabling --snapshot") @@ -1893,7 +1893,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.B cache.Preimages = true log.Info("Enabling recording of key preimages since archive mode is used") } - if !ctx.GlobalIsSet(SnapshotFlag.Name) { + if !ctx.GlobalBool(SnapshotFlag.Name) { cache.SnapshotLimit = 0 // Disabled } if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) { From 477fd420b393e3e5b962ac8c5af90a1add54d9d7 Mon Sep 17 00:00:00 2001 From: isdyaufh8o7cq Date: Mon, 8 Feb 2021 11:36:49 +0100 Subject: [PATCH 178/235] metrics: fix cast omission in cpu_syscall.go (#22262) fixes an regression which caused build failure on certain platforms --- metrics/cpu_syscall.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metrics/cpu_syscall.go b/metrics/cpu_syscall.go index 106637af5a..e245453e82 100644 --- a/metrics/cpu_syscall.go +++ b/metrics/cpu_syscall.go @@ -31,5 +31,5 @@ func getProcessCPUTime() int64 { log.Warn("Failed to retrieve CPU time", "err", err) return 0 } - return (usage.Utime.Sec+usage.Stime.Sec)*100 + int64(usage.Utime.Usec+usage.Stime.Usec)/10000 //nolint:unconvert + return int64(usage.Utime.Sec+usage.Stime.Sec)*100 + int64(usage.Utime.Usec+usage.Stime.Usec)/10000 //nolint:unconvert } From d86906f1e6040e4e57c164fc5dfab0f97329b229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 8 Feb 2021 13:03:06 +0200 Subject: [PATCH 179/235] params: just to make snapshots a bit more official --- params/version.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/params/version.go b/params/version.go index b6d6bd3f1d..3447f0f283 100644 --- a/params/version.go +++ b/params/version.go @@ -22,8 +22,8 @@ import ( const ( VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 26 // Patch version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release VersionMeta = "unstable" // Version metadata to append to the version string ) From f566dd305e7db3a629a783ce89697f49c4ba4a75 Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 8 Feb 2021 19:16:30 +0800 Subject: [PATCH 180/235] all: bloom-filter based pruning mechanism (#21724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * cmd, core, tests: initial state pruner core: fix db inspector cmd/geth: add verify-state cmd/geth: add verification tool core/rawdb: implement flatdb cmd, core: fix rebase core/state: use new contract code layout core/state/pruner: avoid deleting genesis state cmd/geth: add helper function core, cmd: fix extract genesis core: minor fixes contracts: remove useless core/state/snapshot: plugin stacktrie core: polish core/state/snapshot: iterate storage concurrently core/state/snapshot: fix iteration core: add comments core/state/snapshot: polish code core/state: polish core/state/snapshot: rebase core/rawdb: add comments core/rawdb: fix tests core/rawdb: improve tests core/state/snapshot: fix concurrent iteration core/state: run pruning during the recovery core, trie: implement martin's idea core, eth: delete flatdb and polish pruner trie: fix import core/state/pruner: add log core/state/pruner: fix issues core/state/pruner: don't read back core/state/pruner: fix contract code write core/state/pruner: check root node presence cmd, core: polish log core/state: use HEAD-127 as the target core/state/snapshot: improve tests cmd/geth: fix verification tool cmd/geth: use HEAD as the verification default target all: replace the bloomfilter with martin's fork cmd, core: polish code core, cmd: forcibly delete state root core/state/pruner: add hash64 core/state/pruner: fix blacklist core/state: remove blacklist cmd, core: delete trie clean cache before pruning cmd, core: fix lint cmd, core: fix rebase core/state: fix the special case for clique networks core/state/snapshot: remove useless code core/state/pruner: capping the snapshot after pruning cmd, core, eth: fixes core/rawdb: update db inspector cmd/geth: polish code core/state/pruner: fsync bloom filter cmd, core: print warning log core/state/pruner: adjust the parameters for bloom filter cmd, core: create the bloom filter by size core: polish core/state/pruner: sanitize invalid bloomfilter size cmd: address comments cmd/geth: address comments cmd/geth: address comment core/state/pruner: address comments core/state/pruner: rename homedir to datadir cmd, core: address comments core/state/pruner: address comment core/state: address comments core, cmd, tests: address comments core: address comments core/state/pruner: release the iterator after each commit core/state/pruner: improve pruner cmd, core: adjust bloom paramters core/state/pruner: fix lint core/state/pruner: fix tests core: fix rebase core/state/pruner: remove atomic rename core/state/pruner: address comments all: run go mod tidy core/state/pruner: avoid false-positive for the middle state roots core/state/pruner: add checks for middle roots cmd/geth: replace crit with error * core/state/pruner: fix lint * core: drop legacy bloom filter * core/state/snapshot: improve pruner * core/state/snapshot: polish concurrent logs to report ETA vs. hashes * core/state/pruner: add progress report for pruning and compaction too * core: fix snapshot test API * core/state: fix some pruning logs * core/state/pruner: support recovering from bloom flush fail Co-authored-by: Péter Szilágyi --- cmd/geth/main.go | 3 + cmd/geth/snapshot.go | 437 ++++++++++++++++++++++ cmd/geth/usage.go | 1 + cmd/utils/flags.go | 5 + core/blockchain.go | 2 +- core/blockchain_snapshot_test.go | 3 +- core/genesis.go | 4 - core/rawdb/database.go | 19 +- core/rawdb/schema.go | 10 +- core/state/pruner/bloom.go | 132 +++++++ core/state/pruner/pruner.go | 537 +++++++++++++++++++++++++++ core/state/snapshot/conversion.go | 284 +++++++++----- core/state/snapshot/snapshot.go | 78 +++- core/state/snapshot/snapshot_test.go | 67 ++++ eth/backend.go | 4 + go.mod | 16 +- go.sum | 11 +- tests/block_test_util.go | 3 +- tests/state_test_util.go | 2 +- trie/stacktrie.go | 21 +- 20 files changed, 1491 insertions(+), 148 deletions(-) create mode 100644 cmd/geth/snapshot.go create mode 100644 core/state/pruner/bloom.go create mode 100644 core/state/pruner/pruner.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 11829cbad9..0236ffb7d3 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -107,6 +107,7 @@ var ( utils.UltraLightFractionFlag, utils.UltraLightOnlyAnnounceFlag, utils.WhitelistFlag, + utils.BloomFilterSizeFlag, utils.CacheFlag, utils.CacheDatabaseFlag, utils.CacheTrieFlag, @@ -255,6 +256,8 @@ func init() { dumpConfigCommand, // See cmd/utils/flags_legacy.go utils.ShowDeprecated, + // See snapshot.go + snapshotCommand, } sort.Sort(cli.CommandsByName(app.Commands)) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go new file mode 100644 index 0000000000..6805a42585 --- /dev/null +++ b/cmd/geth/snapshot.go @@ -0,0 +1,437 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "bytes" + "errors" + "time" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/pruner" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + cli "gopkg.in/urfave/cli.v1" +) + +var ( + // emptyRoot is the known root hash of an empty trie. + emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // emptyCode is the known hash of the empty EVM bytecode. + emptyCode = crypto.Keccak256(nil) +) + +var ( + snapshotCommand = cli.Command{ + Name: "snapshot", + Usage: "A set of commands based on the snapshot", + Category: "MISCELLANEOUS COMMANDS", + Description: "", + Subcommands: []cli.Command{ + { + Name: "prune-state", + Usage: "Prune stale ethereum state data based on the snapshot", + ArgsUsage: "", + Action: utils.MigrateFlags(pruneState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.LegacyTestnetFlag, + utils.CacheTrieJournalFlag, + utils.BloomFilterSizeFlag, + }, + Description: ` +geth snapshot prune-state +will prune historical state data with the help of the state snapshot. +All trie nodes and contract codes that do not belong to the specified +version state will be deleted from the database. After pruning, only +two version states are available: genesis and the specific one. + +The default pruning target is the HEAD-127 state. + +WARNING: It's necessary to delete the trie clean cache after the pruning. +If you specify another directory for the trie clean cache via "--cache.trie.journal" +during the use of Geth, please also specify it here for correct deletion. Otherwise +the trie clean cache with default directory will be deleted. +`, + }, + { + Name: "verify-state", + Usage: "Recalculate state hash based on the snapshot for verification", + ArgsUsage: "", + Action: utils.MigrateFlags(verifyState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.LegacyTestnetFlag, + }, + Description: ` +geth snapshot verify-state +will traverse the whole accounts and storages set based on the specified +snapshot and recalculate the root hash of state for verification. +In other words, this command does the snapshot to trie conversion. +`, + }, + { + Name: "traverse-state", + Usage: "Traverse the state with given root hash for verification", + ArgsUsage: "", + Action: utils.MigrateFlags(traverseState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.LegacyTestnetFlag, + }, + Description: ` +geth snapshot traverse-state +will traverse the whole state from the given state root and will abort if any +referenced trie node or contract code is missing. This command can be used for +state integrity verification. The default checking target is the HEAD state. + +It's also usable without snapshot enabled. +`, + }, + { + Name: "traverse-rawstate", + Usage: "Traverse the state with given root hash for verification", + ArgsUsage: "", + Action: utils.MigrateFlags(traverseRawState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.LegacyTestnetFlag, + }, + Description: ` +geth snapshot traverse-rawstate +will traverse the whole state from the given root and will abort if any referenced +trie node or contract code is missing. This command can be used for state integrity +verification. The default checking target is the HEAD state. It's basically identical +to traverse-state, but the check granularity is smaller. + +It's also usable without snapshot enabled. +`, + }, + }, + } +) + +func pruneState(ctx *cli.Context) error { + stack, config := makeConfigNode(ctx) + defer stack.Close() + + chain, chaindb := utils.MakeChain(ctx, stack, true) + defer chaindb.Close() + + pruner, err := pruner.NewPruner(chaindb, chain.CurrentBlock().Header(), stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name)) + if err != nil { + log.Error("Failed to open snapshot tree", "error", err) + return err + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var targetRoot common.Hash + if ctx.NArg() == 1 { + targetRoot, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "error", err) + return err + } + } + if err = pruner.Prune(targetRoot); err != nil { + log.Error("Failed to prune state", "error", err) + return err + } + return nil +} + +func verifyState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chain, chaindb := utils.MakeChain(ctx, stack, true) + defer chaindb.Close() + + snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, chain.CurrentBlock().Root(), false, false, false) + if err != nil { + log.Error("Failed to open snapshot tree", "error", err) + return err + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var root = chain.CurrentBlock().Root() + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "error", err) + return err + } + } + if err := snaptree.Verify(root); err != nil { + log.Error("Failed to verfiy state", "error", err) + return err + } + log.Info("Verified the state") + return nil +} + +// traverseState is a helper function used for pruning verification. +// Basically it just iterates the trie, ensure all nodes and associated +// contract codes are present. +func traverseState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chain, chaindb := utils.MakeChain(ctx, stack, true) + defer chaindb.Close() + + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + // Use the HEAD root as the default + head := chain.CurrentBlock() + if head == nil { + log.Error("Head block is missing") + return errors.New("head block is missing") + } + var ( + root common.Hash + err error + ) + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "error", err) + return err + } + log.Info("Start traversing the state", "root", root) + } else { + root = head.Root() + log.Info("Start traversing the state", "root", root, "number", head.NumberU64()) + } + triedb := trie.NewDatabase(chaindb) + t, err := trie.NewSecure(root, triedb) + if err != nil { + log.Error("Failed to open trie", "root", root, "error", err) + return err + } + var ( + accounts int + slots int + codes int + lastReport time.Time + start = time.Now() + ) + accIter := trie.NewIterator(t.NodeIterator(nil)) + for accIter.Next() { + accounts += 1 + var acc state.Account + if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { + log.Error("Invalid account encountered during traversal", "error", err) + return err + } + if acc.Root != emptyRoot { + storageTrie, err := trie.NewSecure(acc.Root, triedb) + if err != nil { + log.Error("Failed to open storage trie", "root", acc.Root, "error", err) + return err + } + storageIter := trie.NewIterator(storageTrie.NodeIterator(nil)) + for storageIter.Next() { + slots += 1 + } + if storageIter.Err != nil { + log.Error("Failed to traverse storage trie", "root", acc.Root, "error", storageIter.Err) + return storageIter.Err + } + } + if !bytes.Equal(acc.CodeHash, emptyCode) { + code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) + if len(code) == 0 { + log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash)) + return errors.New("missing code") + } + codes += 1 + } + if time.Since(lastReport) > time.Second*8 { + log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + lastReport = time.Now() + } + } + if accIter.Err != nil { + log.Error("Failed to traverse state trie", "root", root, "error", accIter.Err) + return accIter.Err + } + log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +// traverseRawState is a helper function used for pruning verification. +// Basically it just iterates the trie, ensure all nodes and associated +// contract codes are present. It's basically identical to traverseState +// but it will check each trie node. +func traverseRawState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chain, chaindb := utils.MakeChain(ctx, stack, true) + defer chaindb.Close() + + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + // Use the HEAD root as the default + head := chain.CurrentBlock() + if head == nil { + log.Error("Head block is missing") + return errors.New("head block is missing") + } + var ( + root common.Hash + err error + ) + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "error", err) + return err + } + log.Info("Start traversing the state", "root", root) + } else { + root = head.Root() + log.Info("Start traversing the state", "root", root, "number", head.NumberU64()) + } + triedb := trie.NewDatabase(chaindb) + t, err := trie.NewSecure(root, triedb) + if err != nil { + log.Error("Failed to open trie", "root", root, "error", err) + return err + } + var ( + nodes int + accounts int + slots int + codes int + lastReport time.Time + start = time.Now() + ) + accIter := t.NodeIterator(nil) + for accIter.Next(true) { + nodes += 1 + node := accIter.Hash() + + if node != (common.Hash{}) { + // Check the present for non-empty hash node(embedded node doesn't + // have their own hash). + blob := rawdb.ReadTrieNode(chaindb, node) + if len(blob) == 0 { + log.Error("Missing trie node(account)", "hash", node) + return errors.New("missing account") + } + } + // If it's a leaf node, yes we are touching an account, + // dig into the storage trie further. + if accIter.Leaf() { + accounts += 1 + var acc state.Account + if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { + log.Error("Invalid account encountered during traversal", "error", err) + return errors.New("invalid account") + } + if acc.Root != emptyRoot { + storageTrie, err := trie.NewSecure(acc.Root, triedb) + if err != nil { + log.Error("Failed to open storage trie", "root", acc.Root, "error", err) + return errors.New("missing storage trie") + } + storageIter := storageTrie.NodeIterator(nil) + for storageIter.Next(true) { + nodes += 1 + node := storageIter.Hash() + + // Check the present for non-empty hash node(embedded node doesn't + // have their own hash). + if node != (common.Hash{}) { + blob := rawdb.ReadTrieNode(chaindb, node) + if len(blob) == 0 { + log.Error("Missing trie node(storage)", "hash", node) + return errors.New("missing storage") + } + } + // Bump the counter if it's leaf node. + if storageIter.Leaf() { + slots += 1 + } + } + if storageIter.Error() != nil { + log.Error("Failed to traverse storage trie", "root", acc.Root, "error", storageIter.Error()) + return storageIter.Error() + } + } + if !bytes.Equal(acc.CodeHash, emptyCode) { + code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) + if len(code) == 0 { + log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey())) + return errors.New("missing code") + } + codes += 1 + } + if time.Since(lastReport) > time.Second*8 { + log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + lastReport = time.Now() + } + } + } + if accIter.Error() != nil { + log.Error("Failed to traverse state trie", "root", root, "error", accIter.Error()) + return accIter.Error() + } + log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +func parseRoot(input string) (common.Hash, error) { + var h common.Hash + if err := h.UnmarshalText([]byte(input)); err != nil { + return h, err + } + return h, nil +} diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 0ed31d7da6..55e934d31b 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -245,6 +245,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ Name: "MISC", Flags: []cli.Flag{ utils.SnapshotFlag, + utils.BloomFilterSizeFlag, cli.HelpFlag, }, }, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 87295fb2f4..f9c5553961 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -225,6 +225,11 @@ var ( Name: "whitelist", Usage: "Comma separated block number-to-hash mappings to enforce (=)", } + BloomFilterSizeFlag = cli.Uint64Flag{ + Name: "bloomfilter.size", + Usage: "Megabytes of memory allocated to bloom-filter for pruning", + Value: 2048, + } // Light server and client settings LightServeFlag = cli.IntFlag{ Name: "light.serve", diff --git a/core/blockchain.go b/core/blockchain.go index c05ebfd549..7dd1097e98 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -372,7 +372,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par log.Warn("Enabling snapshot recovery", "chainhead", head.NumberU64(), "diskbase", *layer) recover = true } - bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Root(), !bc.cacheConfig.SnapshotWait, recover) + bc.snaps, _ = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Root(), !bc.cacheConfig.SnapshotWait, true, recover) } // Take ownership of this particular state go bc.update() diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index cb634a451d..96a5c7a8d4 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" @@ -163,7 +162,7 @@ func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks [ } // Check the snapshot, ensure it's integrated - if err := snapshot.VerifyState(chain.snaps, block.Root()); err != nil { + if err := chain.snaps.Verify(block.Root()); err != nil { t.Errorf("The disk layer is not integrated %v", err) } } diff --git a/core/genesis.go b/core/genesis.go index a01eee9eb2..462aec45bd 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -171,7 +171,6 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig } return genesis.Config, block.Hash(), nil } - // We have the genesis block in database(perhaps in ancient database) // but the corresponding state is missing. header := rawdb.ReadHeader(db, stored, 0) @@ -190,7 +189,6 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig } return genesis.Config, block.Hash(), nil } - // Check whether the genesis block is already written. if genesis != nil { hash := genesis.ToBlock(nil).Hash() @@ -198,7 +196,6 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig return genesis.Config, hash, &GenesisMismatchError{stored, hash} } } - // Get the existing chain configuration. newcfg := genesis.configOrDefault(stored) if err := newcfg.CheckConfigForkOrder(); err != nil { @@ -216,7 +213,6 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig if genesis == nil && stored != params.MainnetGenesisHash { return storedcfg, stored, nil } - // Check config compatibility and write the config. Compatibility errors // are returned to the caller unless we're already at block zero. height := rawdb.ReadHeaderNumber(db, rawdb.ReadHeadHeaderHash(db)) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index a2cc9c7be9..1f8c3f4542 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -335,7 +335,7 @@ func InspectDatabase(db ethdb.Database) error { hashNumPairings.Add(size) case len(key) == common.HashLength: tries.Add(size) - case bytes.HasPrefix(key, codePrefix) && len(key) == len(codePrefix)+common.HashLength: + case bytes.HasPrefix(key, CodePrefix) && len(key) == len(CodePrefix)+common.HashLength: codes.Add(size) case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): txLookups.Add(size) @@ -347,15 +347,26 @@ func InspectDatabase(db ethdb.Database) error { preimages.Add(size) case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength): bloomBits.Add(size) + case bytes.HasPrefix(key, BloomBitsIndexPrefix): + bloomBits.Add(size) case bytes.HasPrefix(key, []byte("clique-")) && len(key) == 7+common.HashLength: cliqueSnaps.Add(size) - case bytes.HasPrefix(key, []byte("cht-")) && len(key) == 4+common.HashLength: + case bytes.HasPrefix(key, []byte("cht-")) || + bytes.HasPrefix(key, []byte("chtIndexV2-")) || + bytes.HasPrefix(key, []byte("chtRootV2-")): // Canonical hash trie chtTrieNodes.Add(size) - case bytes.HasPrefix(key, []byte("blt-")) && len(key) == 4+common.HashLength: + case bytes.HasPrefix(key, []byte("blt-")) || + bytes.HasPrefix(key, []byte("bltIndex-")) || + bytes.HasPrefix(key, []byte("bltRoot-")): // Bloomtrie sub bloomTrieNodes.Add(size) default: var accounted bool - for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey, badBlockKey} { + for _, meta := range [][]byte{ + databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, lastPivotKey, + fastTrieProgressKey, snapshotRootKey, snapshotJournalKey, snapshotGeneratorKey, + snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, uncleanShutdownKey, + badBlockKey, + } { if bytes.Equal(key, meta) { metadata.Add(size) accounted = true diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 9749a30d8a..0b411057f8 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -85,7 +85,7 @@ var ( bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value - codePrefix = []byte("c") // codePrefix + code hash -> account code + CodePrefix = []byte("c") // CodePrefix + code hash -> account code preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db @@ -209,16 +209,16 @@ func preimageKey(hash common.Hash) []byte { return append(preimagePrefix, hash.Bytes()...) } -// codeKey = codePrefix + hash +// codeKey = CodePrefix + hash func codeKey(hash common.Hash) []byte { - return append(codePrefix, hash.Bytes()...) + return append(CodePrefix, hash.Bytes()...) } // IsCodeKey reports whether the given byte slice is the key of contract code, // if so return the raw code hash as well. func IsCodeKey(key []byte) (bool, []byte) { - if bytes.HasPrefix(key, codePrefix) && len(key) == common.HashLength+len(codePrefix) { - return true, key[len(codePrefix):] + if bytes.HasPrefix(key, CodePrefix) && len(key) == common.HashLength+len(CodePrefix) { + return true, key[len(CodePrefix):] } return false, nil } diff --git a/core/state/pruner/bloom.go b/core/state/pruner/bloom.go new file mode 100644 index 0000000000..4aeeb176e8 --- /dev/null +++ b/core/state/pruner/bloom.go @@ -0,0 +1,132 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pruner + +import ( + "encoding/binary" + "errors" + "os" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/log" + bloomfilter "github.com/holiman/bloomfilter/v2" +) + +// stateBloomHasher is a wrapper around a byte blob to satisfy the interface API +// requirements of the bloom library used. It's used to convert a trie hash or +// contract code hash into a 64 bit mini hash. +type stateBloomHasher []byte + +func (f stateBloomHasher) Write(p []byte) (n int, err error) { panic("not implemented") } +func (f stateBloomHasher) Sum(b []byte) []byte { panic("not implemented") } +func (f stateBloomHasher) Reset() { panic("not implemented") } +func (f stateBloomHasher) BlockSize() int { panic("not implemented") } +func (f stateBloomHasher) Size() int { return 8 } +func (f stateBloomHasher) Sum64() uint64 { return binary.BigEndian.Uint64(f) } + +// stateBloom is a bloom filter used during the state convesion(snapshot->state). +// The keys of all generated entries will be recorded here so that in the pruning +// stage the entries belong to the specific version can be avoided for deletion. +// +// The false-positive is allowed here. The "false-positive" entries means they +// actually don't belong to the specific version but they are not deleted in the +// pruning. The downside of the false-positive allowance is we may leave some "dangling" +// nodes in the disk. But in practice the it's very unlike the dangling node is +// state root. So in theory this pruned state shouldn't be visited anymore. Another +// potential issue is for fast sync. If we do another fast sync upon the pruned +// database, it's problematic which will stop the expansion during the syncing. +// TODO address it @rjl493456442 @holiman @karalabe. +// +// After the entire state is generated, the bloom filter should be persisted into +// the disk. It indicates the whole generation procedure is finished. +type stateBloom struct { + bloom *bloomfilter.Filter +} + +// newStateBloomWithSize creates a brand new state bloom for state generation. +// The bloom filter will be created by the passing bloom filter size. According +// to the https://hur.st/bloomfilter/?n=600000000&p=&m=2048MB&k=4, the parameters +// are picked so that the false-positive rate for mainnet is low enough. +func newStateBloomWithSize(size uint64) (*stateBloom, error) { + bloom, err := bloomfilter.New(size*1024*1024*8, 4) + if err != nil { + return nil, err + } + log.Info("Initialized state bloom", "size", common.StorageSize(float64(bloom.M()/8))) + return &stateBloom{bloom: bloom}, nil +} + +// NewStateBloomFromDisk loads the state bloom from the given file. +// In this case the assumption is held the bloom filter is complete. +func NewStateBloomFromDisk(filename string) (*stateBloom, error) { + bloom, _, err := bloomfilter.ReadFile(filename) + if err != nil { + return nil, err + } + return &stateBloom{bloom: bloom}, nil +} + +// Commit flushes the bloom filter content into the disk and marks the bloom +// as complete. +func (bloom *stateBloom) Commit(filename, tempname string) error { + // Write the bloom out into a temporary file + _, err := bloom.bloom.WriteFile(tempname) + if err != nil { + return err + } + // Ensure the file is synced to disk + f, err := os.Open(tempname) + if err != nil { + return err + } + if err := f.Sync(); err != nil { + f.Close() + return err + } + f.Close() + + // Move the teporary file into it's final location + return os.Rename(tempname, filename) +} + +// Put implements the KeyValueWriter interface. But here only the key is needed. +func (bloom *stateBloom) Put(key []byte, value []byte) error { + // If the key length is not 32bytes, ensure it's contract code + // entry with new scheme. + if len(key) != common.HashLength { + isCode, codeKey := rawdb.IsCodeKey(key) + if !isCode { + return errors.New("invalid entry") + } + bloom.bloom.Add(stateBloomHasher(codeKey)) + return nil + } + bloom.bloom.Add(stateBloomHasher(key)) + return nil +} + +// Delete removes the key from the key-value data store. +func (bloom *stateBloom) Delete(key []byte) error { panic("not supported") } + +// Contain is the wrapper of the underlying contains function which +// reports whether the key is contained. +// - If it says yes, the key may be contained +// - If it says no, the key is definitely not contained. +func (bloom *stateBloom) Contain(key []byte) (bool, error) { + return bloom.bloom.Contains(stateBloomHasher(key)), nil +} diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go new file mode 100644 index 0000000000..1cb65b38b1 --- /dev/null +++ b/core/state/pruner/pruner.go @@ -0,0 +1,537 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pruner + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math" + "os" + "path/filepath" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +const ( + // stateBloomFilePrefix is the filename prefix of state bloom filter. + stateBloomFilePrefix = "statebloom" + + // stateBloomFilePrefix is the filename suffix of state bloom filter. + stateBloomFileSuffix = "bf.gz" + + // stateBloomFileTempSuffix is the filename suffix of state bloom filter + // while it is being written out to detect write aborts. + stateBloomFileTempSuffix = ".tmp" + + // rangeCompactionThreshold is the minimal deleted entry number for + // triggering range compaction. It's a quite arbitrary number but just + // to avoid triggering range compaction because of small deletion. + rangeCompactionThreshold = 100000 +) + +var ( + // emptyRoot is the known root hash of an empty trie. + emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // emptyCode is the known hash of the empty EVM bytecode. + emptyCode = crypto.Keccak256(nil) +) + +// Pruner is an offline tool to prune the stale state with the +// help of the snapshot. The workflow of pruner is very simple: +// +// - iterate the snapshot, reconstruct the relevant state +// - iterate the database, delete all other state entries which +// don't belong to the target state and the genesis state +// +// It can take several hours(around 2 hours for mainnet) to finish +// the whole pruning work. It's recommended to run this offline tool +// periodically in order to release the disk usage and improve the +// disk read performance to some extent. +type Pruner struct { + db ethdb.Database + stateBloom *stateBloom + datadir string + trieCachePath string + headHeader *types.Header + snaptree *snapshot.Tree +} + +// NewPruner creates the pruner instance. +func NewPruner(db ethdb.Database, headHeader *types.Header, datadir, trieCachePath string, bloomSize uint64) (*Pruner, error) { + snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headHeader.Root, false, false, false) + if err != nil { + return nil, err // The relevant snapshot(s) might not exist + } + // Sanitize the bloom filter size if it's too small. + if bloomSize < 256 { + log.Warn("Sanitizing bloomfilter size", "provided(MB)", bloomSize, "updated(MB)", 256) + bloomSize = 256 + } + stateBloom, err := newStateBloomWithSize(bloomSize) + if err != nil { + return nil, err + } + return &Pruner{ + db: db, + stateBloom: stateBloom, + datadir: datadir, + trieCachePath: trieCachePath, + headHeader: headHeader, + snaptree: snaptree, + }, nil +} + +func prune(maindb ethdb.Database, stateBloom *stateBloom, middleStateRoots map[common.Hash]struct{}, start time.Time) error { + // Delete all stale trie nodes in the disk. With the help of state bloom + // the trie nodes(and codes) belong to the active state will be filtered + // out. A very small part of stale tries will also be filtered because of + // the false-positive rate of bloom filter. But the assumption is held here + // that the false-positive is low enough(~0.05%). The probablity of the + // dangling node is the state root is super low. So the dangling nodes in + // theory will never ever be visited again. + var ( + count int + size common.StorageSize + pstart = time.Now() + logged = time.Now() + batch = maindb.NewBatch() + iter = maindb.NewIterator(nil, nil) + ) + for iter.Next() { + key := iter.Key() + + // All state entries don't belong to specific state and genesis are deleted here + // - trie node + // - legacy contract code + // - new-scheme contract code + isCode, codeKey := rawdb.IsCodeKey(key) + if len(key) == common.HashLength || isCode { + checkKey := key + if isCode { + checkKey = codeKey + } + if _, exist := middleStateRoots[common.BytesToHash(checkKey)]; exist { + log.Debug("Forcibly delete the middle state roots", "hash", common.BytesToHash(checkKey)) + } else { + if ok, err := stateBloom.Contain(checkKey); err != nil { + return err + } else if ok { + continue + } + } + count += 1 + size += common.StorageSize(len(key) + len(iter.Value())) + batch.Delete(key) + + var eta time.Duration // Realistically will never remain uninited + if done := binary.BigEndian.Uint64(key[:8]); done > 0 { + var ( + left = math.MaxUint64 - binary.BigEndian.Uint64(key[:8]) + speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + ) + eta = time.Duration(left/speed) * time.Millisecond + } + if time.Since(logged) > 8*time.Second { + log.Info("Pruning state data", "nodes", count, "size", size, + "elapsed", common.PrettyDuration(time.Since(pstart)), "eta", common.PrettyDuration(eta)) + logged = time.Now() + } + // Recreate the iterator after every batch commit in order + // to allow the underlying compactor to delete the entries. + if batch.ValueSize() >= ethdb.IdealBatchSize { + batch.Write() + batch.Reset() + + iter.Release() + iter = maindb.NewIterator(nil, key) + } + } + } + if batch.ValueSize() > 0 { + batch.Write() + batch.Reset() + } + iter.Release() + log.Info("Pruned state data", "nodes", count, "size", size, "elapsed", common.PrettyDuration(time.Since(pstart))) + + // Start compactions, will remove the deleted data from the disk immediately. + // Note for small pruning, the compaction is skipped. + if count >= rangeCompactionThreshold { + cstart := time.Now() + + for b := byte(0); b < byte(16); b++ { + log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", b, b+1), "elapsed", common.PrettyDuration(time.Since(cstart))) + if err := maindb.Compact([]byte{b}, []byte{b + 1}); err != nil { + log.Error("Database compaction failed", "error", err) + return err + } + } + log.Info("Database compaction finished", "elapsed", common.PrettyDuration(time.Since(cstart))) + } + log.Info("State pruning successful", "pruned", size, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +// Prune deletes all historical state nodes except the nodes belong to the +// specified state version. If user doesn't specify the state version, use +// the bottom-most snapshot diff layer as the target. +func (p *Pruner) Prune(root common.Hash) error { + // If the state bloom filter is already committed previously, + // reuse it for pruning instead of generating a new one. It's + // mandatory because a part of state may already be deleted, + // the recovery procedure is necessary. + _, stateBloomRoot, err := findBloomFilter(p.datadir) + if err != nil { + return err + } + if stateBloomRoot != (common.Hash{}) { + return RecoverPruning(p.datadir, p.db, p.trieCachePath) + } + // If the target state root is not specified, use the HEAD-127 as the + // target. The reason for picking it is: + // - in most of the normal cases, the related state is available + // - the probability of this layer being reorg is very low + var layers []snapshot.Snapshot + if root == (common.Hash{}) { + // Retrieve all snapshot layers from the current HEAD. + // In theory there are 128 difflayers + 1 disk layer present, + // so 128 diff layers are expected to be returned. + layers = p.snaptree.Snapshots(p.headHeader.Root, 128, true) + if len(layers) != 128 { + // Reject if the accumulated diff layers are less than 128. It + // means in most of normal cases, there is no associated state + // with bottom-most diff layer. + return errors.New("the snapshot difflayers are less than 128") + } + // Use the bottom-most diff layer as the target + root = layers[len(layers)-1].Root() + } + // Ensure the root is really present. The weak assumption + // is the presence of root can indicate the presence of the + // entire trie. + if blob := rawdb.ReadTrieNode(p.db, root); len(blob) == 0 { + // The special case is for clique based networks(rinkeby, goerli + // and some other private networks), it's possible that two + // consecutive blocks will have same root. In this case snapshot + // difflayer won't be created. So HEAD-127 may not paired with + // head-127 layer. Instead the paired layer is higher than the + // bottom-most diff layer. Try to find the bottom-most snapshot + // layer with state available. + // + // Note HEAD and HEAD-1 is ignored. Usually there is the associated + // state available, but we don't want to use the topmost state + // as the pruning target. + var found bool + for i := len(layers) - 2; i >= 2; i-- { + if blob := rawdb.ReadTrieNode(p.db, layers[i].Root()); len(blob) != 0 { + root = layers[i].Root() + found = true + log.Info("Selecting middle-layer as the pruning target", "root", root, "depth", i) + break + } + } + if !found { + if len(layers) > 0 { + return errors.New("no snapshot paired state") + } + return fmt.Errorf("associated state[%x] is not present", root) + } + } else { + if len(layers) > 0 { + log.Info("Selecting bottom-most difflayer as the pruning target", "root", root, "height", p.headHeader.Number.Uint64()-127) + } else { + log.Info("Selecting user-specified state as the pruning target", "root", root) + } + } + // Before start the pruning, delete the clean trie cache first. + // It's necessary otherwise in the next restart we will hit the + // deleted state root in the "clean cache" so that the incomplete + // state is picked for usage. + deleteCleanTrieCache(p.trieCachePath) + + // All the state roots of the middle layer should be forcibly pruned, + // otherwise the dangling state will be left. + middleRoots := make(map[common.Hash]struct{}) + for _, layer := range layers { + if layer.Root() == root { + break + } + middleRoots[layer.Root()] = struct{}{} + } + // Traverse the target state, re-construct the whole state trie and + // commit to the given bloom filter. + start := time.Now() + if err := snapshot.GenerateTrie(p.snaptree, root, p.db, p.stateBloom); err != nil { + return err + } + // Traverse the genesis, put all genesis state entries into the + // bloom filter too. + if err := extractGenesis(p.db, p.stateBloom); err != nil { + return err + } + filterName := bloomFilterName(p.datadir, root) + + log.Info("Writing state bloom to disk", "name", filterName) + if err := p.stateBloom.Commit(filterName, filterName+stateBloomFileTempSuffix); err != nil { + return err + } + log.Info("State bloom filter committed", "name", filterName) + + if err := prune(p.db, p.stateBloom, middleRoots, start); err != nil { + return err + } + // Pruning is done, now drop the "useless" layers from the snapshot. + // Firstly, flushing the target layer into the disk. After that all + // diff layers below the target will all be merged into the disk. + if err := p.snaptree.Cap(root, 0); err != nil { + return err + } + // Secondly, flushing the snapshot journal into the disk. All diff + // layers upon the target layer are dropped silently. Eventually the + // entire snapshot tree is converted into a single disk layer with + // the pruning target as the root. + if _, err := p.snaptree.Journal(root); err != nil { + return err + } + // Delete the state bloom, it marks the entire pruning procedure is + // finished. If any crashes or manual exit happens before this, + // `RecoverPruning` will pick it up in the next restarts to redo all + // the things. + os.RemoveAll(filterName) + return nil +} + +// RecoverPruning will resume the pruning procedure during the system restart. +// This function is used in this case: user tries to prune state data, but the +// system was interrupted midway because of crash or manual-kill. In this case +// if the bloom filter for filtering active state is already constructed, the +// pruning can be resumed. What's more if the bloom filter is constructed, the +// pruning **has to be resumed**. Otherwise a lot of dangling nodes may be left +// in the disk. +func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) error { + stateBloomPath, stateBloomRoot, err := findBloomFilter(datadir) + if err != nil { + return err + } + if stateBloomPath == "" { + return nil // nothing to recover + } + headHeader, err := getHeadHeader(db) + if err != nil { + return err + } + // Initialize the snapshot tree in recovery mode to handle this special case: + // - Users run the `prune-state` command multiple times + // - Neither these `prune-state` running is finished(e.g. interrupted manually) + // - The state bloom filter is already generated, a part of state is deleted, + // so that resuming the pruning here is mandatory + // - The state HEAD is rewound already because of multiple incomplete `prune-state` + // In this case, even the state HEAD is not exactly matched with snapshot, it + // still feasible to recover the pruning correctly. + snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headHeader.Root, false, false, true) + if err != nil { + return err // The relevant snapshot(s) might not exist + } + stateBloom, err := NewStateBloomFromDisk(stateBloomPath) + if err != nil { + return err + } + log.Info("Loaded state bloom filter", "path", stateBloomPath) + + // Before start the pruning, delete the clean trie cache first. + // It's necessary otherwise in the next restart we will hit the + // deleted state root in the "clean cache" so that the incomplete + // state is picked for usage. + deleteCleanTrieCache(trieCachePath) + + // All the state roots of the middle layers should be forcibly pruned, + // otherwise the dangling state will be left. + var ( + found bool + layers = snaptree.Snapshots(headHeader.Root, 128, true) + middleRoots = make(map[common.Hash]struct{}) + ) + for _, layer := range layers { + if layer.Root() == stateBloomRoot { + found = true + break + } + middleRoots[layer.Root()] = struct{}{} + } + if !found { + log.Error("Pruning target state is not existent") + return errors.New("non-existent target state") + } + if err := prune(db, stateBloom, middleRoots, time.Now()); err != nil { + return err + } + // Pruning is done, now drop the "useless" layers from the snapshot. + // Firstly, flushing the target layer into the disk. After that all + // diff layers below the target will all be merged into the disk. + if err := snaptree.Cap(stateBloomRoot, 0); err != nil { + return err + } + // Secondly, flushing the snapshot journal into the disk. All diff + // layers upon are dropped silently. Eventually the entire snapshot + // tree is converted into a single disk layer with the pruning target + // as the root. + if _, err := snaptree.Journal(stateBloomRoot); err != nil { + return err + } + // Delete the state bloom, it marks the entire pruning procedure is + // finished. If any crashes or manual exit happens before this, + // `RecoverPruning` will pick it up in the next restarts to redo all + // the things. + os.RemoveAll(stateBloomPath) + return nil +} + +// extractGenesis loads the genesis state and commits all the state entries +// into the given bloomfilter. +func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { + genesisHash := rawdb.ReadCanonicalHash(db, 0) + if genesisHash == (common.Hash{}) { + return errors.New("missing genesis hash") + } + genesis := rawdb.ReadBlock(db, genesisHash, 0) + if genesis == nil { + return errors.New("missing genesis block") + } + t, err := trie.NewSecure(genesis.Root(), trie.NewDatabase(db)) + if err != nil { + return err + } + accIter := t.NodeIterator(nil) + for accIter.Next(true) { + hash := accIter.Hash() + + // Embedded nodes don't have hash. + if hash != (common.Hash{}) { + stateBloom.Put(hash.Bytes(), nil) + } + // If it's a leaf node, yes we are touching an account, + // dig into the storage trie further. + if accIter.Leaf() { + var acc state.Account + if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { + return err + } + if acc.Root != emptyRoot { + storageTrie, err := trie.NewSecure(acc.Root, trie.NewDatabase(db)) + if err != nil { + return err + } + storageIter := storageTrie.NodeIterator(nil) + for storageIter.Next(true) { + hash := storageIter.Hash() + if hash != (common.Hash{}) { + stateBloom.Put(hash.Bytes(), nil) + } + } + if storageIter.Error() != nil { + return storageIter.Error() + } + } + if !bytes.Equal(acc.CodeHash, emptyCode) { + stateBloom.Put(acc.CodeHash, nil) + } + } + } + return accIter.Error() +} + +func bloomFilterName(datadir string, hash common.Hash) string { + return filepath.Join(datadir, fmt.Sprintf("%s.%s.%s", stateBloomFilePrefix, hash.Hex(), stateBloomFileSuffix)) +} + +func isBloomFilter(filename string) (bool, common.Hash) { + filename = filepath.Base(filename) + if strings.HasPrefix(filename, stateBloomFilePrefix) && strings.HasSuffix(filename, stateBloomFileSuffix) { + return true, common.HexToHash(filename[len(stateBloomFilePrefix)+1 : len(filename)-len(stateBloomFileSuffix)-1]) + } + return false, common.Hash{} +} + +func findBloomFilter(datadir string) (string, common.Hash, error) { + var ( + stateBloomPath string + stateBloomRoot common.Hash + ) + if err := filepath.Walk(datadir, func(path string, info os.FileInfo, err error) error { + if info != nil && !info.IsDir() { + ok, root := isBloomFilter(path) + if ok { + stateBloomPath = path + stateBloomRoot = root + } + } + return nil + }); err != nil { + return "", common.Hash{}, err + } + return stateBloomPath, stateBloomRoot, nil +} + +func getHeadHeader(db ethdb.Database) (*types.Header, error) { + headHeaderHash := rawdb.ReadHeadBlockHash(db) + if headHeaderHash == (common.Hash{}) { + return nil, errors.New("empty head block hash") + } + headHeaderNumber := rawdb.ReadHeaderNumber(db, headHeaderHash) + if headHeaderNumber == nil { + return nil, errors.New("empty head block number") + } + headHeader := rawdb.ReadHeader(db, headHeaderHash, *headHeaderNumber) + if headHeader == nil { + return nil, errors.New("empty head header") + } + return headHeader, nil +} + +const warningLog = ` + +WARNING! + +The clean trie cache is not found. Please delete it by yourself after the +pruning. Remember don't start the Geth without deleting the clean trie cache +otherwise the entire database may be damaged! + +Check the command description "geth snapshot prune-state --help" for more details. +` + +func deleteCleanTrieCache(path string) { + if _, err := os.Stat(path); os.IsNotExist(err) { + log.Warn(warningLog) + return + } + os.RemoveAll(path) + log.Info("Deleted trie clean cache", "path", path) +} diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index 4ec229b7ac..bb87ecddf1 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -18,12 +18,17 @@ package snapshot import ( "bytes" + "encoding/binary" + "errors" "fmt" + "math" + "runtime" "sync" "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" @@ -38,46 +43,56 @@ type trieKV struct { type ( // trieGeneratorFn is the interface of trie generation which can // be implemented by different trie algorithm. - trieGeneratorFn func(in chan trieKV, out chan common.Hash) + trieGeneratorFn func(db ethdb.KeyValueWriter, in chan (trieKV), out chan (common.Hash)) // leafCallbackFn is the callback invoked at the leaves of the trie, // returns the subtrie root with the specified subtrie identifier. - leafCallbackFn func(hash common.Hash, stat *generateStats) common.Hash + leafCallbackFn func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) ) // GenerateAccountTrieRoot takes an account iterator and reproduces the root hash. func GenerateAccountTrieRoot(it AccountIterator) (common.Hash, error) { - return generateTrieRoot(it, common.Hash{}, stdGenerate, nil, &generateStats{start: time.Now()}, true) + return generateTrieRoot(nil, it, common.Hash{}, stackTrieGenerate, nil, newGenerateStats(), true) } // GenerateStorageTrieRoot takes a storage iterator and reproduces the root hash. func GenerateStorageTrieRoot(account common.Hash, it StorageIterator) (common.Hash, error) { - return generateTrieRoot(it, account, stdGenerate, nil, &generateStats{start: time.Now()}, true) + return generateTrieRoot(nil, it, account, stackTrieGenerate, nil, newGenerateStats(), true) } -// VerifyState takes the whole snapshot tree as the input, traverses all the accounts -// as well as the corresponding storages and compares the re-computed hash with the -// original one(state root and the storage root). -func VerifyState(snaptree *Tree, root common.Hash) error { +// GenerateTrie takes the whole snapshot tree as the input, traverses all the +// accounts as well as the corresponding storages and regenerate the whole state +// (account trie + all storage tries). +func GenerateTrie(snaptree *Tree, root common.Hash, src ethdb.Database, dst ethdb.KeyValueWriter) error { + // Traverse all state by snapshot, re-generate the whole state trie acctIt, err := snaptree.AccountIterator(root, common.Hash{}) if err != nil { - return err + return err // The required snapshot might not exist. } defer acctIt.Release() - got, err := generateTrieRoot(acctIt, common.Hash{}, stdGenerate, func(account common.Hash, stat *generateStats) common.Hash { - storageIt, err := snaptree.StorageIterator(root, account, common.Hash{}) + got, err := generateTrieRoot(dst, acctIt, common.Hash{}, stackTrieGenerate, func(dst ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) { + // Migrate the code first, commit the contract code into the tmp db. + if codeHash != emptyCode { + code := rawdb.ReadCode(src, codeHash) + if len(code) == 0 { + return common.Hash{}, errors.New("failed to read contract code") + } + rawdb.WriteCode(dst, codeHash, code) + } + // Then migrate all storage trie nodes into the tmp db. + storageIt, err := snaptree.StorageIterator(root, accountHash, common.Hash{}) if err != nil { - return common.Hash{} + return common.Hash{}, err } defer storageIt.Release() - hash, err := generateTrieRoot(storageIt, account, stdGenerate, nil, stat, false) + hash, err := generateTrieRoot(dst, storageIt, accountHash, stackTrieGenerate, nil, stat, false) if err != nil { - return common.Hash{} + return common.Hash{}, err } - return hash - }, &generateStats{start: time.Now()}, true) + return hash, nil + }, newGenerateStats(), true) if err != nil { return err @@ -91,23 +106,64 @@ func VerifyState(snaptree *Tree, root common.Hash) error { // generateStats is a collection of statistics gathered by the trie generator // for logging purposes. type generateStats struct { - accounts uint64 - slots uint64 - curAccount common.Hash - curSlot common.Hash - start time.Time - lock sync.RWMutex + head common.Hash + start time.Time + + accounts uint64 // Number of accounts done (including those being crawled) + slots uint64 // Number of storage slots done (including those being crawled) + + slotsStart map[common.Hash]time.Time // Start time for account slot crawling + slotsHead map[common.Hash]common.Hash // Slot head for accounts being crawled + + lock sync.RWMutex } -// progress records the progress trie generator made recently. -func (stat *generateStats) progress(accounts, slots uint64, curAccount common.Hash, curSlot common.Hash) { +// newGenerateStats creates a new generator stats. +func newGenerateStats() *generateStats { + return &generateStats{ + slotsStart: make(map[common.Hash]time.Time), + slotsHead: make(map[common.Hash]common.Hash), + start: time.Now(), + } +} + +// progressAccounts updates the generator stats for the account range. +func (stat *generateStats) progressAccounts(account common.Hash, done uint64) { stat.lock.Lock() defer stat.lock.Unlock() - stat.accounts += accounts - stat.slots += slots - stat.curAccount = curAccount - stat.curSlot = curSlot + stat.accounts += done + stat.head = account +} + +// finishAccounts updates the gemerator stats for the finished account range. +func (stat *generateStats) finishAccounts(done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.accounts += done +} + +// progressContract updates the generator stats for a specific in-progress contract. +func (stat *generateStats) progressContract(account common.Hash, slot common.Hash, done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.slots += done + stat.slotsHead[account] = slot + if _, ok := stat.slotsStart[account]; !ok { + stat.slotsStart[account] = time.Now() + } +} + +// finishContract updates the generator stats for a specific just-finished contract. +func (stat *generateStats) finishContract(account common.Hash, done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.slots += done + delete(stat.slotsHead, account) + delete(stat.slotsStart, account) } // report prints the cumulative progress statistic smartly. @@ -115,22 +171,39 @@ func (stat *generateStats) report() { stat.lock.RLock() defer stat.lock.RUnlock() - var ctx []interface{} - if stat.curSlot != (common.Hash{}) { - ctx = append(ctx, []interface{}{ - "in", stat.curAccount, - "at", stat.curSlot, - }...) - } else { - ctx = append(ctx, []interface{}{"at", stat.curAccount}...) + ctx := []interface{}{ + "accounts", stat.accounts, + "slots", stat.slots, + "elapsed", common.PrettyDuration(time.Since(stat.start)), } - // Add the usual measurements - ctx = append(ctx, []interface{}{"accounts", stat.accounts}...) - if stat.slots != 0 { - ctx = append(ctx, []interface{}{"slots", stat.slots}...) + if stat.accounts > 0 { + // If there's progress on the account trie, estimate the time to finish crawling it + if done := binary.BigEndian.Uint64(stat.head[:8]) / stat.accounts; done > 0 { + var ( + left = (math.MaxUint64 - binary.BigEndian.Uint64(stat.head[:8])) / stat.accounts + speed = done/uint64(time.Since(stat.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + eta = time.Duration(left/speed) * time.Millisecond + ) + // If there are large contract crawls in progress, estimate their finish time + for acc, head := range stat.slotsHead { + start := stat.slotsStart[acc] + if done := binary.BigEndian.Uint64(head[:8]); done > 0 { + var ( + left = math.MaxUint64 - binary.BigEndian.Uint64(head[:8]) + speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + ) + // Override the ETA if larger than the largest until now + if slotETA := time.Duration(left/speed) * time.Millisecond; eta < slotETA { + eta = slotETA + } + } + } + ctx = append(ctx, []interface{}{ + "eta", common.PrettyDuration(eta), + }...) + } } - ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...) - log.Info("Generating trie hash from snapshot", ctx...) + log.Info("Iterating state snapshot", ctx...) } // reportDone prints the last log when the whole generation is finished. @@ -144,13 +217,32 @@ func (stat *generateStats) reportDone() { ctx = append(ctx, []interface{}{"slots", stat.slots}...) } ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...) - log.Info("Generated trie hash from snapshot", ctx...) + log.Info("Iterated snapshot", ctx...) +} + +// runReport periodically prints the progress information. +func runReport(stats *generateStats, stop chan bool) { + timer := time.NewTimer(0) + defer timer.Stop() + + for { + select { + case <-timer.C: + stats.report() + timer.Reset(time.Second * 8) + case success := <-stop: + if success { + stats.reportDone() + } + return + } + } } // generateTrieRoot generates the trie hash based on the snapshot iterator. // It can be used for generating account trie, storage trie or even the // whole state which connects the accounts and the corresponding storages. -func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { +func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { var ( in = make(chan trieKV) // chan to pass leaves out = make(chan common.Hash, 1) // chan to collect result @@ -161,46 +253,43 @@ func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGenerato wg.Add(1) go func() { defer wg.Done() - generatorFn(in, out) + generatorFn(db, in, out) }() - // Spin up a go-routine for progress logging if report && stats != nil { wg.Add(1) go func() { defer wg.Done() - - timer := time.NewTimer(0) - defer timer.Stop() - - for { - select { - case <-timer.C: - stats.report() - timer.Reset(time.Second * 8) - case success := <-stoplog: - if success { - stats.reportDone() - } - return - } - } + runReport(stats, stoplog) }() } + // Create a semaphore to assign tasks and collect results through. We'll pre- + // fill it with nils, thus using the same channel for both limiting concurrent + // processing and gathering results. + threads := runtime.NumCPU() + results := make(chan error, threads) + for i := 0; i < threads; i++ { + results <- nil // fill the semaphore + } // stop is a helper function to shutdown the background threads // and return the re-generated trie hash. - stop := func(success bool) common.Hash { + stop := func(fail error) (common.Hash, error) { close(in) result := <-out - stoplog <- success + for i := 0; i < threads; i++ { + if err := <-results; err != nil && fail == nil { + fail = err + } + } + stoplog <- fail == nil + wg.Wait() - return result + return result, fail } var ( logged = time.Now() processed = uint64(0) leaf trieKV - last common.Hash ) // Start to feed leaves for it.Next() { @@ -212,26 +301,35 @@ func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGenerato if leafCallback == nil { fullData, err = FullAccountRLP(it.(AccountIterator).Account()) if err != nil { - stop(false) - return common.Hash{}, err + return stop(err) } } else { + // Wait until the semaphore allows us to continue, aborting if + // a sub-task failed + if err := <-results; err != nil { + results <- nil // stop will drain the results, add a noop back for this error we just consumed + return stop(err) + } + // Fetch the next account and process it concurrently account, err := FullAccount(it.(AccountIterator).Account()) if err != nil { - stop(false) - return common.Hash{}, err - } - // Apply the leaf callback. Normally the callback is used to traverse - // the storage trie and re-generate the subtrie root. - subroot := leafCallback(it.Hash(), stats) - if !bytes.Equal(account.Root, subroot.Bytes()) { - stop(false) - return common.Hash{}, fmt.Errorf("invalid subroot(%x), want %x, got %x", it.Hash(), account.Root, subroot) + return stop(err) } + go func(hash common.Hash) { + subroot, err := leafCallback(db, hash, common.BytesToHash(account.CodeHash), stats) + if err != nil { + results <- err + return + } + if !bytes.Equal(account.Root, subroot.Bytes()) { + results <- fmt.Errorf("invalid subroot(%x), want %x, got %x", it.Hash(), account.Root, subroot) + return + } + results <- nil + }(it.Hash()) fullData, err = rlp.EncodeToBytes(account) if err != nil { - stop(false) - return common.Hash{}, err + return stop(err) } } leaf = trieKV{it.Hash(), fullData} @@ -244,32 +342,34 @@ func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGenerato processed++ if time.Since(logged) > 3*time.Second && stats != nil { if account == (common.Hash{}) { - stats.progress(processed, 0, it.Hash(), common.Hash{}) + stats.progressAccounts(it.Hash(), processed) } else { - stats.progress(0, processed, account, it.Hash()) + stats.progressContract(account, it.Hash(), processed) } logged, processed = time.Now(), 0 } - last = it.Hash() } // Commit the last part statistic. if processed > 0 && stats != nil { if account == (common.Hash{}) { - stats.progress(processed, 0, last, common.Hash{}) + stats.finishAccounts(processed) } else { - stats.progress(0, processed, account, last) + stats.finishContract(account, processed) } } - result := stop(true) - return result, nil + return stop(nil) } -// stdGenerate is a very basic hexary trie builder which uses the same Trie -// as the rest of geth, with no enhancements or optimizations -func stdGenerate(in chan trieKV, out chan common.Hash) { - t, _ := trie.New(common.Hash{}, trie.NewDatabase(memorydb.New())) +func stackTrieGenerate(db ethdb.KeyValueWriter, in chan trieKV, out chan common.Hash) { + t := trie.NewStackTrie(db) for leaf := range in { t.TryUpdate(leaf.key[:], leaf.value) } - out <- t.Hash() + var root common.Hash + if db == nil { + root = t.Hash() + } else { + root, _ = t.Commit() + } + out <- root } diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 443fc8e5c7..df2b1ed330 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -178,7 +178,7 @@ type Tree struct { // store, on a background thread. If the memory layers from the journal is not // continuous with disk layer or the journal is missing, all diffs will be discarded // iff it's in "recovery" mode, otherwise rebuild is mandatory. -func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool, recovery bool) *Tree { +func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool, rebuild bool, recovery bool) (*Tree, error) { // Create a new, empty snapshot tree snap := &Tree{ diskdb: diskdb, @@ -192,16 +192,19 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm // Attempt to load a previously persisted snapshot and rebuild one if failed head, err := loadSnapshot(diskdb, triedb, cache, root, recovery) if err != nil { - log.Warn("Failed to load snapshot, regenerating", "err", err) - snap.Rebuild(root) - return snap + if rebuild { + log.Warn("Failed to load snapshot, regenerating", "err", err) + snap.Rebuild(root) + return snap, nil + } + return nil, err // Bail out the error, don't rebuild automatically. } // Existing snapshot loaded, seed all the layers for head != nil { snap.layers[head.Root()] = head head = head.Parent() } - return snap + return snap, nil } // waitBuild blocks until the snapshot finishes rebuilding. This method is meant @@ -234,6 +237,39 @@ func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot { return t.layers[blockRoot] } +// Snapshots returns all visited layers from the topmost layer with specific +// root and traverses downward. The layer amount is limited by the given number. +// If nodisk is set, then disk layer is excluded. +func (t *Tree) Snapshots(root common.Hash, limits int, nodisk bool) []Snapshot { + t.lock.RLock() + defer t.lock.RUnlock() + + if limits == 0 { + return nil + } + layer := t.layers[root] + if layer == nil { + return nil + } + var ret []Snapshot + for { + if _, isdisk := layer.(*diskLayer); isdisk && nodisk { + break + } + ret = append(ret, layer) + limits -= 1 + if limits == 0 { + break + } + parent := layer.Parent() + if parent == nil { + break + } + layer = parent + } + return ret +} + // Update adds a new snapshot into the tree, if that can be linked to an existing // old parent. It is disallowed to insert a disk layer (the origin of all). func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { @@ -681,6 +717,38 @@ func (t *Tree) StorageIterator(root common.Hash, account common.Hash, seek commo return newFastStorageIterator(t, root, account, seek) } +// Verify iterates the whole state(all the accounts as well as the corresponding storages) +// with the specific root and compares the re-computed hash with the original one. +func (t *Tree) Verify(root common.Hash) error { + acctIt, err := t.AccountIterator(root, common.Hash{}) + if err != nil { + return err + } + defer acctIt.Release() + + got, err := generateTrieRoot(nil, acctIt, common.Hash{}, stackTrieGenerate, func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) { + storageIt, err := t.StorageIterator(root, accountHash, common.Hash{}) + if err != nil { + return common.Hash{}, err + } + defer storageIt.Release() + + hash, err := generateTrieRoot(nil, storageIt, accountHash, stackTrieGenerate, nil, stat, false) + if err != nil { + return common.Hash{}, err + } + return hash, nil + }, newGenerateStats(), true) + + if err != nil { + return err + } + if got != root { + return fmt.Errorf("state root hash mismatch: got %x, want %x", got, root) + } + return nil +} + // disklayer is an internal helper function to return the disk layer. // The lock of snapTree is assumed to be held already. func (t *Tree) disklayer() *diskLayer { diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index ca4fa0a055..a315fd216c 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -17,6 +17,7 @@ package snapshot import ( + "encoding/binary" "fmt" "math/big" "math/rand" @@ -369,3 +370,69 @@ func TestPostCapBasicDataAccess(t *testing.T) { t.Error("expected error capping the disk layer, got none") } } + +// TestSnaphots tests the functionality for retrieveing the snapshot +// with given head root and the desired depth. +func TestSnaphots(t *testing.T) { + // setAccount is a helper to construct a random account entry and assign it to + // an account slot in a snapshot + setAccount := func(accKey string) map[common.Hash][]byte { + return map[common.Hash][]byte{ + common.HexToHash(accKey): randomAccount(), + } + } + makeRoot := func(height uint64) common.Hash { + var buffer [8]byte + binary.BigEndian.PutUint64(buffer[:], height) + return common.BytesToHash(buffer[:]) + } + // Create a starting base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Construct the snapshots with 128 layers + var ( + last = common.HexToHash("0x01") + head common.Hash + ) + // Flush another 128 layers, one diff will be flatten into the parent. + for i := 0; i < 128; i++ { + head = makeRoot(uint64(i + 2)) + snaps.Update(head, last, nil, setAccount(fmt.Sprintf("%d", i+2)), nil) + last = head + snaps.Cap(head, 128) // 129 layers(128 diffs + 1 disk) are allowed, 129th is the persistent layer + } + var cases = []struct { + headRoot common.Hash + limit int + nodisk bool + expected int + expectBottom common.Hash + }{ + {head, 0, false, 0, common.Hash{}}, + {head, 64, false, 64, makeRoot(127 + 2 - 63)}, + {head, 128, false, 128, makeRoot(2)}, // All diff layers + {head, 129, true, 128, makeRoot(2)}, // All diff layers + {head, 129, false, 129, common.HexToHash("0x01")}, // All diff layers + disk layer + } + for _, c := range cases { + layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk) + if len(layers) != c.expected { + t.Fatalf("Returned snapshot layers are mismatched, want %v, got %v", c.expected, len(layers)) + } + if len(layers) == 0 { + continue + } + bottommost := layers[len(layers)-1] + if bottommost.Root() != c.expectBottom { + t.Fatalf("Snapshot mismatch, want %v, get %v", c.expectBottom, bottommost.Root()) + } + } +} diff --git a/eth/backend.go b/eth/backend.go index 51ad0ccf3f..4170ecc94e 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/pruner" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" @@ -131,6 +132,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } log.Info("Initialised chain configuration", "config", chainConfig) + if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil { + log.Error("Failed to recover state", "error", err) + } eth := &Ethereum{ config: config, chainDb: chainDb, diff --git a/go.mod b/go.mod index 5fd3f772a4..cddce4e0d8 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,7 @@ module github.com/ethereum/go-ethereum go 1.13 require ( - github.com/Azure/azure-pipeline-go v0.2.2 // indirect github.com/Azure/azure-storage-blob-go v0.7.0 - github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect - github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.5.7 github.com/aws/aws-sdk-go v1.25.48 github.com/btcsuite/btcd v0.20.1-beta @@ -15,15 +12,12 @@ require ( github.com/consensys/gurvy v0.3.8 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea - github.com/dlclark/regexp2 v1.2.0 // indirect github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 github.com/edsrzf/mmap-go v1.0.0 - github.com/fatih/color v1.3.0 + github.com/fatih/color v1.7.0 github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff - github.com/go-ole/go-ole v1.2.1 // indirect - github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-stack/stack v1.8.0 github.com/golang/protobuf v1.4.3 github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 @@ -39,15 +33,13 @@ require ( github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e github.com/julienschmidt/httprouter v1.2.0 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 - github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.0 github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 - github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 - github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 + github.com/prometheus/tsdb v0.7.1 github.com/rjeczalik/notify v0.9.1 github.com/rs/cors v1.7.0 github.com/shirou/gopsutil v2.20.5+incompatible @@ -56,12 +48,10 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect - golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c golang.org/x/text v0.3.3 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 - gotest.tools v2.2.0+incompatible // indirect ) diff --git a/go.sum b/go.sum index 1ac0cf81f2..4b788824c7 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,7 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -253,8 +254,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 h1:HarGZ5h9HD9LgEg1yRVMXyfiw4wlXiLiYM2oMjeA/SE= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= @@ -263,6 +264,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= @@ -275,12 +277,10 @@ github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJye github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89 h1:12K8AlpT0/6QUXSfV0yi4Q0jkbq8NDtIKFtF61AoqV0= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -298,7 +298,6 @@ github.com/kilic/bls12-381 v0.0.0-20201226121925-69dacb279461/go.mod h1:vDTTHJON github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= @@ -401,7 +400,6 @@ github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 h1:ZeU+auZj1iNzN8iVhff6M38Mfu73FQiJve/GEXYJBjE= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= @@ -440,13 +438,11 @@ github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57N github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -570,7 +566,6 @@ golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/tests/block_test_util.go b/tests/block_test_util.go index c043f0b3eb..745208b5b8 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -32,7 +32,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -147,7 +146,7 @@ func (t *BlockTest) Run(snapshotter bool) error { } // Cross-check the snapshot-to-hash against the trie hash if snapshotter { - if err := snapshot.VerifyState(chain.Snapshots(), chain.CurrentBlock().Root()); err != nil { + if err := chain.Snapshots().Verify(chain.CurrentBlock().Root()); err != nil { return err } } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 7bd2e801e4..53c7d955be 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -236,7 +236,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo var snaps *snapshot.Tree if snapshotter { - snaps = snapshot.New(db, sdb.TrieDB(), 1, root, false, false) + snaps, _ = snapshot.New(db, sdb.TrieDB(), 1, root, false, true, false) } statedb, _ = state.New(root, sdb, snaps) return snaps, statedb diff --git a/trie/stacktrie.go b/trie/stacktrie.go index 575a04022f..a198eb204b 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -35,7 +35,7 @@ var stPool = sync.Pool{ }, } -func stackTrieFromPool(db ethdb.KeyValueStore) *StackTrie { +func stackTrieFromPool(db ethdb.KeyValueWriter) *StackTrie { st := stPool.Get().(*StackTrie) st.db = db return st @@ -50,24 +50,23 @@ func returnToPool(st *StackTrie) { // in order. Once it determines that a subtree will no longer be inserted // into, it will hash it and free up the memory it uses. type StackTrie struct { - nodeType uint8 // node type (as in branch, ext, leaf) - val []byte // value contained by this node if it's a leaf - key []byte // key chunk covered by this (full|ext) node - keyOffset int // offset of the key chunk inside a full key - children [16]*StackTrie // list of children (for fullnodes and exts) - - db ethdb.KeyValueStore // Pointer to the commit db, can be nil + nodeType uint8 // node type (as in branch, ext, leaf) + val []byte // value contained by this node if it's a leaf + key []byte // key chunk covered by this (full|ext) node + keyOffset int // offset of the key chunk inside a full key + children [16]*StackTrie // list of children (for fullnodes and exts) + db ethdb.KeyValueWriter // Pointer to the commit db, can be nil } // NewStackTrie allocates and initializes an empty trie. -func NewStackTrie(db ethdb.KeyValueStore) *StackTrie { +func NewStackTrie(db ethdb.KeyValueWriter) *StackTrie { return &StackTrie{ nodeType: emptyNode, db: db, } } -func newLeaf(ko int, key, val []byte, db ethdb.KeyValueStore) *StackTrie { +func newLeaf(ko int, key, val []byte, db ethdb.KeyValueWriter) *StackTrie { st := stackTrieFromPool(db) st.nodeType = leafNode st.keyOffset = ko @@ -76,7 +75,7 @@ func newLeaf(ko int, key, val []byte, db ethdb.KeyValueStore) *StackTrie { return st } -func newExt(ko int, key []byte, child *StackTrie, db ethdb.KeyValueStore) *StackTrie { +func newExt(ko int, key []byte, child *StackTrie, db ethdb.KeyValueWriter) *StackTrie { st := stackTrieFromPool(db) st.nodeType = extNode st.keyOffset = ko From 2728672c28183da21028379ea5497debe92325b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 8 Feb 2021 17:02:30 +0200 Subject: [PATCH 181/235] core/state/pruner: fix compaction after pruning --- core/state/pruner/pruner.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 1cb65b38b1..84a8952b57 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -188,8 +188,15 @@ func prune(maindb ethdb.Database, stateBloom *stateBloom, middleStateRoots map[c cstart := time.Now() for b := byte(0); b < byte(16); b++ { - log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", b, b+1), "elapsed", common.PrettyDuration(time.Since(cstart))) - if err := maindb.Compact([]byte{b}, []byte{b + 1}); err != nil { + var ( + start = []byte{b << 4} + end = []byte{(b+1)<<4 - 1} + ) + log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart))) + if b == 15 { + end = nil + } + if err := maindb.Compact(start, end); err != nil { log.Error("Database compaction failed", "error", err) return err } @@ -229,7 +236,7 @@ func (p *Pruner) Prune(root common.Hash) error { // Reject if the accumulated diff layers are less than 128. It // means in most of normal cases, there is no associated state // with bottom-most diff layer. - return errors.New("the snapshot difflayers are less than 128") + return fmt.Errorf("snapshot not old enough yet: need %d more blocks", 128-len(layers)) } // Use the bottom-most diff layer as the target root = layers[len(layers)-1].Root() From 74dbc20260caac0159ca59bfad1f41321130e676 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 8 Feb 2021 20:31:52 +0100 Subject: [PATCH 182/235] core/state/pruner: fix compaction range error --- core/state/pruner/pruner.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 84a8952b57..1fbfa55b6a 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -186,16 +186,15 @@ func prune(maindb ethdb.Database, stateBloom *stateBloom, middleStateRoots map[c // Note for small pruning, the compaction is skipped. if count >= rangeCompactionThreshold { cstart := time.Now() - - for b := byte(0); b < byte(16); b++ { + for b := 0x00; b <= 0xf0; b += 0x10 { var ( - start = []byte{b << 4} - end = []byte{(b+1)<<4 - 1} + start = []byte{byte(b)} + end = []byte{byte(b + 0x10)} ) - log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart))) - if b == 15 { + if b == 0xf0 { end = nil } + log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart))) if err := maindb.Compact(start, end); err != nil { log.Error("Database compaction failed", "error", err) return err From 27786671d28705159f15cd458045d29d732110e5 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Tue, 9 Feb 2021 10:42:55 +0100 Subject: [PATCH 183/235] internal/debug: add switch to format logs with json (#22207) adds a flag --log.json which if enabled makes the client format logs with JSON. --- internal/debug/flags.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/internal/debug/flags.go b/internal/debug/flags.go index e3a01378ca..fc4ea9f518 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -41,6 +41,10 @@ var ( Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail", Value: 3, } + logjsonFlag = cli.BoolFlag{ + Name: "log.json", + Usage: "Format logs with JSON", + } vmoduleFlag = cli.StringFlag{ Name: "vmodule", Usage: "Per-module verbosity: comma-separated list of = (e.g. eth/*=5,p2p=4)", @@ -114,7 +118,7 @@ var ( // Flags holds all command-line flags required for debugging. var Flags = []cli.Flag{ - verbosityFlag, vmoduleFlag, backtraceAtFlag, debugFlag, + verbosityFlag, logjsonFlag, vmoduleFlag, backtraceAtFlag, debugFlag, pprofFlag, pprofAddrFlag, pprofPortFlag, memprofilerateFlag, blockprofilerateFlag, cpuprofileFlag, traceFlag, } @@ -124,24 +128,29 @@ var DeprecatedFlags = []cli.Flag{ legacyBlockprofilerateFlag, legacyCpuprofileFlag, } -var ( - ostream log.Handler - glogger *log.GlogHandler -) +var glogger *log.GlogHandler func init() { - usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" - output := io.Writer(os.Stderr) - if usecolor { - output = colorable.NewColorableStderr() - } - ostream = log.StreamHandler(output, log.TerminalFormat(usecolor)) - glogger = log.NewGlogHandler(ostream) + glogger = log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) + glogger.Verbosity(log.LvlInfo) + log.Root().SetHandler(glogger) } // Setup initializes profiling and logging based on the CLI flags. // It should be called as early as possible in the program. func Setup(ctx *cli.Context) error { + var ostream log.Handler + output := io.Writer(os.Stderr) + if ctx.GlobalBool(logjsonFlag.Name) { + ostream = log.StreamHandler(output, log.JSONFormat()) + } else { + usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" + if usecolor { + output = colorable.NewColorableStderr() + } + ostream = log.StreamHandler(output, log.TerminalFormat(usecolor)) + } + glogger.SetHandler(ostream) // logging log.PrintOrigins(ctx.GlobalBool(debugFlag.Name)) glogger.Verbosity(log.Lvl(ctx.GlobalInt(verbosityFlag.Name))) From cb3c7e431978f0bd5efb19b378aa9e42d940986d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 10 Feb 2021 13:12:13 +0100 Subject: [PATCH 184/235] accounts/abi/bind: fixed unpacking error (#22230) There was a dormant error with structured inputs that failed unpacking. This commit fixes the error by switching casting to the better abi.ConvertType function. It also adds a test for calling a view function that returns a struct --- accounts/abi/abi_test.go | 3 ++ accounts/abi/bind/bind_test.go | 64 ++++++++++++++++++++++++++++++++++ accounts/abi/bind/template.go | 2 +- 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index ad8acdf522..a022ec5f9d 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -43,6 +43,7 @@ const jsondata = ` { "type" : "function", "name" : "uint64[2]", "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, { "type" : "function", "name" : "uint64[]", "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, { "type" : "function", "name" : "int8", "inputs" : [ { "name" : "inputs", "type" : "int8" } ] }, + { "type" : "function", "name" : "bytes32", "inputs" : [ { "name" : "inputs", "type" : "bytes32" } ] }, { "type" : "function", "name" : "foo", "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, { "type" : "function", "name" : "bar", "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, { "type" : "function", "name" : "slice", "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, @@ -68,6 +69,7 @@ var ( String, _ = NewType("string", "", nil) Bool, _ = NewType("bool", "", nil) Bytes, _ = NewType("bytes", "", nil) + Bytes32, _ = NewType("bytes32", "", nil) Address, _ = NewType("address", "", nil) Uint64Arr, _ = NewType("uint64[]", "", nil) AddressArr, _ = NewType("address[]", "", nil) @@ -98,6 +100,7 @@ var methods = map[string]Method{ "uint64[]": NewMethod("uint64[]", "uint64[]", Function, "", false, false, []Argument{{"inputs", Uint64Arr, false}}, nil), "uint64[2]": NewMethod("uint64[2]", "uint64[2]", Function, "", false, false, []Argument{{"inputs", Uint64Arr2, false}}, nil), "int8": NewMethod("int8", "int8", Function, "", false, false, []Argument{{"inputs", Int8, false}}, nil), + "bytes32": NewMethod("bytes32", "bytes32", Function, "", false, false, []Argument{{"inputs", Bytes32, false}}, nil), "foo": NewMethod("foo", "foo", Function, "", false, false, []Argument{{"inputs", Uint32, false}}, nil), "bar": NewMethod("bar", "bar", Function, "", false, false, []Argument{{"inputs", Uint32, false}, {"string", Uint16, false}}, nil), "slice": NewMethod("slice", "slice", Function, "", false, false, []Argument{{"inputs", Uint32Arr2, false}}, nil), diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index db87703d03..400545fd23 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -529,6 +529,70 @@ var bindTests = []struct { nil, nil, }, + // Tests that structs are correctly unpacked + { + + `Structs`, + ` + pragma solidity ^0.6.5; + pragma experimental ABIEncoderV2; + contract Structs { + struct A { + bytes32 B; + } + + function F() public view returns (A[] memory a, uint256[] memory c, bool[] memory d) { + A[] memory a = new A[](2); + a[0].B = bytes32(uint256(1234) << 96); + uint256[] memory c; + bool[] memory d; + return (a, c, d); + } + + function G() public view returns (A[] memory a) { + A[] memory a = new A[](2); + a[0].B = bytes32(uint256(1234) << 96); + return a; + } + } + `, + []string{`608060405234801561001057600080fd5b50610278806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806328811f591461003b5780636fecb6231461005b575b600080fd5b610043610070565b604051610052939291906101a0565b60405180910390f35b6100636100d6565b6040516100529190610186565b604080516002808252606082810190935282918291829190816020015b610095610131565b81526020019060019003908161008d575050805190915061026960611b9082906000906100be57fe5b60209081029190910101515293606093508392509050565b6040805160028082526060828101909352829190816020015b6100f7610131565b8152602001906001900390816100ef575050805190915061026960611b90829060009061012057fe5b602090810291909101015152905090565b60408051602081019091526000815290565b815260200190565b6000815180845260208085019450808401835b8381101561017b578151518752958201959082019060010161015e565b509495945050505050565b600060208252610199602083018461014b565b9392505050565b6000606082526101b3606083018661014b565b6020838203818501528186516101c98185610239565b91508288019350845b818110156101f3576101e5838651610143565b9484019492506001016101d2565b505084810360408601528551808252908201925081860190845b8181101561022b57825115158552938301939183019160010161020d565b509298975050505050505050565b9081526020019056fea2646970667358221220eb85327e285def14230424c52893aebecec1e387a50bb6b75fc4fdbed647f45f64736f6c63430006050033`}, + []string{`[{"inputs":[],"name":"F","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"structStructs.A[]","name":"a","type":"tuple[]"},{"internalType":"uint256[]","name":"c","type":"uint256[]"},{"internalType":"bool[]","name":"d","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"G","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"structStructs.A[]","name":"a","type":"tuple[]"}],"stateMutability":"view","type":"function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + defer sim.Close() + + // Deploy a structs method invoker contract and execute its default method + _, _, structs, err := DeployStructs(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy defaulter contract: %v", err) + } + sim.Commit() + opts := bind.CallOpts{} + if _, err := structs.F(&opts); err != nil { + t.Fatalf("Failed to invoke F method: %v", err) + } + if _, err := structs.G(&opts); err != nil { + t.Fatalf("Failed to invoke G method: %v", err) + } + `, + nil, + nil, + nil, + nil, + }, // Tests that non-existent contracts are reported as such (though only simulator test) { `NonExistent`, diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 351eabd258..e9bdd3d414 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -308,7 +308,7 @@ var ( return *outstruct, err } {{range $i, $t := .Normalized.Outputs}} - outstruct.{{.Name}} = out[{{$i}}].({{bindtype .Type $structs}}){{end}} + outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} return *outstruct, err {{else}} From 409b16e5abac3a48c21142fdfa68d33cf6c95fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 8 Feb 2021 20:44:05 +0200 Subject: [PATCH 185/235] cmd/utils, eth/ethconfig: unindex txs older than ~1 year --- cmd/utils/flags.go | 6 +++--- eth/ethconfig/config.go | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index f9c5553961..0d7b0e1bf5 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -212,10 +212,10 @@ var ( Name: "snapshot", Usage: `Enables snapshot-database mode (default = enable)`, } - TxLookupLimitFlag = cli.Int64Flag{ + TxLookupLimitFlag = cli.Uint64Flag{ Name: "txlookuplimit", - Usage: "Number of recent blocks to maintain transactions index by-hash for (default = index all blocks)", - Value: 0, + Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain)", + Value: ethconfig.Defaults.TxLookupLimit, } LightKDFFlag = cli.BoolFlag{ Name: "lightkdf", diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 9147a602d5..e192e4d333 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -66,6 +66,7 @@ var Defaults = Config{ DatasetsLockMmap: false, }, NetworkId: 1, + TxLookupLimit: 2350000, LightPeers: 100, UltraLightFraction: 75, DatabaseCache: 512, From 111abdcfbdc3c73b527589dce7863d3b93eca91d Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 11 Feb 2021 12:09:13 +0100 Subject: [PATCH 186/235] cmd/devp2p: fix documentation for eth-test (#22298) --- cmd/devp2p/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md index e1372d0158..ca0b852fda 100644 --- a/cmd/devp2p/README.md +++ b/cmd/devp2p/README.md @@ -94,10 +94,12 @@ To run the eth protocol test suite against your implementation, the node needs t geth --datadir --nodiscover --nat=none --networkid 19763 --verbosity 5 ``` -Then, run the following command, replacing `` with the enode of the geth node: +Then, run the following command, replacing `` with the enode of the geth node: ``` - devp2p rlpx eth-test cmd/devp2p/internal/ethtest/testdata/fullchain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json + devp2p rlpx eth-test cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json ``` + +Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again. [eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md [dns-tutorial]: https://geth.ethereum.org/docs/developers/dns-discovery-setup From ef227c5f42a2e180b0e3b57d38ef5018fc8733d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 12 Feb 2021 12:45:34 +0200 Subject: [PATCH 187/235] core: fix temp memory blowup caused by defers holding on to state --- core/blockchain.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index 7dd1097e98..d65ce4f048 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1805,6 +1805,17 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er return it.index, err } // No validation errors for the first block (or chain prefix skipped) + var activeState *state.StateDB + defer func() { + // The chain importer is starting and stopping trie prefetchers. If a bad + // block or other error is hit however, an early return may not properly + // terminate the background threads. This defer ensures that we clean up + // and dangling prefetcher, without defering each and holding on live refs. + if activeState != nil { + activeState.StopPrefetcher() + } + }() + for ; block != nil && err == nil || err == ErrKnownBlock; block, err = it.next() { // If the chain is terminating, stop processing blocks if bc.insertStopped() { @@ -1867,7 +1878,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er } // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain") - defer statedb.StopPrefetcher() // stopped on write anyway, defer meant to catch early error returns + activeState = statedb // If we have a followup block, run that against the current state to pre-cache // transactions and probabilistically some of the account/storage trie nodes. From 7d1b711c7d0f27efd7772c81bb73b9b29720515a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Fri, 12 Feb 2021 20:48:18 +0100 Subject: [PATCH 188/235] les: enable les/4 and add tests (#22321) --- les/handler_test.go | 10 ++++++++++ les/odr_test.go | 5 +++++ les/protocol.go | 4 ++-- les/request_test.go | 4 ++++ les/test_helper.go | 12 ++++++++++-- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/les/handler_test.go b/les/handler_test.go index f5cbeb8efc..83be312081 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -49,6 +49,7 @@ func expectResponse(r p2p.MsgReader, msgcode, reqID, bv uint64, data interface{} // Tests that block headers can be retrieved from a remote chain based on user queries. func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) } func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) } +func TestGetBlockHeadersLes4(t *testing.T) { testGetBlockHeaders(t, 4) } func testGetBlockHeaders(t *testing.T, protocol int) { server, tearDown := newServerEnv(t, downloader.MaxHeaderFetch+15, protocol, nil, false, true, 0) @@ -178,6 +179,7 @@ func testGetBlockHeaders(t *testing.T, protocol int) { // Tests that block contents can be retrieved from a remote chain based on their hashes. func TestGetBlockBodiesLes2(t *testing.T) { testGetBlockBodies(t, 2) } func TestGetBlockBodiesLes3(t *testing.T) { testGetBlockBodies(t, 3) } +func TestGetBlockBodiesLes4(t *testing.T) { testGetBlockBodies(t, 4) } func testGetBlockBodies(t *testing.T, protocol int) { server, tearDown := newServerEnv(t, downloader.MaxBlockFetch+15, protocol, nil, false, true, 0) @@ -255,6 +257,7 @@ func testGetBlockBodies(t *testing.T, protocol int) { // Tests that the contract codes can be retrieved based on account addresses. func TestGetCodeLes2(t *testing.T) { testGetCode(t, 2) } func TestGetCodeLes3(t *testing.T) { testGetCode(t, 3) } +func TestGetCodeLes4(t *testing.T) { testGetCode(t, 4) } func testGetCode(t *testing.T, protocol int) { // Assemble the test environment @@ -285,6 +288,7 @@ func testGetCode(t *testing.T, protocol int) { // Tests that the stale contract codes can't be retrieved based on account addresses. func TestGetStaleCodeLes2(t *testing.T) { testGetStaleCode(t, 2) } func TestGetStaleCodeLes3(t *testing.T) { testGetStaleCode(t, 3) } +func TestGetStaleCodeLes4(t *testing.T) { testGetStaleCode(t, 4) } func testGetStaleCode(t *testing.T, protocol int) { server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0) @@ -309,6 +313,7 @@ func testGetStaleCode(t *testing.T, protocol int) { // Tests that the transaction receipts can be retrieved based on hashes. func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) } func TestGetReceiptLes3(t *testing.T) { testGetReceipt(t, 3) } +func TestGetReceiptLes4(t *testing.T) { testGetReceipt(t, 4) } func testGetReceipt(t *testing.T, protocol int) { // Assemble the test environment @@ -336,6 +341,7 @@ func testGetReceipt(t *testing.T, protocol int) { // Tests that trie merkle proofs can be retrieved func TestGetProofsLes2(t *testing.T) { testGetProofs(t, 2) } func TestGetProofsLes3(t *testing.T) { testGetProofs(t, 3) } +func TestGetProofsLes4(t *testing.T) { testGetProofs(t, 4) } func testGetProofs(t *testing.T, protocol int) { // Assemble the test environment @@ -371,6 +377,7 @@ func testGetProofs(t *testing.T, protocol int) { // Tests that the stale contract codes can't be retrieved based on account addresses. func TestGetStaleProofLes2(t *testing.T) { testGetStaleProof(t, 2) } func TestGetStaleProofLes3(t *testing.T) { testGetStaleProof(t, 3) } +func TestGetStaleProofLes4(t *testing.T) { testGetStaleProof(t, 4) } func testGetStaleProof(t *testing.T, protocol int) { server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0) @@ -407,6 +414,7 @@ func testGetStaleProof(t *testing.T, protocol int) { // Tests that CHT proofs can be correctly retrieved. func TestGetCHTProofsLes2(t *testing.T) { testGetCHTProofs(t, 2) } func TestGetCHTProofsLes3(t *testing.T) { testGetCHTProofs(t, 3) } +func TestGetCHTProofsLes4(t *testing.T) { testGetCHTProofs(t, 4) } func testGetCHTProofs(t *testing.T, protocol int) { config := light.TestServerIndexerConfig @@ -454,6 +462,7 @@ func testGetCHTProofs(t *testing.T, protocol int) { func TestGetBloombitsProofsLes2(t *testing.T) { testGetBloombitsProofs(t, 2) } func TestGetBloombitsProofsLes3(t *testing.T) { testGetBloombitsProofs(t, 3) } +func TestGetBloombitsProofsLes4(t *testing.T) { testGetBloombitsProofs(t, 4) } // Tests that bloombits proofs can be correctly retrieved. func testGetBloombitsProofs(t *testing.T, protocol int) { @@ -503,6 +512,7 @@ func testGetBloombitsProofs(t *testing.T, protocol int) { func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, 2) } func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, 3) } +func TestTransactionStatusLes4(t *testing.T) { testTransactionStatus(t, 4) } func testTransactionStatus(t *testing.T, protocol int) { server, tearDown := newServerEnv(t, 0, protocol, nil, false, true, 0) diff --git a/les/odr_test.go b/les/odr_test.go index c157507dd2..2a70a296c8 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -40,6 +40,7 @@ type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.Chain func TestOdrGetBlockLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetBlock) } func TestOdrGetBlockLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetBlock) } +func TestOdrGetBlockLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetBlock) } func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { var block *types.Block @@ -57,6 +58,7 @@ func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainCon func TestOdrGetReceiptsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetReceipts) } func TestOdrGetReceiptsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetReceipts) } +func TestOdrGetReceiptsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetReceipts) } func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { var receipts types.Receipts @@ -78,6 +80,7 @@ func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.Chain func TestOdrAccountsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrAccounts) } func TestOdrAccountsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrAccounts) } +func TestOdrAccountsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrAccounts) } func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678") @@ -107,6 +110,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainCon func TestOdrContractCallLes2(t *testing.T) { testOdr(t, 2, 2, true, odrContractCall) } func TestOdrContractCallLes3(t *testing.T) { testOdr(t, 3, 2, true, odrContractCall) } +func TestOdrContractCallLes4(t *testing.T) { testOdr(t, 4, 2, true, odrContractCall) } type callmsg struct { types.Message @@ -159,6 +163,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai func TestOdrTxStatusLes2(t *testing.T) { testOdr(t, 2, 1, false, odrTxStatus) } func TestOdrTxStatusLes3(t *testing.T) { testOdr(t, 3, 1, false, odrTxStatus) } +func TestOdrTxStatusLes4(t *testing.T) { testOdr(t, 4, 1, false, odrTxStatus) } func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { var txs types.Transactions diff --git a/les/protocol.go b/les/protocol.go index 39d9f5152f..9eb6ec7471 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -39,8 +39,8 @@ const ( // Supported versions of the les protocol (first is primary) var ( - ClientProtocolVersions = []uint{lpv2, lpv3} - ServerProtocolVersions = []uint{lpv2, lpv3} + ClientProtocolVersions = []uint{lpv2, lpv3, lpv4} + ServerProtocolVersions = []uint{lpv2, lpv3, lpv4} AdvertiseProtocolVersions = []uint{lpv2} // clients are searching for the first advertised protocol in the list ) diff --git a/les/request_test.go b/les/request_test.go index 4851274382..b054fd88df 100644 --- a/les/request_test.go +++ b/les/request_test.go @@ -38,6 +38,7 @@ type accessTestFn func(db ethdb.Database, bhash common.Hash, number uint64) ligh func TestBlockAccessLes2(t *testing.T) { testAccess(t, 2, tfBlockAccess) } func TestBlockAccessLes3(t *testing.T) { testAccess(t, 3, tfBlockAccess) } +func TestBlockAccessLes4(t *testing.T) { testAccess(t, 4, tfBlockAccess) } func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { return &light.BlockRequest{Hash: bhash, Number: number} @@ -45,6 +46,7 @@ func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.Od func TestReceiptsAccessLes2(t *testing.T) { testAccess(t, 2, tfReceiptsAccess) } func TestReceiptsAccessLes3(t *testing.T) { testAccess(t, 3, tfReceiptsAccess) } +func TestReceiptsAccessLes4(t *testing.T) { testAccess(t, 4, tfReceiptsAccess) } func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { return &light.ReceiptsRequest{Hash: bhash, Number: number} @@ -52,6 +54,7 @@ func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light func TestTrieEntryAccessLes2(t *testing.T) { testAccess(t, 2, tfTrieEntryAccess) } func TestTrieEntryAccessLes3(t *testing.T) { testAccess(t, 3, tfTrieEntryAccess) } +func TestTrieEntryAccessLes4(t *testing.T) { testAccess(t, 4, tfTrieEntryAccess) } func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { @@ -62,6 +65,7 @@ func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) ligh func TestCodeAccessLes2(t *testing.T) { testAccess(t, 2, tfCodeAccess) } func TestCodeAccessLes3(t *testing.T) { testAccess(t, 3, tfCodeAccess) } +func TestCodeAccessLes4(t *testing.T) { testAccess(t, 4, tfCodeAccess) } func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrRequest { number := rawdb.ReadHeaderNumber(db, bhash) diff --git a/les/test_helper.go b/les/test_helper.go index e3f0616a88..1afc9be7d8 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -344,7 +345,8 @@ func newTestPeer(t *testing.T, name string, version int, handler *serverHandler, head = handler.blockchain.CurrentHeader() td = handler.blockchain.GetTd(head.Hash(), head.Number.Uint64()) ) - tp.handshake(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), testCostList(testCost)) + forkID := forkid.NewID(handler.blockchain.Config(), genesis.Hash(), head.Number.Uint64()) + tp.handshake(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID, testCostList(testCost)) } return tp, errCh } @@ -402,7 +404,7 @@ func newTestPeerPair(name string, version int, server *serverHandler, client *cl // handshake simulates a trivial handshake that expects the same state from the // remote side as we are simulating locally. -func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, costList RequestCostList) { +func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, costList RequestCostList) { var expList keyValueList expList = expList.add("protocolVersion", uint64(p.cpeer.version)) expList = expList.add("networkId", uint64(NetworkId)) @@ -410,6 +412,9 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNu expList = expList.add("headHash", head) expList = expList.add("headNum", headNum) expList = expList.add("genesisHash", genesis) + if p.cpeer.version >= lpv4 { + expList = expList.add("forkID", &forkID) + } sendList := make(keyValueList, len(expList)) copy(sendList, expList) expList = expList.add("serveHeaders", nil) @@ -417,6 +422,9 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNu expList = expList.add("serveStateSince", uint64(0)) expList = expList.add("serveRecentState", uint64(core.TriesInMemory-4)) expList = expList.add("txRelay", nil) + if p.cpeer.version >= lpv4 { + expList = expList.add("recentTxLookup", uint64(0)) + } expList = expList.add("flowControl/BL", testBufLimit) expList = expList.add("flowControl/MRR", testBufRecharge) expList = expList.add("flowControl/MRC", costList) From 08c878acd235fdc908b3a7a3c43dfc9fc5e9b2ef Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Mon, 15 Feb 2021 19:37:09 +0100 Subject: [PATCH 189/235] cmd/utils: add workaround for FreeBSD statfs quirk (#22310) Make geth build on FreeBSD, fixes #22309. --- cmd/utils/diskusage.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/utils/diskusage.go b/cmd/utils/diskusage.go index a822118a39..da696de6bf 100644 --- a/cmd/utils/diskusage.go +++ b/cmd/utils/diskusage.go @@ -31,5 +31,12 @@ func getFreeDiskSpace(path string) (uint64, error) { } // Available blocks * size per block = available space in bytes - return stat.Bavail * uint64(stat.Bsize), nil + var bavail = stat.Bavail + if stat.Bavail < 0 { + // FreeBSD can have a negative number of blocks available + // because of the grace limit. + bavail = 0 + } + //nolint:unconvert + return uint64(bavail) * uint64(stat.Bsize), nil } From 77787802fe8f8415638480066ecace73037f1eed Mon Sep 17 00:00:00 2001 From: Alex Mazalov Date: Mon, 15 Feb 2021 18:47:47 +0000 Subject: [PATCH 190/235] cmd/geth: fix js unclean shutdown (#22302) --- cmd/geth/consolecmd.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 5369612e87..cc88b9eec4 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -19,10 +19,8 @@ package main import ( "fmt" "os" - "os/signal" "path/filepath" "strings" - "syscall" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/console" @@ -218,13 +216,10 @@ func ephemeralConsole(ctx *cli.Context) error { utils.Fatalf("Failed to execute %s: %v", file, err) } } - // Wait for pending callbacks, but stop for Ctrl-C. - abort := make(chan os.Signal, 1) - signal.Notify(abort, syscall.SIGINT, syscall.SIGTERM) go func() { - <-abort - os.Exit(0) + stack.Wait() + console.Stop(false) }() console.Stop(true) From e991bdae2458dbee5a28addd188a897858aa34dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 16 Feb 2021 10:44:38 +0200 Subject: [PATCH 191/235] trie: fix bloom crash on fast sync restart --- trie/sync.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/trie/sync.go b/trie/sync.go index 05b9f55d33..dd8279b665 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -313,11 +313,15 @@ func (s *Sync) Commit(dbw ethdb.Batch) error { // Dump the membatch into a database dbw for key, value := range s.membatch.nodes { rawdb.WriteTrieNode(dbw, key, value) - s.bloom.Add(key[:]) + if s.bloom != nil { + s.bloom.Add(key[:]) + } } for key, value := range s.membatch.codes { rawdb.WriteCode(dbw, key, value) - s.bloom.Add(key[:]) + if s.bloom != nil { + s.bloom.Add(key[:]) + } } // Drop the membatch data and return s.membatch = newSyncMemBatch() From f4fcd4f506661c7cece755b90b8a84e51d5925ac Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 16 Feb 2021 10:40:59 +0100 Subject: [PATCH 192/235] rpc: increase the number of subscriptions in storm test (#22316) --- rpc/client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/client_test.go b/rpc/client_test.go index 5b1f960352..5d301a07a7 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -427,7 +427,7 @@ func TestClientNotificationStorm(t *testing.T) { } doTest(8000, false) - doTest(23000, true) + doTest(24000, true) } func TestClientSetHeader(t *testing.T) { From 9ec3329899a0ff62ed2f83c61b50140881a577a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 16 Feb 2021 09:04:07 +0200 Subject: [PATCH 193/235] core/state/snapshot: ensure Cap retains a min number of layers --- core/state/snapshot/snapshot.go | 47 ++++-------- core/state/snapshot/snapshot_test.go | 111 ++++++++++++--------------- 2 files changed, 65 insertions(+), 93 deletions(-) diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index df2b1ed330..aa5f5900b0 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -300,6 +300,12 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs m // Cap traverses downwards the snapshot tree from a head block hash until the // number of allowed layers are crossed. All layers beyond the permitted number // are flattened downwards. +// +// Note, the final diff layer count in general will be one more than the amount +// requested. This happens because the bottom-most diff layer is the accumulator +// which may or may not overflow and cascade to disk. Since this last layer's +// survival is only known *after* capping, we need to omit it from the count if +// we want to ensure that *at least* the requested number of diff layers remain. func (t *Tree) Cap(root common.Hash, layers int) error { // Retrieve the head snapshot to cap from snap := t.Snapshot(root) @@ -324,10 +330,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error { // Flattening the bottom-most diff layer requires special casing since there's // no child to rewire to the grandparent. In that case we can fake a temporary // child for the capping and then remove it. - var persisted *diskLayer - - switch layers { - case 0: + if layers == 0 { // If full commit was requested, flatten the diffs and merge onto disk diff.lock.RLock() base := diffToDisk(diff.flatten().(*diffLayer)) @@ -336,33 +339,9 @@ func (t *Tree) Cap(root common.Hash, layers int) error { // Replace the entire snapshot tree with the flat base t.layers = map[common.Hash]snapshot{base.root: base} return nil - - case 1: - // If full flattening was requested, flatten the diffs but only merge if the - // memory limit was reached - var ( - bottom *diffLayer - base *diskLayer - ) - diff.lock.RLock() - bottom = diff.flatten().(*diffLayer) - if bottom.memory >= aggregatorMemoryLimit { - base = diffToDisk(bottom) - } - diff.lock.RUnlock() - - // If all diff layers were removed, replace the entire snapshot tree - if base != nil { - t.layers = map[common.Hash]snapshot{base.root: base} - return nil - } - // Merge the new aggregated layer into the snapshot tree, clean stales below - t.layers[bottom.root] = bottom - - default: - // Many layers requested to be retained, cap normally - persisted = t.cap(diff, layers) } + persisted := t.cap(diff, layers) + // Remove any layer that is stale or links into a stale layer children := make(map[common.Hash][]common.Hash) for root, snap := range t.layers { @@ -405,9 +384,15 @@ func (t *Tree) Cap(root common.Hash, layers int) error { // layer limit is reached, memory cap is also enforced (but not before). // // The method returns the new disk layer if diffs were persisted into it. +// +// Note, the final diff layer count in general will be one more than the amount +// requested. This happens because the bottom-most diff layer is the accumulator +// which may or may not overflow and cascade to disk. Since this last layer's +// survival is only known *after* capping, we need to omit it from the count if +// we want to ensure that *at least* the requested number of diff layers remain. func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer { // Dive until we run out of layers or reach the persistent database - for ; layers > 2; layers-- { + for i := 0; i < layers-1; i++ { // If we still have diff layers below, continue down if parent, ok := diff.parent.(*diffLayer); ok { diff = parent diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index a315fd216c..4b787cfe2e 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -162,57 +162,10 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit) aggregatorMemoryLimit = 0 - if err := snaps.Cap(common.HexToHash("0x03"), 2); err != nil { - t.Fatalf("failed to merge diff layer onto disk: %v", err) - } - // Since the base layer was modified, ensure that data retrievald on the external reference fail - if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { - t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) - } - if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { - t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) - } - if n := len(snaps.layers); n != 2 { - t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2) - fmt.Println(snaps.layers) - } -} - -// Tests that if a diff layer becomes stale, no active external references will -// be returned with junk data. This version of the test flattens every diff layer -// to check internal corner case around the bottom-most memory accumulator. -func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Commit two diffs on top and retrieve a reference to the bottommost - accounts := map[common.Hash][]byte{ - common.HexToHash("0xa1"): randomAccount(), - } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if n := len(snaps.layers); n != 3 { - t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) - } - ref := snaps.Snapshot(common.HexToHash("0x02")) - - // Flatten the diff layer into the bottom accumulator if err := snaps.Cap(common.HexToHash("0x03"), 1); err != nil { - t.Fatalf("failed to flatten diff layer into accumulator: %v", err) + t.Fatalf("failed to merge accumulator onto disk: %v", err) } - // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail + // Since the base layer was modified, ensure that data retrievald on the external reference fail if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) } @@ -267,7 +220,7 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { t.Errorf("layers modified, got %d exp %d", got, exp) } // Flatten the diff layer into the bottom accumulator - if err := snaps.Cap(common.HexToHash("0x04"), 2); err != nil { + if err := snaps.Cap(common.HexToHash("0x04"), 1); err != nil { t.Fatalf("failed to flatten diff layer into accumulator: %v", err) } // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail @@ -389,7 +342,7 @@ func TestSnaphots(t *testing.T) { // Create a starting base layer and a snapshot tree out of it base := &diskLayer{ diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), + root: makeRoot(1), cache: fastcache.New(1024 * 500), } snaps := &Tree{ @@ -397,17 +350,16 @@ func TestSnaphots(t *testing.T) { base.root: base, }, } - // Construct the snapshots with 128 layers + // Construct the snapshots with 129 layers, flattening whatever's above that var ( last = common.HexToHash("0x01") head common.Hash ) - // Flush another 128 layers, one diff will be flatten into the parent. - for i := 0; i < 128; i++ { + for i := 0; i < 129; i++ { head = makeRoot(uint64(i + 2)) snaps.Update(head, last, nil, setAccount(fmt.Sprintf("%d", i+2)), nil) last = head - snaps.Cap(head, 128) // 129 layers(128 diffs + 1 disk) are allowed, 129th is the persistent layer + snaps.Cap(head, 128) // 130 layers (128 diffs + 1 accumulator + 1 disk) } var cases = []struct { headRoot common.Hash @@ -417,22 +369,57 @@ func TestSnaphots(t *testing.T) { expectBottom common.Hash }{ {head, 0, false, 0, common.Hash{}}, - {head, 64, false, 64, makeRoot(127 + 2 - 63)}, - {head, 128, false, 128, makeRoot(2)}, // All diff layers - {head, 129, true, 128, makeRoot(2)}, // All diff layers - {head, 129, false, 129, common.HexToHash("0x01")}, // All diff layers + disk layer + {head, 64, false, 64, makeRoot(129 + 2 - 64)}, + {head, 128, false, 128, makeRoot(3)}, // Normal diff layers, no accumulator + {head, 129, true, 129, makeRoot(2)}, // All diff layers, including accumulator + {head, 130, false, 130, makeRoot(1)}, // All diff layers + disk layer + } + for i, c := range cases { + layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk) + if len(layers) != c.expected { + t.Errorf("non-overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers)) + } + if len(layers) == 0 { + continue + } + bottommost := layers[len(layers)-1] + if bottommost.Root() != c.expectBottom { + t.Errorf("non-overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root()) + } + } + // Above we've tested the normal capping, which leaves the accumulator live. + // Test that if the bottommost accumulator diff layer overflows the allowed + // memory limit, the snapshot tree gets capped to one less layer. + // Commit the diff layer onto the disk and ensure it's persisted + defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit) + aggregatorMemoryLimit = 0 + + snaps.Cap(head, 128) // 129 (128 diffs + 1 overflown accumulator + 1 disk) + + cases = []struct { + headRoot common.Hash + limit int + nodisk bool + expected int + expectBottom common.Hash + }{ + {head, 0, false, 0, common.Hash{}}, + {head, 64, false, 64, makeRoot(129 + 2 - 64)}, + {head, 128, false, 128, makeRoot(3)}, // All diff layers, accumulator was flattened + {head, 129, true, 128, makeRoot(3)}, // All diff layers, accumulator was flattened + {head, 130, false, 129, makeRoot(2)}, // All diff layers + disk layer } - for _, c := range cases { + for i, c := range cases { layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk) if len(layers) != c.expected { - t.Fatalf("Returned snapshot layers are mismatched, want %v, got %v", c.expected, len(layers)) + t.Errorf("overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers)) } if len(layers) == 0 { continue } bottommost := layers[len(layers)-1] if bottommost.Root() != c.expectBottom { - t.Fatalf("Snapshot mismatch, want %v, get %v", c.expectBottom, bottommost.Root()) + t.Errorf("overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root()) } } } From bfdff4c5b83cc09b2f91377f87e7757ddbe7fd63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 16 Feb 2021 16:11:33 +0200 Subject: [PATCH 194/235] eth: fix snap sync cancellation --- eth/downloader/downloader.go | 3 +-- eth/protocols/snap/protocol.go | 1 - eth/protocols/snap/sync.go | 6 +++++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 421803876e..416a387e31 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -346,7 +346,6 @@ func (d *Downloader) Synchronise(id string, head common.Hash, td *big.Int, mode case nil, errBusy, errCanceled: return err } - if errors.Is(err, errInvalidChain) || errors.Is(err, errBadPeer) || errors.Is(err, errTimeout) || errors.Is(err, errStallingPeer) || errors.Is(err, errUnsyncedPeer) || errors.Is(err, errEmptyHeaderSet) || errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) { @@ -1764,7 +1763,7 @@ func (d *Downloader) processFastSyncContent() error { }() closeOnErr := func(s *stateSync) { - if err := s.Wait(); err != nil && err != errCancelStateFetch && err != errCanceled { + if err := s.Wait(); err != nil && err != errCancelStateFetch && err != errCanceled && err != snap.ErrCancelled { d.queue.Close() // wake up Results } } diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go index a74142cafb..5528e9212e 100644 --- a/eth/protocols/snap/protocol.go +++ b/eth/protocols/snap/protocol.go @@ -61,7 +61,6 @@ var ( errDecode = errors.New("invalid message") errInvalidMsgCode = errors.New("invalid message code") errBadRequest = errors.New("bad request") - errCancelled = errors.New("sync cancelled") ) // Packet represents a p2p message in the `snap` protocol. diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 422cdf8f72..c31e4a5dae 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -88,6 +88,10 @@ var ( requestTimeout = 10 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? ) +// ErrCancelled is returned from snap syncing if the operation was prematurely +// terminated. +var ErrCancelled = errors.New("sync cancelled") + // accountRequest tracks a pending account range request to ensure responses are // to actual requests and to validate any security constraints. // @@ -615,7 +619,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { case id := <-peerDrop: s.revertRequests(id) case <-cancel: - return errCancelled + return ErrCancelled case req := <-s.accountReqFails: s.revertAccountRequest(req) From f9445e93bb72aedec953e65734ec18b4e1eaac3d Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 16 Feb 2021 15:23:03 +0100 Subject: [PATCH 195/235] cmd/devp2p/internal/ethtest: use shared message types (#22315) This updates the eth protocol test suite to use the message type definitions of the 'production' protocol implementation in eth/protocols/eth. --- cmd/devp2p/internal/ethtest/chain_test.go | 7 +- cmd/devp2p/internal/ethtest/suite.go | 19 +++-- cmd/devp2p/internal/ethtest/transaction.go | 4 +- cmd/devp2p/internal/ethtest/types.go | 90 +++++----------------- 4 files changed, 36 insertions(+), 84 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go index ac3907ce8d..5e4289d80a 100644 --- a/cmd/devp2p/internal/ethtest/chain_test.go +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -21,6 +21,7 @@ import ( "strconv" "testing" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/p2p" "github.com/stretchr/testify/assert" ) @@ -93,7 +94,7 @@ func TestChain_GetHeaders(t *testing.T) { }{ { req: GetBlockHeaders{ - Origin: hashOrNumber{ + Origin: eth.HashOrNumber{ Number: uint64(2), }, Amount: uint64(5), @@ -110,7 +111,7 @@ func TestChain_GetHeaders(t *testing.T) { }, { req: GetBlockHeaders{ - Origin: hashOrNumber{ + Origin: eth.HashOrNumber{ Number: uint64(chain.Len() - 1), }, Amount: uint64(3), @@ -125,7 +126,7 @@ func TestChain_GetHeaders(t *testing.T) { }, { req: GetBlockHeaders{ - Origin: hashOrNumber{ + Origin: eth.HashOrNumber{ Hash: chain.Head().Hash(), }, Amount: uint64(1), diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 5d0cdda720..edf7bb7e31 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -24,6 +24,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" @@ -143,7 +144,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { // get block headers req := &GetBlockHeaders{ - Origin: hashOrNumber{ + Origin: eth.HashOrNumber{ Hash: s.chain.blocks[1].Hash(), }, Amount: 2, @@ -157,8 +158,8 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *BlockHeaders: - headers := msg - for _, header := range *headers { + headers := *msg + for _, header := range headers { num := header.Number.Uint64() t.Logf("received header (%d): %s", num, pretty.Sdump(header)) assert.Equal(t, s.chain.blocks[int(num)].Header(), header) @@ -179,7 +180,10 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { conn.handshake(t) conn.statusExchange(t, s.chain, nil) // create block bodies request - req := &GetBlockBodies{s.chain.blocks[54].Hash(), s.chain.blocks[75].Hash()} + req := &GetBlockBodies{ + s.chain.blocks[54].Hash(), + s.chain.blocks[75].Hash(), + } if err := conn.Write(req); err != nil { t.Fatalf("could not write to connection: %v", err) } @@ -357,10 +361,9 @@ func (s *Suite) waitAnnounce(t *utesting.T, conn *Conn, blockAnnouncement *NewBl "wrong TD in announcement", ) case *NewBlockHashes: - hashes := *msg - t.Logf("received NewBlockHashes message: %s", pretty.Sdump(hashes)) - assert.Equal(t, - blockAnnouncement.Block.Hash(), hashes[0].Hash, + message := *msg + t.Logf("received NewBlockHashes message: %s", pretty.Sdump(message)) + assert.Equal(t, blockAnnouncement.Block.Hash(), message[0].Hash, "wrong block hash in announcement", ) default: diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go index 4aaab8bf97..effcc3af29 100644 --- a/cmd/devp2p/internal/ethtest/transaction.go +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -32,7 +32,7 @@ func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { sendConn := s.setupConnection(t) t.Logf("sending tx: %v %v %v\n", tx.Hash().String(), tx.GasPrice(), tx.Gas()) // Send the transaction - if err := sendConn.Write(Transactions([]*types.Transaction{tx})); err != nil { + if err := sendConn.Write(&Transactions{tx}); err != nil { t.Fatal(err) } time.Sleep(100 * time.Millisecond) @@ -70,7 +70,7 @@ func sendFailingTx(t *utesting.T, s *Suite, tx *types.Transaction) { t.Logf("unexpected message, logging: %v", pretty.Sdump(msg)) } // Send the transaction - if err := sendConn.Write(Transactions([]*types.Transaction{tx})); err != nil { + if err := sendConn.Write(&Transactions{tx}); err != nil { t.Fatal(err) } // Wait for another transaction announcement diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index f768d61d5a..b901f50700 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -19,15 +19,12 @@ package ethtest import ( "crypto/ecdsa" "fmt" - "io" - "math/big" "reflect" "time" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/rlpx" @@ -81,102 +78,48 @@ type Pong struct{} func (p Pong) Code() int { return 0x03 } // Status is the network packet for the status message for eth/64 and later. -type Status struct { - ProtocolVersion uint32 - NetworkID uint64 - TD *big.Int - Head common.Hash - Genesis common.Hash - ForkID forkid.ID -} +type Status eth.StatusPacket func (s Status) Code() int { return 16 } // NewBlockHashes is the network packet for the block announcements. -type NewBlockHashes []struct { - Hash common.Hash // Hash of one particular block being announced - Number uint64 // Number of one particular block being announced -} +type NewBlockHashes eth.NewBlockHashesPacket func (nbh NewBlockHashes) Code() int { return 17 } -type Transactions []*types.Transaction +type Transactions eth.TransactionsPacket func (t Transactions) Code() int { return 18 } // GetBlockHeaders represents a block header query. -type GetBlockHeaders struct { - Origin hashOrNumber // Block from which to retrieve headers - Amount uint64 // Maximum number of headers to retrieve - Skip uint64 // Blocks to skip between consecutive headers - Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) -} +type GetBlockHeaders eth.GetBlockHeadersPacket func (g GetBlockHeaders) Code() int { return 19 } -type BlockHeaders []*types.Header +type BlockHeaders eth.BlockHeadersPacket func (bh BlockHeaders) Code() int { return 20 } // GetBlockBodies represents a GetBlockBodies request -type GetBlockBodies []common.Hash +type GetBlockBodies eth.GetBlockBodiesPacket func (gbb GetBlockBodies) Code() int { return 21 } // BlockBodies is the network packet for block content distribution. -type BlockBodies []*types.Body +type BlockBodies eth.BlockBodiesPacket func (bb BlockBodies) Code() int { return 22 } // NewBlock is the network packet for the block propagation message. -type NewBlock struct { - Block *types.Block - TD *big.Int -} +type NewBlock eth.NewBlockPacket func (nb NewBlock) Code() int { return 23 } // NewPooledTransactionHashes is the network packet for the tx hash propagation message. -type NewPooledTransactionHashes [][32]byte +type NewPooledTransactionHashes eth.NewPooledTransactionHashesPacket func (nb NewPooledTransactionHashes) Code() int { return 24 } -// HashOrNumber is a combined field for specifying an origin block. -type hashOrNumber struct { - Hash common.Hash // Block hash from which to retrieve headers (excludes Number) - Number uint64 // Block hash from which to retrieve headers (excludes Hash) -} - -// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the -// two contained union fields. -func (hn *hashOrNumber) EncodeRLP(w io.Writer) error { - if hn.Hash == (common.Hash{}) { - return rlp.Encode(w, hn.Number) - } - if hn.Number != 0 { - return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number) - } - return rlp.Encode(w, hn.Hash) -} - -// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents -// into either a block hash or a block number. -func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error { - _, size, _ := s.Kind() - origin, err := s.Raw() - if err == nil { - switch { - case size == 32: - err = rlp.DecodeBytes(origin, &hn.Hash) - case size <= 8: - err = rlp.DecodeBytes(origin, &hn.Number) - default: - err = fmt.Errorf("invalid input size %d for origin", size) - } - } - return err -} - // Conn represents an individual connection with a peer type Conn struct { *rlpx.Conn @@ -221,7 +164,7 @@ func (c *Conn) Read() Message { default: return errorf("invalid message code: %d", code) } - + // if message is devp2p, decode here if err := rlp.DecodeBytes(rawData, msg); err != nil { return errorf("could not rlp decode message: %v", err) } @@ -256,7 +199,12 @@ func (c *Conn) ReadAndServe(chain *Chain, timeout time.Duration) Message { } func (c *Conn) Write(msg Message) error { - payload, err := rlp.EncodeToBytes(msg) + // check if message is eth protocol message + var ( + payload []byte + err error + ) + payload, err = rlp.EncodeToBytes(msg) if err != nil { return err } @@ -363,7 +311,7 @@ loop: } } - if err := c.Write(*status); err != nil { + if err := c.Write(status); err != nil { t.Fatalf("could not write to connection: %v", err) } @@ -378,7 +326,7 @@ func (c *Conn) waitForBlock(block *types.Block) error { timeout := time.Now().Add(20 * time.Second) c.SetReadDeadline(timeout) for { - req := &GetBlockHeaders{Origin: hashOrNumber{Hash: block.Hash()}, Amount: 1} + req := &GetBlockHeaders{Origin: eth.HashOrNumber{Hash: block.Hash()}, Amount: 1} if err := c.Write(req); err != nil { return err } From e01096f531862b982833732514376cead8d58e82 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 17 Feb 2021 14:59:00 +0100 Subject: [PATCH 196/235] eth/handler, broadcast: optimize tx broadcast mechanism (#22176) This PR optimizes the broadcast loop. Instead of iterating twice through a given set of transactions to weed out which peers have and which do not have a tx, to send/announce transactions, we do it only once. --- eth/handler.go | 56 ++++++++++++++++++---------------- eth/protocols/eth/broadcast.go | 12 ++++---- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index a5a62b894d..13fa701935 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -456,44 +456,51 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { } } -// BroadcastTransactions will propagate a batch of transactions to all peers which are not known to +// BroadcastTransactions will propagate a batch of transactions +// - To a square root of all peers +// - And, separately, as announcements to all peers which are not known to // already have the given transaction. -func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) { +func (h *handler) BroadcastTransactions(txs types.Transactions) { var ( - txset = make(map[*ethPeer][]common.Hash) - annos = make(map[*ethPeer][]common.Hash) + annoCount int // Count of announcements made + annoPeers int + directCount int // Count of the txs sent directly to peers + directPeers int // Count of the peers that were sent transactions directly + + txset = make(map[*ethPeer][]common.Hash) // Set peer->hash to transfer directly + annos = make(map[*ethPeer][]common.Hash) // Set peer->hash to announce + ) // Broadcast transactions to a batch of peers not knowing about it - if propagate { - for _, tx := range txs { - peers := h.peers.peersWithoutTransaction(tx.Hash()) - - // Send the block to a subset of our peers - transfer := peers[:int(math.Sqrt(float64(len(peers))))] - for _, peer := range transfer { - txset[peer] = append(txset[peer], tx.Hash()) - } - log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(transfer)) - } - for peer, hashes := range txset { - peer.AsyncSendTransactions(hashes) - } - return - } - // Otherwise only broadcast the announcement to peers for _, tx := range txs { peers := h.peers.peersWithoutTransaction(tx.Hash()) - for _, peer := range peers { + // Send the tx unconditionally to a subset of our peers + numDirect := int(math.Sqrt(float64(len(peers)))) + for _, peer := range peers[:numDirect] { + txset[peer] = append(txset[peer], tx.Hash()) + } + // For the remaining peers, send announcement only + for _, peer := range peers[numDirect:] { annos[peer] = append(annos[peer], tx.Hash()) } } + for peer, hashes := range txset { + directPeers++ + directCount += len(hashes) + peer.AsyncSendTransactions(hashes) + } for peer, hashes := range annos { + annoPeers++ + annoCount += len(hashes) if peer.Version() >= eth.ETH65 { peer.AsyncSendPooledTransactionHashes(hashes) } else { peer.AsyncSendTransactions(hashes) } } + log.Debug("Transaction broadcast", "txs", len(txs), + "announce packs", annoPeers, "announced hashes", annoCount, + "tx packs", directPeers, "broadcast txs", directCount) } // minedBroadcastLoop sends mined blocks to connected peers. @@ -511,13 +518,10 @@ func (h *handler) minedBroadcastLoop() { // txBroadcastLoop announces new transactions to connected peers. func (h *handler) txBroadcastLoop() { defer h.wg.Done() - for { select { case event := <-h.txsCh: - h.BroadcastTransactions(event.Txs, true) // First propagate transactions to peers - h.BroadcastTransactions(event.Txs, false) // Only then announce to the rest - + h.BroadcastTransactions(event.Txs) case <-h.txsSub.Err(): return } diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go index 74ec2f0654..328396d510 100644 --- a/eth/protocols/eth/broadcast.go +++ b/eth/protocols/eth/broadcast.go @@ -142,18 +142,18 @@ func (p *Peer) announceTransactions() { if done == nil && len(queue) > 0 { // Pile transaction hashes until we reach our allowed network limit var ( - hashes []common.Hash + count int pending []common.Hash size common.StorageSize ) - for i := 0; i < len(queue) && size < maxTxPacketSize; i++ { - if p.txpool.Get(queue[i]) != nil { - pending = append(pending, queue[i]) + for count = 0; count < len(queue) && size < maxTxPacketSize; count++ { + if p.txpool.Get(queue[count]) != nil { + pending = append(pending, queue[count]) size += common.HashLength } - hashes = append(hashes, queue[i]) } - queue = queue[:copy(queue, queue[len(hashes):])] + // Shift and trim queue + queue = queue[:copy(queue, queue[count:])] // If there's anything available to transfer, fire up an async writer if len(pending) > 0 { From 52e5c38aa5dcc01566bb6d05a5312b5b642899b4 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 18 Feb 2021 09:05:47 +0100 Subject: [PATCH 197/235] core/state: copy the snap when copying the state (#22340) * core/state: copy the snap when copying the state * core/state: deep-copy snap stuff during state Copy --- core/state/statedb.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/core/state/statedb.go b/core/state/statedb.go index 49f457a99f..8fd066e24f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -728,6 +728,31 @@ func (s *StateDB) Copy() *StateDB { if s.prefetcher != nil { state.prefetcher = s.prefetcher.copy() } + if s.snaps != nil { + // In order for the miner to be able to use and make additions + // to the snapshot tree, we need to copy that aswell. + // Otherwise, any block mined by ourselves will cause gaps in the tree, + // and force the miner to operate trie-backed only + state.snaps = s.snaps + state.snap = s.snap + // deep copy needed + state.snapDestructs = make(map[common.Hash]struct{}) + for k, v := range s.snapDestructs { + state.snapDestructs[k] = v + } + state.snapAccounts = make(map[common.Hash][]byte) + for k, v := range s.snapAccounts { + state.snapAccounts[k] = v + } + state.snapStorage = make(map[common.Hash]map[common.Hash][]byte) + for k, v := range s.snapStorage { + temp := make(map[common.Hash][]byte) + for kk, vv := range v { + temp[kk] = vv + } + state.snapStorage[k] = temp + } + } return state } From 9ec32a9e7b2a39103c905d57e270d99463e6aa99 Mon Sep 17 00:00:00 2001 From: Or Neeman Date: Thu, 18 Feb 2021 03:19:49 -0600 Subject: [PATCH 198/235] rlp: handle case of normal EOF in Stream.readFull (#22336) io.Reader may return n > 0 and io.EOF at the end of the input stream. readFull did not handle this correctly, looking only at the error. This fixes it to check for n == len(buf) as well. --- rlp/decode.go | 8 +++++++- rlp/decode_test.go | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/rlp/decode.go b/rlp/decode.go index 5f3f5eedfd..79b7ef0626 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -952,7 +952,13 @@ func (s *Stream) readFull(buf []byte) (err error) { n += nn } if err == io.EOF { - err = io.ErrUnexpectedEOF + if n < len(buf) { + err = io.ErrUnexpectedEOF + } else { + // Readers are allowed to give EOF even though the read succeeded. + // In such cases, we discard the EOF, like io.ReadFull() does. + err = nil + } } return err } diff --git a/rlp/decode_test.go b/rlp/decode_test.go index 167e9974b9..d94c3969b2 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -665,6 +665,26 @@ func TestDecodeWithByteReader(t *testing.T) { }) } +func testDecodeWithEncReader(t *testing.T, n int) { + s := strings.Repeat("0", n) + _, r, _ := EncodeToReader(s) + var decoded string + err := Decode(r, &decoded) + if err != nil { + t.Errorf("Unexpected decode error with n=%v: %v", n, err) + } + if decoded != s { + t.Errorf("Decode mismatch with n=%v", n) + } +} + +// This is a regression test checking that decoding from encReader +// works for RLP values of size 8192 bytes or more. +func TestDecodeWithEncReader(t *testing.T) { + testDecodeWithEncReader(t, 8188) // length with header is 8191 + testDecodeWithEncReader(t, 8189) // length with header is 8192 +} + // plainReader reads from a byte slice but does not // implement ReadByte. It is also not recognized by the // size validation. This is useful to test how the decoder From b1835b3855ebee0aa8c63d18b8f0671168ceced5 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 18 Feb 2021 10:40:19 +0100 Subject: [PATCH 199/235] node: always show websocket url in logs (#22307) --- node/rpcstack.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index d693bb0bbd..56e23cc5c7 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -144,13 +144,15 @@ func (h *httpServer) start() error { h.listener = listener go h.server.Serve(listener) - // if server is websocket only, return after logging - if h.wsAllowed() && !h.rpcAllowed() { + if h.wsAllowed() { url := fmt.Sprintf("ws://%v", listener.Addr()) if h.wsConfig.prefix != "" { url += h.wsConfig.prefix } h.log.Info("WebSocket enabled", "url", url) + } + // if server is websocket only, return after logging + if !h.rpcAllowed() { return nil } // Log http endpoint. From 6ec15610443b28eabf665199f1dc5be2b3e3f7cb Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 18 Feb 2021 17:54:29 +0100 Subject: [PATCH 200/235] eth: implement eth66 (#22241) * eth/protocols/eth: split up the eth protocol handlers * eth/protocols/eth: define eth-66 protocol messages * eth/protocols/eth: poc implement getblockheaders on eth/66 * eth/protocols/eth: implement remaining eth-66 handlers * eth/protocols: define handler map for eth 66 * eth/downloader: use protocol constants from eth package * eth/protocols/eth: add ETH66 capability * eth/downloader: tests for eth66 * eth/downloader: fix error in tests * eth/protocols/eth: use eth66 for outgoing requests * eth/protocols/eth: remove unused error type * eth/protocols/eth: define protocol length * eth/protocols/eth: fix pooled tx over eth66 * protocols/eth/handlers: revert behavioural change which caused tests to fail * eth/downloader: fix failing test * eth/protocols/eth: add testcases + fix flaw with header requests * eth/protocols: change comments * eth/protocols/eth: review fixes + fixed flaw in RequestOneHeader * eth/protocols: documentation * eth/protocols/eth: review concerns about types --- eth/downloader/downloader_test.go | 185 ++++++++--- eth/downloader/peer.go | 9 +- eth/protocols/eth/handler.go | 399 ++++------------------ eth/protocols/eth/handlers.go | 510 +++++++++++++++++++++++++++++ eth/protocols/eth/peer.go | 113 ++++++- eth/protocols/eth/protocol.go | 95 +++++- eth/protocols/eth/protocol_test.go | 200 +++++++++++ p2p/message.go | 4 + 8 files changed, 1120 insertions(+), 395 deletions(-) create mode 100644 eth/protocols/eth/handlers.go diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 5de1ef3f8e..2917116144 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -515,18 +515,18 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng } } -// Tests that simple synchronization against a canonical chain works correctly. -// In this test common ancestor lookup should be short circuited and not require -// binary searching. -func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonicalSynchronisation(t, 64, FullSync) } -func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonicalSynchronisation(t, 64, FastSync) } -func TestCanonicalSynchronisation65Full(t *testing.T) { testCanonicalSynchronisation(t, 65, FullSync) } -func TestCanonicalSynchronisation65Fast(t *testing.T) { testCanonicalSynchronisation(t, 65, FastSync) } -func TestCanonicalSynchronisation65Light(t *testing.T) { - testCanonicalSynchronisation(t, 65, LightSync) -} +func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonSync(t, 64, FullSync) } +func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonSync(t, 64, FastSync) } + +func TestCanonicalSynchronisation65Full(t *testing.T) { testCanonSync(t, 65, FullSync) } +func TestCanonicalSynchronisation65Fast(t *testing.T) { testCanonSync(t, 65, FastSync) } +func TestCanonicalSynchronisation65Light(t *testing.T) { testCanonSync(t, 65, LightSync) } + +func TestCanonicalSynchronisation66Full(t *testing.T) { testCanonSync(t, 66, FullSync) } +func TestCanonicalSynchronisation66Fast(t *testing.T) { testCanonSync(t, 66, FastSync) } +func TestCanonicalSynchronisation66Light(t *testing.T) { testCanonSync(t, 66, LightSync) } -func testCanonicalSynchronisation(t *testing.T, protocol uint, mode SyncMode) { +func testCanonSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -547,9 +547,13 @@ func testCanonicalSynchronisation(t *testing.T, protocol uint, mode SyncMode) { // until the cached blocks are retrieved. func TestThrottling64Full(t *testing.T) { testThrottling(t, 64, FullSync) } func TestThrottling64Fast(t *testing.T) { testThrottling(t, 64, FastSync) } + func TestThrottling65Full(t *testing.T) { testThrottling(t, 65, FullSync) } func TestThrottling65Fast(t *testing.T) { testThrottling(t, 65, FastSync) } +func TestThrottling66Full(t *testing.T) { testThrottling(t, 66, FullSync) } +func TestThrottling66Fast(t *testing.T) { testThrottling(t, 66, FastSync) } + func testThrottling(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -629,12 +633,17 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { // Tests that simple synchronization against a forked chain works correctly. In // this test common ancestor lookup should *not* be short circuited, and a full // binary search should be executed. -func TestForkedSync64Full(t *testing.T) { testForkedSync(t, 64, FullSync) } -func TestForkedSync64Fast(t *testing.T) { testForkedSync(t, 64, FastSync) } +func TestForkedSync64Full(t *testing.T) { testForkedSync(t, 64, FullSync) } +func TestForkedSync64Fast(t *testing.T) { testForkedSync(t, 64, FastSync) } + func TestForkedSync65Full(t *testing.T) { testForkedSync(t, 65, FullSync) } func TestForkedSync65Fast(t *testing.T) { testForkedSync(t, 65, FastSync) } func TestForkedSync65Light(t *testing.T) { testForkedSync(t, 65, LightSync) } +func TestForkedSync66Full(t *testing.T) { testForkedSync(t, 66, FullSync) } +func TestForkedSync66Fast(t *testing.T) { testForkedSync(t, 66, FastSync) } +func TestForkedSync66Light(t *testing.T) { testForkedSync(t, 66, LightSync) } + func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -660,12 +669,17 @@ func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that synchronising against a much shorter but much heavyer fork works // corrently and is not dropped. -func TestHeavyForkedSync64Full(t *testing.T) { testHeavyForkedSync(t, 64, FullSync) } -func TestHeavyForkedSync64Fast(t *testing.T) { testHeavyForkedSync(t, 64, FastSync) } +func TestHeavyForkedSync64Full(t *testing.T) { testHeavyForkedSync(t, 64, FullSync) } +func TestHeavyForkedSync64Fast(t *testing.T) { testHeavyForkedSync(t, 64, FastSync) } + func TestHeavyForkedSync65Full(t *testing.T) { testHeavyForkedSync(t, 65, FullSync) } func TestHeavyForkedSync65Fast(t *testing.T) { testHeavyForkedSync(t, 65, FastSync) } func TestHeavyForkedSync65Light(t *testing.T) { testHeavyForkedSync(t, 65, LightSync) } +func TestHeavyForkedSync66Full(t *testing.T) { testHeavyForkedSync(t, 66, FullSync) } +func TestHeavyForkedSync66Fast(t *testing.T) { testHeavyForkedSync(t, 66, FastSync) } +func TestHeavyForkedSync66Light(t *testing.T) { testHeavyForkedSync(t, 66, LightSync) } + func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -693,12 +707,17 @@ func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head, ensuring that malicious peers cannot waste resources by feeding // long dead chains. -func TestBoundedForkedSync64Full(t *testing.T) { testBoundedForkedSync(t, 64, FullSync) } -func TestBoundedForkedSync64Fast(t *testing.T) { testBoundedForkedSync(t, 64, FastSync) } +func TestBoundedForkedSync64Full(t *testing.T) { testBoundedForkedSync(t, 64, FullSync) } +func TestBoundedForkedSync64Fast(t *testing.T) { testBoundedForkedSync(t, 64, FastSync) } + func TestBoundedForkedSync65Full(t *testing.T) { testBoundedForkedSync(t, 65, FullSync) } func TestBoundedForkedSync65Fast(t *testing.T) { testBoundedForkedSync(t, 65, FastSync) } func TestBoundedForkedSync65Light(t *testing.T) { testBoundedForkedSync(t, 65, LightSync) } +func TestBoundedForkedSync66Full(t *testing.T) { testBoundedForkedSync(t, 66, FullSync) } +func TestBoundedForkedSync66Fast(t *testing.T) { testBoundedForkedSync(t, 66, FastSync) } +func TestBoundedForkedSync66Light(t *testing.T) { testBoundedForkedSync(t, 66, LightSync) } + func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -725,12 +744,17 @@ func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head for short but heavy forks too. These are a bit special because they // take different ancestor lookup paths. -func TestBoundedHeavyForkedSync64Full(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FullSync) } -func TestBoundedHeavyForkedSync64Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FastSync) } +func TestBoundedHeavyForkedSync64Full(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FullSync) } +func TestBoundedHeavyForkedSync64Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FastSync) } + func TestBoundedHeavyForkedSync65Full(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FullSync) } func TestBoundedHeavyForkedSync65Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FastSync) } func TestBoundedHeavyForkedSync65Light(t *testing.T) { testBoundedHeavyForkedSync(t, 65, LightSync) } +func TestBoundedHeavyForkedSync66Full(t *testing.T) { testBoundedHeavyForkedSync(t, 66, FullSync) } +func TestBoundedHeavyForkedSync66Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 66, FastSync) } +func TestBoundedHeavyForkedSync66Light(t *testing.T) { testBoundedHeavyForkedSync(t, 66, LightSync) } + func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -775,12 +799,17 @@ func TestInactiveDownloader63(t *testing.T) { } // Tests that a canceled download wipes all previously accumulated state. -func TestCancel64Full(t *testing.T) { testCancel(t, 64, FullSync) } -func TestCancel64Fast(t *testing.T) { testCancel(t, 64, FastSync) } +func TestCancel64Full(t *testing.T) { testCancel(t, 64, FullSync) } +func TestCancel64Fast(t *testing.T) { testCancel(t, 64, FastSync) } + func TestCancel65Full(t *testing.T) { testCancel(t, 65, FullSync) } func TestCancel65Fast(t *testing.T) { testCancel(t, 65, FastSync) } func TestCancel65Light(t *testing.T) { testCancel(t, 65, LightSync) } +func TestCancel66Full(t *testing.T) { testCancel(t, 66, FullSync) } +func TestCancel66Fast(t *testing.T) { testCancel(t, 66, FastSync) } +func TestCancel66Light(t *testing.T) { testCancel(t, 66, LightSync) } + func testCancel(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -806,12 +835,17 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) { } // Tests that synchronisation from multiple peers works as intended (multi thread sanity test). -func TestMultiSynchronisation64Full(t *testing.T) { testMultiSynchronisation(t, 64, FullSync) } -func TestMultiSynchronisation64Fast(t *testing.T) { testMultiSynchronisation(t, 64, FastSync) } +func TestMultiSynchronisation64Full(t *testing.T) { testMultiSynchronisation(t, 64, FullSync) } +func TestMultiSynchronisation64Fast(t *testing.T) { testMultiSynchronisation(t, 64, FastSync) } + func TestMultiSynchronisation65Full(t *testing.T) { testMultiSynchronisation(t, 65, FullSync) } func TestMultiSynchronisation65Fast(t *testing.T) { testMultiSynchronisation(t, 65, FastSync) } func TestMultiSynchronisation65Light(t *testing.T) { testMultiSynchronisation(t, 65, LightSync) } +func TestMultiSynchronisation66Full(t *testing.T) { testMultiSynchronisation(t, 66, FullSync) } +func TestMultiSynchronisation66Fast(t *testing.T) { testMultiSynchronisation(t, 66, FastSync) } +func TestMultiSynchronisation66Light(t *testing.T) { testMultiSynchronisation(t, 66, LightSync) } + func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -834,12 +868,17 @@ func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { // Tests that synchronisations behave well in multi-version protocol environments // and not wreak havoc on other nodes in the network. -func TestMultiProtoSynchronisation64Full(t *testing.T) { testMultiProtoSync(t, 64, FullSync) } -func TestMultiProtoSynchronisation64Fast(t *testing.T) { testMultiProtoSync(t, 64, FastSync) } +func TestMultiProtoSynchronisation64Full(t *testing.T) { testMultiProtoSync(t, 64, FullSync) } +func TestMultiProtoSynchronisation64Fast(t *testing.T) { testMultiProtoSync(t, 64, FastSync) } + func TestMultiProtoSynchronisation65Full(t *testing.T) { testMultiProtoSync(t, 65, FullSync) } func TestMultiProtoSynchronisation65Fast(t *testing.T) { testMultiProtoSync(t, 65, FastSync) } func TestMultiProtoSynchronisation65Light(t *testing.T) { testMultiProtoSync(t, 65, LightSync) } +func TestMultiProtoSynchronisation66Full(t *testing.T) { testMultiProtoSync(t, 66, FullSync) } +func TestMultiProtoSynchronisation66Fast(t *testing.T) { testMultiProtoSync(t, 66, FastSync) } +func TestMultiProtoSynchronisation66Light(t *testing.T) { testMultiProtoSync(t, 66, LightSync) } + func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -850,9 +889,9 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { chain := testChainBase.shorten(blockCacheMaxItems - 15) // Create peers of every type - tester.newPeer("peer 63", 63, chain) tester.newPeer("peer 64", 64, chain) tester.newPeer("peer 65", 65, chain) + tester.newPeer("peer 66", 66, chain) // Synchronise with the requested peer and make sure all blocks were retrieved if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil { @@ -861,7 +900,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { assertOwnChain(t, tester, chain.len()) // Check that no peers have been dropped off - for _, version := range []int{63, 64, 65} { + for _, version := range []int{64, 65, 66} { peer := fmt.Sprintf("peer %d", version) if _, ok := tester.peers[peer]; !ok { t.Errorf("%s dropped", peer) @@ -871,12 +910,17 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that if a block is empty (e.g. header only), no body request should be // made, and instead the header should be assembled into a whole block in itself. -func TestEmptyShortCircuit64Full(t *testing.T) { testEmptyShortCircuit(t, 64, FullSync) } -func TestEmptyShortCircuit64Fast(t *testing.T) { testEmptyShortCircuit(t, 64, FastSync) } +func TestEmptyShortCircuit64Full(t *testing.T) { testEmptyShortCircuit(t, 64, FullSync) } +func TestEmptyShortCircuit64Fast(t *testing.T) { testEmptyShortCircuit(t, 64, FastSync) } + func TestEmptyShortCircuit65Full(t *testing.T) { testEmptyShortCircuit(t, 65, FullSync) } func TestEmptyShortCircuit65Fast(t *testing.T) { testEmptyShortCircuit(t, 65, FastSync) } func TestEmptyShortCircuit65Light(t *testing.T) { testEmptyShortCircuit(t, 65, LightSync) } +func TestEmptyShortCircuit66Full(t *testing.T) { testEmptyShortCircuit(t, 66, FullSync) } +func TestEmptyShortCircuit66Fast(t *testing.T) { testEmptyShortCircuit(t, 66, FastSync) } +func TestEmptyShortCircuit66Light(t *testing.T) { testEmptyShortCircuit(t, 66, LightSync) } + func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -923,12 +967,17 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { // Tests that headers are enqueued continuously, preventing malicious nodes from // stalling the downloader by feeding gapped header chains. -func TestMissingHeaderAttack64Full(t *testing.T) { testMissingHeaderAttack(t, 64, FullSync) } -func TestMissingHeaderAttack64Fast(t *testing.T) { testMissingHeaderAttack(t, 64, FastSync) } +func TestMissingHeaderAttack64Full(t *testing.T) { testMissingHeaderAttack(t, 64, FullSync) } +func TestMissingHeaderAttack64Fast(t *testing.T) { testMissingHeaderAttack(t, 64, FastSync) } + func TestMissingHeaderAttack65Full(t *testing.T) { testMissingHeaderAttack(t, 65, FullSync) } func TestMissingHeaderAttack65Fast(t *testing.T) { testMissingHeaderAttack(t, 65, FastSync) } func TestMissingHeaderAttack65Light(t *testing.T) { testMissingHeaderAttack(t, 65, LightSync) } +func TestMissingHeaderAttack66Full(t *testing.T) { testMissingHeaderAttack(t, 66, FullSync) } +func TestMissingHeaderAttack66Fast(t *testing.T) { testMissingHeaderAttack(t, 66, FastSync) } +func TestMissingHeaderAttack66Light(t *testing.T) { testMissingHeaderAttack(t, 66, LightSync) } + func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -953,12 +1002,17 @@ func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { // Tests that if requested headers are shifted (i.e. first is missing), the queue // detects the invalid numbering. -func TestShiftedHeaderAttack64Full(t *testing.T) { testShiftedHeaderAttack(t, 64, FullSync) } -func TestShiftedHeaderAttack64Fast(t *testing.T) { testShiftedHeaderAttack(t, 64, FastSync) } +func TestShiftedHeaderAttack64Full(t *testing.T) { testShiftedHeaderAttack(t, 64, FullSync) } +func TestShiftedHeaderAttack64Fast(t *testing.T) { testShiftedHeaderAttack(t, 64, FastSync) } + func TestShiftedHeaderAttack65Full(t *testing.T) { testShiftedHeaderAttack(t, 65, FullSync) } func TestShiftedHeaderAttack65Fast(t *testing.T) { testShiftedHeaderAttack(t, 65, FastSync) } func TestShiftedHeaderAttack65Light(t *testing.T) { testShiftedHeaderAttack(t, 65, LightSync) } +func TestShiftedHeaderAttack66Full(t *testing.T) { testShiftedHeaderAttack(t, 66, FullSync) } +func TestShiftedHeaderAttack66Fast(t *testing.T) { testShiftedHeaderAttack(t, 66, FastSync) } +func TestShiftedHeaderAttack66Light(t *testing.T) { testShiftedHeaderAttack(t, 66, LightSync) } + func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -990,6 +1044,7 @@ func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { // sure no state was corrupted. func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) } func TestInvalidHeaderRollback65Fast(t *testing.T) { testInvalidHeaderRollback(t, 65, FastSync) } +func TestInvalidHeaderRollback66Fast(t *testing.T) { testInvalidHeaderRollback(t, 66, FastSync) } func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1079,12 +1134,17 @@ func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { // Tests that a peer advertising a high TD doesn't get to stall the downloader // afterwards by not sending any useful hashes. -func TestHighTDStarvationAttack64Full(t *testing.T) { testHighTDStarvationAttack(t, 64, FullSync) } -func TestHighTDStarvationAttack64Fast(t *testing.T) { testHighTDStarvationAttack(t, 64, FastSync) } +func TestHighTDStarvationAttack64Full(t *testing.T) { testHighTDStarvationAttack(t, 64, FullSync) } +func TestHighTDStarvationAttack64Fast(t *testing.T) { testHighTDStarvationAttack(t, 64, FastSync) } + func TestHighTDStarvationAttack65Full(t *testing.T) { testHighTDStarvationAttack(t, 65, FullSync) } func TestHighTDStarvationAttack65Fast(t *testing.T) { testHighTDStarvationAttack(t, 65, FastSync) } func TestHighTDStarvationAttack65Light(t *testing.T) { testHighTDStarvationAttack(t, 65, LightSync) } +func TestHighTDStarvationAttack66Full(t *testing.T) { testHighTDStarvationAttack(t, 66, FullSync) } +func TestHighTDStarvationAttack66Fast(t *testing.T) { testHighTDStarvationAttack(t, 66, FastSync) } +func TestHighTDStarvationAttack66Light(t *testing.T) { testHighTDStarvationAttack(t, 66, LightSync) } + func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1101,6 +1161,7 @@ func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { // Tests that misbehaving peers are disconnected, whilst behaving ones are not. func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) } func TestBlockHeaderAttackerDropping65(t *testing.T) { testBlockHeaderAttackerDropping(t, 65) } +func TestBlockHeaderAttackerDropping66(t *testing.T) { testBlockHeaderAttackerDropping(t, 66) } func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { t.Parallel() @@ -1152,12 +1213,17 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { // Tests that synchronisation progress (origin block number, current block number // and highest block number) is tracked and updated correctly. -func TestSyncProgress64Full(t *testing.T) { testSyncProgress(t, 64, FullSync) } -func TestSyncProgress64Fast(t *testing.T) { testSyncProgress(t, 64, FastSync) } +func TestSyncProgress64Full(t *testing.T) { testSyncProgress(t, 64, FullSync) } +func TestSyncProgress64Fast(t *testing.T) { testSyncProgress(t, 64, FastSync) } + func TestSyncProgress65Full(t *testing.T) { testSyncProgress(t, 65, FullSync) } func TestSyncProgress65Fast(t *testing.T) { testSyncProgress(t, 65, FastSync) } func TestSyncProgress65Light(t *testing.T) { testSyncProgress(t, 65, LightSync) } +func TestSyncProgress66Full(t *testing.T) { testSyncProgress(t, 66, FullSync) } +func TestSyncProgress66Fast(t *testing.T) { testSyncProgress(t, 66, FastSync) } +func TestSyncProgress66Light(t *testing.T) { testSyncProgress(t, 66, LightSync) } + func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1234,12 +1300,17 @@ func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.Sync // Tests that synchronisation progress (origin block number and highest block // number) is tracked and updated correctly in case of a fork (or manual head // revertal). -func TestForkedSyncProgress64Full(t *testing.T) { testForkedSyncProgress(t, 64, FullSync) } -func TestForkedSyncProgress64Fast(t *testing.T) { testForkedSyncProgress(t, 64, FastSync) } +func TestForkedSyncProgress64Full(t *testing.T) { testForkedSyncProgress(t, 64, FullSync) } +func TestForkedSyncProgress64Fast(t *testing.T) { testForkedSyncProgress(t, 64, FastSync) } + func TestForkedSyncProgress65Full(t *testing.T) { testForkedSyncProgress(t, 65, FullSync) } func TestForkedSyncProgress65Fast(t *testing.T) { testForkedSyncProgress(t, 65, FastSync) } func TestForkedSyncProgress65Light(t *testing.T) { testForkedSyncProgress(t, 65, LightSync) } +func TestForkedSyncProgress66Full(t *testing.T) { testForkedSyncProgress(t, 66, FullSync) } +func TestForkedSyncProgress66Fast(t *testing.T) { testForkedSyncProgress(t, 66, FastSync) } +func TestForkedSyncProgress66Light(t *testing.T) { testForkedSyncProgress(t, 66, LightSync) } + func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1308,12 +1379,17 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Tests that if synchronisation is aborted due to some failure, then the progress // origin is not updated in the next sync cycle, as it should be considered the // continuation of the previous sync and not a new instance. -func TestFailedSyncProgress64Full(t *testing.T) { testFailedSyncProgress(t, 64, FullSync) } -func TestFailedSyncProgress64Fast(t *testing.T) { testFailedSyncProgress(t, 64, FastSync) } +func TestFailedSyncProgress64Full(t *testing.T) { testFailedSyncProgress(t, 64, FullSync) } +func TestFailedSyncProgress64Fast(t *testing.T) { testFailedSyncProgress(t, 64, FastSync) } + func TestFailedSyncProgress65Full(t *testing.T) { testFailedSyncProgress(t, 65, FullSync) } func TestFailedSyncProgress65Fast(t *testing.T) { testFailedSyncProgress(t, 65, FastSync) } func TestFailedSyncProgress65Light(t *testing.T) { testFailedSyncProgress(t, 65, LightSync) } +func TestFailedSyncProgress66Full(t *testing.T) { testFailedSyncProgress(t, 66, FullSync) } +func TestFailedSyncProgress66Fast(t *testing.T) { testFailedSyncProgress(t, 66, FastSync) } +func TestFailedSyncProgress66Light(t *testing.T) { testFailedSyncProgress(t, 66, LightSync) } + func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1379,12 +1455,17 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Tests that if an attacker fakes a chain height, after the attack is detected, // the progress height is successfully reduced at the next sync invocation. -func TestFakedSyncProgress64Full(t *testing.T) { testFakedSyncProgress(t, 64, FullSync) } -func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, FastSync) } +func TestFakedSyncProgress64Full(t *testing.T) { testFakedSyncProgress(t, 64, FullSync) } +func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, FastSync) } + func TestFakedSyncProgress65Full(t *testing.T) { testFakedSyncProgress(t, 65, FullSync) } func TestFakedSyncProgress65Fast(t *testing.T) { testFakedSyncProgress(t, 65, FastSync) } func TestFakedSyncProgress65Light(t *testing.T) { testFakedSyncProgress(t, 65, LightSync) } +func TestFakedSyncProgress66Full(t *testing.T) { testFakedSyncProgress(t, 66, FullSync) } +func TestFakedSyncProgress66Fast(t *testing.T) { testFakedSyncProgress(t, 66, FastSync) } +func TestFakedSyncProgress66Light(t *testing.T) { testFakedSyncProgress(t, 66, LightSync) } + func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1454,12 +1535,17 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // This test reproduces an issue where unexpected deliveries would // block indefinitely if they arrived at the right time. -func TestDeliverHeadersHang64Full(t *testing.T) { testDeliverHeadersHang(t, 64, FullSync) } -func TestDeliverHeadersHang64Fast(t *testing.T) { testDeliverHeadersHang(t, 64, FastSync) } +func TestDeliverHeadersHang64Full(t *testing.T) { testDeliverHeadersHang(t, 64, FullSync) } +func TestDeliverHeadersHang64Fast(t *testing.T) { testDeliverHeadersHang(t, 64, FastSync) } + func TestDeliverHeadersHang65Full(t *testing.T) { testDeliverHeadersHang(t, 65, FullSync) } func TestDeliverHeadersHang65Fast(t *testing.T) { testDeliverHeadersHang(t, 65, FastSync) } func TestDeliverHeadersHang65Light(t *testing.T) { testDeliverHeadersHang(t, 65, LightSync) } +func TestDeliverHeadersHang66Full(t *testing.T) { testDeliverHeadersHang(t, 66, FullSync) } +func TestDeliverHeadersHang66Fast(t *testing.T) { testDeliverHeadersHang(t, 66, FastSync) } +func TestDeliverHeadersHang66Light(t *testing.T) { testDeliverHeadersHang(t, 66, LightSync) } + func testDeliverHeadersHang(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1613,12 +1699,17 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { // Tests that peers below a pre-configured checkpoint block are prevented from // being fast-synced from, avoiding potential cheap eclipse attacks. -func TestCheckpointEnforcement64Full(t *testing.T) { testCheckpointEnforcement(t, 64, FullSync) } -func TestCheckpointEnforcement64Fast(t *testing.T) { testCheckpointEnforcement(t, 64, FastSync) } +func TestCheckpointEnforcement64Full(t *testing.T) { testCheckpointEnforcement(t, 64, FullSync) } +func TestCheckpointEnforcement64Fast(t *testing.T) { testCheckpointEnforcement(t, 64, FastSync) } + func TestCheckpointEnforcement65Full(t *testing.T) { testCheckpointEnforcement(t, 65, FullSync) } func TestCheckpointEnforcement65Fast(t *testing.T) { testCheckpointEnforcement(t, 65, FastSync) } func TestCheckpointEnforcement65Light(t *testing.T) { testCheckpointEnforcement(t, 65, LightSync) } +func TestCheckpointEnforcement66Full(t *testing.T) { testCheckpointEnforcement(t, 66, FullSync) } +func TestCheckpointEnforcement66Fast(t *testing.T) { testCheckpointEnforcement(t, 66, FastSync) } +func TestCheckpointEnforcement66Light(t *testing.T) { testCheckpointEnforcement(t, 66, LightSync) } + func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index ba90bf31cb..7852569d8e 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -29,6 +29,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" ) @@ -457,7 +458,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.headerThroughput } - return ps.idlePeers(64, 65, idle, throughput) + return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) } // BodyIdlePeers retrieves a flat list of all the currently body-idle peers within @@ -471,7 +472,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.blockThroughput } - return ps.idlePeers(64, 65, idle, throughput) + return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) } // ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers @@ -485,7 +486,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.receiptThroughput } - return ps.idlePeers(64, 65, idle, throughput) + return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) } // NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle @@ -499,7 +500,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.stateThroughput } - return ps.idlePeers(64, 65, idle, throughput) + return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) } // idlePeers retrieves a flat list of all currently idle peers satisfying the diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index e32008fb41..64648ed419 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -17,19 +17,17 @@ package eth import ( - "encoding/json" "fmt" "math/big" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -166,6 +164,64 @@ func Handle(backend Backend, peer *Peer) error { } } +type msgHandler func(backend Backend, msg Decoder, peer *Peer) error +type Decoder interface { + Decode(val interface{}) error + Time() time.Time +} + +var eth64 = map[uint64]msgHandler{ + GetBlockHeadersMsg: handleGetBlockHeaders, + BlockHeadersMsg: handleBlockHeaders, + GetBlockBodiesMsg: handleGetBlockBodies, + BlockBodiesMsg: handleBlockBodies, + GetNodeDataMsg: handleGetNodeData, + NodeDataMsg: handleNodeData, + GetReceiptsMsg: handleGetReceipts, + ReceiptsMsg: handleReceipts, + NewBlockHashesMsg: handleNewBlockhashes, + NewBlockMsg: handleNewBlock, + TransactionsMsg: handleTransactions, +} +var eth65 = map[uint64]msgHandler{ + // old 64 messages + GetBlockHeadersMsg: handleGetBlockHeaders, + BlockHeadersMsg: handleBlockHeaders, + GetBlockBodiesMsg: handleGetBlockBodies, + BlockBodiesMsg: handleBlockBodies, + GetNodeDataMsg: handleGetNodeData, + NodeDataMsg: handleNodeData, + GetReceiptsMsg: handleGetReceipts, + ReceiptsMsg: handleReceipts, + NewBlockHashesMsg: handleNewBlockhashes, + NewBlockMsg: handleNewBlock, + TransactionsMsg: handleTransactions, + // New eth65 messages + NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes, + GetPooledTransactionsMsg: handleGetPooledTransactions, + PooledTransactionsMsg: handlePooledTransactions, +} + +var eth66 = map[uint64]msgHandler{ + // eth64 announcement messages (no id) + NewBlockHashesMsg: handleNewBlockhashes, + NewBlockMsg: handleNewBlock, + TransactionsMsg: handleTransactions, + // eth65 announcement messages (no id) + NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes, + // eth66 messages with request-id + GetBlockHeadersMsg: handleGetBlockHeaders66, + BlockHeadersMsg: handleBlockHeaders66, + GetBlockBodiesMsg: handleGetBlockBodies66, + BlockBodiesMsg: handleBlockBodies66, + GetNodeDataMsg: handleGetNodeData66, + NodeDataMsg: handleNodeData66, + GetReceiptsMsg: handleGetReceipts66, + ReceiptsMsg: handleReceipts66, + GetPooledTransactionsMsg: handleGetPooledTransactions66, + PooledTransactionsMsg: handlePooledTransactions66, +} + // handleMessage is invoked whenever an inbound message is received from a remote // peer. The remote connection is torn down upon returning any error. func handleMessage(backend Backend, peer *Peer) error { @@ -179,334 +235,15 @@ func handleMessage(backend Backend, peer *Peer) error { } defer msg.Discard() - // Handle the message depending on its contents - switch { - case msg.Code == StatusMsg: - // Status messages should never arrive after the handshake - return fmt.Errorf("%w: uncontrolled status message", errExtraStatusMsg) - - // Block header query, collect the requested headers and reply - case msg.Code == GetBlockHeadersMsg: - // Decode the complex header query - var query GetBlockHeadersPacket - if err := msg.Decode(&query); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - hashMode := query.Origin.Hash != (common.Hash{}) - first := true - maxNonCanonical := uint64(100) - - // Gather headers until the fetch or network limits is reached - var ( - bytes common.StorageSize - headers []*types.Header - unknown bool - lookups int - ) - for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && - len(headers) < maxHeadersServe && lookups < 2*maxHeadersServe { - lookups++ - // Retrieve the next header satisfying the query - var origin *types.Header - if hashMode { - if first { - first = false - origin = backend.Chain().GetHeaderByHash(query.Origin.Hash) - if origin != nil { - query.Origin.Number = origin.Number.Uint64() - } - } else { - origin = backend.Chain().GetHeader(query.Origin.Hash, query.Origin.Number) - } - } else { - origin = backend.Chain().GetHeaderByNumber(query.Origin.Number) - } - if origin == nil { - break - } - headers = append(headers, origin) - bytes += estHeaderSize - - // Advance to the next header of the query - switch { - case hashMode && query.Reverse: - // Hash based traversal towards the genesis block - ancestor := query.Skip + 1 - if ancestor == 0 { - unknown = true - } else { - query.Origin.Hash, query.Origin.Number = backend.Chain().GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) - unknown = (query.Origin.Hash == common.Hash{}) - } - case hashMode && !query.Reverse: - // Hash based traversal towards the leaf block - var ( - current = origin.Number.Uint64() - next = current + query.Skip + 1 - ) - if next <= current { - infos, _ := json.MarshalIndent(peer.Peer.Info(), "", " ") - peer.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) - unknown = true - } else { - if header := backend.Chain().GetHeaderByNumber(next); header != nil { - nextHash := header.Hash() - expOldHash, _ := backend.Chain().GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) - if expOldHash == query.Origin.Hash { - query.Origin.Hash, query.Origin.Number = nextHash, next - } else { - unknown = true - } - } else { - unknown = true - } - } - case query.Reverse: - // Number based traversal towards the genesis block - if query.Origin.Number >= query.Skip+1 { - query.Origin.Number -= query.Skip + 1 - } else { - unknown = true - } - - case !query.Reverse: - // Number based traversal towards the leaf block - query.Origin.Number += query.Skip + 1 - } - } - return peer.SendBlockHeaders(headers) - - case msg.Code == BlockHeadersMsg: - // A batch of headers arrived to one of our previous requests - res := new(BlockHeadersPacket) - if err := msg.Decode(res); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - return backend.Handle(peer, res) - - case msg.Code == GetBlockBodiesMsg: - // Decode the block body retrieval message - var query GetBlockBodiesPacket - if err := msg.Decode(&query); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Gather blocks until the fetch or network limits is reached - var ( - bytes int - bodies []rlp.RawValue - ) - for lookups, hash := range query { - if bytes >= softResponseLimit || len(bodies) >= maxBodiesServe || - lookups >= 2*maxBodiesServe { - break - } - if data := backend.Chain().GetBodyRLP(hash); len(data) != 0 { - bodies = append(bodies, data) - bytes += len(data) - } - } - return peer.SendBlockBodiesRLP(bodies) - - case msg.Code == BlockBodiesMsg: - // A batch of block bodies arrived to one of our previous requests - res := new(BlockBodiesPacket) - if err := msg.Decode(res); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - return backend.Handle(peer, res) - - case msg.Code == GetNodeDataMsg: - // Decode the trie node data retrieval message - var query GetNodeDataPacket - if err := msg.Decode(&query); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - bytes int - nodes [][]byte - ) - for lookups, hash := range query { - if bytes >= softResponseLimit || len(nodes) >= maxNodeDataServe || - lookups >= 2*maxNodeDataServe { - break - } - // Retrieve the requested state entry - if bloom := backend.StateBloom(); bloom != nil && !bloom.Contains(hash[:]) { - // Only lookup the trie node if there's chance that we actually have it - continue - } - entry, err := backend.Chain().TrieNode(hash) - if len(entry) == 0 || err != nil { - // Read the contract code with prefix only to save unnecessary lookups. - entry, err = backend.Chain().ContractCodeWithPrefix(hash) - } - if err == nil && len(entry) > 0 { - nodes = append(nodes, entry) - bytes += len(entry) - } - } - return peer.SendNodeData(nodes) - - case msg.Code == NodeDataMsg: - // A batch of node state data arrived to one of our previous requests - res := new(NodeDataPacket) - if err := msg.Decode(res); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - return backend.Handle(peer, res) - - case msg.Code == GetReceiptsMsg: - // Decode the block receipts retrieval message - var query GetReceiptsPacket - if err := msg.Decode(&query); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - bytes int - receipts []rlp.RawValue - ) - for lookups, hash := range query { - if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe || - lookups >= 2*maxReceiptsServe { - break - } - // Retrieve the requested block's receipts - results := backend.Chain().GetReceiptsByHash(hash) - if results == nil { - if header := backend.Chain().GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { - continue - } - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(results); err != nil { - log.Error("Failed to encode receipt", "err", err) - } else { - receipts = append(receipts, encoded) - bytes += len(encoded) - } - } - return peer.SendReceiptsRLP(receipts) - - case msg.Code == ReceiptsMsg: - // A batch of receipts arrived to one of our previous requests - res := new(ReceiptsPacket) - if err := msg.Decode(res); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - return backend.Handle(peer, res) - - case msg.Code == NewBlockHashesMsg: - // A batch of new block announcements just arrived - ann := new(NewBlockHashesPacket) - if err := msg.Decode(ann); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Mark the hashes as present at the remote node - for _, block := range *ann { - peer.markBlock(block.Hash) - } - // Deliver them all to the backend for queuing - return backend.Handle(peer, ann) - - case msg.Code == NewBlockMsg: - // Retrieve and decode the propagated block - ann := new(NewBlockPacket) - if err := msg.Decode(ann); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - if hash := types.CalcUncleHash(ann.Block.Uncles()); hash != ann.Block.UncleHash() { - log.Warn("Propagated block has invalid uncles", "have", hash, "exp", ann.Block.UncleHash()) - break // TODO(karalabe): return error eventually, but wait a few releases - } - if hash := types.DeriveSha(ann.Block.Transactions(), trie.NewStackTrie(nil)); hash != ann.Block.TxHash() { - log.Warn("Propagated block has invalid body", "have", hash, "exp", ann.Block.TxHash()) - break // TODO(karalabe): return error eventually, but wait a few releases - } - if err := ann.sanityCheck(); err != nil { - return err - } - ann.Block.ReceivedAt = msg.ReceivedAt - ann.Block.ReceivedFrom = peer - - // Mark the peer as owning the block - peer.markBlock(ann.Block.Hash()) - - return backend.Handle(peer, ann) - - case msg.Code == NewPooledTransactionHashesMsg && peer.version >= ETH65: - // New transaction announcement arrived, make sure we have - // a valid and fresh chain to handle them - if !backend.AcceptTxs() { - break - } - ann := new(NewPooledTransactionHashesPacket) - if err := msg.Decode(ann); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Schedule all the unknown hashes for retrieval - for _, hash := range *ann { - peer.markTransaction(hash) - } - return backend.Handle(peer, ann) - - case msg.Code == GetPooledTransactionsMsg && peer.version >= ETH65: - // Decode the pooled transactions retrieval message - var query GetPooledTransactionsPacket - if err := msg.Decode(&query); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Gather transactions until the fetch or network limits is reached - var ( - bytes int - hashes []common.Hash - txs []rlp.RawValue - ) - for _, hash := range query { - if bytes >= softResponseLimit { - break - } - // Retrieve the requested transaction, skipping if unknown to us - tx := backend.TxPool().Get(hash) - if tx == nil { - continue - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(tx); err != nil { - log.Error("Failed to encode transaction", "err", err) - } else { - hashes = append(hashes, hash) - txs = append(txs, encoded) - bytes += len(encoded) - } - } - return peer.SendPooledTransactionsRLP(hashes, txs) - - case msg.Code == TransactionsMsg || (msg.Code == PooledTransactionsMsg && peer.version >= ETH65): - // Transactions arrived, make sure we have a valid and fresh chain to handle them - if !backend.AcceptTxs() { - break - } - // Transactions can be processed, parse all of them and deliver to the pool - var txs []*types.Transaction - if err := msg.Decode(&txs); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - for i, tx := range txs { - // Validate and mark the remote transaction - if tx == nil { - return fmt.Errorf("%w: transaction %d is nil", errDecode, i) - } - peer.markTransaction(tx.Hash()) - } - if msg.Code == PooledTransactionsMsg { - return backend.Handle(peer, (*PooledTransactionsPacket)(&txs)) - } - return backend.Handle(peer, (*TransactionsPacket)(&txs)) + var handlers = eth64 + if peer.Version() == ETH65 { + handlers = eth65 + } else if peer.Version() >= ETH66 { + handlers = eth66 + } - default: - return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) + if handler := handlers[msg.Code]; handler != nil { + return handler(backend, msg, peer) } - return nil + return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) } diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go new file mode 100644 index 0000000000..8433fa343a --- /dev/null +++ b/eth/protocols/eth/handlers.go @@ -0,0 +1,510 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// handleGetBlockHeaders handles Block header query, collect the requested headers and reply +func handleGetBlockHeaders(backend Backend, msg Decoder, peer *Peer) error { + // Decode the complex header query + var query GetBlockHeadersPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetBlockHeadersQuery(backend, &query, peer) + return peer.SendBlockHeaders(response) +} + +// handleGetBlockHeaders66 is the eth/66 version of handleGetBlockHeaders +func handleGetBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { + // Decode the complex header query + var query GetBlockHeadersPacket66 + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetBlockHeadersQuery(backend, query.GetBlockHeadersPacket, peer) + return peer.ReplyBlockHeaders(query.RequestId, response) +} + +func answerGetBlockHeadersQuery(backend Backend, query *GetBlockHeadersPacket, peer *Peer) []*types.Header { + hashMode := query.Origin.Hash != (common.Hash{}) + first := true + maxNonCanonical := uint64(100) + + // Gather headers until the fetch or network limits is reached + var ( + bytes common.StorageSize + headers []*types.Header + unknown bool + lookups int + ) + for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && + len(headers) < maxHeadersServe && lookups < 2*maxHeadersServe { + lookups++ + // Retrieve the next header satisfying the query + var origin *types.Header + if hashMode { + if first { + first = false + origin = backend.Chain().GetHeaderByHash(query.Origin.Hash) + if origin != nil { + query.Origin.Number = origin.Number.Uint64() + } + } else { + origin = backend.Chain().GetHeader(query.Origin.Hash, query.Origin.Number) + } + } else { + origin = backend.Chain().GetHeaderByNumber(query.Origin.Number) + } + if origin == nil { + break + } + headers = append(headers, origin) + bytes += estHeaderSize + + // Advance to the next header of the query + switch { + case hashMode && query.Reverse: + // Hash based traversal towards the genesis block + ancestor := query.Skip + 1 + if ancestor == 0 { + unknown = true + } else { + query.Origin.Hash, query.Origin.Number = backend.Chain().GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) + unknown = (query.Origin.Hash == common.Hash{}) + } + case hashMode && !query.Reverse: + // Hash based traversal towards the leaf block + var ( + current = origin.Number.Uint64() + next = current + query.Skip + 1 + ) + if next <= current { + infos, _ := json.MarshalIndent(peer.Peer.Info(), "", " ") + peer.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) + unknown = true + } else { + if header := backend.Chain().GetHeaderByNumber(next); header != nil { + nextHash := header.Hash() + expOldHash, _ := backend.Chain().GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) + if expOldHash == query.Origin.Hash { + query.Origin.Hash, query.Origin.Number = nextHash, next + } else { + unknown = true + } + } else { + unknown = true + } + } + case query.Reverse: + // Number based traversal towards the genesis block + if query.Origin.Number >= query.Skip+1 { + query.Origin.Number -= query.Skip + 1 + } else { + unknown = true + } + + case !query.Reverse: + // Number based traversal towards the leaf block + query.Origin.Number += query.Skip + 1 + } + } + return headers +} + +func handleGetBlockBodies(backend Backend, msg Decoder, peer *Peer) error { + // Decode the block body retrieval message + var query GetBlockBodiesPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetBlockBodiesQuery(backend, query, peer) + return peer.SendBlockBodiesRLP(response) +} + +func handleGetBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { + // Decode the block body retrieval message + var query GetBlockBodiesPacket66 + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetBlockBodiesQuery(backend, query.GetBlockBodiesPacket, peer) + return peer.ReplyBlockBodiesRLP(query.RequestId, response) +} + +func answerGetBlockBodiesQuery(backend Backend, query GetBlockBodiesPacket, peer *Peer) []rlp.RawValue { + // Gather blocks until the fetch or network limits is reached + var ( + bytes int + bodies []rlp.RawValue + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(bodies) >= maxBodiesServe || + lookups >= 2*maxBodiesServe { + break + } + if data := backend.Chain().GetBodyRLP(hash); len(data) != 0 { + bodies = append(bodies, data) + bytes += len(data) + } + } + return bodies +} + +func handleGetNodeData(backend Backend, msg Decoder, peer *Peer) error { + // Decode the trie node data retrieval message + var query GetNodeDataPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetNodeDataQuery(backend, query, peer) + return peer.SendNodeData(response) +} + +func handleGetNodeData66(backend Backend, msg Decoder, peer *Peer) error { + // Decode the trie node data retrieval message + var query GetNodeDataPacket66 + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetNodeDataQuery(backend, query.GetNodeDataPacket, peer) + return peer.ReplyNodeData(query.RequestId, response) +} + +func answerGetNodeDataQuery(backend Backend, query GetNodeDataPacket, peer *Peer) [][]byte { + // Gather state data until the fetch or network limits is reached + var ( + bytes int + nodes [][]byte + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(nodes) >= maxNodeDataServe || + lookups >= 2*maxNodeDataServe { + break + } + // Retrieve the requested state entry + if bloom := backend.StateBloom(); bloom != nil && !bloom.Contains(hash[:]) { + // Only lookup the trie node if there's chance that we actually have it + continue + } + entry, err := backend.Chain().TrieNode(hash) + if len(entry) == 0 || err != nil { + // Read the contract code with prefix only to save unnecessary lookups. + entry, err = backend.Chain().ContractCodeWithPrefix(hash) + } + if err == nil && len(entry) > 0 { + nodes = append(nodes, entry) + bytes += len(entry) + } + } + return nodes +} + +func handleGetReceipts(backend Backend, msg Decoder, peer *Peer) error { + // Decode the block receipts retrieval message + var query GetReceiptsPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetReceiptsQuery(backend, query, peer) + return peer.SendReceiptsRLP(response) +} + +func handleGetReceipts66(backend Backend, msg Decoder, peer *Peer) error { + // Decode the block receipts retrieval message + var query GetReceiptsPacket66 + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetReceiptsQuery(backend, query.GetReceiptsPacket, peer) + return peer.ReplyReceiptsRLP(query.RequestId, response) +} + +func answerGetReceiptsQuery(backend Backend, query GetReceiptsPacket, peer *Peer) []rlp.RawValue { + // Gather state data until the fetch or network limits is reached + var ( + bytes int + receipts []rlp.RawValue + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe || + lookups >= 2*maxReceiptsServe { + break + } + // Retrieve the requested block's receipts + results := backend.Chain().GetReceiptsByHash(hash) + if results == nil { + if header := backend.Chain().GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { + continue + } + } + // If known, encode and queue for response packet + if encoded, err := rlp.EncodeToBytes(results); err != nil { + log.Error("Failed to encode receipt", "err", err) + } else { + receipts = append(receipts, encoded) + bytes += len(encoded) + } + } + return receipts +} + +func handleNewBlockhashes(backend Backend, msg Decoder, peer *Peer) error { + // A batch of new block announcements just arrived + ann := new(NewBlockHashesPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Mark the hashes as present at the remote node + for _, block := range *ann { + peer.markBlock(block.Hash) + } + // Deliver them all to the backend for queuing + return backend.Handle(peer, ann) +} + +func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error { + // Retrieve and decode the propagated block + ann := new(NewBlockPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if hash := types.CalcUncleHash(ann.Block.Uncles()); hash != ann.Block.UncleHash() { + log.Warn("Propagated block has invalid uncles", "have", hash, "exp", ann.Block.UncleHash()) + return nil // TODO(karalabe): return error eventually, but wait a few releases + } + if hash := types.DeriveSha(ann.Block.Transactions(), trie.NewStackTrie(nil)); hash != ann.Block.TxHash() { + log.Warn("Propagated block has invalid body", "have", hash, "exp", ann.Block.TxHash()) + return nil // TODO(karalabe): return error eventually, but wait a few releases + } + if err := ann.sanityCheck(); err != nil { + return err + } + ann.Block.ReceivedAt = msg.Time() + ann.Block.ReceivedFrom = peer + + // Mark the peer as owning the block + peer.markBlock(ann.Block.Hash()) + + return backend.Handle(peer, ann) +} + +func handleBlockHeaders(backend Backend, msg Decoder, peer *Peer) error { + // A batch of headers arrived to one of our previous requests + res := new(BlockHeadersPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) +} + +func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { + // A batch of headers arrived to one of our previous requests + res := new(BlockHeadersPacket66) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, &res.BlockHeadersPacket) +} + +func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error { + // A batch of block bodies arrived to one of our previous requests + res := new(BlockBodiesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) +} + +func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { + // A batch of block bodies arrived to one of our previous requests + res := new(BlockBodiesPacket66) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, &res.BlockBodiesPacket) +} + +func handleNodeData(backend Backend, msg Decoder, peer *Peer) error { + // A batch of node state data arrived to one of our previous requests + res := new(NodeDataPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) +} + +func handleNodeData66(backend Backend, msg Decoder, peer *Peer) error { + // A batch of node state data arrived to one of our previous requests + res := new(NodeDataPacket66) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, &res.NodeDataPacket) +} + +func handleReceipts(backend Backend, msg Decoder, peer *Peer) error { + // A batch of receipts arrived to one of our previous requests + res := new(ReceiptsPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) +} + +func handleReceipts66(backend Backend, msg Decoder, peer *Peer) error { + // A batch of receipts arrived to one of our previous requests + res := new(ReceiptsPacket66) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, &res.ReceiptsPacket) +} + +func handleNewPooledTransactionHashes(backend Backend, msg Decoder, peer *Peer) error { + // New transaction announcement arrived, make sure we have + // a valid and fresh chain to handle them + if !backend.AcceptTxs() { + return nil + } + ann := new(NewPooledTransactionHashesPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Schedule all the unknown hashes for retrieval + for _, hash := range *ann { + peer.markTransaction(hash) + } + return backend.Handle(peer, ann) +} + +func handleGetPooledTransactions(backend Backend, msg Decoder, peer *Peer) error { + // Decode the pooled transactions retrieval message + var query GetPooledTransactionsPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + hashes, txs := answerGetPooledTransactions(backend, query, peer) + return peer.SendPooledTransactionsRLP(hashes, txs) +} + +func handleGetPooledTransactions66(backend Backend, msg Decoder, peer *Peer) error { + // Decode the pooled transactions retrieval message + var query GetPooledTransactionsPacket66 + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + hashes, txs := answerGetPooledTransactions(backend, query.GetPooledTransactionsPacket, peer) + return peer.ReplyPooledTransactionsRLP(query.RequestId, hashes, txs) +} + +func answerGetPooledTransactions(backend Backend, query GetPooledTransactionsPacket, peer *Peer) ([]common.Hash, []rlp.RawValue) { + // Gather transactions until the fetch or network limits is reached + var ( + bytes int + hashes []common.Hash + txs []rlp.RawValue + ) + for _, hash := range query { + if bytes >= softResponseLimit { + break + } + // Retrieve the requested transaction, skipping if unknown to us + tx := backend.TxPool().Get(hash) + if tx == nil { + continue + } + // If known, encode and queue for response packet + if encoded, err := rlp.EncodeToBytes(tx); err != nil { + log.Error("Failed to encode transaction", "err", err) + } else { + hashes = append(hashes, hash) + txs = append(txs, encoded) + bytes += len(encoded) + } + } + return hashes, txs +} + +func handleTransactions(backend Backend, msg Decoder, peer *Peer) error { + // Transactions arrived, make sure we have a valid and fresh chain to handle them + if !backend.AcceptTxs() { + return nil + } + // Transactions can be processed, parse all of them and deliver to the pool + var txs TransactionsPacket + if err := msg.Decode(&txs); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + for i, tx := range txs { + // Validate and mark the remote transaction + if tx == nil { + return fmt.Errorf("%w: transaction %d is nil", errDecode, i) + } + peer.markTransaction(tx.Hash()) + } + return backend.Handle(peer, &txs) +} + +func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error { + // Transactions arrived, make sure we have a valid and fresh chain to handle them + if !backend.AcceptTxs() { + return nil + } + // Transactions can be processed, parse all of them and deliver to the pool + var txs PooledTransactionsPacket + if err := msg.Decode(&txs); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + for i, tx := range txs { + // Validate and mark the remote transaction + if tx == nil { + return fmt.Errorf("%w: transaction %d is nil", errDecode, i) + } + peer.markTransaction(tx.Hash()) + } + return backend.Handle(peer, &txs) +} + +func handlePooledTransactions66(backend Backend, msg Decoder, peer *Peer) error { + // Transactions arrived, make sure we have a valid and fresh chain to handle them + if !backend.AcceptTxs() { + return nil + } + // Transactions can be processed, parse all of them and deliver to the pool + var txs PooledTransactionsPacket66 + if err := msg.Decode(&txs); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + for i, tx := range txs.PooledTransactionsPacket { + // Validate and mark the remote transaction + if tx == nil { + return fmt.Errorf("%w: transaction %d is nil", errDecode, i) + } + peer.markTransaction(tx.Hash()) + } + return backend.Handle(peer, &txs.PooledTransactionsPacket) +} diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 735ef78ce7..709fca8655 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -18,6 +18,7 @@ package eth import ( "math/big" + "math/rand" "sync" mapset "github.com/deckarep/golang-set" @@ -267,6 +268,22 @@ func (p *Peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValu return p2p.Send(p.rw, PooledTransactionsMsg, txs) // Not packed into PooledTransactionsPacket to avoid RLP decoding } +// ReplyPooledTransactionsRLP is the eth/66 version of SendPooledTransactionsRLP. +func (p *Peer) ReplyPooledTransactionsRLP(id uint64, hashes []common.Hash, txs []rlp.RawValue) error { + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { + p.knownTxs.Pop() + } + for _, hash := range hashes { + p.knownTxs.Add(hash) + } + // Not packed into PooledTransactionsPacket to avoid RLP decoding + return p2p.Send(p.rw, PooledTransactionsMsg, PooledTransactionsRLPPacket66{ + RequestId: id, + PooledTransactionsRLPPacket: txs, + }) +} + // SendNewBlockHashes announces the availability of a number of blocks through // a hash notification. func (p *Peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error { @@ -308,7 +325,10 @@ func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error { p.knownBlocks.Pop() } p.knownBlocks.Add(block.Hash()) - return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{block, td}) + return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{ + Block: block, + TD: td, + }) } // AsyncSendNewBlock queues an entire block for propagation to a remote peer. If @@ -331,9 +351,12 @@ func (p *Peer) SendBlockHeaders(headers []*types.Header) error { return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket(headers)) } -// SendBlockBodies sends a batch of block contents to the remote peer. -func (p *Peer) SendBlockBodies(bodies []*BlockBody) error { - return p2p.Send(p.rw, BlockBodiesMsg, BlockBodiesPacket(bodies)) +// ReplyBlockHeaders is the eth/66 version of SendBlockHeaders. +func (p *Peer) ReplyBlockHeaders(id uint64, headers []*types.Header) error { + return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket66{ + RequestId: id, + BlockHeadersPacket: headers, + }) } // SendBlockBodiesRLP sends a batch of block contents to the remote peer from @@ -342,52 +365,98 @@ func (p *Peer) SendBlockBodiesRLP(bodies []rlp.RawValue) error { return p2p.Send(p.rw, BlockBodiesMsg, bodies) // Not packed into BlockBodiesPacket to avoid RLP decoding } +// ReplyBlockBodiesRLP is the eth/66 version of SendBlockBodiesRLP. +func (p *Peer) ReplyBlockBodiesRLP(id uint64, bodies []rlp.RawValue) error { + // Not packed into BlockBodiesPacket to avoid RLP decoding + return p2p.Send(p.rw, BlockBodiesMsg, BlockBodiesRLPPacket66{ + RequestId: id, + BlockBodiesRLPPacket: bodies, + }) +} + // SendNodeDataRLP sends a batch of arbitrary internal data, corresponding to the // hashes requested. func (p *Peer) SendNodeData(data [][]byte) error { return p2p.Send(p.rw, NodeDataMsg, NodeDataPacket(data)) } +// ReplyNodeData is the eth/66 response to GetNodeData. +func (p *Peer) ReplyNodeData(id uint64, data [][]byte) error { + return p2p.Send(p.rw, NodeDataMsg, NodeDataPacket66{ + RequestId: id, + NodeDataPacket: data, + }) +} + // SendReceiptsRLP sends a batch of transaction receipts, corresponding to the // ones requested from an already RLP encoded format. func (p *Peer) SendReceiptsRLP(receipts []rlp.RawValue) error { return p2p.Send(p.rw, ReceiptsMsg, receipts) // Not packed into ReceiptsPacket to avoid RLP decoding } +// ReplyReceiptsRLP is the eth/66 response to GetReceipts. +func (p *Peer) ReplyReceiptsRLP(id uint64, receipts []rlp.RawValue) error { + return p2p.Send(p.rw, ReceiptsMsg, ReceiptsRLPPacket66{ + RequestId: id, + ReceiptsRLPPacket: receipts, + }) +} + // RequestOneHeader is a wrapper around the header query functions to fetch a // single header. It is used solely by the fetcher. func (p *Peer) RequestOneHeader(hash common.Hash) error { p.Log().Debug("Fetching single header", "hash", hash) - return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + query := GetBlockHeadersPacket{ Origin: HashOrNumber{Hash: hash}, Amount: uint64(1), Skip: uint64(0), Reverse: false, - }) + } + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{ + RequestId: rand.Uint64(), + GetBlockHeadersPacket: &query, + }) + } + return p2p.Send(p.rw, GetBlockHeadersMsg, &query) } // RequestHeadersByHash fetches a batch of blocks' headers corresponding to the // specified header query, based on the hash of an origin block. func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) - return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + query := GetBlockHeadersPacket{ Origin: HashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse, - }) + } + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{ + RequestId: rand.Uint64(), + GetBlockHeadersPacket: &query, + }) + } + return p2p.Send(p.rw, GetBlockHeadersMsg, &query) } // RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the // specified header query, based on the number of an origin block. func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) - return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + query := GetBlockHeadersPacket{ Origin: HashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse, - }) + } + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{ + RequestId: rand.Uint64(), + GetBlockHeadersPacket: &query, + }) + } + return p2p.Send(p.rw, GetBlockHeadersMsg, &query) } // ExpectRequestHeadersByNumber is a testing method to mirror the recipient side @@ -406,6 +475,12 @@ func (p *Peer) ExpectRequestHeadersByNumber(origin uint64, amount int, skip int, // specified. func (p *Peer) RequestBodies(hashes []common.Hash) error { p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetBlockBodiesMsg, &GetBlockBodiesPacket66{ + RequestId: rand.Uint64(), + GetBlockBodiesPacket: hashes, + }) + } return p2p.Send(p.rw, GetBlockBodiesMsg, GetBlockBodiesPacket(hashes)) } @@ -413,17 +488,35 @@ func (p *Peer) RequestBodies(hashes []common.Hash) error { // data, corresponding to the specified hashes. func (p *Peer) RequestNodeData(hashes []common.Hash) error { p.Log().Debug("Fetching batch of state data", "count", len(hashes)) + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetNodeDataMsg, &GetNodeDataPacket66{ + RequestId: rand.Uint64(), + GetNodeDataPacket: hashes, + }) + } return p2p.Send(p.rw, GetNodeDataMsg, GetNodeDataPacket(hashes)) } // RequestReceipts fetches a batch of transaction receipts from a remote node. func (p *Peer) RequestReceipts(hashes []common.Hash) error { p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetReceiptsMsg, &GetReceiptsPacket66{ + RequestId: rand.Uint64(), + GetReceiptsPacket: hashes, + }) + } return p2p.Send(p.rw, GetReceiptsMsg, GetReceiptsPacket(hashes)) } // RequestTxs fetches a batch of transactions from a remote node. func (p *Peer) RequestTxs(hashes []common.Hash) error { p.Log().Debug("Fetching batch of transactions", "count", len(hashes)) + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetPooledTransactionsMsg, &GetPooledTransactionsPacket66{ + RequestId: rand.Uint64(), + GetPooledTransactionsPacket: hashes, + }) + } return p2p.Send(p.rw, GetPooledTransactionsMsg, GetPooledTransactionsPacket(hashes)) } diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 9fff64b72a..7f1832754f 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -32,6 +32,7 @@ import ( const ( ETH64 = 64 ETH65 = 65 + ETH66 = 66 ) // ProtocolName is the official short name of the `eth` protocol used during @@ -40,11 +41,11 @@ const ProtocolName = "eth" // ProtocolVersions are the supported versions of the `eth` protocol (first // is primary). -var ProtocolVersions = []uint{ETH65, ETH64} +var ProtocolVersions = []uint{ETH66, ETH65, ETH64} // protocolLengths are the number of implemented message corresponding to // different protocol versions. -var protocolLengths = map[uint]uint64{ETH65: 17, ETH64: 17} +var protocolLengths = map[uint]uint64{ETH66: 17, ETH65: 17, ETH64: 17} // maxMessageSize is the maximum cap on the size of a protocol message. const maxMessageSize = 10 * 1024 * 1024 @@ -79,7 +80,6 @@ var ( errNetworkIDMismatch = errors.New("network ID mismatch") errGenesisMismatch = errors.New("genesis mismatch") errForkIDRejected = errors.New("fork ID rejected") - errExtraStatusMsg = errors.New("extra status message") ) // Packet represents a p2p message in the `eth` protocol. @@ -129,6 +129,12 @@ type GetBlockHeadersPacket struct { Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) } +// GetBlockHeadersPacket represents a block header query over eth/66 +type GetBlockHeadersPacket66 struct { + RequestId uint64 + *GetBlockHeadersPacket +} + // HashOrNumber is a combined field for specifying an origin block. type HashOrNumber struct { Hash common.Hash // Block hash from which to retrieve headers (excludes Number) @@ -168,6 +174,12 @@ func (hn *HashOrNumber) DecodeRLP(s *rlp.Stream) error { // BlockHeadersPacket represents a block header response. type BlockHeadersPacket []*types.Header +// BlockHeadersPacket represents a block header response over eth/66. +type BlockHeadersPacket66 struct { + RequestId uint64 + BlockHeadersPacket +} + // NewBlockPacket is the network packet for the block propagation message. type NewBlockPacket struct { Block *types.Block @@ -190,9 +202,32 @@ func (request *NewBlockPacket) sanityCheck() error { // GetBlockBodiesPacket represents a block body query. type GetBlockBodiesPacket []common.Hash +// GetBlockBodiesPacket represents a block body query over eth/66. +type GetBlockBodiesPacket66 struct { + RequestId uint64 + GetBlockBodiesPacket +} + // BlockBodiesPacket is the network packet for block content distribution. type BlockBodiesPacket []*BlockBody +// BlockBodiesPacket is the network packet for block content distribution over eth/66. +type BlockBodiesPacket66 struct { + RequestId uint64 + BlockBodiesPacket +} + +// BlockBodiesRLPPacket is used for replying to block body requests, in cases +// where we already have them RLP-encoded, and thus can avoid the decode-encode +// roundtrip. +type BlockBodiesRLPPacket []rlp.RawValue + +// BlockBodiesRLPPacket66 is the BlockBodiesRLPPacket over eth/66 +type BlockBodiesRLPPacket66 struct { + RequestId uint64 + BlockBodiesRLPPacket +} + // BlockBody represents the data content of a single block. type BlockBody struct { Transactions []*types.Transaction // Transactions contained within a block @@ -215,24 +250,78 @@ func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header) // GetNodeDataPacket represents a trie node data query. type GetNodeDataPacket []common.Hash +// GetNodeDataPacket represents a trie node data query over eth/66. +type GetNodeDataPacket66 struct { + RequestId uint64 + GetNodeDataPacket +} + // NodeDataPacket is the network packet for trie node data distribution. type NodeDataPacket [][]byte +// NodeDataPacket is the network packet for trie node data distribution over eth/66. +type NodeDataPacket66 struct { + RequestId uint64 + NodeDataPacket +} + // GetReceiptsPacket represents a block receipts query. type GetReceiptsPacket []common.Hash +// GetReceiptsPacket represents a block receipts query over eth/66. +type GetReceiptsPacket66 struct { + RequestId uint64 + GetReceiptsPacket +} + // ReceiptsPacket is the network packet for block receipts distribution. type ReceiptsPacket [][]*types.Receipt +// ReceiptsPacket is the network packet for block receipts distribution over eth/66. +type ReceiptsPacket66 struct { + RequestId uint64 + ReceiptsPacket +} + +// ReceiptsRLPPacket is used for receipts, when we already have it encoded +type ReceiptsRLPPacket []rlp.RawValue + +// ReceiptsPacket66 is the eth-66 version of ReceiptsRLPPacket +type ReceiptsRLPPacket66 struct { + RequestId uint64 + ReceiptsRLPPacket +} + // NewPooledTransactionHashesPacket represents a transaction announcement packet. type NewPooledTransactionHashesPacket []common.Hash // GetPooledTransactionsPacket represents a transaction query. type GetPooledTransactionsPacket []common.Hash +type GetPooledTransactionsPacket66 struct { + RequestId uint64 + GetPooledTransactionsPacket +} + // PooledTransactionsPacket is the network packet for transaction distribution. type PooledTransactionsPacket []*types.Transaction +// PooledTransactionsPacket is the network packet for transaction distribution over eth/66. +type PooledTransactionsPacket66 struct { + RequestId uint64 + PooledTransactionsPacket +} + +// PooledTransactionsPacket is the network packet for transaction distribution, used +// in the cases we already have them in rlp-encoded form +type PooledTransactionsRLPPacket []rlp.RawValue + +// PooledTransactionsRLPPacket66 is the eth/66 form of PooledTransactionsRLPPacket +type PooledTransactionsRLPPacket66 struct { + RequestId uint64 + PooledTransactionsRLPPacket +} + func (*StatusPacket) Name() string { return "Status" } func (*StatusPacket) Kind() byte { return StatusMsg } diff --git a/eth/protocols/eth/protocol_test.go b/eth/protocols/eth/protocol_test.go index 056ea56480..d92f3ea837 100644 --- a/eth/protocols/eth/protocol_test.go +++ b/eth/protocols/eth/protocol_test.go @@ -17,9 +17,12 @@ package eth import ( + "bytes" + "math/big" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" ) @@ -66,3 +69,200 @@ func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { } } } + +// TestEth66EmptyMessages tests encoding of empty eth66 messages +func TestEth66EmptyMessages(t *testing.T) { + // All empty messages encodes to the same format + want := common.FromHex("c4820457c0") + + for i, msg := range []interface{}{ + // Headers + GetBlockHeadersPacket66{1111, nil}, + BlockHeadersPacket66{1111, nil}, + // Bodies + GetBlockBodiesPacket66{1111, nil}, + BlockBodiesPacket66{1111, nil}, + BlockBodiesRLPPacket66{1111, nil}, + // Node data + GetNodeDataPacket66{1111, nil}, + NodeDataPacket66{1111, nil}, + // Receipts + GetReceiptsPacket66{1111, nil}, + ReceiptsPacket66{1111, nil}, + // Transactions + GetPooledTransactionsPacket66{1111, nil}, + PooledTransactionsPacket66{1111, nil}, + PooledTransactionsRLPPacket66{1111, nil}, + + // Headers + BlockHeadersPacket66{1111, BlockHeadersPacket([]*types.Header{})}, + // Bodies + GetBlockBodiesPacket66{1111, GetBlockBodiesPacket([]common.Hash{})}, + BlockBodiesPacket66{1111, BlockBodiesPacket([]*BlockBody{})}, + BlockBodiesRLPPacket66{1111, BlockBodiesRLPPacket([]rlp.RawValue{})}, + // Node data + GetNodeDataPacket66{1111, GetNodeDataPacket([]common.Hash{})}, + NodeDataPacket66{1111, NodeDataPacket([][]byte{})}, + // Receipts + GetReceiptsPacket66{1111, GetReceiptsPacket([]common.Hash{})}, + ReceiptsPacket66{1111, ReceiptsPacket([][]*types.Receipt{})}, + // Transactions + GetPooledTransactionsPacket66{1111, GetPooledTransactionsPacket([]common.Hash{})}, + PooledTransactionsPacket66{1111, PooledTransactionsPacket([]*types.Transaction{})}, + PooledTransactionsRLPPacket66{1111, PooledTransactionsRLPPacket([]rlp.RawValue{})}, + } { + if have, _ := rlp.EncodeToBytes(msg); !bytes.Equal(have, want) { + t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, msg, have, want) + } + } + +} + +// TestEth66Messages tests the encoding of all redefined eth66 messages +func TestEth66Messages(t *testing.T) { + + // Some basic structs used during testing + var ( + header *types.Header + blockBody *BlockBody + blockBodyRlp rlp.RawValue + txs []*types.Transaction + txRlps []rlp.RawValue + hashes []common.Hash + receipts []*types.Receipt + receiptsRlp rlp.RawValue + + err error + ) + header = &types.Header{ + Difficulty: big.NewInt(2222), + Number: big.NewInt(3333), + GasLimit: 4444, + GasUsed: 5555, + Time: 6666, + Extra: []byte{0x77, 0x88}, + } + // Init the transactions, taken from a different test + { + for _, hexrlp := range []string{ + "f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10", + "f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb", + } { + var tx *types.Transaction + rlpdata := common.FromHex(hexrlp) + if err := rlp.DecodeBytes(rlpdata, &tx); err != nil { + t.Fatal(err) + } + txs = append(txs, tx) + txRlps = append(txRlps, rlpdata) + } + } + // init the block body data, both object and rlp form + blockBody = &BlockBody{ + Transactions: txs, + Uncles: []*types.Header{header}, + } + blockBodyRlp, err = rlp.EncodeToBytes(blockBody) + if err != nil { + t.Fatal(err) + } + + hashes = []common.Hash{ + common.HexToHash("deadc0de"), + common.HexToHash("feedbeef"), + } + byteSlices := [][]byte{ + common.FromHex("deadc0de"), + common.FromHex("feedbeef"), + } + // init the receipts + { + receipts = []*types.Receipt{ + &types.Receipt{ + Status: types.ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*types.Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + }, + TxHash: hashes[0], + ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), + GasUsed: 111111, + }, + } + rlpData, err := rlp.EncodeToBytes(receipts) + if err != nil { + t.Fatal(err) + } + receiptsRlp = rlpData + } + + for i, tc := range []struct { + message interface{} + want []byte + }{ + { + GetBlockHeadersPacket66{1111, &GetBlockHeadersPacket{HashOrNumber{hashes[0], 0}, 5, 5, false}}, + common.FromHex("e8820457e4a000000000000000000000000000000000000000000000000000000000deadc0de050580"), + }, + { + GetBlockHeadersPacket66{1111, &GetBlockHeadersPacket{HashOrNumber{common.Hash{}, 9999}, 5, 5, false}}, + common.FromHex("ca820457c682270f050580"), + }, + { + BlockHeadersPacket66{1111, BlockHeadersPacket{header}}, + common.FromHex("f90202820457f901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), + }, + { + GetBlockBodiesPacket66{1111, GetBlockBodiesPacket(hashes)}, + common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), + }, + { + BlockBodiesPacket66{1111, BlockBodiesPacket([]*BlockBody{blockBody})}, + common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), + }, + { // Identical to non-rlp-shortcut version + BlockBodiesRLPPacket66{1111, BlockBodiesRLPPacket([]rlp.RawValue{blockBodyRlp})}, + common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), + }, + { + GetNodeDataPacket66{1111, GetNodeDataPacket(hashes)}, + common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), + }, + { + NodeDataPacket66{1111, NodeDataPacket(byteSlices)}, + common.FromHex("ce820457ca84deadc0de84feedbeef"), + }, + { + GetReceiptsPacket66{1111, GetReceiptsPacket(hashes)}, + common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), + }, + { + ReceiptsPacket66{1111, ReceiptsPacket([][]*types.Receipt{receipts})}, + common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"), + }, + { + ReceiptsRLPPacket66{1111, ReceiptsRLPPacket([]rlp.RawValue{receiptsRlp})}, + common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"), + }, + { + GetPooledTransactionsPacket66{1111, GetPooledTransactionsPacket(hashes)}, + common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), + }, + { + PooledTransactionsPacket66{1111, PooledTransactionsPacket(txs)}, + common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"), + }, + { + PooledTransactionsRLPPacket66{1111, PooledTransactionsRLPPacket(txRlps)}, + common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"), + }, + } { + if have, _ := rlp.EncodeToBytes(tc.message); !bytes.Equal(have, tc.want) { + t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, tc.message, have, tc.want) + } + } +} diff --git a/p2p/message.go b/p2p/message.go index 10b55a939c..bd048138c3 100644 --- a/p2p/message.go +++ b/p2p/message.go @@ -70,6 +70,10 @@ func (msg Msg) Discard() error { return err } +func (msg Msg) Time() time.Time { + return msg.ReceivedAt +} + type MsgReader interface { ReadMsg() (Msg, error) } From d36276d85e39f7a0071d3f5d948785e008ca1519 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 19 Feb 2021 09:54:46 +0100 Subject: [PATCH 201/235] p2p/dnsdisc: fix hot-spin when all trees are empty (#22313) In the random sync algorithm used by the DNS node iterator, we first pick a random tree and then perform one sync action on that tree. This happens in a loop until any node is found. If no trees contain any nodes, the iterator will enter a hot loop spinning at 100% CPU. The fix is complicated. The iterator now checks if a meaningful sync action can be performed on any tree. If there is nothing to do, it waits for the next root record recheck time to arrive and then tries again. Fixes #22306 --- p2p/dnsdisc/client.go | 82 ++++++++++++++++++++++++++++++++------ p2p/dnsdisc/client_test.go | 47 ++++++++++++++++++++++ p2p/dnsdisc/sync.go | 28 ++++++++++--- 3 files changed, 138 insertions(+), 19 deletions(-) diff --git a/p2p/dnsdisc/client.go b/p2p/dnsdisc/client.go index b872784828..d3e8111ab5 100644 --- a/p2p/dnsdisc/client.go +++ b/p2p/dnsdisc/client.go @@ -217,8 +217,11 @@ type randomIterator struct { c *Client mu sync.Mutex - trees map[string]*clientTree // all trees lc linkCache // tracks tree dependencies + trees map[string]*clientTree // all trees + // buffers for syncableTrees + syncableList []*clientTree + disabledList []*clientTree } func (c *Client) newRandomIterator() *randomIterator { @@ -238,10 +241,10 @@ func (it *randomIterator) Node() *enode.Node { // Close closes the iterator. func (it *randomIterator) Close() { + it.cancelFn() + it.mu.Lock() defer it.mu.Unlock() - - it.cancelFn() it.trees = nil } @@ -264,7 +267,7 @@ func (it *randomIterator) addTree(url string) error { // nextNode syncs random tree entries until it finds a node. func (it *randomIterator) nextNode() *enode.Node { for { - ct := it.nextTree() + ct := it.pickTree() if ct == nil { return nil } @@ -282,26 +285,79 @@ func (it *randomIterator) nextNode() *enode.Node { } } -// nextTree returns a random tree. -func (it *randomIterator) nextTree() *clientTree { +// pickTree returns a random tree to sync from. +func (it *randomIterator) pickTree() *clientTree { it.mu.Lock() defer it.mu.Unlock() + // Rebuild the trees map if any links have changed. if it.lc.changed { it.rebuildTrees() it.lc.changed = false } - if len(it.trees) == 0 { - return nil + + for { + canSync, trees := it.syncableTrees() + switch { + case canSync: + // Pick a random tree. + return trees[rand.Intn(len(trees))] + case len(trees) > 0: + // No sync action can be performed on any tree right now. The only meaningful + // thing to do is waiting for any root record to get updated. + if !it.waitForRootUpdates(trees) { + // Iterator was closed while waiting. + return nil + } + default: + // There are no trees left, the iterator was closed. + return nil + } } - limit := rand.Intn(len(it.trees)) +} + +// syncableTrees finds trees on which any meaningful sync action can be performed. +func (it *randomIterator) syncableTrees() (canSync bool, trees []*clientTree) { + // Resize tree lists. + it.syncableList = it.syncableList[:0] + it.disabledList = it.disabledList[:0] + + // Partition them into the two lists. for _, ct := range it.trees { - if limit == 0 { - return ct + if ct.canSyncRandom() { + it.syncableList = append(it.syncableList, ct) + } else { + it.disabledList = append(it.disabledList, ct) } - limit-- } - return nil + if len(it.syncableList) > 0 { + return true, it.syncableList + } + return false, it.disabledList +} + +// waitForRootUpdates waits for the closest scheduled root check time on the given trees. +func (it *randomIterator) waitForRootUpdates(trees []*clientTree) bool { + var minTree *clientTree + var nextCheck mclock.AbsTime + for _, ct := range trees { + check := ct.nextScheduledRootCheck() + if minTree == nil || check < nextCheck { + minTree = ct + nextCheck = check + } + } + + sleep := nextCheck.Sub(it.c.clock.Now()) + it.c.cfg.Logger.Debug("DNS iterator waiting for root updates", "sleep", sleep, "tree", minTree.loc.domain) + timeout := it.c.clock.NewTimer(sleep) + defer timeout.Stop() + select { + case <-timeout.C(): + return true + case <-it.ctx.Done(): + return false // Iterator was closed. + } } // rebuildTrees rebuilds the 'trees' map. diff --git a/p2p/dnsdisc/client_test.go b/p2p/dnsdisc/client_test.go index 6a6705abf2..741bee4230 100644 --- a/p2p/dnsdisc/client_test.go +++ b/p2p/dnsdisc/client_test.go @@ -231,6 +231,53 @@ func TestIteratorRootRecheckOnFail(t *testing.T) { checkIterator(t, it, nodes) } +// This test checks that the iterator works correctly when the tree is initially empty. +func TestIteratorEmptyTree(t *testing.T) { + var ( + clock = new(mclock.Simulated) + nodes = testNodes(nodesSeed1, 1) + resolver = newMapResolver() + c = NewClient(Config{ + Resolver: resolver, + Logger: testlog.Logger(t, log.LvlTrace), + RecheckInterval: 20 * time.Minute, + RateLimit: 500, + }) + ) + c.clock = clock + tree1, url := makeTestTree("n", nil, nil) + tree2, _ := makeTestTree("n", nodes, nil) + resolver.add(tree1.ToTXT("n")) + + // Start the iterator. + node := make(chan *enode.Node) + it, err := c.NewIterator(url) + if err != nil { + t.Fatal(err) + } + go func() { + it.Next() + node <- it.Node() + }() + + // Wait for the client to get stuck in waitForRootUpdates. + clock.WaitForTimers(1) + + // Now update the root. + resolver.add(tree2.ToTXT("n")) + + // Wait for it to pick up the root change. + clock.Run(c.cfg.RecheckInterval) + select { + case n := <-node: + if n.ID() != nodes[0].ID() { + t.Fatalf("wrong node returned") + } + case <-time.After(5 * time.Second): + t.Fatal("it.Next() did not unblock within 5s of real time") + } +} + // updateSomeNodes applies ENR updates to some of the given nodes. func updateSomeNodes(keySeed int64, nodes []*enode.Node) { keys := testKeys(nodesSeed1, len(nodes)) diff --git a/p2p/dnsdisc/sync.go b/p2p/dnsdisc/sync.go index 36f02acba6..073547c90d 100644 --- a/p2p/dnsdisc/sync.go +++ b/p2p/dnsdisc/sync.go @@ -25,9 +25,9 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" ) -const ( - rootRecheckFailCount = 5 // update root if this many leaf requests fail -) +// This is the number of consecutive leaf requests that may fail before +// we consider re-resolving the tree root. +const rootRecheckFailCount = 5 // clientTree is a full tree being synced. type clientTree struct { @@ -89,13 +89,22 @@ func (ct *clientTree) syncRandom(ctx context.Context) (n *enode.Node, err error) ct.gcLinks() // Sync next random entry in ENR tree. Once every node has been visited, we simply - // start over. This is fine because entries are cached. + // start over. This is fine because entries are cached internally by the client LRU + // also by DNS resolvers. if ct.enrs.done() { ct.enrs = newSubtreeSync(ct.c, ct.loc, ct.root.eroot, false) } return ct.syncNextRandomENR(ctx) } +// canSyncRandom checks if any meaningful action can be performed by syncRandom. +func (ct *clientTree) canSyncRandom() bool { + // Note: the check for non-zero leaf count is very important here. + // If we're done syncing all nodes, and no leaves were found, the tree + // is empty and we can't use it for sync. + return ct.rootUpdateDue() || !ct.links.done() || !ct.enrs.done() || ct.enrs.leaves != 0 +} + // gcLinks removes outdated links from the global link cache. GC runs once // when the link sync finishes. func (ct *clientTree) gcLinks() { @@ -184,10 +193,14 @@ func (ct *clientTree) updateRoot(ctx context.Context) error { // rootUpdateDue returns true when a root update is needed. func (ct *clientTree) rootUpdateDue() bool { tooManyFailures := ct.leafFailCount > rootRecheckFailCount - scheduledCheck := ct.c.clock.Now().Sub(ct.lastRootCheck) > ct.c.cfg.RecheckInterval + scheduledCheck := ct.c.clock.Now() >= ct.nextScheduledRootCheck() return ct.root == nil || tooManyFailures || scheduledCheck } +func (ct *clientTree) nextScheduledRootCheck() mclock.AbsTime { + return ct.lastRootCheck.Add(ct.c.cfg.RecheckInterval) +} + // slowdownRootUpdate applies a delay to root resolution if is tried // too frequently. This avoids busy polling when the client is offline. // Returns true if the timeout passed, false if sync was canceled. @@ -218,10 +231,11 @@ type subtreeSync struct { root string missing []string // missing tree node hashes link bool // true if this sync is for the link tree + leaves int // counter of synced leaves } func newSubtreeSync(c *Client, loc *linkEntry, root string, link bool) *subtreeSync { - return &subtreeSync{c, loc, root, []string{root}, link} + return &subtreeSync{c, loc, root, []string{root}, link, 0} } func (ts *subtreeSync) done() bool { @@ -253,10 +267,12 @@ func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, err if ts.link { return nil, errENRInLinkTree } + ts.leaves++ case *linkEntry: if !ts.link { return nil, errLinkInENRTree } + ts.leaves++ case *branchEntry: ts.missing = append(ts.missing, e.children...) } From c027507e036683f555f63baa4cd02a81696fea6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Fri, 19 Feb 2021 14:44:16 +0100 Subject: [PATCH 202/235] les: renamed lespay to vflux (#22347) --- les/api.go | 6 ++-- les/client.go | 16 +++++------ les/clientpool.go | 26 ++++++++--------- les/clientpool_test.go | 28 +++++++++---------- les/peer.go | 20 ++++++------- les/protocol.go | 14 +++++----- les/server.go | 8 +++--- les/server_handler.go | 4 +-- les/serverpool.go | 28 +++++++++---------- les/serverpool_test.go | 8 +++--- les/{lespay => vflux}/client/api.go | 2 +- les/{lespay => vflux}/client/fillset.go | 0 les/{lespay => vflux}/client/fillset_test.go | 0 les/{lespay => vflux}/client/queueiterator.go | 0 .../client/queueiterator_test.go | 0 les/{lespay => vflux}/client/requestbasket.go | 0 .../client/requestbasket_test.go | 0 les/{lespay => vflux}/client/timestats.go | 0 .../client/timestats_test.go | 0 les/{lespay => vflux}/client/valuetracker.go | 0 .../client/valuetracker_test.go | 0 les/{lespay => vflux}/client/wrsiterator.go | 0 .../client/wrsiterator_test.go | 0 les/{lespay => vflux}/server/balance.go | 0 les/{lespay => vflux}/server/balance_test.go | 0 .../server/balance_tracker.go | 0 les/{lespay => vflux}/server/clientdb.go | 0 les/{lespay => vflux}/server/clientdb_test.go | 0 les/{lespay => vflux}/server/prioritypool.go | 0 .../server/prioritypool_test.go | 0 30 files changed, 80 insertions(+), 80 deletions(-) rename les/{lespay => vflux}/client/api.go (98%) rename les/{lespay => vflux}/client/fillset.go (100%) rename les/{lespay => vflux}/client/fillset_test.go (100%) rename les/{lespay => vflux}/client/queueiterator.go (100%) rename les/{lespay => vflux}/client/queueiterator_test.go (100%) rename les/{lespay => vflux}/client/requestbasket.go (100%) rename les/{lespay => vflux}/client/requestbasket_test.go (100%) rename les/{lespay => vflux}/client/timestats.go (100%) rename les/{lespay => vflux}/client/timestats_test.go (100%) rename les/{lespay => vflux}/client/valuetracker.go (100%) rename les/{lespay => vflux}/client/valuetracker_test.go (100%) rename les/{lespay => vflux}/client/wrsiterator.go (100%) rename les/{lespay => vflux}/client/wrsiterator_test.go (100%) rename les/{lespay => vflux}/server/balance.go (100%) rename les/{lespay => vflux}/server/balance_test.go (100%) rename les/{lespay => vflux}/server/balance_tracker.go (100%) rename les/{lespay => vflux}/server/clientdb.go (100%) rename les/{lespay => vflux}/server/clientdb_test.go (100%) rename les/{lespay => vflux}/server/prioritypool.go (100%) rename les/{lespay => vflux}/server/prioritypool_test.go (100%) diff --git a/les/api.go b/les/api.go index 66d133b854..6491c4dcc4 100644 --- a/les/api.go +++ b/les/api.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/mclock" - lps "github.com/ethereum/go-ethereum/les/lespay/server" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/p2p/enode" ) @@ -37,7 +37,7 @@ var ( // PrivateLightServerAPI provides an API to access the LES light server. type PrivateLightServerAPI struct { server *LesServer - defaultPosFactors, defaultNegFactors lps.PriceFactors + defaultPosFactors, defaultNegFactors vfs.PriceFactors } // NewPrivateLightServerAPI creates a new LES light server API. @@ -107,7 +107,7 @@ func (api *PrivateLightServerAPI) clientInfo(c *clientInfo) map[string]interface // setParams either sets the given parameters for a single connected client (if specified) // or the default parameters applicable to clients connected in the future -func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, client *clientInfo, posFactors, negFactors *lps.PriceFactors) (updateFactors bool, err error) { +func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, client *clientInfo, posFactors, negFactors *vfs.PriceFactors) (updateFactors bool, err error) { defParams := client == nil for name, value := range params { errValue := func() error { diff --git a/les/client.go b/les/client.go index 1b26e9a9b5..9f0afc96c5 100644 --- a/les/client.go +++ b/les/client.go @@ -36,7 +36,7 @@ import ( "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" - lpc "github.com/ethereum/go-ethereum/les/lespay/client" + vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -58,7 +58,7 @@ type LightEthereum struct { txPool *light.TxPool blockchain *light.LightChain serverPool *serverPool - valueTracker *lpc.ValueTracker + valueTracker *vfc.ValueTracker dialCandidates enode.Iterator pruner *pruner @@ -108,7 +108,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), - valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), + valueTracker: vfc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), p2pServer: stack.Server(), p2pConfig: &stack.Config().P2P, } @@ -193,18 +193,18 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { } // vtSubscription implements serverPeerSubscriber -type vtSubscription lpc.ValueTracker +type vtSubscription vfc.ValueTracker // registerPeer implements serverPeerSubscriber func (v *vtSubscription) registerPeer(p *serverPeer) { - vt := (*lpc.ValueTracker)(v) + vt := (*vfc.ValueTracker)(v) p.setValueTracker(vt, vt.Register(p.ID())) p.updateVtParams() } // unregisterPeer implements serverPeerSubscriber func (v *vtSubscription) unregisterPeer(p *serverPeer) { - vt := (*lpc.ValueTracker)(v) + vt := (*vfc.ValueTracker)(v) vt.Unregister(p.ID()) p.setValueTracker(nil, nil) } @@ -263,9 +263,9 @@ func (s *LightEthereum) APIs() []rpc.API { Service: NewPrivateLightAPI(&s.lesCommons), Public: false, }, { - Namespace: "lespay", + Namespace: "vflux", Version: "1.0", - Service: lpc.NewPrivateClientAPI(s.valueTracker), + Service: vfc.NewPrivateClientAPI(s.valueTracker), Public: false, }, }...) diff --git a/les/clientpool.go b/les/clientpool.go index da0db6e622..96c0f0f99e 100644 --- a/les/clientpool.go +++ b/les/clientpool.go @@ -23,8 +23,8 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" - lps "github.com/ethereum/go-ethereum/les/lespay/server" "github.com/ethereum/go-ethereum/les/utils" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" @@ -64,17 +64,17 @@ const ( // and negative banalce. Boeth positive balance and negative balance will decrease // exponentially. If the balance is low enough, then the record will be dropped. type clientPool struct { - lps.BalanceTrackerSetup - lps.PriorityPoolSetup + vfs.BalanceTrackerSetup + vfs.PriorityPoolSetup lock sync.Mutex clock mclock.Clock closed bool removePeer func(enode.ID) ns *nodestate.NodeStateMachine - pp *lps.PriorityPool - bt *lps.BalanceTracker + pp *vfs.PriorityPool + bt *vfs.BalanceTracker - defaultPosFactors, defaultNegFactors lps.PriceFactors + defaultPosFactors, defaultNegFactors vfs.PriceFactors posExpTC, negExpTC uint64 minCap uint64 // The minimal capacity value allowed for any client connectedBias time.Duration @@ -101,7 +101,7 @@ type clientInfo struct { peer clientPoolPeer connected, priority bool connectedAt mclock.AbsTime - balance *lps.NodeBalance + balance *vfs.NodeBalance } // newClientPool creates a new client pool @@ -115,8 +115,8 @@ func newClientPool(ns *nodestate.NodeStateMachine, lespayDb ethdb.Database, minC connectedBias: connectedBias, removePeer: removePeer, } - pool.bt = lps.NewBalanceTracker(ns, balanceTrackerSetup, lespayDb, clock, &utils.Expirer{}, &utils.Expirer{}) - pool.pp = lps.NewPriorityPool(ns, priorityPoolSetup, clock, minCap, connectedBias, 4) + pool.bt = vfs.NewBalanceTracker(ns, balanceTrackerSetup, lespayDb, clock, &utils.Expirer{}, &utils.Expirer{}) + pool.pp = vfs.NewPriorityPool(ns, priorityPoolSetup, clock, minCap, connectedBias, 4) // set default expiration constants used by tests // Note: server overwrites this if token sale is active @@ -221,7 +221,7 @@ func (f *clientPool) connect(peer clientPoolPeer) (uint64, error) { } f.ns.SetField(node, clientInfoField, c) f.ns.SetField(node, connAddressField, freeID) - if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil { + if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance == nil { f.disconnect(peer) return 0, nil } @@ -266,7 +266,7 @@ func (f *clientPool) disconnectNode(node *enode.Node) { } // setDefaultFactors sets the default price factors applied to subsequently connected clients -func (f *clientPool) setDefaultFactors(posFactors, negFactors lps.PriceFactors) { +func (f *clientPool) setDefaultFactors(posFactors, negFactors vfs.PriceFactors) { f.lock.Lock() defer f.lock.Unlock() @@ -305,7 +305,7 @@ func (f *clientPool) setCapacity(node *enode.Node, freeID string, capacity uint6 c = &clientInfo{node: node} f.ns.SetField(node, clientInfoField, c) f.ns.SetField(node, connAddressField, freeID) - if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil { + if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance == nil { log.Error("BalanceField is missing", "node", node.ID()) return 0, fmt.Errorf("BalanceField of %064x is missing", node.ID()) } @@ -371,7 +371,7 @@ func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) { c = &clientInfo{node: node} f.ns.SetField(node, clientInfoField, c) f.ns.SetField(node, connAddressField, "") - if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance != nil { + if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance != nil { cb(c) } else { log.Error("BalanceField is missing") diff --git a/les/clientpool_test.go b/les/clientpool_test.go index b1c38d374c..5cff010409 100644 --- a/les/clientpool_test.go +++ b/les/clientpool_test.go @@ -24,7 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core/rawdb" - lps "github.com/ethereum/go-ethereum/les/lespay/server" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nodestate" @@ -100,7 +100,7 @@ func getBalance(pool *clientPool, p *poolTestPeer) (pos, neg uint64) { if temp { pool.ns.SetField(p.node, connAddressField, p.freeClientId()) } - n, _ := pool.ns.GetField(p.node, pool.BalanceField).(*lps.NodeBalance) + n, _ := pool.ns.GetField(p.node, pool.BalanceField).(*vfs.NodeBalance) pos, neg = n.GetBalance() if temp { pool.ns.SetField(p.node, connAddressField, nil) @@ -138,7 +138,7 @@ func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, rando pool.ns.Start() pool.setLimits(activeLimit, uint64(activeLimit)) - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // pool should accept new peers up to its connected limit for i := 0; i < activeLimit; i++ { @@ -243,7 +243,7 @@ func TestConnectPaidClient(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // Add balance for an external client and mark it as paid client addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute)) @@ -259,7 +259,7 @@ func TestConnectPaidClientToSmallPool(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // Add balance for an external client and mark it as paid client addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute)) @@ -278,7 +278,7 @@ func TestConnectPaidClientToFullPool(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { addBalance(pool, newPoolTestPeer(i, nil).node.ID(), int64(time.Second*20)) @@ -309,7 +309,7 @@ func TestPaidClientKickedOut(t *testing.T) { pool.bt.SetExpirationTCs(0, 0) defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { addBalance(pool, newPoolTestPeer(i, kickedCh).node.ID(), 10000000000) // 10 second allowance @@ -339,7 +339,7 @@ func TestConnectFreeClient(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) if cap, _ := pool.connect(newPoolTestPeer(0, nil)); cap == 0 { t.Fatalf("Failed to connect free client") } @@ -356,7 +356,7 @@ func TestConnectFreeClientToFullPool(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { pool.connect(newPoolTestPeer(i, nil)) @@ -386,7 +386,7 @@ func TestFreeClientKickedOut(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { pool.connect(newPoolTestPeer(i, kicked)) @@ -428,7 +428,7 @@ func TestPositiveBalanceCalculation(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute*3)) testPriorityConnect(t, pool, newPoolTestPeer(0, kicked), 10, true) @@ -452,7 +452,7 @@ func TestDowngradePriorityClient(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) p := newPoolTestPeer(0, kicked) addBalance(pool, p.node.ID(), int64(time.Minute)) @@ -487,7 +487,7 @@ func TestNegativeBalanceCalculation(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { pool.connect(newPoolTestPeer(i, nil)) @@ -564,7 +564,7 @@ func TestInactiveClient(t *testing.T) { if p2.cap != 0 { t.Fatalf("Failed to deactivate peer #2") } - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}) p4 := newPoolTestPeer(4, nil) addBalance(pool, p4.node.ID(), 1500*int64(time.Second)) // p1: 1000 p2: 500 p3: 2000 p4: 1500 diff --git a/les/peer.go b/les/peer.go index 0e2ed52c12..52ab506368 100644 --- a/les/peer.go +++ b/les/peer.go @@ -32,9 +32,9 @@ import ( "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/les/flowcontrol" - lpc "github.com/ethereum/go-ethereum/les/lespay/client" - lps "github.com/ethereum/go-ethereum/les/lespay/server" "github.com/ethereum/go-ethereum/les/utils" + vfc "github.com/ethereum/go-ethereum/les/vflux/client" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" @@ -349,8 +349,8 @@ type serverPeer struct { fcServer *flowcontrol.ServerNode // Client side mirror token bucket. vtLock sync.Mutex - valueTracker *lpc.ValueTracker - nodeValueTracker *lpc.NodeValueTracker + valueTracker *vfc.ValueTracker + nodeValueTracker *vfc.NodeValueTracker sentReqs map[uint64]sentReqEntry // Statistics @@ -676,7 +676,7 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter // setValueTracker sets the value tracker references for connected servers. Note that the // references should be removed upon disconnection by setValueTracker(nil, nil). -func (p *serverPeer) setValueTracker(vt *lpc.ValueTracker, nvt *lpc.NodeValueTracker) { +func (p *serverPeer) setValueTracker(vt *vfc.ValueTracker, nvt *vfc.NodeValueTracker) { p.vtLock.Lock() p.valueTracker = vt p.nodeValueTracker = nvt @@ -739,17 +739,17 @@ func (p *serverPeer) answeredRequest(id uint64) { return } var ( - vtReqs [2]lpc.ServedRequest + vtReqs [2]vfc.ServedRequest reqCount int ) m := requestMapping[e.reqType] if m.rest == -1 || e.amount <= 1 { reqCount = 1 - vtReqs[0] = lpc.ServedRequest{ReqType: uint32(m.first), Amount: e.amount} + vtReqs[0] = vfc.ServedRequest{ReqType: uint32(m.first), Amount: e.amount} } else { reqCount = 2 - vtReqs[0] = lpc.ServedRequest{ReqType: uint32(m.first), Amount: 1} - vtReqs[1] = lpc.ServedRequest{ReqType: uint32(m.rest), Amount: e.amount - 1} + vtReqs[0] = vfc.ServedRequest{ReqType: uint32(m.first), Amount: 1} + vtReqs[1] = vfc.ServedRequest{ReqType: uint32(m.rest), Amount: e.amount - 1} } dt := time.Duration(mclock.Now() - e.at) vt.Served(nvt, vtReqs[:reqCount], dt) @@ -765,7 +765,7 @@ type clientPeer struct { responseLock sync.Mutex responseCount uint64 // Counter to generate an unique id for request processing. - balance *lps.NodeBalance + balance *vfs.NodeBalance // invalidLock is used for protecting invalidCount. invalidLock sync.RWMutex diff --git a/les/protocol.go b/les/protocol.go index 9eb6ec7471..909d25d375 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -25,7 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - lpc "github.com/ethereum/go-ethereum/les/lespay/client" + vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" ) @@ -89,7 +89,7 @@ type requestInfo struct { refBasketFirst, refBasketRest float64 } -// reqMapping maps an LES request to one or two lespay service vector entries. +// reqMapping maps an LES request to one or two vflux service vector entries. // If rest != -1 and the request type is used with amounts larger than one then the // first one of the multi-request is mapped to first while the rest is mapped to rest. type reqMapping struct { @@ -98,7 +98,7 @@ type reqMapping struct { var ( // requests describes the available LES request types and their initializing amounts - // in the lespay/client.ValueTracker reference basket. Initial values are estimates + // in the vfc.ValueTracker reference basket. Initial values are estimates // based on the same values as the server's default cost estimates (reqAvgTimeCost). requests = map[uint64]requestInfo{ GetBlockHeadersMsg: {"GetBlockHeaders", MaxHeaderFetch, 10, 1000}, @@ -110,25 +110,25 @@ var ( SendTxV2Msg: {"SendTxV2", MaxTxSend, 1, 0}, GetTxStatusMsg: {"GetTxStatus", MaxTxStatus, 10, 0}, } - requestList []lpc.RequestInfo + requestList []vfc.RequestInfo requestMapping map[uint32]reqMapping ) -// init creates a request list and mapping between protocol message codes and lespay +// init creates a request list and mapping between protocol message codes and vflux // service vector indices. func init() { requestMapping = make(map[uint32]reqMapping) for code, req := range requests { cost := reqAvgTimeCost[code] rm := reqMapping{len(requestList), -1} - requestList = append(requestList, lpc.RequestInfo{ + requestList = append(requestList, vfc.RequestInfo{ Name: req.name + ".first", InitAmount: req.refBasketFirst, InitValue: float64(cost.baseCost + cost.reqCost), }) if req.refBasketRest != 0 { rm.rest = len(requestList) - requestList = append(requestList, lpc.RequestInfo{ + requestList = append(requestList, vfc.RequestInfo{ Name: req.name + ".rest", InitAmount: req.refBasketRest, InitValue: float64(cost.reqCost), diff --git a/les/server.go b/les/server.go index 44495eb311..351a53f690 100644 --- a/les/server.go +++ b/les/server.go @@ -26,7 +26,7 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/flowcontrol" - lps "github.com/ethereum/go-ethereum/les/lespay/server" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -43,8 +43,8 @@ var ( clientPeerField = serverSetup.NewField("clientPeer", reflect.TypeOf(&clientPeer{})) clientInfoField = serverSetup.NewField("clientInfo", reflect.TypeOf(&clientInfo{})) connAddressField = serverSetup.NewField("connAddr", reflect.TypeOf("")) - balanceTrackerSetup = lps.NewBalanceTrackerSetup(serverSetup) - priorityPoolSetup = lps.NewPriorityPoolSetup(serverSetup) + balanceTrackerSetup = vfs.NewBalanceTrackerSetup(serverSetup) + priorityPoolSetup = vfs.NewPriorityPoolSetup(serverSetup) ) func init() { @@ -137,7 +137,7 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les } srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2) srv.clientPool = newClientPool(ns, srv.chainDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient) - srv.clientPool.setDefaultFactors(lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}) + srv.clientPool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}) checkpoint := srv.latestLocalCheckpoint() if !checkpoint.Empty() { diff --git a/les/server_handler.go b/les/server_handler.go index bec4206e2b..fd81e273c9 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" - lps "github.com/ethereum/go-ethereum/les/lespay/server" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -169,7 +169,7 @@ func (h *serverHandler) handle(p *clientPeer) error { p.Log().Debug("Light Ethereum peer rejected", "err", errFullClientPool) return errFullClientPool } - p.balance, _ = h.server.ns.GetField(p.Node(), h.server.clientPool.BalanceField).(*lps.NodeBalance) + p.balance, _ = h.server.ns.GetField(p.Node(), h.server.clientPool.BalanceField).(*vfs.NodeBalance) if p.balance == nil { return p2p.DiscRequested } diff --git a/les/serverpool.go b/les/serverpool.go index ac87acf6da..977579988e 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -26,8 +26,8 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" - lpc "github.com/ethereum/go-ethereum/les/lespay/client" "github.com/ethereum/go-ethereum/les/utils" + vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" @@ -58,23 +58,23 @@ type serverPool struct { db ethdb.KeyValueStore ns *nodestate.NodeStateMachine - vt *lpc.ValueTracker + vt *vfc.ValueTracker mixer *enode.FairMix mixSources []enode.Iterator dialIterator enode.Iterator validSchemes enr.IdentityScheme trustedURLs []string - fillSet *lpc.FillSet + fillSet *vfc.FillSet queryFails uint32 timeoutLock sync.RWMutex timeout time.Duration - timeWeights lpc.ResponseTimeWeights + timeWeights vfc.ResponseTimeWeights timeoutRefreshed mclock.AbsTime } // nodeHistory keeps track of dial costs which determine node weight together with the -// service value calculated by lpc.ValueTracker. +// service value calculated by vfc.ValueTracker. type nodeHistory struct { dialCost utils.ExpiredValue redialWaitStart, redialWaitEnd int64 // unix time (seconds) @@ -127,11 +127,11 @@ var ( }, ) sfiNodeWeight = serverPoolSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0))) - sfiConnectedStats = serverPoolSetup.NewField("connectedStats", reflect.TypeOf(lpc.ResponseTimeStats{})) + sfiConnectedStats = serverPoolSetup.NewField("connectedStats", reflect.TypeOf(vfc.ResponseTimeStats{})) ) // newServerPool creates a new server pool -func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string) *serverPool { +func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *vfc.ValueTracker, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string) *serverPool { s := &serverPool{ db: db, clock: clock, @@ -143,8 +143,8 @@ func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, m } s.recalTimeout() s.mixer = enode.NewFairMix(mixTimeout) - knownSelector := lpc.NewWrsIterator(s.ns, sfHasValue, sfDisableSelection, sfiNodeWeight) - alwaysConnect := lpc.NewQueueIterator(s.ns, sfAlwaysConnect, sfDisableSelection, true, nil) + knownSelector := vfc.NewWrsIterator(s.ns, sfHasValue, sfDisableSelection, sfiNodeWeight) + alwaysConnect := vfc.NewQueueIterator(s.ns, sfAlwaysConnect, sfDisableSelection, true, nil) s.mixSources = append(s.mixSources, knownSelector) s.mixSources = append(s.mixSources, alwaysConnect) @@ -183,7 +183,7 @@ func (s *serverPool) addSource(source enode.Iterator) { // Nodes that are filtered out and does not appear on the output iterator are put back // into redialWait state. func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enode.Iterator { - s.fillSet = lpc.NewFillSet(s.ns, input, sfQueried) + s.fillSet = vfc.NewFillSet(s.ns, input, sfQueried) s.ns.SubscribeState(sfQueried, func(n *enode.Node, oldState, newState nodestate.Flags) { if newState.Equals(sfQueried) { fails := atomic.LoadUint32(&s.queryFails) @@ -221,7 +221,7 @@ func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enod }() } }) - return lpc.NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) { + return vfc.NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) { if waiting { s.fillSet.SetTarget(preNegLimit) } else { @@ -330,7 +330,7 @@ func (s *serverPool) recalTimeout() { s.timeoutLock.Lock() if s.timeout != timeout { s.timeout = timeout - s.timeWeights = lpc.TimeoutWeights(s.timeout) + s.timeWeights = vfc.TimeoutWeights(s.timeout) suggestedTimeoutGauge.Update(int64(s.timeout / time.Millisecond)) totalValueGauge.Update(int64(rts.Value(s.timeWeights, s.vt.StatsExpFactor()))) @@ -349,7 +349,7 @@ func (s *serverPool) getTimeout() time.Duration { // getTimeoutAndWeight returns the recommended request timeout as well as the // response time weight which is necessary to calculate service value. -func (s *serverPool) getTimeoutAndWeight() (time.Duration, lpc.ResponseTimeWeights) { +func (s *serverPool) getTimeoutAndWeight() (time.Duration, vfc.ResponseTimeWeights) { s.recalTimeout() s.timeoutLock.RLock() defer s.timeoutLock.RUnlock() @@ -381,7 +381,7 @@ func (s *serverPool) serviceValue(node *enode.Node) (sessionValue, totalValue fl expFactor := s.vt.StatsExpFactor() totalValue = currentStats.Value(timeWeights, expFactor) - if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(lpc.ResponseTimeStats); ok { + if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(vfc.ResponseTimeStats); ok { diff := currentStats diff.SubStats(&connStats) sessionValue = diff.Value(timeWeights, expFactor) diff --git a/les/serverpool_test.go b/les/serverpool_test.go index 3b7ae65d5d..5c8ae56f6c 100644 --- a/les/serverpool_test.go +++ b/les/serverpool_test.go @@ -25,7 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/memorydb" - lpc "github.com/ethereum/go-ethereum/les/lespay/client" + vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" @@ -55,7 +55,7 @@ type serverPoolTest struct { clock *mclock.Simulated quit chan struct{} preNeg, preNegFail bool - vt *lpc.ValueTracker + vt *vfc.ValueTracker sp *serverPool input enode.Iterator testNodes []spTestNode @@ -144,7 +144,7 @@ func (s *serverPoolTest) start() { } } - s.vt = lpc.NewValueTracker(s.db, s.clock, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)) + s.vt = vfc.NewValueTracker(s.db, s.clock, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)) s.sp = newServerPool(s.db, []byte("serverpool:"), s.vt, 0, testQuery, s.clock, s.trusted) s.sp.addSource(s.input) s.sp.validSchemes = enode.ValidSchemesForTesting @@ -224,7 +224,7 @@ func (s *serverPoolTest) run() { n.peer = &serverPeer{peerCommons: peerCommons{Peer: p2p.NewPeer(id, "", nil)}} s.sp.registerPeer(n.peer) if n.service { - s.vt.Served(s.vt.GetNode(id), []lpc.ServedRequest{{ReqType: 0, Amount: 100}}, 0) + s.vt.Served(s.vt.GetNode(id), []vfc.ServedRequest{{ReqType: 0, Amount: 100}}, 0) } } } diff --git a/les/lespay/client/api.go b/les/vflux/client/api.go similarity index 98% rename from les/lespay/client/api.go rename to les/vflux/client/api.go index 5ad6ffd77e..135273ef96 100644 --- a/les/lespay/client/api.go +++ b/les/vflux/client/api.go @@ -24,7 +24,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" ) -// PrivateClientAPI implements the lespay client side API +// PrivateClientAPI implements the vflux client side API type PrivateClientAPI struct { vt *ValueTracker } diff --git a/les/lespay/client/fillset.go b/les/vflux/client/fillset.go similarity index 100% rename from les/lespay/client/fillset.go rename to les/vflux/client/fillset.go diff --git a/les/lespay/client/fillset_test.go b/les/vflux/client/fillset_test.go similarity index 100% rename from les/lespay/client/fillset_test.go rename to les/vflux/client/fillset_test.go diff --git a/les/lespay/client/queueiterator.go b/les/vflux/client/queueiterator.go similarity index 100% rename from les/lespay/client/queueiterator.go rename to les/vflux/client/queueiterator.go diff --git a/les/lespay/client/queueiterator_test.go b/les/vflux/client/queueiterator_test.go similarity index 100% rename from les/lespay/client/queueiterator_test.go rename to les/vflux/client/queueiterator_test.go diff --git a/les/lespay/client/requestbasket.go b/les/vflux/client/requestbasket.go similarity index 100% rename from les/lespay/client/requestbasket.go rename to les/vflux/client/requestbasket.go diff --git a/les/lespay/client/requestbasket_test.go b/les/vflux/client/requestbasket_test.go similarity index 100% rename from les/lespay/client/requestbasket_test.go rename to les/vflux/client/requestbasket_test.go diff --git a/les/lespay/client/timestats.go b/les/vflux/client/timestats.go similarity index 100% rename from les/lespay/client/timestats.go rename to les/vflux/client/timestats.go diff --git a/les/lespay/client/timestats_test.go b/les/vflux/client/timestats_test.go similarity index 100% rename from les/lespay/client/timestats_test.go rename to les/vflux/client/timestats_test.go diff --git a/les/lespay/client/valuetracker.go b/les/vflux/client/valuetracker.go similarity index 100% rename from les/lespay/client/valuetracker.go rename to les/vflux/client/valuetracker.go diff --git a/les/lespay/client/valuetracker_test.go b/les/vflux/client/valuetracker_test.go similarity index 100% rename from les/lespay/client/valuetracker_test.go rename to les/vflux/client/valuetracker_test.go diff --git a/les/lespay/client/wrsiterator.go b/les/vflux/client/wrsiterator.go similarity index 100% rename from les/lespay/client/wrsiterator.go rename to les/vflux/client/wrsiterator.go diff --git a/les/lespay/client/wrsiterator_test.go b/les/vflux/client/wrsiterator_test.go similarity index 100% rename from les/lespay/client/wrsiterator_test.go rename to les/vflux/client/wrsiterator_test.go diff --git a/les/lespay/server/balance.go b/les/vflux/server/balance.go similarity index 100% rename from les/lespay/server/balance.go rename to les/vflux/server/balance.go diff --git a/les/lespay/server/balance_test.go b/les/vflux/server/balance_test.go similarity index 100% rename from les/lespay/server/balance_test.go rename to les/vflux/server/balance_test.go diff --git a/les/lespay/server/balance_tracker.go b/les/vflux/server/balance_tracker.go similarity index 100% rename from les/lespay/server/balance_tracker.go rename to les/vflux/server/balance_tracker.go diff --git a/les/lespay/server/clientdb.go b/les/vflux/server/clientdb.go similarity index 100% rename from les/lespay/server/clientdb.go rename to les/vflux/server/clientdb.go diff --git a/les/lespay/server/clientdb_test.go b/les/vflux/server/clientdb_test.go similarity index 100% rename from les/lespay/server/clientdb_test.go rename to les/vflux/server/clientdb_test.go diff --git a/les/lespay/server/prioritypool.go b/les/vflux/server/prioritypool.go similarity index 100% rename from les/lespay/server/prioritypool.go rename to les/vflux/server/prioritypool.go diff --git a/les/lespay/server/prioritypool_test.go b/les/vflux/server/prioritypool_test.go similarity index 100% rename from les/lespay/server/prioritypool_test.go rename to les/vflux/server/prioritypool_test.go From ca76db6116b64bb10c83085a70898750668593d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 19 Feb 2021 15:53:05 +0200 Subject: [PATCH 203/235] cmd/utils: disable caching preimages by default --- cmd/utils/flags.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0d7b0e1bf5..d065e02047 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -401,9 +401,9 @@ var ( Name: "cache.noprefetch", Usage: "Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)", } - CachePreimagesFlag = cli.BoolTFlag{ + CachePreimagesFlag = cli.BoolFlag{ Name: "cache.preimages", - Usage: "Enable recording the SHA3/keccak preimages of trie keys (default: true)", + Usage: "Enable recording the SHA3/keccak preimages of trie keys", } // Miner settings MiningEnabledFlag = cli.BoolFlag{ From c5023e1dc56f3ced0e3a24733e533bf962515844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 19 Feb 2021 16:03:17 +0200 Subject: [PATCH 204/235] travis, appveyor, build: bump Go to 1.16 --- .travis.yml | 22 +++++++++++----------- appveyor.yml | 4 ++-- build/checksums.txt | 24 ++++++++++++------------ build/ci.go | 2 +- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1268c6d657..39a0456c3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ jobs: allow_failures: - stage: build os: osx - go: 1.14.x + go: 1.15.x env: - azure-osx - azure-ios @@ -16,7 +16,7 @@ jobs: - stage: lint os: linux dist: xenial - go: 1.15.x + go: 1.16.x env: - lint git: @@ -29,7 +29,7 @@ jobs: if: type = push os: linux dist: xenial - go: 1.15.x + go: 1.16.x env: - ubuntu-ppa - GO111MODULE=on @@ -54,7 +54,7 @@ jobs: os: linux dist: xenial sudo: required - go: 1.15.x + go: 1.16.x env: - azure-linux - GO111MODULE=on @@ -91,7 +91,7 @@ jobs: dist: xenial services: - docker - go: 1.15.x + go: 1.16.x env: - azure-linux-mips - GO111MODULE=on @@ -139,7 +139,7 @@ jobs: git: submodules: false # avoid cloning ethereum/tests before_install: - - curl https://dl.google.com/go/go1.15.5.linux-amd64.tar.gz | tar -xz + - curl https://dl.google.com/go/go1.16.linux-amd64.tar.gz | tar -xz - export PATH=`pwd`/go/bin:$PATH - export GOROOT=`pwd`/go - export GOPATH=$HOME/go @@ -157,7 +157,7 @@ jobs: - stage: build if: type = push os: osx - go: 1.15.x + go: 1.16.x env: - azure-osx - azure-ios @@ -189,7 +189,7 @@ jobs: os: linux arch: amd64 dist: xenial - go: 1.15.x + go: 1.16.x env: - GO111MODULE=on script: @@ -200,7 +200,7 @@ jobs: os: linux arch: arm64 dist: xenial - go: 1.15.x + go: 1.16.x env: - GO111MODULE=on script: @@ -209,7 +209,7 @@ jobs: - stage: build os: linux dist: xenial - go: 1.14.x + go: 1.15.x env: - GO111MODULE=on script: @@ -220,7 +220,7 @@ jobs: if: type = cron os: linux dist: xenial - go: 1.15.x + go: 1.16.x env: - azure-purge - GO111MODULE=on diff --git a/appveyor.yml b/appveyor.yml index 2bf67d4568..052280be15 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,8 +24,8 @@ environment: install: - git submodule update --init - rmdir C:\go /s /q - - appveyor DownloadFile https://dl.google.com/go/go1.15.5.windows-%GETH_ARCH%.zip - - 7z x go1.15.5.windows-%GETH_ARCH%.zip -y -oC:\ > NUL + - appveyor DownloadFile https://dl.google.com/go/go1.16.windows-%GETH_ARCH%.zip + - 7z x go1.16.windows-%GETH_ARCH%.zip -y -oC:\ > NUL - go version - gcc --version diff --git a/build/checksums.txt b/build/checksums.txt index a7a6a657e9..d5bd4d0cd3 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,17 +1,17 @@ # This file contains sha256 checksums of optional build dependencies. -890bba73c5e2b19ffb1180e385ea225059eb008eb91b694875dd86ea48675817 go1.15.6.src.tar.gz -940a73b45993a3bae5792cf324140dded34af97c548af4864d22fd6d49f3bd9f go1.15.6.darwin-amd64.tar.gz -ad187f02158b9a9013ef03f41d14aa69c402477f178825a3940280814bcbb755 go1.15.6.linux-386.tar.gz -3918e6cc85e7eaaa6f859f1bdbaac772e7a825b0eb423c63d3ae68b21f84b844 go1.15.6.linux-amd64.tar.gz -f87515b9744154ffe31182da9341d0a61eb0795551173d242c8cad209239e492 go1.15.6.linux-arm64.tar.gz -40ba9a57764e374195018ef37c38a5fbac9bbce908eab436370631a84bfc5788 go1.15.6.linux-armv6l.tar.gz -5872eff6746a0a5f304272b27cbe9ce186f468454e95749cce01e903fbfc0e17 go1.15.6.windows-386.zip -b7b3808bb072c2bab73175009187fd5a7f20ffe0a31739937003a14c5c4d9006 go1.15.6.windows-amd64.zip -9d9dd5c217c1392f1b2ed5e03e1c71bf4cf8553884e57a38e68fd37fdcfe31a8 go1.15.6.freebsd-386.tar.gz -609f065d855aed5a0b40ef0245aacbcc0b4b7882dc3b1e75ae50576cf25265ee go1.15.6.freebsd-amd64.tar.gz -d4174fc217e749ac049eacc8827df879689f2246ac230d04991ae7df336f7cb2 go1.15.6.linux-ppc64le.tar.gz -839cc6b67687d8bb7cb044e4a9a2eac0c090765cc8ec55ffe714dfb7cd51cf3a go1.15.6.linux-s390x.tar.gz +7688063d55656105898f323d90a79a39c378d86fe89ae192eb3b7fc46347c95a go1.16.src.tar.gz +6000a9522975d116bf76044967d7e69e04e982e9625330d9a539a8b45395f9a8 go1.16.darwin-amd64.tar.gz +ea435a1ac6d497b03e367fdfb74b33e961d813883468080f6e239b3b03bea6aa go1.16.linux-386.tar.gz +013a489ebb3e24ef3d915abe5b94c3286c070dfe0818d5bca8108f1d6e8440d2 go1.16.linux-amd64.tar.gz +3770f7eb22d05e25fbee8fb53c2a4e897da043eb83c69b9a14f8d98562cd8098 go1.16.linux-arm64.tar.gz +d1d9404b1dbd77afa2bdc70934e10fbfcf7d785c372efc29462bb7d83d0a32fd go1.16.linux-armv6l.tar.gz +481492a17d42193d471b93b7a06da3555331bd833b76336afc87be820c48933f go1.16.windows-386.zip +5cc88fa506b3d5c453c54c3ea218fc8dd05d7362ae1de15bb67986b72089ce93 go1.16.windows-amd64.zip +d7d6c70b05a7c2f68b48aab5ab8cb5116b8444c9ddad131673b152e7cff7c726 go1.16.freebsd-386.tar.gz +40b03216f6945fb6883a50604fc7f409a83f62171607229a9c598e701e684f8a go1.16.freebsd-amd64.tar.gz +27a1aaa988e930b7932ce459c8a63ad5b3333b3a06b016d87ff289f2a11aacd6 go1.16.linux-ppc64le.tar.gz +be4c9e4e2cf058efc4e3eb013a760cb989ddc4362f111950c990d1c63b27ccbe go1.16.linux-s390x.tar.gz d998a84eea42f2271aca792a7b027ca5c1edfcba229e8e5a844c9ac3f336df35 golangci-lint-1.27.0-linux-armv7.tar.gz bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint.exe-1.27.0-windows-amd64.zip diff --git a/build/ci.go b/build/ci.go index 73d8961629..756fba8399 100644 --- a/build/ci.go +++ b/build/ci.go @@ -152,7 +152,7 @@ var ( // This is the version of go that will be downloaded by // // go run ci.go install -dlgo - dlgoVersion = "1.15.6" + dlgoVersion = "1.16" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) From 8647233a8ec2a2410a078013ca12c38fdc229866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Fri, 19 Feb 2021 15:53:12 +0100 Subject: [PATCH 205/235] les: fix balance expiration (#22343) * les/lespay/server: fix balance expiration and add test * les: move client balances to a new db * les: rename lespayDb to lesDb --- les/client.go | 8 ++-- les/clientpool.go | 4 +- les/commons.go | 2 +- les/server.go | 8 +++- les/vflux/server/balance_test.go | 73 ++++++++++++++++++++++++++++- les/vflux/server/balance_tracker.go | 6 ++- 6 files changed, 92 insertions(+), 9 deletions(-) diff --git a/les/client.go b/les/client.go index 9f0afc96c5..d08c9feba5 100644 --- a/les/client.go +++ b/les/client.go @@ -81,7 +81,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if err != nil { return nil, err } - lespayDb, err := stack.OpenDatabase("lespay", 0, 0, "eth/db/lespay") + lesDb, err := stack.OpenDatabase("les.client", 0, 0, "eth/db/les.client") if err != nil { return nil, err } @@ -99,6 +99,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { chainConfig: chainConfig, iConfig: light.DefaultClientIndexerConfig, chainDb: chainDb, + lesDb: lesDb, closeCh: make(chan struct{}), }, peers: peers, @@ -108,13 +109,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), - valueTracker: vfc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), + valueTracker: vfc.NewValueTracker(lesDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), p2pServer: stack.Server(), p2pConfig: &stack.Config().P2P, } peers.subscribe((*vtSubscription)(leth.valueTracker)) - leth.serverPool = newServerPool(lespayDb, []byte("serverpool:"), leth.valueTracker, time.Second, nil, &mclock.System{}, config.UltraLightServers) + leth.serverPool = newServerPool(lesDb, []byte("serverpool:"), leth.valueTracker, time.Second, nil, &mclock.System{}, config.UltraLightServers) peers.subscribe(leth.serverPool) leth.dialCandidates = leth.serverPool.dialIterator @@ -331,6 +332,7 @@ func (s *LightEthereum) Stop() error { s.eventMux.Stop() rawdb.PopUncleanShutdownMarker(s.chainDb) s.chainDb.Close() + s.lesDb.Close() s.wg.Wait() log.Info("Light ethereum stopped") return nil diff --git a/les/clientpool.go b/les/clientpool.go index 96c0f0f99e..4e1499bf5d 100644 --- a/les/clientpool.go +++ b/les/clientpool.go @@ -105,7 +105,7 @@ type clientInfo struct { } // newClientPool creates a new client pool -func newClientPool(ns *nodestate.NodeStateMachine, lespayDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID)) *clientPool { +func newClientPool(ns *nodestate.NodeStateMachine, lesDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID)) *clientPool { pool := &clientPool{ ns: ns, BalanceTrackerSetup: balanceTrackerSetup, @@ -115,7 +115,7 @@ func newClientPool(ns *nodestate.NodeStateMachine, lespayDb ethdb.Database, minC connectedBias: connectedBias, removePeer: removePeer, } - pool.bt = vfs.NewBalanceTracker(ns, balanceTrackerSetup, lespayDb, clock, &utils.Expirer{}, &utils.Expirer{}) + pool.bt = vfs.NewBalanceTracker(ns, balanceTrackerSetup, lesDb, clock, &utils.Expirer{}, &utils.Expirer{}) pool.pp = vfs.NewPriorityPool(ns, priorityPoolSetup, clock, minCap, connectedBias, 4) // set default expiration constants used by tests diff --git a/les/commons.go b/les/commons.go index a2fce1dc97..d090fc21fc 100644 --- a/les/commons.go +++ b/les/commons.go @@ -51,7 +51,7 @@ type lesCommons struct { config *ethconfig.Config chainConfig *params.ChainConfig iConfig *light.IndexerConfig - chainDb ethdb.Database + chainDb, lesDb ethdb.Database chainReader chainReader chtIndexer, bloomTrieIndexer *core.ChainIndexer oracle *checkpointoracle.CheckpointOracle diff --git a/les/server.go b/les/server.go index 351a53f690..e34647f290 100644 --- a/les/server.go +++ b/les/server.go @@ -85,6 +85,10 @@ type LesServer struct { } func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*LesServer, error) { + lesDb, err := node.OpenDatabase("les.server", 0, 0, "eth/db/les.server") + if err != nil { + return nil, err + } ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) // Calculate the number of threads used to service the light client // requests based on the user-specified value. @@ -99,6 +103,7 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les chainConfig: e.BlockChain().Config(), iConfig: light.DefaultServerIndexerConfig, chainDb: e.ChainDb(), + lesDb: lesDb, chainReader: e.BlockChain(), chtIndexer: light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations, true), bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency, true), @@ -136,7 +141,7 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les srv.maxCapacity = totalRecharge } srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2) - srv.clientPool = newClientPool(ns, srv.chainDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient) + srv.clientPool = newClientPool(ns, lesDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient) srv.clientPool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}) checkpoint := srv.latestLocalCheckpoint() @@ -222,6 +227,7 @@ func (s *LesServer) Stop() error { // Note, bloom trie indexer is closed by parent bloombits indexer. s.chtIndexer.Close() + s.lesDb.Close() s.wg.Wait() log.Info("Les server stopped") diff --git a/les/vflux/server/balance_test.go b/les/vflux/server/balance_test.go index 67e1944373..6c817aa26c 100644 --- a/les/vflux/server/balance_test.go +++ b/les/vflux/server/balance_test.go @@ -17,6 +17,7 @@ package server import ( + "math" "math/rand" "reflect" "testing" @@ -69,7 +70,9 @@ func (b *balanceTestSetup) newNode(capacity uint64) *NodeBalance { node := enode.SignNull(&enr.Record{}, enode.ID{}) b.ns.SetState(node, testFlag, nodestate.Flags{}, 0) b.ns.SetField(node, btTestSetup.connAddressField, "") - b.ns.SetField(node, ppTestSetup.CapacityField, capacity) + if capacity != 0 { + b.ns.SetField(node, ppTestSetup.CapacityField, capacity) + } n, _ := b.ns.GetField(node, btTestSetup.BalanceField).(*NodeBalance) return n } @@ -398,3 +401,71 @@ func TestCallback(t *testing.T) { case <-time.NewTimer(time.Millisecond * 100).C: } } + +func TestBalancePersistence(t *testing.T) { + clock := &mclock.Simulated{} + ns := nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) + db := memorydb.New() + posExp := &utils.Expirer{} + negExp := &utils.Expirer{} + posExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour*2)) // halves every two hours + negExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour)) // halves every hour + bt := NewBalanceTracker(ns, btTestSetup, db, clock, posExp, negExp) + ns.Start() + bts := &balanceTestSetup{ + clock: clock, + ns: ns, + bt: bt, + } + var nb *NodeBalance + exp := func(expPos, expNeg uint64) { + pos, neg := nb.GetBalance() + if pos != expPos { + t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos) + } + if neg != expNeg { + t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos) + } + } + expTotal := func(expTotal uint64) { + total := bt.TotalTokenAmount() + if total != expTotal { + t.Fatalf("Total token amount incorrect, want %v, got %v", expTotal, total) + } + } + + expTotal(0) + nb = bts.newNode(0) + expTotal(0) + nb.SetBalance(16000000000, 16000000000) + exp(16000000000, 16000000000) + expTotal(16000000000) + clock.Run(time.Hour * 2) + exp(8000000000, 4000000000) + expTotal(8000000000) + bt.Stop() + ns.Stop() + + clock = &mclock.Simulated{} + ns = nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) + posExp = &utils.Expirer{} + negExp = &utils.Expirer{} + posExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour*2)) // halves every two hours + negExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour)) // halves every hour + bt = NewBalanceTracker(ns, btTestSetup, db, clock, posExp, negExp) + ns.Start() + bts = &balanceTestSetup{ + clock: clock, + ns: ns, + bt: bt, + } + expTotal(8000000000) + nb = bts.newNode(0) + exp(8000000000, 4000000000) + expTotal(8000000000) + clock.Run(time.Hour * 2) + exp(4000000000, 1000000000) + expTotal(4000000000) + bt.Stop() + ns.Stop() +} diff --git a/les/vflux/server/balance_tracker.go b/les/vflux/server/balance_tracker.go index edd4b288d9..1708019de4 100644 --- a/les/vflux/server/balance_tracker.go +++ b/les/vflux/server/balance_tracker.go @@ -99,6 +99,10 @@ func NewBalanceTracker(ns *nodestate.NodeStateMachine, setup BalanceTrackerSetup balanceTimer: utils.NewUpdateTimer(clock, time.Second*10), quit: make(chan struct{}), } + posOffset, negOffset := bt.ndb.getExpiration() + posExp.SetLogOffset(clock.Now(), posOffset) + negExp.SetLogOffset(clock.Now(), negOffset) + bt.ndb.forEachBalance(false, func(id enode.ID, balance utils.ExpiredValue) bool { bt.inactive.AddExp(balance) return true @@ -177,7 +181,7 @@ func (bt *BalanceTracker) TotalTokenAmount() uint64 { bt.balanceTimer.Update(func(_ time.Duration) bool { bt.active = utils.ExpiredValue{} bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok { + if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok && n.active { pos, _ := n.GetRawBalance() bt.active.AddExp(pos) } From 8f03e3b107c0f7a39de31a9e7deb658431a937ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Sat, 20 Feb 2021 10:40:38 +0100 Subject: [PATCH 206/235] tests/fuzzers/les: add fuzzer for les server handler (#22282) * les: refactored server handler * tests/fuzzers/les: add fuzzer for les server handler * tests, les: update les fuzzer tests: update les fuzzer tests/fuzzer/les: release resources tests/fuzzer/les: pre-initialize all resources * les: refactored server handler and fuzzer Co-authored-by: rjl493456442 --- les/handler_test.go | 34 +- les/peer.go | 4 +- les/protocol.go | 65 ++- les/server_handler.go | 780 +++++--------------------------- les/server_requests.go | 569 +++++++++++++++++++++++ les/test_helper.go | 4 + oss-fuzz.sh | 1 + tests/fuzzers/les/debug/main.go | 41 ++ tests/fuzzers/les/les-fuzzer.go | 407 +++++++++++++++++ 9 files changed, 1218 insertions(+), 687 deletions(-) create mode 100644 les/server_requests.go create mode 100644 tests/fuzzers/les/debug/main.go create mode 100644 tests/fuzzers/les/les-fuzzer.go diff --git a/les/handler_test.go b/les/handler_test.go index 83be312081..e251f4503b 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -65,27 +65,27 @@ func testGetBlockHeaders(t *testing.T, protocol int) { // Create a batch of tests for various scenarios limit := uint64(MaxHeaderFetch) tests := []struct { - query *getBlockHeadersData // The query to execute for header retrieval + query *GetBlockHeadersData // The query to execute for header retrieval expect []common.Hash // The hashes of the block whose headers are expected }{ // A single random block should be retrievable by hash and number too { - &getBlockHeadersData{Origin: hashOrNumber{Hash: bc.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Hash: bc.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, []common.Hash{bc.GetBlockByNumber(limit / 2).Hash()}, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1}, []common.Hash{bc.GetBlockByNumber(limit / 2).Hash()}, }, // Multiple headers should be retrievable in both directions { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3}, []common.Hash{ bc.GetBlockByNumber(limit / 2).Hash(), bc.GetBlockByNumber(limit/2 + 1).Hash(), bc.GetBlockByNumber(limit/2 + 2).Hash(), }, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, []common.Hash{ bc.GetBlockByNumber(limit / 2).Hash(), bc.GetBlockByNumber(limit/2 - 1).Hash(), @@ -94,14 +94,14 @@ func testGetBlockHeaders(t *testing.T, protocol int) { }, // Multiple headers with skip lists should be retrievable { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, []common.Hash{ bc.GetBlockByNumber(limit / 2).Hash(), bc.GetBlockByNumber(limit/2 + 4).Hash(), bc.GetBlockByNumber(limit/2 + 8).Hash(), }, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, []common.Hash{ bc.GetBlockByNumber(limit / 2).Hash(), bc.GetBlockByNumber(limit/2 - 4).Hash(), @@ -110,26 +110,26 @@ func testGetBlockHeaders(t *testing.T, protocol int) { }, // The chain endpoints should be retrievable { - &getBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1}, []common.Hash{bc.GetBlockByNumber(0).Hash()}, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64()}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64()}, Amount: 1}, []common.Hash{bc.CurrentBlock().Hash()}, }, // Ensure protocol limits are honored //{ - // &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true}, + // &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true}, // []common.Hash{}, //}, // Check that requesting more than available is handled gracefully { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3}, []common.Hash{ bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 4).Hash(), bc.GetBlockByNumber(bc.CurrentBlock().NumberU64()).Hash(), }, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, []common.Hash{ bc.GetBlockByNumber(4).Hash(), bc.GetBlockByNumber(0).Hash(), @@ -137,13 +137,13 @@ func testGetBlockHeaders(t *testing.T, protocol int) { }, // Check that requesting more than available is handled gracefully, even if mid skip { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3}, []common.Hash{ bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 4).Hash(), bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1).Hash(), }, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, []common.Hash{ bc.GetBlockByNumber(4).Hash(), bc.GetBlockByNumber(1).Hash(), @@ -151,10 +151,10 @@ func testGetBlockHeaders(t *testing.T, protocol int) { }, // Check that non existing headers aren't returned { - &getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1}, []common.Hash{}, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() + 1}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() + 1}, Amount: 1}, []common.Hash{}, }, } @@ -619,7 +619,7 @@ func TestStopResumeLes3(t *testing.T) { header := server.handler.blockchain.CurrentHeader() req := func() { reqID++ - sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1}) + sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1}) } for i := 1; i <= 5; i++ { // send requests while we still have enough buffer and expect a response diff --git a/les/peer.go b/les/peer.go index 52ab506368..479b4034bc 100644 --- a/les/peer.go +++ b/les/peer.go @@ -432,14 +432,14 @@ func (p *serverPeer) sendRequest(msgcode, reqID uint64, data interface{}, amount // specified header query, based on the hash of an origin block. func (p *serverPeer) requestHeadersByHash(reqID uint64, origin common.Hash, amount int, skip int, reverse bool) error { p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) - return p.sendRequest(GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) + return p.sendRequest(GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) } // requestHeadersByNumber fetches a batch of blocks' headers corresponding to the // specified header query, based on the number of an origin block. func (p *serverPeer) requestHeadersByNumber(reqID, origin uint64, amount int, skip int, reverse bool) error { p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) - return p.sendRequest(GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) + return p.sendRequest(GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) } // requestBodies fetches a batch of blocks' bodies corresponding to the hashes diff --git a/les/protocol.go b/les/protocol.go index 909d25d375..07a4452f40 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -24,6 +24,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/p2p/enode" @@ -83,6 +84,62 @@ const ( ResumeMsg = 0x17 ) +// GetBlockHeadersData represents a block header query (the request ID is not included) +type GetBlockHeadersData struct { + Origin hashOrNumber // Block from which to retrieve headers + Amount uint64 // Maximum number of headers to retrieve + Skip uint64 // Blocks to skip between consecutive headers + Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) +} + +// GetBlockHeadersPacket represents a block header request +type GetBlockHeadersPacket struct { + ReqID uint64 + Query GetBlockHeadersData +} + +// GetBlockBodiesPacket represents a block body request +type GetBlockBodiesPacket struct { + ReqID uint64 + Hashes []common.Hash +} + +// GetCodePacket represents a contract code request +type GetCodePacket struct { + ReqID uint64 + Reqs []CodeReq +} + +// GetReceiptsPacket represents a block receipts request +type GetReceiptsPacket struct { + ReqID uint64 + Hashes []common.Hash +} + +// GetProofsPacket represents a proof request +type GetProofsPacket struct { + ReqID uint64 + Reqs []ProofReq +} + +// GetHelperTrieProofsPacket represents a helper trie proof request +type GetHelperTrieProofsPacket struct { + ReqID uint64 + Reqs []HelperTrieReq +} + +// SendTxPacket represents a transaction propagation request +type SendTxPacket struct { + ReqID uint64 + Txs []*types.Transaction +} + +// GetTxStatusPacket represents a transaction status query +type GetTxStatusPacket struct { + ReqID uint64 + Hashes []common.Hash +} + type requestInfo struct { name string maxCount uint64 @@ -229,14 +286,6 @@ type blockInfo struct { Td *big.Int // Total difficulty of one particular block being announced } -// getBlockHeadersData represents a block header query. -type getBlockHeadersData struct { - Origin hashOrNumber // Block from which to retrieve headers - Amount uint64 // Maximum number of headers to retrieve - Skip uint64 // Blocks to skip between consecutive headers - Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) -} - // hashOrNumber is a combined field for specifying an origin block. type hashOrNumber struct { Hash common.Hash // Block hash from which to retrieve headers (excludes Number) diff --git a/les/server_handler.go b/les/server_handler.go index fd81e273c9..b6e8b050b1 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -18,8 +18,6 @@ package les import ( "crypto/ecdsa" - "encoding/binary" - "encoding/json" "errors" "sync" "sync/atomic" @@ -223,648 +221,109 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { } defer msg.Discard() - var ( - maxCost uint64 - task *servingTask - ) p.responseCount++ responseCount := p.responseCount - // accept returns an indicator whether the request can be served. - // If so, deduct the max cost from the flow control buffer. - accept := func(reqID, reqCnt, maxCnt uint64) bool { - // Short circuit if the peer is already frozen or the request is invalid. - inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0) - if p.isFrozen() || reqCnt == 0 || reqCnt > maxCnt { - p.fcClient.OneTimeCost(inSizeCost) - return false - } - // Prepaid max cost units before request been serving. - maxCost = p.fcCosts.getMaxCost(msg.Code, reqCnt) - accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost) - if !accepted { - p.freeze() - p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge))) - p.fcClient.OneTimeCost(inSizeCost) - return false - } - // Create a multi-stage task, estimate the time it takes for the task to - // execute, and cache it in the request service queue. - factor := h.server.costTracker.globalFactor() - if factor < 0.001 { - factor = 1 - p.Log().Error("Invalid global cost factor", "factor", factor) - } - maxTime := uint64(float64(maxCost) / factor) - task = h.server.servingQueue.newTask(p, maxTime, priority) - if task.start() { - return true - } - p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost) - return false - } - // sendResponse sends back the response and updates the flow control statistic. - sendResponse := func(reqID, amount uint64, reply *reply, servingTime uint64) { - p.responseLock.Lock() - defer p.responseLock.Unlock() - // Short circuit if the client is already frozen. - if p.isFrozen() { - realCost := h.server.costTracker.realCost(servingTime, msg.Size, 0) - p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) - return - } - // Positive correction buffer value with real cost. - var replySize uint32 - if reply != nil { - replySize = reply.size() - } - var realCost uint64 - if h.server.costTracker.testing { - realCost = maxCost // Assign a fake cost for testing purpose - } else { - realCost = h.server.costTracker.realCost(servingTime, msg.Size, replySize) - if realCost > maxCost { - realCost = maxCost - } - } - bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) - if amount != 0 { - // Feed cost tracker request serving statistic. - h.server.costTracker.updateStats(msg.Code, amount, servingTime, realCost) - // Reduce priority "balance" for the specific peer. - p.balance.RequestServed(realCost) - } - if reply != nil { - p.queueSend(func() { - if err := reply.send(bv); err != nil { - select { - case p.errCh <- err: - default: - } - } - }) - } + req, ok := Les3[msg.Code] + if !ok { + p.Log().Trace("Received invalid message", "code", msg.Code) + clientErrorMeter.Mark(1) + return errResp(ErrInvalidMsgCode, "%v", msg.Code) } - switch msg.Code { - case GetBlockHeadersMsg: - p.Log().Trace("Received block header request") - if metrics.EnabledExpensive { - miscInHeaderPacketsMeter.Mark(1) - miscInHeaderTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Query getBlockHeadersData - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "%v: %v", msg, err) - } - query := req.Query - if accept(req.ReqID, query.Amount, MaxHeaderFetch) { - wg.Add(1) - go func() { - defer wg.Done() - hashMode := query.Origin.Hash != (common.Hash{}) - first := true - maxNonCanonical := uint64(100) - - // Gather headers until the fetch or network limits is reached - var ( - bytes common.StorageSize - headers []*types.Header - unknown bool - ) - for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit { - if !first && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - // Retrieve the next header satisfying the query - var origin *types.Header - if hashMode { - if first { - origin = h.blockchain.GetHeaderByHash(query.Origin.Hash) - if origin != nil { - query.Origin.Number = origin.Number.Uint64() - } - } else { - origin = h.blockchain.GetHeader(query.Origin.Hash, query.Origin.Number) - } - } else { - origin = h.blockchain.GetHeaderByNumber(query.Origin.Number) - } - if origin == nil { - break - } - headers = append(headers, origin) - bytes += estHeaderRlpSize - - // Advance to the next header of the query - switch { - case hashMode && query.Reverse: - // Hash based traversal towards the genesis block - ancestor := query.Skip + 1 - if ancestor == 0 { - unknown = true - } else { - query.Origin.Hash, query.Origin.Number = h.blockchain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) - unknown = query.Origin.Hash == common.Hash{} - } - case hashMode && !query.Reverse: - // Hash based traversal towards the leaf block - var ( - current = origin.Number.Uint64() - next = current + query.Skip + 1 - ) - if next <= current { - infos, _ := json.MarshalIndent(p.Peer.Info(), "", " ") - p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) - unknown = true - } else { - if header := h.blockchain.GetHeaderByNumber(next); header != nil { - nextHash := header.Hash() - expOldHash, _ := h.blockchain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) - if expOldHash == query.Origin.Hash { - query.Origin.Hash, query.Origin.Number = nextHash, next - } else { - unknown = true - } - } else { - unknown = true - } - } - case query.Reverse: - // Number based traversal towards the genesis block - if query.Origin.Number >= query.Skip+1 { - query.Origin.Number -= query.Skip + 1 - } else { - unknown = true - } - - case !query.Reverse: - // Number based traversal towards the leaf block - query.Origin.Number += query.Skip + 1 - } - first = false - } - reply := p.replyBlockHeaders(req.ReqID, headers) - sendResponse(req.ReqID, query.Amount, reply, task.done()) - if metrics.EnabledExpensive { - miscOutHeaderPacketsMeter.Mark(1) - miscOutHeaderTrafficMeter.Mark(int64(reply.size())) - miscServingTimeHeaderTimer.Update(time.Duration(task.servingTime)) - } - }() - } - - case GetBlockBodiesMsg: - p.Log().Trace("Received block bodies request") - if metrics.EnabledExpensive { - miscInBodyPacketsMeter.Mark(1) - miscInBodyTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Hashes []common.Hash - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - var ( - bytes int - bodies []rlp.RawValue - ) - reqCnt := len(req.Hashes) - if accept(req.ReqID, uint64(reqCnt), MaxBodyFetch) { - wg.Add(1) - go func() { - defer wg.Done() - for i, hash := range req.Hashes { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - if bytes >= softResponseLimit { - break - } - body := h.blockchain.GetBodyRLP(hash) - if body == nil { - p.bumpInvalid() - continue - } - bodies = append(bodies, body) - bytes += len(body) - } - reply := p.replyBlockBodiesRLP(req.ReqID, bodies) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutBodyPacketsMeter.Mark(1) - miscOutBodyTrafficMeter.Mark(int64(reply.size())) - miscServingTimeBodyTimer.Update(time.Duration(task.servingTime)) - } - }() - } - - case GetCodeMsg: - p.Log().Trace("Received code request") - if metrics.EnabledExpensive { - miscInCodePacketsMeter.Mark(1) - miscInCodeTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Reqs []CodeReq - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - var ( - bytes int - data [][]byte - ) - reqCnt := len(req.Reqs) - if accept(req.ReqID, uint64(reqCnt), MaxCodeFetch) { - wg.Add(1) - go func() { - defer wg.Done() - for i, request := range req.Reqs { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - // Look up the root hash belonging to the request - header := h.blockchain.GetHeaderByHash(request.BHash) - if header == nil { - p.Log().Warn("Failed to retrieve associate header for code", "hash", request.BHash) - p.bumpInvalid() - continue - } - // Refuse to search stale state data in the database since looking for - // a non-exist key is kind of expensive. - local := h.blockchain.CurrentHeader().Number.Uint64() - if !h.server.archiveMode && header.Number.Uint64()+core.TriesInMemory <= local { - p.Log().Debug("Reject stale code request", "number", header.Number.Uint64(), "head", local) - p.bumpInvalid() - continue - } - triedb := h.blockchain.StateCache().TrieDB() - - account, err := h.getAccount(triedb, header.Root, common.BytesToHash(request.AccKey)) - if err != nil { - p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err) - p.bumpInvalid() - continue - } - code, err := h.blockchain.StateCache().ContractCode(common.BytesToHash(request.AccKey), common.BytesToHash(account.CodeHash)) - if err != nil { - p.Log().Warn("Failed to retrieve account code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "codehash", common.BytesToHash(account.CodeHash), "err", err) - continue - } - // Accumulate the code and abort if enough data was retrieved - data = append(data, code) - if bytes += len(code); bytes >= softResponseLimit { - break - } - } - reply := p.replyCode(req.ReqID, data) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutCodePacketsMeter.Mark(1) - miscOutCodeTrafficMeter.Mark(int64(reply.size())) - miscServingTimeCodeTimer.Update(time.Duration(task.servingTime)) - } - }() - } - - case GetReceiptsMsg: - p.Log().Trace("Received receipts request") - if metrics.EnabledExpensive { - miscInReceiptPacketsMeter.Mark(1) - miscInReceiptTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Hashes []common.Hash - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - var ( - bytes int - receipts []rlp.RawValue - ) - reqCnt := len(req.Hashes) - if accept(req.ReqID, uint64(reqCnt), MaxReceiptFetch) { - wg.Add(1) - go func() { - defer wg.Done() - for i, hash := range req.Hashes { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - if bytes >= softResponseLimit { - break - } - // Retrieve the requested block's receipts, skipping if unknown to us - results := h.blockchain.GetReceiptsByHash(hash) - if results == nil { - if header := h.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { - p.bumpInvalid() - continue - } - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(results); err != nil { - log.Error("Failed to encode receipt", "err", err) - } else { - receipts = append(receipts, encoded) - bytes += len(encoded) - } - } - reply := p.replyReceiptsRLP(req.ReqID, receipts) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutReceiptPacketsMeter.Mark(1) - miscOutReceiptTrafficMeter.Mark(int64(reply.size())) - miscServingTimeReceiptTimer.Update(time.Duration(task.servingTime)) - } - }() - } - - case GetProofsV2Msg: - p.Log().Trace("Received les/2 proofs request") - if metrics.EnabledExpensive { - miscInTrieProofPacketsMeter.Mark(1) - miscInTrieProofTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Reqs []ProofReq - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - lastBHash common.Hash - root common.Hash - header *types.Header - ) - reqCnt := len(req.Reqs) - if accept(req.ReqID, uint64(reqCnt), MaxProofsFetch) { - wg.Add(1) - go func() { - defer wg.Done() - nodes := light.NewNodeSet() + p.Log().Trace("Received " + req.Name) - for i, request := range req.Reqs { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - // Look up the root hash belonging to the request - if request.BHash != lastBHash { - root, lastBHash = common.Hash{}, request.BHash + serve, reqID, reqCnt, err := req.Handle(msg) + if err != nil { + clientErrorMeter.Mark(1) + return errResp(ErrDecode, "%v: %v", msg, err) + } - if header = h.blockchain.GetHeaderByHash(request.BHash); header == nil { - p.Log().Warn("Failed to retrieve header for proof", "hash", request.BHash) - p.bumpInvalid() - continue - } - // Refuse to search stale state data in the database since looking for - // a non-exist key is kind of expensive. - local := h.blockchain.CurrentHeader().Number.Uint64() - if !h.server.archiveMode && header.Number.Uint64()+core.TriesInMemory <= local { - p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local) - p.bumpInvalid() - continue - } - root = header.Root - } - // If a header lookup failed (non existent), ignore subsequent requests for the same header - if root == (common.Hash{}) { - p.bumpInvalid() - continue - } - // Open the account or storage trie for the request - statedb := h.blockchain.StateCache() + if metrics.EnabledExpensive { + req.InPacketsMeter.Mark(1) + req.InTrafficMeter.Mark(int64(msg.Size)) + } - var trie state.Trie - switch len(request.AccKey) { - case 0: - // No account key specified, open an account trie - trie, err = statedb.OpenTrie(root) - if trie == nil || err != nil { - p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err) - continue - } - default: - // Account key specified, open a storage trie - account, err := h.getAccount(statedb.TrieDB(), root, common.BytesToHash(request.AccKey)) - if err != nil { - p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err) - p.bumpInvalid() - continue - } - trie, err = statedb.OpenStorageTrie(common.BytesToHash(request.AccKey), account.Root) - if trie == nil || err != nil { - p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "root", account.Root, "err", err) - continue - } - } - // Prove the user's request from the account or stroage trie - if err := trie.Prove(request.Key, request.FromLevel, nodes); err != nil { - p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err) - continue - } - if nodes.DataSize() >= softResponseLimit { - break - } - } - reply := p.replyProofsV2(req.ReqID, nodes.NodeList()) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutTrieProofPacketsMeter.Mark(1) - miscOutTrieProofTrafficMeter.Mark(int64(reply.size())) - miscServingTimeTrieProofTimer.Update(time.Duration(task.servingTime)) - } - }() - } + // Short circuit if the peer is already frozen or the request is invalid. + inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0) + if p.isFrozen() || reqCnt == 0 || reqCnt > req.MaxCount { + p.fcClient.OneTimeCost(inSizeCost) + return nil + } + // Prepaid max cost units before request been serving. + maxCost := p.fcCosts.getMaxCost(msg.Code, reqCnt) + accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost) + if !accepted { + p.freeze() + p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge))) + p.fcClient.OneTimeCost(inSizeCost) + return nil + } + // Create a multi-stage task, estimate the time it takes for the task to + // execute, and cache it in the request service queue. + factor := h.server.costTracker.globalFactor() + if factor < 0.001 { + factor = 1 + p.Log().Error("Invalid global cost factor", "factor", factor) + } + maxTime := uint64(float64(maxCost) / factor) + task := h.server.servingQueue.newTask(p, maxTime, priority) + if task.start() { + wg.Add(1) + go func() { + defer wg.Done() + reply := serve(h, p, task.waitOrStop) + if reply != nil { + task.done() + } - case GetHelperTrieProofsMsg: - p.Log().Trace("Received helper trie proof request") - if metrics.EnabledExpensive { - miscInHelperTriePacketsMeter.Mark(1) - miscInHelperTrieTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Reqs []HelperTrieReq - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - auxBytes int - auxData [][]byte - ) - reqCnt := len(req.Reqs) - if accept(req.ReqID, uint64(reqCnt), MaxHelperTrieProofsFetch) { - wg.Add(1) - go func() { - defer wg.Done() - var ( - lastIdx uint64 - lastType uint - root common.Hash - auxTrie *trie.Trie - ) - nodes := light.NewNodeSet() - for i, request := range req.Reqs { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - if auxTrie == nil || request.Type != lastType || request.TrieIdx != lastIdx { - auxTrie, lastType, lastIdx = nil, request.Type, request.TrieIdx + p.responseLock.Lock() + defer p.responseLock.Unlock() - var prefix string - if root, prefix = h.getHelperTrie(request.Type, request.TrieIdx); root != (common.Hash{}) { - auxTrie, _ = trie.New(root, trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix))) - } - } - if auxTrie == nil { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - // TODO(rjl493456442) short circuit if the proving is failed. - // The original client side code has a dirty hack to retrieve - // the headers with no valid proof. Keep the compatibility for - // legacy les protocol and drop this hack when the les2/3 are - // not supported. - err := auxTrie.Prove(request.Key, request.FromLevel, nodes) - if p.version >= lpv4 && err != nil { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - if request.AuxReq == htAuxHeader { - data := h.getAuxiliaryHeaders(request) - auxData = append(auxData, data) - auxBytes += len(data) - } - if nodes.DataSize()+auxBytes >= softResponseLimit { - break - } - } - reply := p.replyHelperTrieProofs(req.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutHelperTriePacketsMeter.Mark(1) - miscOutHelperTrieTrafficMeter.Mark(int64(reply.size())) - miscServingTimeHelperTrieTimer.Update(time.Duration(task.servingTime)) + // Short circuit if the client is already frozen. + if p.isFrozen() { + realCost := h.server.costTracker.realCost(task.servingTime, msg.Size, 0) + p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) + return + } + // Positive correction buffer value with real cost. + var replySize uint32 + if reply != nil { + replySize = reply.size() + } + var realCost uint64 + if h.server.costTracker.testing { + realCost = maxCost // Assign a fake cost for testing purpose + } else { + realCost = h.server.costTracker.realCost(task.servingTime, msg.Size, replySize) + if realCost > maxCost { + realCost = maxCost } - }() - } - - case SendTxV2Msg: - p.Log().Trace("Received new transactions") - if metrics.EnabledExpensive { - miscInTxsPacketsMeter.Mark(1) - miscInTxsTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Txs []*types.Transaction - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - reqCnt := len(req.Txs) - if accept(req.ReqID, uint64(reqCnt), MaxTxSend) { - wg.Add(1) - go func() { - defer wg.Done() - stats := make([]light.TxStatus, len(req.Txs)) - for i, tx := range req.Txs { - if i != 0 && !task.waitOrStop() { - return - } - hash := tx.Hash() - stats[i] = h.txStatus(hash) - if stats[i].Status == core.TxStatusUnknown { - addFn := h.txpool.AddRemotes - // Add txs synchronously for testing purpose - if h.addTxsSync { - addFn = h.txpool.AddRemotesSync - } - if errs := addFn([]*types.Transaction{tx}); errs[0] != nil { - stats[i].Error = errs[0].Error() - continue + } + bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) + if reply != nil { + // Feed cost tracker request serving statistic. + h.server.costTracker.updateStats(msg.Code, reqCnt, task.servingTime, realCost) + // Reduce priority "balance" for the specific peer. + p.balance.RequestServed(realCost) + p.queueSend(func() { + if err := reply.send(bv); err != nil { + select { + case p.errCh <- err: + default: } - stats[i] = h.txStatus(hash) - } - } - reply := p.replyTxStatus(req.ReqID, stats) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutTxsPacketsMeter.Mark(1) - miscOutTxsTrafficMeter.Mark(int64(reply.size())) - miscServingTimeTxTimer.Update(time.Duration(task.servingTime)) - } - }() - } - - case GetTxStatusMsg: - p.Log().Trace("Received transaction status query request") - if metrics.EnabledExpensive { - miscInTxStatusPacketsMeter.Mark(1) - miscInTxStatusTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Hashes []common.Hash - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - reqCnt := len(req.Hashes) - if accept(req.ReqID, uint64(reqCnt), MaxTxStatus) { - wg.Add(1) - go func() { - defer wg.Done() - stats := make([]light.TxStatus, len(req.Hashes)) - for i, hash := range req.Hashes { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return } - stats[i] = h.txStatus(hash) - } - reply := p.replyTxStatus(req.ReqID, stats) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) + }) if metrics.EnabledExpensive { - miscOutTxStatusPacketsMeter.Mark(1) - miscOutTxStatusTrafficMeter.Mark(int64(reply.size())) - miscServingTimeTxStatusTimer.Update(time.Duration(task.servingTime)) + req.OutPacketsMeter.Mark(1) + req.OutTrafficMeter.Mark(int64(replySize)) + req.ServingTimeMeter.Update(time.Duration(task.servingTime)) } - }() - } - - default: - p.Log().Trace("Received invalid message", "code", msg.Code) - clientErrorMeter.Mark(1) - return errResp(ErrInvalidMsgCode, "%v", msg.Code) + } + }() + } else { + p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost) } + // If the client has made too much invalid request(e.g. request a non-existent data), // reject them to prevent SPAM attack. if p.getInvalid() > maxRequestErrors { @@ -874,8 +333,28 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { return nil } +// BlockChain implements serverBackend +func (h *serverHandler) BlockChain() *core.BlockChain { + return h.blockchain +} + +// TxPool implements serverBackend +func (h *serverHandler) TxPool() *core.TxPool { + return h.txpool +} + +// ArchiveMode implements serverBackend +func (h *serverHandler) ArchiveMode() bool { + return h.server.archiveMode +} + +// AddTxsSync implements serverBackend +func (h *serverHandler) AddTxsSync() bool { + return h.addTxsSync +} + // getAccount retrieves an account from the state based on root. -func (h *serverHandler) getAccount(triedb *trie.Database, root, hash common.Hash) (state.Account, error) { +func getAccount(triedb *trie.Database, root, hash common.Hash) (state.Account, error) { trie, err := trie.New(root, triedb) if err != nil { return state.Account{}, err @@ -892,43 +371,24 @@ func (h *serverHandler) getAccount(triedb *trie.Database, root, hash common.Hash } // getHelperTrie returns the post-processed trie root for the given trie ID and section index -func (h *serverHandler) getHelperTrie(typ uint, index uint64) (common.Hash, string) { +func (h *serverHandler) GetHelperTrie(typ uint, index uint64) *trie.Trie { + var ( + root common.Hash + prefix string + ) switch typ { case htCanonical: sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.ChtSize-1) - return light.GetChtRoot(h.chainDb, index, sectionHead), light.ChtTablePrefix + root, prefix = light.GetChtRoot(h.chainDb, index, sectionHead), light.ChtTablePrefix case htBloomBits: sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.BloomTrieSize-1) - return light.GetBloomTrieRoot(h.chainDb, index, sectionHead), light.BloomTrieTablePrefix - } - return common.Hash{}, "" -} - -// getAuxiliaryHeaders returns requested auxiliary headers for the CHT request. -func (h *serverHandler) getAuxiliaryHeaders(req HelperTrieReq) []byte { - if req.Type == htCanonical && req.AuxReq == htAuxHeader && len(req.Key) == 8 { - blockNum := binary.BigEndian.Uint64(req.Key) - hash := rawdb.ReadCanonicalHash(h.chainDb, blockNum) - return rawdb.ReadHeaderRLP(h.chainDb, hash, blockNum) + root, prefix = light.GetBloomTrieRoot(h.chainDb, index, sectionHead), light.BloomTrieTablePrefix } - return nil -} - -// txStatus returns the status of a specified transaction. -func (h *serverHandler) txStatus(hash common.Hash) light.TxStatus { - var stat light.TxStatus - // Looking the transaction in txpool first. - stat.Status = h.txpool.Status([]common.Hash{hash})[0] - - // If the transaction is unknown to the pool, try looking it up locally. - if stat.Status == core.TxStatusUnknown { - lookup := h.blockchain.GetTransactionLookup(hash) - if lookup != nil { - stat.Status = core.TxStatusIncluded - stat.Lookup = lookup - } + if root == (common.Hash{}) { + return nil } - return stat + trie, _ := trie.New(root, trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix))) + return trie } // broadcastLoop broadcasts new block information to all connected light diff --git a/les/server_requests.go b/les/server_requests.go new file mode 100644 index 0000000000..d4af8006f0 --- /dev/null +++ b/les/server_requests.go @@ -0,0 +1,569 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package les + +import ( + "encoding/binary" + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// serverBackend defines the backend functions needed for serving LES requests +type serverBackend interface { + ArchiveMode() bool + AddTxsSync() bool + BlockChain() *core.BlockChain + TxPool() *core.TxPool + GetHelperTrie(typ uint, index uint64) *trie.Trie +} + +// Decoder is implemented by the messages passed to the handler functions +type Decoder interface { + Decode(val interface{}) error +} + +// RequestType is a static struct that describes an LES request type and references +// its handler function. +type RequestType struct { + Name string + MaxCount uint64 + InPacketsMeter, InTrafficMeter, OutPacketsMeter, OutTrafficMeter metrics.Meter + ServingTimeMeter metrics.Timer + Handle func(msg Decoder) (serve serveRequestFn, reqID, amount uint64, err error) +} + +// serveRequestFn is returned by the request handler functions after decoding the request. +// This function does the actual request serving using the supplied backend. waitOrStop is +// called between serving individual request items and may block if the serving process +// needs to be throttled. If it returns false then the process is terminated. +// The reply is not sent by this function yet. The flow control feedback value is supplied +// by the protocol handler when calling the send function of the returned reply struct. +type serveRequestFn func(backend serverBackend, peer *clientPeer, waitOrStop func() bool) *reply + +// Les3 contains the request types supported by les/2 and les/3 +var Les3 = map[uint64]RequestType{ + GetBlockHeadersMsg: RequestType{ + Name: "block header request", + MaxCount: MaxHeaderFetch, + InPacketsMeter: miscInHeaderPacketsMeter, + InTrafficMeter: miscInHeaderTrafficMeter, + OutPacketsMeter: miscOutHeaderPacketsMeter, + OutTrafficMeter: miscOutHeaderTrafficMeter, + ServingTimeMeter: miscServingTimeHeaderTimer, + Handle: handleGetBlockHeaders, + }, + GetBlockBodiesMsg: RequestType{ + Name: "block bodies request", + MaxCount: MaxBodyFetch, + InPacketsMeter: miscInBodyPacketsMeter, + InTrafficMeter: miscInBodyTrafficMeter, + OutPacketsMeter: miscOutBodyPacketsMeter, + OutTrafficMeter: miscOutBodyTrafficMeter, + ServingTimeMeter: miscServingTimeBodyTimer, + Handle: handleGetBlockBodies, + }, + GetCodeMsg: RequestType{ + Name: "code request", + MaxCount: MaxCodeFetch, + InPacketsMeter: miscInCodePacketsMeter, + InTrafficMeter: miscInCodeTrafficMeter, + OutPacketsMeter: miscOutCodePacketsMeter, + OutTrafficMeter: miscOutCodeTrafficMeter, + ServingTimeMeter: miscServingTimeCodeTimer, + Handle: handleGetCode, + }, + GetReceiptsMsg: RequestType{ + Name: "receipts request", + MaxCount: MaxReceiptFetch, + InPacketsMeter: miscInReceiptPacketsMeter, + InTrafficMeter: miscInReceiptTrafficMeter, + OutPacketsMeter: miscOutReceiptPacketsMeter, + OutTrafficMeter: miscOutReceiptTrafficMeter, + ServingTimeMeter: miscServingTimeReceiptTimer, + Handle: handleGetReceipts, + }, + GetProofsV2Msg: RequestType{ + Name: "les/2 proofs request", + MaxCount: MaxProofsFetch, + InPacketsMeter: miscInTrieProofPacketsMeter, + InTrafficMeter: miscInTrieProofTrafficMeter, + OutPacketsMeter: miscOutTrieProofPacketsMeter, + OutTrafficMeter: miscOutTrieProofTrafficMeter, + ServingTimeMeter: miscServingTimeTrieProofTimer, + Handle: handleGetProofs, + }, + GetHelperTrieProofsMsg: RequestType{ + Name: "helper trie proof request", + MaxCount: MaxHelperTrieProofsFetch, + InPacketsMeter: miscInHelperTriePacketsMeter, + InTrafficMeter: miscInHelperTrieTrafficMeter, + OutPacketsMeter: miscOutHelperTriePacketsMeter, + OutTrafficMeter: miscOutHelperTrieTrafficMeter, + ServingTimeMeter: miscServingTimeHelperTrieTimer, + Handle: handleGetHelperTrieProofs, + }, + SendTxV2Msg: RequestType{ + Name: "new transactions", + MaxCount: MaxTxSend, + InPacketsMeter: miscInTxsPacketsMeter, + InTrafficMeter: miscInTxsTrafficMeter, + OutPacketsMeter: miscOutTxsPacketsMeter, + OutTrafficMeter: miscOutTxsTrafficMeter, + ServingTimeMeter: miscServingTimeTxTimer, + Handle: handleSendTx, + }, + GetTxStatusMsg: RequestType{ + Name: "transaction status query request", + MaxCount: MaxTxStatus, + InPacketsMeter: miscInTxStatusPacketsMeter, + InTrafficMeter: miscInTxStatusTrafficMeter, + OutPacketsMeter: miscOutTxStatusPacketsMeter, + OutTrafficMeter: miscOutTxStatusTrafficMeter, + ServingTimeMeter: miscServingTimeTxStatusTimer, + Handle: handleGetTxStatus, + }, +} + +// handleGetBlockHeaders handles a block header request +func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetBlockHeadersPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + // Gather headers until the fetch or network limits is reached + var ( + bc = backend.BlockChain() + hashMode = r.Query.Origin.Hash != (common.Hash{}) + first = true + maxNonCanonical = uint64(100) + bytes common.StorageSize + headers []*types.Header + unknown bool + ) + for !unknown && len(headers) < int(r.Query.Amount) && bytes < softResponseLimit { + if !first && !waitOrStop() { + return nil + } + // Retrieve the next header satisfying the r + var origin *types.Header + if hashMode { + if first { + origin = bc.GetHeaderByHash(r.Query.Origin.Hash) + if origin != nil { + r.Query.Origin.Number = origin.Number.Uint64() + } + } else { + origin = bc.GetHeader(r.Query.Origin.Hash, r.Query.Origin.Number) + } + } else { + origin = bc.GetHeaderByNumber(r.Query.Origin.Number) + } + if origin == nil { + break + } + headers = append(headers, origin) + bytes += estHeaderRlpSize + + // Advance to the next header of the r + switch { + case hashMode && r.Query.Reverse: + // Hash based traversal towards the genesis block + ancestor := r.Query.Skip + 1 + if ancestor == 0 { + unknown = true + } else { + r.Query.Origin.Hash, r.Query.Origin.Number = bc.GetAncestor(r.Query.Origin.Hash, r.Query.Origin.Number, ancestor, &maxNonCanonical) + unknown = r.Query.Origin.Hash == common.Hash{} + } + case hashMode && !r.Query.Reverse: + // Hash based traversal towards the leaf block + var ( + current = origin.Number.Uint64() + next = current + r.Query.Skip + 1 + ) + if next <= current { + infos, _ := json.MarshalIndent(p.Peer.Info(), "", " ") + p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", r.Query.Skip, "next", next, "attacker", infos) + unknown = true + } else { + if header := bc.GetHeaderByNumber(next); header != nil { + nextHash := header.Hash() + expOldHash, _ := bc.GetAncestor(nextHash, next, r.Query.Skip+1, &maxNonCanonical) + if expOldHash == r.Query.Origin.Hash { + r.Query.Origin.Hash, r.Query.Origin.Number = nextHash, next + } else { + unknown = true + } + } else { + unknown = true + } + } + case r.Query.Reverse: + // Number based traversal towards the genesis block + if r.Query.Origin.Number >= r.Query.Skip+1 { + r.Query.Origin.Number -= r.Query.Skip + 1 + } else { + unknown = true + } + + case !r.Query.Reverse: + // Number based traversal towards the leaf block + r.Query.Origin.Number += r.Query.Skip + 1 + } + first = false + } + return p.replyBlockHeaders(r.ReqID, headers) + }, r.ReqID, r.Query.Amount, nil +} + +// handleGetBlockBodies handles a block body request +func handleGetBlockBodies(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetBlockBodiesPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + var ( + bytes int + bodies []rlp.RawValue + ) + bc := backend.BlockChain() + for i, hash := range r.Hashes { + if i != 0 && !waitOrStop() { + return nil + } + if bytes >= softResponseLimit { + break + } + body := bc.GetBodyRLP(hash) + if body == nil { + p.bumpInvalid() + continue + } + bodies = append(bodies, body) + bytes += len(body) + } + return p.replyBlockBodiesRLP(r.ReqID, bodies) + }, r.ReqID, uint64(len(r.Hashes)), nil +} + +// handleGetCode handles a contract code request +func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetCodePacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + var ( + bytes int + data [][]byte + ) + bc := backend.BlockChain() + for i, request := range r.Reqs { + if i != 0 && !waitOrStop() { + return nil + } + // Look up the root hash belonging to the request + header := bc.GetHeaderByHash(request.BHash) + if header == nil { + p.Log().Warn("Failed to retrieve associate header for code", "hash", request.BHash) + p.bumpInvalid() + continue + } + // Refuse to search stale state data in the database since looking for + // a non-exist key is kind of expensive. + local := bc.CurrentHeader().Number.Uint64() + if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local { + p.Log().Debug("Reject stale code request", "number", header.Number.Uint64(), "head", local) + p.bumpInvalid() + continue + } + triedb := bc.StateCache().TrieDB() + + account, err := getAccount(triedb, header.Root, common.BytesToHash(request.AccKey)) + if err != nil { + p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err) + p.bumpInvalid() + continue + } + code, err := bc.StateCache().ContractCode(common.BytesToHash(request.AccKey), common.BytesToHash(account.CodeHash)) + if err != nil { + p.Log().Warn("Failed to retrieve account code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "codehash", common.BytesToHash(account.CodeHash), "err", err) + continue + } + // Accumulate the code and abort if enough data was retrieved + data = append(data, code) + if bytes += len(code); bytes >= softResponseLimit { + break + } + } + return p.replyCode(r.ReqID, data) + }, r.ReqID, uint64(len(r.Reqs)), nil +} + +// handleGetReceipts handles a block receipts request +func handleGetReceipts(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetReceiptsPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + var ( + bytes int + receipts []rlp.RawValue + ) + bc := backend.BlockChain() + for i, hash := range r.Hashes { + if i != 0 && !waitOrStop() { + return nil + } + if bytes >= softResponseLimit { + break + } + // Retrieve the requested block's receipts, skipping if unknown to us + results := bc.GetReceiptsByHash(hash) + if results == nil { + if header := bc.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { + p.bumpInvalid() + continue + } + } + // If known, encode and queue for response packet + if encoded, err := rlp.EncodeToBytes(results); err != nil { + log.Error("Failed to encode receipt", "err", err) + } else { + receipts = append(receipts, encoded) + bytes += len(encoded) + } + } + return p.replyReceiptsRLP(r.ReqID, receipts) + }, r.ReqID, uint64(len(r.Hashes)), nil +} + +// handleGetProofs handles a proof request +func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetProofsPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + var ( + lastBHash common.Hash + root common.Hash + header *types.Header + err error + ) + bc := backend.BlockChain() + nodes := light.NewNodeSet() + + for i, request := range r.Reqs { + if i != 0 && !waitOrStop() { + return nil + } + // Look up the root hash belonging to the request + if request.BHash != lastBHash { + root, lastBHash = common.Hash{}, request.BHash + + if header = bc.GetHeaderByHash(request.BHash); header == nil { + p.Log().Warn("Failed to retrieve header for proof", "hash", request.BHash) + p.bumpInvalid() + continue + } + // Refuse to search stale state data in the database since looking for + // a non-exist key is kind of expensive. + local := bc.CurrentHeader().Number.Uint64() + if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local { + p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local) + p.bumpInvalid() + continue + } + root = header.Root + } + // If a header lookup failed (non existent), ignore subsequent requests for the same header + if root == (common.Hash{}) { + p.bumpInvalid() + continue + } + // Open the account or storage trie for the request + statedb := bc.StateCache() + + var trie state.Trie + switch len(request.AccKey) { + case 0: + // No account key specified, open an account trie + trie, err = statedb.OpenTrie(root) + if trie == nil || err != nil { + p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err) + continue + } + default: + // Account key specified, open a storage trie + account, err := getAccount(statedb.TrieDB(), root, common.BytesToHash(request.AccKey)) + if err != nil { + p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err) + p.bumpInvalid() + continue + } + trie, err = statedb.OpenStorageTrie(common.BytesToHash(request.AccKey), account.Root) + if trie == nil || err != nil { + p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "root", account.Root, "err", err) + continue + } + } + // Prove the user's request from the account or stroage trie + if err := trie.Prove(request.Key, request.FromLevel, nodes); err != nil { + p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err) + continue + } + if nodes.DataSize() >= softResponseLimit { + break + } + } + return p.replyProofsV2(r.ReqID, nodes.NodeList()) + }, r.ReqID, uint64(len(r.Reqs)), nil +} + +// handleGetHelperTrieProofs handles a helper trie proof request +func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetHelperTrieProofsPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + var ( + lastIdx uint64 + lastType uint + auxTrie *trie.Trie + auxBytes int + auxData [][]byte + ) + bc := backend.BlockChain() + nodes := light.NewNodeSet() + for i, request := range r.Reqs { + if i != 0 && !waitOrStop() { + return nil + } + if auxTrie == nil || request.Type != lastType || request.TrieIdx != lastIdx { + lastType, lastIdx = request.Type, request.TrieIdx + auxTrie = backend.GetHelperTrie(request.Type, request.TrieIdx) + } + if auxTrie == nil { + return nil + } + // TODO(rjl493456442) short circuit if the proving is failed. + // The original client side code has a dirty hack to retrieve + // the headers with no valid proof. Keep the compatibility for + // legacy les protocol and drop this hack when the les2/3 are + // not supported. + err := auxTrie.Prove(request.Key, request.FromLevel, nodes) + if p.version >= lpv4 && err != nil { + return nil + } + if request.Type == htCanonical && request.AuxReq == htAuxHeader && len(request.Key) == 8 { + header := bc.GetHeaderByNumber(binary.BigEndian.Uint64(request.Key)) + data, err := rlp.EncodeToBytes(header) + if err != nil { + log.Error("Failed to encode header", "err", err) + return nil + } + auxData = append(auxData, data) + auxBytes += len(data) + } + if nodes.DataSize()+auxBytes >= softResponseLimit { + break + } + } + return p.replyHelperTrieProofs(r.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}) + }, r.ReqID, uint64(len(r.Reqs)), nil +} + +// handleSendTx handles a transaction propagation request +func handleSendTx(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r SendTxPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + amount := uint64(len(r.Txs)) + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + stats := make([]light.TxStatus, len(r.Txs)) + for i, tx := range r.Txs { + if i != 0 && !waitOrStop() { + return nil + } + hash := tx.Hash() + stats[i] = txStatus(backend, hash) + if stats[i].Status == core.TxStatusUnknown { + addFn := backend.TxPool().AddRemotes + // Add txs synchronously for testing purpose + if backend.AddTxsSync() { + addFn = backend.TxPool().AddRemotesSync + } + if errs := addFn([]*types.Transaction{tx}); errs[0] != nil { + stats[i].Error = errs[0].Error() + continue + } + stats[i] = txStatus(backend, hash) + } + } + return p.replyTxStatus(r.ReqID, stats) + }, r.ReqID, amount, nil +} + +// handleGetTxStatus handles a transaction status query +func handleGetTxStatus(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetTxStatusPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + stats := make([]light.TxStatus, len(r.Hashes)) + for i, hash := range r.Hashes { + if i != 0 && !waitOrStop() { + return nil + } + stats[i] = txStatus(backend, hash) + } + return p.replyTxStatus(r.ReqID, stats) + }, r.ReqID, uint64(len(r.Hashes)), nil +} + +// txStatus returns the status of a specified transaction. +func txStatus(b serverBackend, hash common.Hash) light.TxStatus { + var stat light.TxStatus + // Looking the transaction in txpool first. + stat.Status = b.TxPool().Status([]common.Hash{hash})[0] + + // If the transaction is unknown to the pool, try looking it up locally. + if stat.Status == core.TxStatusUnknown { + lookup := b.BlockChain().GetTransactionLookup(hash) + if lookup != nil { + stat.Status = core.TxStatusIncluded + stat.Lookup = lookup + } + } + return stat +} diff --git a/les/test_helper.go b/les/test_helper.go index 1afc9be7d8..6f93c8a48f 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -594,3 +594,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer } return s, c, teardown } + +func NewFuzzerPeer(version int) *clientPeer { + return newClientPeer(version, 0, p2p.NewPeer(enode.ID{}, "", nil), nil) +} diff --git a/oss-fuzz.sh b/oss-fuzz.sh index 860ce0952f..ac93a5a467 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -101,6 +101,7 @@ compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty compile_fuzzer tests/fuzzers/abi Fuzz fuzzAbi +compile_fuzzer tests/fuzzers/les Fuzz fuzzLes compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul diff --git a/tests/fuzzers/les/debug/main.go b/tests/fuzzers/les/debug/main.go new file mode 100644 index 0000000000..09e087d4c8 --- /dev/null +++ b/tests/fuzzers/les/debug/main.go @@ -0,0 +1,41 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/tests/fuzzers/les" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: debug \n") + fmt.Fprintf(os.Stderr, "Example\n") + fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") + os.Exit(1) + } + crasher := os.Args[1] + data, err := ioutil.ReadFile(crasher) + if err != nil { + fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) + os.Exit(1) + } + les.Fuzz(data) +} diff --git a/tests/fuzzers/les/les-fuzzer.go b/tests/fuzzers/les/les-fuzzer.go new file mode 100644 index 0000000000..9e896c2c1b --- /dev/null +++ b/tests/fuzzers/les/les-fuzzer.go @@ -0,0 +1,407 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package les + +import ( + "bytes" + "encoding/binary" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "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/core/vm" + "github.com/ethereum/go-ethereum/crypto" + l "github.com/ethereum/go-ethereum/les" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +var ( + bankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + bankAddr = crypto.PubkeyToAddress(bankKey.PublicKey) + bankFunds = new(big.Int).Mul(big.NewInt(100), big.NewInt(params.Ether)) + + testChainLen = 256 + testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056") + + chain *core.BlockChain + addrHashes []common.Hash + txHashes []common.Hash + + chtTrie *trie.Trie + bloomTrie *trie.Trie + chtKeys [][]byte + bloomKeys [][]byte +) + +func makechain() (bc *core.BlockChain, addrHashes, txHashes []common.Hash) { + db := rawdb.NewMemoryDatabase() + gspec := core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, + GasLimit: 100000000, + } + genesis := gspec.MustCommit(db) + signer := types.HomesteadSigner{} + blocks, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, testChainLen, + func(i int, gen *core.BlockGen) { + var ( + tx *types.Transaction + addr common.Address + ) + nonce := uint64(i) + if i%4 == 0 { + tx, _ = types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 200000, big.NewInt(0), testContractCode), signer, bankKey) + addr = crypto.CreateAddress(bankAddr, nonce) + } else { + addr = common.BigToAddress(big.NewInt(int64(i))) + tx, _ = types.SignTx(types.NewTransaction(nonce, addr, big.NewInt(10000), params.TxGas, big.NewInt(params.GWei), nil), signer, bankKey) + } + gen.AddTx(tx) + addrHashes = append(addrHashes, crypto.Keccak256Hash(addr[:])) + txHashes = append(txHashes, tx.Hash()) + }) + bc, _ = core.NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + if _, err := bc.InsertChain(blocks); err != nil { + panic(err) + } + return +} + +func makeTries() (chtTrie *trie.Trie, bloomTrie *trie.Trie, chtKeys, bloomKeys [][]byte) { + chtTrie, _ = trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase())) + bloomTrie, _ = trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase())) + for i := 0; i < testChainLen; i++ { + // The element in CHT is -> + key := make([]byte, 8) + binary.BigEndian.PutUint64(key, uint64(i+1)) + chtTrie.Update(key, []byte{0x1, 0xf}) + chtKeys = append(chtKeys, key) + + // The element in Bloom trie is <2 byte bit index> + -> bloom + key2 := make([]byte, 10) + binary.BigEndian.PutUint64(key2[2:], uint64(i+1)) + bloomTrie.Update(key2, []byte{0x2, 0xe}) + bloomKeys = append(bloomKeys, key2) + } + return +} + +func init() { + chain, addrHashes, txHashes = makechain() + chtTrie, bloomTrie, chtKeys, bloomKeys = makeTries() +} + +type fuzzer struct { + chain *core.BlockChain + pool *core.TxPool + + chainLen int + addr, txs []common.Hash + nonce uint64 + + chtKeys [][]byte + bloomKeys [][]byte + chtTrie *trie.Trie + bloomTrie *trie.Trie + + input io.Reader + exhausted bool +} + +func newFuzzer(input []byte) *fuzzer { + return &fuzzer{ + chain: chain, + chainLen: testChainLen, + addr: addrHashes, + txs: txHashes, + chtTrie: chtTrie, + bloomTrie: bloomTrie, + chtKeys: chtKeys, + bloomKeys: bloomKeys, + nonce: uint64(len(txHashes)), + pool: core.NewTxPool(core.DefaultTxPoolConfig, params.TestChainConfig, chain), + input: bytes.NewReader(input), + } +} + +func (f *fuzzer) read(size int) []byte { + out := make([]byte, size) + if _, err := f.input.Read(out); err != nil { + f.exhausted = true + } + return out +} + +func (f *fuzzer) randomByte() byte { + d := f.read(1) + return d[0] +} + +func (f *fuzzer) randomBool() bool { + d := f.read(1) + return d[0]&1 == 1 +} + +func (f *fuzzer) randomInt(max int) int { + if max == 0 { + return 0 + } + if max <= 256 { + return int(f.randomByte()) % max + } + var a uint16 + if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { + f.exhausted = true + } + return int(a % uint16(max)) +} + +func (f *fuzzer) randomX(max int) uint64 { + var a uint16 + if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { + f.exhausted = true + } + if a < 0x8000 { + return uint64(a%uint16(max+1)) - 1 + } + return (uint64(1)<<(a%64+1) - 1) & (uint64(a) * 343897772345826595) +} + +func (f *fuzzer) randomBlockHash() common.Hash { + h := f.chain.GetCanonicalHash(uint64(f.randomInt(3 * f.chainLen))) + if h != (common.Hash{}) { + return h + } + return common.BytesToHash(f.read(common.HashLength)) +} + +func (f *fuzzer) randomAddrHash() []byte { + i := f.randomInt(3 * len(f.addr)) + if i < len(f.addr) { + return f.addr[i].Bytes() + } + return f.read(common.HashLength) +} + +func (f *fuzzer) randomCHTTrieKey() []byte { + i := f.randomInt(3 * len(f.chtKeys)) + if i < len(f.chtKeys) { + return f.chtKeys[i] + } + return f.read(8) +} + +func (f *fuzzer) randomBloomTrieKey() []byte { + i := f.randomInt(3 * len(f.bloomKeys)) + if i < len(f.bloomKeys) { + return f.bloomKeys[i] + } + return f.read(10) +} + +func (f *fuzzer) randomTxHash() common.Hash { + i := f.randomInt(3 * len(f.txs)) + if i < len(f.txs) { + return f.txs[i] + } + return common.BytesToHash(f.read(common.HashLength)) +} + +func (f *fuzzer) BlockChain() *core.BlockChain { + return f.chain +} + +func (f *fuzzer) TxPool() *core.TxPool { + return f.pool +} + +func (f *fuzzer) ArchiveMode() bool { + return false +} + +func (f *fuzzer) AddTxsSync() bool { + return false +} + +func (f *fuzzer) GetHelperTrie(typ uint, index uint64) *trie.Trie { + if typ == 0 { + return f.chtTrie + } else if typ == 1 { + return f.bloomTrie + } + return nil +} + +type dummyMsg struct { + data []byte +} + +func (d dummyMsg) Decode(val interface{}) error { + return rlp.DecodeBytes(d.data, val) +} + +func (f *fuzzer) doFuzz(msgCode uint64, packet interface{}) { + version := f.randomInt(3) + 2 // [LES2, LES3, LES4] + peer := l.NewFuzzerPeer(version) + enc, err := rlp.EncodeToBytes(packet) + if err != nil { + panic(err) + } + fn, _, _, err := l.Les3[msgCode].Handle(dummyMsg{enc}) + if err != nil { + panic(err) + } + fn(f, peer, func() bool { return true }) + +} + +func Fuzz(input []byte) int { + // We expect some large inputs + if len(input) < 100 { + return -1 + } + f := newFuzzer(input) + if f.exhausted { + return -1 + } + for !f.exhausted { + switch f.randomInt(8) { + case 0: + req := &l.GetBlockHeadersPacket{ + Query: l.GetBlockHeadersData{ + Amount: f.randomX(l.MaxHeaderFetch + 1), + Skip: f.randomX(10), + Reverse: f.randomBool(), + }, + } + if f.randomBool() { + req.Query.Origin.Hash = f.randomBlockHash() + } else { + req.Query.Origin.Number = uint64(f.randomInt(f.chainLen * 2)) + } + f.doFuzz(l.GetBlockHeadersMsg, req) + + case 1: + req := &l.GetBlockBodiesPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxBodyFetch+1))} + for i := range req.Hashes { + req.Hashes[i] = f.randomBlockHash() + } + f.doFuzz(l.GetBlockBodiesMsg, req) + + case 2: + req := &l.GetCodePacket{Reqs: make([]l.CodeReq, f.randomInt(l.MaxCodeFetch+1))} + for i := range req.Reqs { + req.Reqs[i] = l.CodeReq{ + BHash: f.randomBlockHash(), + AccKey: f.randomAddrHash(), + } + } + f.doFuzz(l.GetCodeMsg, req) + + case 3: + req := &l.GetReceiptsPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxReceiptFetch+1))} + for i := range req.Hashes { + req.Hashes[i] = f.randomBlockHash() + } + f.doFuzz(l.GetReceiptsMsg, req) + + case 4: + req := &l.GetProofsPacket{Reqs: make([]l.ProofReq, f.randomInt(l.MaxProofsFetch+1))} + for i := range req.Reqs { + if f.randomBool() { + req.Reqs[i] = l.ProofReq{ + BHash: f.randomBlockHash(), + AccKey: f.randomAddrHash(), + Key: f.randomAddrHash(), + FromLevel: uint(f.randomX(3)), + } + } else { + req.Reqs[i] = l.ProofReq{ + BHash: f.randomBlockHash(), + Key: f.randomAddrHash(), + FromLevel: uint(f.randomX(3)), + } + } + } + f.doFuzz(l.GetProofsV2Msg, req) + + case 5: + req := &l.GetHelperTrieProofsPacket{Reqs: make([]l.HelperTrieReq, f.randomInt(l.MaxHelperTrieProofsFetch+1))} + for i := range req.Reqs { + switch f.randomInt(3) { + case 0: + // Canonical hash trie + req.Reqs[i] = l.HelperTrieReq{ + Type: 0, + TrieIdx: f.randomX(3), + Key: f.randomCHTTrieKey(), + FromLevel: uint(f.randomX(3)), + AuxReq: uint(2), + } + case 1: + // Bloom trie + req.Reqs[i] = l.HelperTrieReq{ + Type: 1, + TrieIdx: f.randomX(3), + Key: f.randomBloomTrieKey(), + FromLevel: uint(f.randomX(3)), + AuxReq: 0, + } + default: + // Random trie + req.Reqs[i] = l.HelperTrieReq{ + Type: 2, + TrieIdx: f.randomX(3), + Key: f.randomCHTTrieKey(), + FromLevel: uint(f.randomX(3)), + AuxReq: 0, + } + } + } + f.doFuzz(l.GetHelperTrieProofsMsg, req) + + case 6: + req := &l.SendTxPacket{Txs: make([]*types.Transaction, f.randomInt(l.MaxTxSend+1))} + signer := types.HomesteadSigner{} + for i := range req.Txs { + var nonce uint64 + if f.randomBool() { + nonce = uint64(f.randomByte()) + } else { + nonce = f.nonce + f.nonce += 1 + } + req.Txs[i], _ = types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(10000), params.TxGas, big.NewInt(1000000000*int64(f.randomByte())), nil), signer, bankKey) + } + f.doFuzz(l.SendTxV2Msg, req) + + case 7: + req := &l.GetTxStatusPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxTxStatus+1))} + for i := range req.Hashes { + req.Hashes[i] = f.randomTxHash() + } + f.doFuzz(l.GetTxStatusMsg, req) + } + } + return 0 +} From 3ecfdccd9a0065365a00e7c8b60de7ee2df4e40b Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 22 Feb 2021 21:33:11 +0800 Subject: [PATCH 207/235] les: clean up server handler (#22357) --- les/server_handler.go | 185 +++++++++++++++++++++++------------------ les/server_requests.go | 16 ++-- 2 files changed, 114 insertions(+), 87 deletions(-) diff --git a/les/server_handler.go b/les/server_handler.go index b6e8b050b1..7651d03cab 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -204,6 +204,90 @@ func (h *serverHandler) handle(p *clientPeer) error { } } +// beforeHandle will do a series of prechecks before handling message. +func (h *serverHandler) beforeHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, reqCnt uint64, maxCount uint64) (*servingTask, uint64) { + // Ensure that the request sent by client peer is valid + inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0) + if reqCnt == 0 || reqCnt > maxCount { + p.fcClient.OneTimeCost(inSizeCost) + return nil, 0 + } + // Ensure that the client peer complies with the flow control + // rules agreed by both sides. + if p.isFrozen() { + p.fcClient.OneTimeCost(inSizeCost) + return nil, 0 + } + maxCost := p.fcCosts.getMaxCost(msg.Code, reqCnt) + accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost) + if !accepted { + p.freeze() + p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge))) + p.fcClient.OneTimeCost(inSizeCost) + return nil, 0 + } + // Create a multi-stage task, estimate the time it takes for the task to + // execute, and cache it in the request service queue. + factor := h.server.costTracker.globalFactor() + if factor < 0.001 { + factor = 1 + p.Log().Error("Invalid global cost factor", "factor", factor) + } + maxTime := uint64(float64(maxCost) / factor) + task := h.server.servingQueue.newTask(p, maxTime, priority) + if !task.start() { + p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost) + return nil, 0 + } + return task, maxCost +} + +// Afterhandle will perform a series of operations after message handling, +// such as updating flow control data, sending reply, etc. +func (h *serverHandler) afterHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, maxCost uint64, reqCnt uint64, task *servingTask, reply *reply) { + if reply != nil { + task.done() + } + p.responseLock.Lock() + defer p.responseLock.Unlock() + + // Short circuit if the client is already frozen. + if p.isFrozen() { + realCost := h.server.costTracker.realCost(task.servingTime, msg.Size, 0) + p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) + return + } + // Positive correction buffer value with real cost. + var replySize uint32 + if reply != nil { + replySize = reply.size() + } + var realCost uint64 + if h.server.costTracker.testing { + realCost = maxCost // Assign a fake cost for testing purpose + } else { + realCost = h.server.costTracker.realCost(task.servingTime, msg.Size, replySize) + if realCost > maxCost { + realCost = maxCost + } + } + bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) + if reply != nil { + // Feed cost tracker request serving statistic. + h.server.costTracker.updateStats(msg.Code, reqCnt, task.servingTime, realCost) + // Reduce priority "balance" for the specific peer. + p.balance.RequestServed(realCost) + p.queueSend(func() { + if err := reply.send(bv); err != nil { + select { + case p.errCh <- err: + default: + } + } + }) + } +} + // handleMsg is invoked whenever an inbound message is received from a remote // peer. The remote connection is torn down upon returning any error. func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { @@ -221,9 +305,8 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { } defer msg.Discard() - p.responseCount++ - responseCount := p.responseCount - + // Lookup the request handler table, ensure it's supported + // message type by the protocol. req, ok := Les3[msg.Code] if !ok { p.Log().Trace("Received invalid message", "code", msg.Code) @@ -232,98 +315,42 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { } p.Log().Trace("Received " + req.Name) + // Decode the p2p message, resolve the concrete handler for it. serve, reqID, reqCnt, err := req.Handle(msg) if err != nil { clientErrorMeter.Mark(1) return errResp(ErrDecode, "%v: %v", msg, err) } - if metrics.EnabledExpensive { req.InPacketsMeter.Mark(1) req.InTrafficMeter.Mark(int64(msg.Size)) } + p.responseCount++ + responseCount := p.responseCount - // Short circuit if the peer is already frozen or the request is invalid. - inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0) - if p.isFrozen() || reqCnt == 0 || reqCnt > req.MaxCount { - p.fcClient.OneTimeCost(inSizeCost) - return nil - } - // Prepaid max cost units before request been serving. - maxCost := p.fcCosts.getMaxCost(msg.Code, reqCnt) - accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost) - if !accepted { - p.freeze() - p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge))) - p.fcClient.OneTimeCost(inSizeCost) + // First check this client message complies all rules before + // handling it and return a processor if all checks are passed. + task, maxCost := h.beforeHandle(p, reqID, responseCount, msg, reqCnt, req.MaxCount) + if task == nil { return nil } - // Create a multi-stage task, estimate the time it takes for the task to - // execute, and cache it in the request service queue. - factor := h.server.costTracker.globalFactor() - if factor < 0.001 { - factor = 1 - p.Log().Error("Invalid global cost factor", "factor", factor) - } - maxTime := uint64(float64(maxCost) / factor) - task := h.server.servingQueue.newTask(p, maxTime, priority) - if task.start() { - wg.Add(1) - go func() { - defer wg.Done() - reply := serve(h, p, task.waitOrStop) - if reply != nil { - task.done() - } + wg.Add(1) + go func() { + defer wg.Done() - p.responseLock.Lock() - defer p.responseLock.Unlock() + reply := serve(h, p, task.waitOrStop) + h.afterHandle(p, reqID, responseCount, msg, maxCost, reqCnt, task, reply) - // Short circuit if the client is already frozen. - if p.isFrozen() { - realCost := h.server.costTracker.realCost(task.servingTime, msg.Size, 0) - p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) - return - } - // Positive correction buffer value with real cost. - var replySize uint32 + if metrics.EnabledExpensive { + size := uint32(0) if reply != nil { - replySize = reply.size() - } - var realCost uint64 - if h.server.costTracker.testing { - realCost = maxCost // Assign a fake cost for testing purpose - } else { - realCost = h.server.costTracker.realCost(task.servingTime, msg.Size, replySize) - if realCost > maxCost { - realCost = maxCost - } + size = reply.size() } - bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) - if reply != nil { - // Feed cost tracker request serving statistic. - h.server.costTracker.updateStats(msg.Code, reqCnt, task.servingTime, realCost) - // Reduce priority "balance" for the specific peer. - p.balance.RequestServed(realCost) - p.queueSend(func() { - if err := reply.send(bv); err != nil { - select { - case p.errCh <- err: - default: - } - } - }) - if metrics.EnabledExpensive { - req.OutPacketsMeter.Mark(1) - req.OutTrafficMeter.Mark(int64(replySize)) - req.ServingTimeMeter.Update(time.Duration(task.servingTime)) - } - } - }() - } else { - p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost) - } - + req.OutPacketsMeter.Mark(1) + req.OutTrafficMeter.Mark(int64(size)) + req.ServingTimeMeter.Update(time.Duration(task.servingTime)) + } + }() // If the client has made too much invalid request(e.g. request a non-existent data), // reject them to prevent SPAM attack. if p.getInvalid() > maxRequestErrors { diff --git a/les/server_requests.go b/les/server_requests.go index d4af8006f0..07f30b1b73 100644 --- a/les/server_requests.go +++ b/les/server_requests.go @@ -65,7 +65,7 @@ type serveRequestFn func(backend serverBackend, peer *clientPeer, waitOrStop fun // Les3 contains the request types supported by les/2 and les/3 var Les3 = map[uint64]RequestType{ - GetBlockHeadersMsg: RequestType{ + GetBlockHeadersMsg: { Name: "block header request", MaxCount: MaxHeaderFetch, InPacketsMeter: miscInHeaderPacketsMeter, @@ -75,7 +75,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeHeaderTimer, Handle: handleGetBlockHeaders, }, - GetBlockBodiesMsg: RequestType{ + GetBlockBodiesMsg: { Name: "block bodies request", MaxCount: MaxBodyFetch, InPacketsMeter: miscInBodyPacketsMeter, @@ -85,7 +85,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeBodyTimer, Handle: handleGetBlockBodies, }, - GetCodeMsg: RequestType{ + GetCodeMsg: { Name: "code request", MaxCount: MaxCodeFetch, InPacketsMeter: miscInCodePacketsMeter, @@ -95,7 +95,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeCodeTimer, Handle: handleGetCode, }, - GetReceiptsMsg: RequestType{ + GetReceiptsMsg: { Name: "receipts request", MaxCount: MaxReceiptFetch, InPacketsMeter: miscInReceiptPacketsMeter, @@ -105,7 +105,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeReceiptTimer, Handle: handleGetReceipts, }, - GetProofsV2Msg: RequestType{ + GetProofsV2Msg: { Name: "les/2 proofs request", MaxCount: MaxProofsFetch, InPacketsMeter: miscInTrieProofPacketsMeter, @@ -115,7 +115,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeTrieProofTimer, Handle: handleGetProofs, }, - GetHelperTrieProofsMsg: RequestType{ + GetHelperTrieProofsMsg: { Name: "helper trie proof request", MaxCount: MaxHelperTrieProofsFetch, InPacketsMeter: miscInHelperTriePacketsMeter, @@ -125,7 +125,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeHelperTrieTimer, Handle: handleGetHelperTrieProofs, }, - SendTxV2Msg: RequestType{ + SendTxV2Msg: { Name: "new transactions", MaxCount: MaxTxSend, InPacketsMeter: miscInTxsPacketsMeter, @@ -135,7 +135,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeTxTimer, Handle: handleSendTx, }, - GetTxStatusMsg: RequestType{ + GetTxStatusMsg: { Name: "transaction status query request", MaxCount: MaxTxStatus, InPacketsMeter: miscInTxStatusPacketsMeter, From c4a2b682ff3ea2465417671de76c4d1e9a29fef8 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 23 Feb 2021 11:27:32 +0100 Subject: [PATCH 208/235] cmd/geth: add db commands stats, compact, put, get, delete (#22014) This PR introduces: - db.put to put a value into the database - db.get to read a value from the database - db.delete to delete a value from the database - db.stats to check compaction info from the database - db.compact to trigger a db compaction It also moves inspectdb to db.inspect. --- cmd/geth/chaincmd.go | 129 +------------- cmd/geth/dbcmd.go | 341 ++++++++++++++++++++++++++++++++++++++ cmd/geth/main.go | 3 +- cmd/utils/flags.go | 8 +- core/rawdb/database.go | 12 +- ethdb/leveldb/leveldb.go | 56 +++++-- internal/flags/helpers.go | 4 +- 7 files changed, 401 insertions(+), 152 deletions(-) create mode 100644 cmd/geth/dbcmd.go diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 9eec30f813..ba4289ddc3 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -20,7 +20,6 @@ import ( "encoding/json" "fmt" "os" - "path/filepath" "runtime" "strconv" "sync/atomic" @@ -28,7 +27,6 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/console/prompt" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" @@ -170,18 +168,6 @@ The export-preimages command export hash preimages to an RLP encoded stream`, Category: "BLOCKCHAIN COMMANDS", Description: ` The first argument must be the directory containing the blockchain to download from`, - } - removedbCommand = cli.Command{ - Action: utils.MigrateFlags(removeDB), - Name: "removedb", - Usage: "Remove blockchain and state databases", - ArgsUsage: " ", - Flags: []cli.Flag{ - utils.DataDirFlag, - }, - Category: "BLOCKCHAIN COMMANDS", - Description: ` -Remove blockchain and state databases`, } dumpCommand = cli.Command{ Action: utils.MigrateFlags(dump), @@ -202,25 +188,6 @@ Remove blockchain and state databases`, The arguments are interpreted as block numbers or hashes. Use "ethereum dump 0" to dump the genesis block.`, } - inspectCommand = cli.Command{ - Action: utils.MigrateFlags(inspect), - Name: "inspect", - Usage: "Inspect the storage size for each type of data in the database", - ArgsUsage: " ", - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.AncientFlag, - utils.CacheFlag, - utils.MainnetFlag, - utils.RopstenFlag, - utils.RinkebyFlag, - utils.GoerliFlag, - utils.YoloV3Flag, - utils.LegacyTestnetFlag, - utils.SyncModeFlag, - }, - Category: "BLOCKCHAIN COMMANDS", - } ) // initGenesis will initialise the given JSON format genesis file and writes it as @@ -323,17 +290,7 @@ func importChain(ctx *cli.Context) error { fmt.Printf("Import done in %v.\n\n", time.Since(start)) // Output pre-compaction stats mostly to see the import trashing - stats, err := db.Stat("leveldb.stats") - if err != nil { - utils.Fatalf("Failed to read database stats: %v", err) - } - fmt.Println(stats) - - ioStats, err := db.Stat("leveldb.iostats") - if err != nil { - utils.Fatalf("Failed to read database iostats: %v", err) - } - fmt.Println(ioStats) + showLeveldbStats(db) // Print the memory statistics used by the importing mem := new(runtime.MemStats) @@ -351,22 +308,12 @@ func importChain(ctx *cli.Context) error { // Compact the entire database to more accurately measure disk io and print the stats start = time.Now() fmt.Println("Compacting entire database...") - if err = db.Compact(nil, nil); err != nil { + if err := db.Compact(nil, nil); err != nil { utils.Fatalf("Compaction failed: %v", err) } fmt.Printf("Compaction done in %v.\n\n", time.Since(start)) - stats, err = db.Stat("leveldb.stats") - if err != nil { - utils.Fatalf("Failed to read database stats: %v", err) - } - fmt.Println(stats) - - ioStats, err = db.Stat("leveldb.iostats") - if err != nil { - utils.Fatalf("Failed to read database iostats: %v", err) - } - fmt.Println(ioStats) + showLeveldbStats(db) return importErr } @@ -499,66 +446,6 @@ func copyDb(ctx *cli.Context) error { return nil } -func removeDB(ctx *cli.Context) error { - stack, config := makeConfigNode(ctx) - - // Remove the full node state database - path := stack.ResolvePath("chaindata") - if common.FileExist(path) { - confirmAndRemoveDB(path, "full node state database") - } else { - log.Info("Full node state database missing", "path", path) - } - // Remove the full node ancient database - path = config.Eth.DatabaseFreezer - switch { - case path == "": - path = filepath.Join(stack.ResolvePath("chaindata"), "ancient") - case !filepath.IsAbs(path): - path = config.Node.ResolvePath(path) - } - if common.FileExist(path) { - confirmAndRemoveDB(path, "full node ancient database") - } else { - log.Info("Full node ancient database missing", "path", path) - } - // Remove the light node database - path = stack.ResolvePath("lightchaindata") - if common.FileExist(path) { - confirmAndRemoveDB(path, "light node database") - } else { - log.Info("Light node database missing", "path", path) - } - return nil -} - -// confirmAndRemoveDB prompts the user for a last confirmation and removes the -// folder if accepted. -func confirmAndRemoveDB(database string, kind string) { - confirm, err := prompt.Stdin.PromptConfirm(fmt.Sprintf("Remove %s (%s)?", kind, database)) - switch { - case err != nil: - utils.Fatalf("%v", err) - case !confirm: - log.Info("Database deletion skipped", "path", database) - default: - start := time.Now() - filepath.Walk(database, func(path string, info os.FileInfo, err error) error { - // If we're at the top level folder, recurse into - if path == database { - return nil - } - // Delete all the files, but not subfolders - if !info.IsDir() { - os.Remove(path) - return nil - } - return filepath.SkipDir - }) - log.Info("Database successfully deleted", "path", database, "elapsed", common.PrettyDuration(time.Since(start))) - } -} - func dump(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() @@ -598,16 +485,6 @@ func dump(ctx *cli.Context) error { return nil } -func inspect(ctx *cli.Context) error { - node, _ := makeConfigNode(ctx) - defer node.Close() - - _, chainDb := utils.MakeChain(ctx, node, true) - defer chainDb.Close() - - return rawdb.InspectDatabase(chainDb) -} - // hashish returns true for strings that look like hashes. func hashish(x string) bool { _, err := strconv.Atoi(x) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go new file mode 100644 index 0000000000..48478f613e --- /dev/null +++ b/cmd/geth/dbcmd.go @@ -0,0 +1,341 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/console/prompt" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/leveldb" + "github.com/ethereum/go-ethereum/log" + "github.com/syndtr/goleveldb/leveldb/opt" + "gopkg.in/urfave/cli.v1" +) + +var ( + removedbCommand = cli.Command{ + Action: utils.MigrateFlags(removeDB), + Name: "removedb", + Usage: "Remove blockchain and state databases", + ArgsUsage: "", + Flags: []cli.Flag{ + utils.DataDirFlag, + }, + Category: "DATABASE COMMANDS", + Description: ` +Remove blockchain and state databases`, + } + dbCommand = cli.Command{ + Name: "db", + Usage: "Low level database operations", + ArgsUsage: "", + Category: "DATABASE COMMANDS", + Subcommands: []cli.Command{ + dbInspectCmd, + dbStatCmd, + dbCompactCmd, + dbGetCmd, + dbDeleteCmd, + dbPutCmd, + }, + } + dbInspectCmd = cli.Command{ + Action: utils.MigrateFlags(inspect), + Name: "inspect", + ArgsUsage: " ", + + Usage: "Inspect the storage size for each type of data in the database", + Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`, + } + dbStatCmd = cli.Command{ + Action: dbStats, + Name: "stats", + Usage: "Print leveldb statistics", + } + dbCompactCmd = cli.Command{ + Action: dbCompact, + Name: "compact", + Usage: "Compact leveldb database. WARNING: May take a very long time", + Description: `This command performs a database compaction. +WARNING: This operation may take a very long time to finish, and may cause database +corruption if it is aborted during execution'!`, + } + dbGetCmd = cli.Command{ + Action: dbGet, + Name: "get", + Usage: "Show the value of a database key", + ArgsUsage: "", + Description: "This command looks up the specified database key from the database.", + } + dbDeleteCmd = cli.Command{ + Action: dbDelete, + Name: "delete", + Usage: "Delete a database key (WARNING: may corrupt your database)", + ArgsUsage: "", + Description: `This command deletes the specified database key from the database. +WARNING: This is a low-level operation which may cause database corruption!`, + } + dbPutCmd = cli.Command{ + Action: dbPut, + Name: "put", + Usage: "Set the value of a database key (WARNING: may corrupt your database)", + ArgsUsage: " ", + Description: `This command sets a given database key to the given value. +WARNING: This is a low-level operation which may cause database corruption!`, + } +) + +func removeDB(ctx *cli.Context) error { + stack, config := makeConfigNode(ctx) + + // Remove the full node state database + path := stack.ResolvePath("chaindata") + if common.FileExist(path) { + confirmAndRemoveDB(path, "full node state database") + } else { + log.Info("Full node state database missing", "path", path) + } + // Remove the full node ancient database + path = config.Eth.DatabaseFreezer + switch { + case path == "": + path = filepath.Join(stack.ResolvePath("chaindata"), "ancient") + case !filepath.IsAbs(path): + path = config.Node.ResolvePath(path) + } + if common.FileExist(path) { + confirmAndRemoveDB(path, "full node ancient database") + } else { + log.Info("Full node ancient database missing", "path", path) + } + // Remove the light node database + path = stack.ResolvePath("lightchaindata") + if common.FileExist(path) { + confirmAndRemoveDB(path, "light node database") + } else { + log.Info("Light node database missing", "path", path) + } + return nil +} + +// confirmAndRemoveDB prompts the user for a last confirmation and removes the +// folder if accepted. +func confirmAndRemoveDB(database string, kind string) { + confirm, err := prompt.Stdin.PromptConfirm(fmt.Sprintf("Remove %s (%s)?", kind, database)) + switch { + case err != nil: + utils.Fatalf("%v", err) + case !confirm: + log.Info("Database deletion skipped", "path", database) + default: + start := time.Now() + filepath.Walk(database, func(path string, info os.FileInfo, err error) error { + // If we're at the top level folder, recurse into + if path == database { + return nil + } + // Delete all the files, but not subfolders + if !info.IsDir() { + os.Remove(path) + return nil + } + return filepath.SkipDir + }) + log.Info("Database successfully deleted", "path", database, "elapsed", common.PrettyDuration(time.Since(start))) + } +} + +func inspect(ctx *cli.Context) error { + var ( + prefix []byte + start []byte + ) + if ctx.NArg() > 2 { + return fmt.Errorf("Max 2 arguments: %v", ctx.Command.ArgsUsage) + } + if ctx.NArg() >= 1 { + if d, err := hexutil.Decode(ctx.Args().Get(0)); err != nil { + return fmt.Errorf("failed to hex-decode 'prefix': %v", err) + } else { + prefix = d + } + } + if ctx.NArg() >= 2 { + if d, err := hexutil.Decode(ctx.Args().Get(1)); err != nil { + return fmt.Errorf("failed to hex-decode 'start': %v", err) + } else { + start = d + } + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + _, chainDb := utils.MakeChain(ctx, stack, true) + defer chainDb.Close() + + return rawdb.InspectDatabase(chainDb, prefix, start) +} + +func showLeveldbStats(db ethdb.Stater) { + if stats, err := db.Stat("leveldb.stats"); err != nil { + log.Warn("Failed to read database stats", "error", err) + } else { + fmt.Println(stats) + } + if ioStats, err := db.Stat("leveldb.iostats"); err != nil { + log.Warn("Failed to read database iostats", "error", err) + } else { + fmt.Println(ioStats) + } +} + +func dbStats(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + path := stack.ResolvePath("chaindata") + db, err := leveldb.NewCustom(path, "", func(options *opt.Options) { + options.ReadOnly = true + }) + if err != nil { + return err + } + showLeveldbStats(db) + err = db.Close() + if err != nil { + log.Info("Close err", "error", err) + } + return nil +} + +func dbCompact(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + path := stack.ResolvePath("chaindata") + cache := ctx.GlobalInt(utils.CacheFlag.Name) * ctx.GlobalInt(utils.CacheDatabaseFlag.Name) / 100 + db, err := leveldb.NewCustom(path, "", func(options *opt.Options) { + options.OpenFilesCacheCapacity = utils.MakeDatabaseHandles() + options.BlockCacheCapacity = cache / 2 * opt.MiB + options.WriteBuffer = cache / 4 * opt.MiB // Two of these are used internally + }) + if err != nil { + return err + } + showLeveldbStats(db) + log.Info("Triggering compaction") + err = db.Compact(nil, nil) + if err != nil { + log.Info("Compact err", "error", err) + } + showLeveldbStats(db) + log.Info("Closing db") + err = db.Close() + if err != nil { + log.Info("Close err", "error", err) + } + log.Info("Exiting") + return err +} + +// dbGet shows the value of a given database key +func dbGet(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + path := stack.ResolvePath("chaindata") + db, err := leveldb.NewCustom(path, "", func(options *opt.Options) { + options.ReadOnly = true + }) + if err != nil { + return err + } + defer db.Close() + key, err := hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + data, err := db.Get(key) + if err != nil { + log.Info("Get operation failed", "error", err) + return err + } + fmt.Printf("key %#x:\n\t%#x\n", key, data) + return nil +} + +// dbDelete deletes a key from the database +func dbDelete(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + db := utils.MakeChainDatabase(ctx, stack) + defer db.Close() + key, err := hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + if err = db.Delete(key); err != nil { + log.Info("Delete operation returned an error", "error", err) + return err + } + return nil +} + +// dbPut overwrite a value in the database +func dbPut(ctx *cli.Context) error { + if ctx.NArg() != 2 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + db := utils.MakeChainDatabase(ctx, stack) + defer db.Close() + var ( + key []byte + value []byte + data []byte + err error + ) + key, err = hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + value, err = hexutil.Decode(ctx.Args().Get(1)) + if err != nil { + log.Info("Could not decode the value", "error", err) + return err + } + data, err = db.Get(key) + if err == nil { + fmt.Printf("Previous value:\n%#x\n", data) + } + return db.Put(key, value) +} diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0236ffb7d3..4fa24cc1c2 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -238,7 +238,6 @@ func init() { removedbCommand, dumpCommand, dumpGenesisCommand, - inspectCommand, // See accountcmd.go: accountCommand, walletCommand, @@ -254,6 +253,8 @@ func init() { licenseCommand, // See config.go dumpConfigCommand, + // see dbcmd.go + dbCommand, // See cmd/utils/flags_legacy.go utils.ShowDeprecated, // See snapshot.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0d7b0e1bf5..ba643efcc3 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1073,9 +1073,9 @@ func setLes(ctx *cli.Context, cfg *ethconfig.Config) { } } -// makeDatabaseHandles raises out the number of allowed file handles per process +// MakeDatabaseHandles raises out the number of allowed file handles per process // for Geth and returns half of the allowance to assign to the database. -func makeDatabaseHandles() int { +func MakeDatabaseHandles() int { limit, err := fdlimit.Maximum() if err != nil { Fatalf("Failed to retrieve file descriptor allowance: %v", err) @@ -1546,7 +1546,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheDatabaseFlag.Name) { cfg.DatabaseCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 } - cfg.DatabaseHandles = makeDatabaseHandles() + cfg.DatabaseHandles = MakeDatabaseHandles() if ctx.GlobalIsSet(AncientFlag.Name) { cfg.DatabaseFreezer = ctx.GlobalString(AncientFlag.Name) } @@ -1821,7 +1821,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string { func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 - handles = makeDatabaseHandles() + handles = MakeDatabaseHandles() err error chainDb ethdb.Database diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 1f8c3f4542..91171ef92c 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -270,8 +270,8 @@ func (s *stat) Count() string { // InspectDatabase traverses the entire database and checks the size // of all different categories of data. -func InspectDatabase(db ethdb.Database) error { - it := db.NewIterator(nil, nil) +func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { + it := db.NewIterator(keyPrefix, keyStart) defer it.Release() var ( @@ -307,8 +307,9 @@ func InspectDatabase(db ethdb.Database) error { bloomTrieNodes stat // Meta- and unaccounted data - metadata stat - unaccounted stat + metadata stat + unaccounted stat + shutdownInfo stat // Totals total common.StorageSize @@ -359,6 +360,8 @@ func InspectDatabase(db ethdb.Database) error { bytes.HasPrefix(key, []byte("bltIndex-")) || bytes.HasPrefix(key, []byte("bltRoot-")): // Bloomtrie sub bloomTrieNodes.Add(size) + case bytes.Equal(key, uncleanShutdownKey): + shutdownInfo.Add(size) default: var accounted bool for _, meta := range [][]byte{ @@ -413,6 +416,7 @@ func InspectDatabase(db ethdb.Database) error { {"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()}, {"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()}, {"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, + {"Key-Value store", "Shutdown metadata", shutdownInfo.Size(), shutdownInfo.Count()}, {"Ancient store", "Headers", ancientHeadersSize.String(), ancients.String()}, {"Ancient store", "Bodies", ancientBodiesSize.String(), ancients.String()}, {"Ancient store", "Receipt lists", ancientReceiptsSize.String(), ancients.String()}, diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index 80380db325..70ac7a91ac 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -84,24 +84,36 @@ type Database struct { // New returns a wrapped LevelDB object. The namespace is the prefix that the // metrics reporting should use for surfacing internal stats. func New(file string, cache int, handles int, namespace string) (*Database, error) { - // Ensure we have some minimal caching and file guarantees - if cache < minCache { - cache = minCache - } - if handles < minHandles { - handles = minHandles - } + return NewCustom(file, namespace, func(options *opt.Options) { + // Ensure we have some minimal caching and file guarantees + if cache < minCache { + cache = minCache + } + if handles < minHandles { + handles = minHandles + } + // Set default options + options.OpenFilesCacheCapacity = handles + options.BlockCacheCapacity = cache / 2 * opt.MiB + options.WriteBuffer = cache / 4 * opt.MiB // Two of these are used internally + }) +} + +// NewCustom returns a wrapped LevelDB object. The namespace is the prefix that the +// metrics reporting should use for surfacing internal stats. +// The customize function allows the caller to modify the leveldb options. +func NewCustom(file string, namespace string, customize func(options *opt.Options)) (*Database, error) { + options := configureOptions(customize) logger := log.New("database", file) - logger.Info("Allocated cache and file handles", "cache", common.StorageSize(cache*1024*1024), "handles", handles) + usedCache := options.GetBlockCacheCapacity() + options.GetWriteBuffer()*2 + logCtx := []interface{}{"cache", common.StorageSize(usedCache), "handles", options.GetOpenFilesCacheCapacity()} + if options.ReadOnly { + logCtx = append(logCtx, "readonly", "true") + } + logger.Info("Allocated cache and file handles", logCtx...) // Open the db and recover any potential corruptions - db, err := leveldb.OpenFile(file, &opt.Options{ - OpenFilesCacheCapacity: handles, - BlockCacheCapacity: cache / 2 * opt.MiB, - WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally - Filter: filter.NewBloomFilter(10), - DisableSeeksCompaction: true, - }) + db, err := leveldb.OpenFile(file, options) if _, corrupted := err.(*errors.ErrCorrupted); corrupted { db, err = leveldb.RecoverFile(file, nil) } @@ -133,6 +145,20 @@ func New(file string, cache int, handles int, namespace string) (*Database, erro return ldb, nil } +// configureOptions sets some default options, then runs the provided setter. +func configureOptions(customizeFn func(*opt.Options)) *opt.Options { + // Set default options + options := &opt.Options{ + Filter: filter.NewBloomFilter(10), + DisableSeeksCompaction: true, + } + // Allow caller to make custom modifications to the options + if customizeFn != nil { + customizeFn(options) + } + return options +} + // Close stops the metrics collection, flushes any pending data to disk and closes // all io accesses to the underlying key-value store. func (db *Database) Close() error { diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go index eb5f5547b1..43bbcf0201 100644 --- a/internal/flags/helpers.go +++ b/internal/flags/helpers.go @@ -25,7 +25,7 @@ import ( ) var ( - CommandHelpTemplate = `{{.cmd.Name}}{{if .cmd.Subcommands}} command{{end}}{{if .cmd.Flags}} [command options]{{end}} [arguments...] + CommandHelpTemplate = `{{.cmd.Name}}{{if .cmd.Subcommands}} command{{end}}{{if .cmd.Flags}} [command options]{{end}} {{.cmd.ArgsUsage}} {{if .cmd.Description}}{{.cmd.Description}} {{end}}{{if .cmd.Subcommands}} SUBCOMMANDS: @@ -36,7 +36,7 @@ SUBCOMMANDS: {{end}} {{end}}{{end}}` - OriginCommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...] + OriginCommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} {{.ArgsUsage}} {{if .Description}}{{.Description}} {{end}}{{if .Subcommands}} SUBCOMMANDS: From 142fbcfd6f4fad825e2ce2684f9d5a487ffb3f84 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 23 Feb 2021 13:09:19 +0100 Subject: [PATCH 209/235] internal/ethapi: reject non-replay-protected txs over RPC (#22339) This PR prevents users from submitting transactions without EIP-155 enabled. This behaviour can be overridden by specifying the flag --rpc.allow-unprotected-txs=true. --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 7 +++++++ eth/api_backend.go | 11 ++++++++--- eth/backend.go | 5 ++++- internal/ethapi/api.go | 4 ++++ internal/ethapi/backend.go | 5 +++-- les/api_backend.go | 11 ++++++++--- les/client.go | 2 +- node/config.go | 3 +++ 9 files changed, 39 insertions(+), 10 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 4fa24cc1c2..b0316c5409 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -199,6 +199,7 @@ var ( utils.InsecureUnlockAllowedFlag, utils.RPCGlobalGasCapFlag, utils.RPCGlobalTxFeeCapFlag, + utils.AllowUnprotectedTxs, } whisperFlags = []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ba643efcc3..070b3a1de1 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -593,6 +593,10 @@ var ( Name: "preload", Usage: "Comma separated list of JavaScript files to preload into the console", } + AllowUnprotectedTxs = cli.BoolFlag{ + Name: "rpc.allow-unprotected-txs", + Usage: "Allow for unprotected (non EIP155 signed) transactions to be submitted via RPC", + } // Network Settings MaxPeersFlag = cli.IntFlag{ @@ -966,6 +970,9 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(HTTPPathPrefixFlag.Name) { cfg.HTTPPathPrefix = ctx.GlobalString(HTTPPathPrefixFlag.Name) } + if ctx.GlobalIsSet(AllowUnprotectedTxs.Name) { + cfg.AllowUnprotectedTxs = ctx.GlobalBool(AllowUnprotectedTxs.Name) + } } // setGraphQL creates the GraphQL listener interface string from the set diff --git a/eth/api_backend.go b/eth/api_backend.go index 17de83a28f..2569972e52 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -41,9 +41,10 @@ import ( // EthAPIBackend implements ethapi.Backend for full nodes type EthAPIBackend struct { - extRPCEnabled bool - eth *Ethereum - gpo *gasprice.Oracle + extRPCEnabled bool + allowUnprotectedTxs bool + eth *Ethereum + gpo *gasprice.Oracle } // ChainConfig returns the active chain configuration. @@ -292,6 +293,10 @@ func (b *EthAPIBackend) ExtRPCEnabled() bool { return b.extRPCEnabled } +func (b *EthAPIBackend) UnprotectedAllowed() bool { + return b.allowUnprotectedTxs +} + func (b *EthAPIBackend) RPCGasCap() uint64 { return b.eth.config.RPCGasCap } diff --git a/eth/backend.go b/eth/backend.go index 4170ecc94e..044422763b 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -222,7 +222,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) - eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), eth, nil} + eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil} + if eth.APIBackend.allowUnprotectedTxs { + log.Info("Unprotected transactions allowed") + } gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index d3c007b9bf..52b4f5f506 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1555,6 +1555,10 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c if err := checkTxFee(tx.GasPrice(), tx.Gas(), b.RPCTxFeeCap()); err != nil { return common.Hash{}, err } + if !b.UnprotectedAllowed() && !tx.Protected() { + // Ensure only eip155 signed transactions are submitted if EIP155Required is set. + return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC") + } if err := b.SendTx(ctx, tx); err != nil { return common.Hash{}, err } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index f0a4c0493c..ebb088fef5 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -45,8 +45,9 @@ type Backend interface { ChainDb() ethdb.Database AccountManager() *accounts.Manager ExtRPCEnabled() bool - RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection - RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs + RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection + RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs + UnprotectedAllowed() bool // allows only for EIP155 transactions. // Blockchain API SetHead(number uint64) diff --git a/les/api_backend.go b/les/api_backend.go index 0839614901..f5d2354b60 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -40,9 +40,10 @@ import ( ) type LesApiBackend struct { - extRPCEnabled bool - eth *LightEthereum - gpo *gasprice.Oracle + extRPCEnabled bool + allowUnprotectedTxs bool + eth *LightEthereum + gpo *gasprice.Oracle } func (b *LesApiBackend) ChainConfig() *params.ChainConfig { @@ -263,6 +264,10 @@ func (b *LesApiBackend) ExtRPCEnabled() bool { return b.extRPCEnabled } +func (b *LesApiBackend) UnprotectedAllowed() bool { + return b.allowUnprotectedTxs +} + func (b *LesApiBackend) RPCGasCap() uint64 { return b.eth.config.RPCGasCap } diff --git a/les/client.go b/les/client.go index d08c9feba5..faaf6095e8 100644 --- a/les/client.go +++ b/les/client.go @@ -157,7 +157,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } - leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), leth, nil} + leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, leth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice diff --git a/node/config.go b/node/config.go index 447a69505c..ef1da15d70 100644 --- a/node/config.go +++ b/node/config.go @@ -191,6 +191,9 @@ type Config struct { staticNodesWarning bool trustedNodesWarning bool oldGethResourceWarning bool + + // AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC. + AllowUnprotectedTxs bool `toml:",omitempty"` } // IPCEndpoint resolves an IPC endpoint based on a configured value, taking into From 2d1a0e9b03f636babe8785dc833960a5d11e4403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 23 Feb 2021 18:12:25 +0200 Subject: [PATCH 210/235] accounts/abi/bind: fix up Go mod files for Go 1.16 --- accounts/abi/bind/bind_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 400545fd23..d0958cb62f 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -1846,11 +1846,16 @@ func TestGolangBindings(t *testing.T) { t.Fatalf("failed to convert binding test to modules: %v\n%s", err, out) } pwd, _ := os.Getwd() - replacer := exec.Command(gocmd, "mod", "edit", "-replace", "github.com/ethereum/go-ethereum="+filepath.Join(pwd, "..", "..", "..")) // Repo root + replacer := exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/ethereum/go-ethereum@v0.0.0", "-replace", "github.com/ethereum/go-ethereum="+filepath.Join(pwd, "..", "..", "..")) // Repo root replacer.Dir = pkg if out, err := replacer.CombinedOutput(); err != nil { t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) } + tidier := exec.Command(gocmd, "mod", "tidy") + tidier.Dir = pkg + if out, err := tidier.CombinedOutput(); err != nil { + t.Fatalf("failed to tidy Go module file: %v\n%s", err, out) + } // Test the entire package and report any failures cmd := exec.Command(gocmd, "test", "-v", "-count", "1") cmd.Dir = pkg From 2743fb042945add8dfe4ca782310e123318c7d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 23 Feb 2021 18:28:24 +0200 Subject: [PATCH 211/235] Dockerfile: bump to Go 1.16 base images --- Dockerfile | 2 +- Dockerfile.alltools | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index d86b776611..6e0dea11b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build Geth in a stock Go builder container -FROM golang:1.15-alpine as builder +FROM golang:1.16-alpine as builder RUN apk add --no-cache make gcc musl-dev linux-headers git diff --git a/Dockerfile.alltools b/Dockerfile.alltools index 715213c5de..483afad8c3 100644 --- a/Dockerfile.alltools +++ b/Dockerfile.alltools @@ -1,5 +1,5 @@ # Build Geth in a stock Go builder container -FROM golang:1.15-alpine as builder +FROM golang:1.16-alpine as builder RUN apk add --no-cache make gcc musl-dev linux-headers git From c9aa2670499a874a28c44424c29268889b18d027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 23 Feb 2021 19:57:39 +0200 Subject: [PATCH 212/235] travis: bump Android NDK version --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39a0456c3b..2e85e5297d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -145,9 +145,9 @@ jobs: - export GOPATH=$HOME/go script: # Build the Android archive and upload it to Maven Central and Azure - - curl https://dl.google.com/android/repository/android-ndk-r19b-linux-x86_64.zip -o android-ndk-r19b.zip - - unzip -q android-ndk-r19b.zip && rm android-ndk-r19b.zip - - mv android-ndk-r19b $ANDROID_HOME/ndk-bundle + - curl https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip -o android-ndk-r21e.zip + - unzip -q android-ndk-r21e.zip && rm android-ndk-r21e.zip + - mv android-ndk-r21e $ANDROID_HOME/ndk-bundle - mkdir -p $GOPATH/src/github.com/ethereum - ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum From 70afe15f680250e69b459d8d9539f594b5fb7491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 23 Feb 2021 20:31:09 +0200 Subject: [PATCH 213/235] travis: bump builders to Bionic --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2e85e5297d..122dee3b36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ jobs: # This builder only tests code linters on latest version of Go - stage: lint os: linux - dist: xenial + dist: bionic go: 1.16.x env: - lint @@ -28,7 +28,7 @@ jobs: - stage: build if: type = push os: linux - dist: xenial + dist: bionic go: 1.16.x env: - ubuntu-ppa @@ -52,7 +52,7 @@ jobs: - stage: build if: type = push os: linux - dist: xenial + dist: bionic sudo: required go: 1.16.x env: @@ -88,7 +88,7 @@ jobs: - stage: build if: type = push os: linux - dist: xenial + dist: bionic services: - docker go: 1.16.x @@ -118,7 +118,7 @@ jobs: - stage: build if: type = push os: linux - dist: xenial + dist: bionic addons: apt: packages: @@ -188,7 +188,7 @@ jobs: - stage: build os: linux arch: amd64 - dist: xenial + dist: bionic go: 1.16.x env: - GO111MODULE=on @@ -199,7 +199,7 @@ jobs: if: type = pull_request os: linux arch: arm64 - dist: xenial + dist: bionic go: 1.16.x env: - GO111MODULE=on @@ -208,7 +208,7 @@ jobs: - stage: build os: linux - dist: xenial + dist: bionic go: 1.15.x env: - GO111MODULE=on @@ -219,7 +219,7 @@ jobs: - stage: build if: type = cron os: linux - dist: xenial + dist: bionic go: 1.16.x env: - azure-purge From f54dc4ab3db0592cf81b3b7ca2ed7a5136ea38a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 24 Feb 2021 11:36:08 +0200 Subject: [PATCH 214/235] travis: manually install Android since Travis is stale (#22373) --- .travis.yml | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 122dee3b36..7406f31fe7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -122,16 +122,7 @@ jobs: addons: apt: packages: - - oracle-java8-installer - - oracle-java8-set-default - language: android - android: - components: - - platform-tools - - tools - - android-15 - - android-19 - - android-24 + - openjdk-8-jdk env: - azure-android - maven-android @@ -139,16 +130,24 @@ jobs: git: submodules: false # avoid cloning ethereum/tests before_install: + # Install Android and it's dependencies manually, Travis is stale + - export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 + - curl https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip -o android.zip + - unzip -q android.zip -d $HOME/sdk && rm android.zip + - mv $HOME/sdk/cmdline-tools $HOME/sdk/latest && mkdir $HOME/sdk/cmdline-tools && mv $HOME/sdk/latest $HOME/sdk/cmdline-tools + - export PATH=$PATH:$HOME/sdk/cmdline-tools/latest/bin + - export ANDROID_HOME=$HOME/sdk + + - yes | sdkmanager --licenses >/dev/null + - sdkmanager "platform-tools" "platforms;android-15" "platforms;android-19" "platforms;android-24" "ndk-bundle" + + # Install Go to allow building with - curl https://dl.google.com/go/go1.16.linux-amd64.tar.gz | tar -xz - export PATH=`pwd`/go/bin:$PATH - export GOROOT=`pwd`/go - export GOPATH=$HOME/go script: # Build the Android archive and upload it to Maven Central and Azure - - curl https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip -o android-ndk-r21e.zip - - unzip -q android-ndk-r21e.zip && rm android-ndk-r21e.zip - - mv android-ndk-r21e $ANDROID_HOME/ndk-bundle - - mkdir -p $GOPATH/src/github.com/ethereum - ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -signify SIGNIFY_KEY -deploy https://oss.sonatype.org -upload gethstore/builds From 8e547eecd592fe3306e39a4fea703dc1307b8651 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 24 Feb 2021 14:07:58 +0100 Subject: [PATCH 215/235] cmd/utils: remove deprecated command line flags (#22263) This removes support for all deprecated flags except --rpc*. --- README.md | 8 +-- cmd/clef/main.go | 18 ------ cmd/geth/chaincmd.go | 1 - cmd/geth/consolecmd.go | 2 +- cmd/geth/consolecmd_test.go | 4 +- cmd/geth/main.go | 34 +--------- cmd/geth/run_test.go | 2 +- cmd/geth/snapshot.go | 4 -- cmd/geth/usage.go | 14 +--- cmd/puppeth/module_node.go | 2 +- cmd/utils/flags.go | 116 ++++++++------------------------- cmd/utils/flags_legacy.go | 126 +++--------------------------------- internal/debug/flags.go | 55 +--------------- 13 files changed, 53 insertions(+), 333 deletions(-) diff --git a/README.md b/README.md index 57d431db1e..4a083d117a 100644 --- a/README.md +++ b/README.md @@ -314,13 +314,13 @@ ones either). To start a `geth` instance for mining, run it with all your usual by: ```shell -$ geth --mine --miner.threads=1 --etherbase=0x0000000000000000000000000000000000000000 +$ geth --mine --miner.threads=1 --miner.etherbase=0x0000000000000000000000000000000000000000 ``` Which will start mining blocks and transactions on a single CPU thread, crediting all -proceedings to the account specified by `--etherbase`. You can further tune the mining -by changing the default gas limit blocks converge to (`--targetgaslimit`) and the price -transactions are accepted at (`--gasprice`). +proceedings to the account specified by `--miner.etherbase`. You can further tune the mining +by changing the default gas limit blocks converge to (`--miner.targetgaslimit`) and the price +transactions are accepted at (`--miner.gasprice`). ## Contribution diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 090edd4507..dbbb410fb6 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -107,11 +107,6 @@ var ( Usage: "HTTP-RPC server listening port", Value: node.DefaultHTTPPort + 5, } - legacyRPCPortFlag = cli.IntFlag{ - Name: "rpcport", - Usage: "HTTP-RPC server listening port (Deprecated, please use --http.port).", - Value: node.DefaultHTTPPort + 5, - } signerSecretFlag = cli.StringFlag{ Name: "signersecret", Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash", @@ -250,12 +245,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{ acceptFlag, }, }, - { - Name: "ALIASED (deprecated)", - Flags: []cli.Flag{ - legacyRPCPortFlag, - }, - }, } func init() { @@ -283,7 +272,6 @@ func init() { testFlag, advancedMode, acceptFlag, - legacyRPCPortFlag, } app.Action = signer app.Commands = []cli.Command{initCommand, @@ -677,12 +665,6 @@ func signer(c *cli.Context) error { // set port port := c.Int(rpcPortFlag.Name) - if c.GlobalIsSet(legacyRPCPortFlag.Name) { - if !c.GlobalIsSet(rpcPortFlag.Name) { - port = c.Int(legacyRPCPortFlag.Name) - } - log.Warn("The flag --rpcport is deprecated and will be removed in the future, please use --http.port") - } // start http server httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.HTTPListenAddrFlag.Name), port) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index ba4289ddc3..ff9581fd88 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -163,7 +163,6 @@ The export-preimages command export hash preimages to an RLP encoded stream`, utils.TxLookupLimitFlag, utils.GoerliFlag, utils.YoloV3Flag, - utils.LegacyTestnetFlag, }, Category: "BLOCKCHAIN COMMANDS", Description: ` diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index cc88b9eec4..e26481003d 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -121,7 +121,7 @@ func remoteConsole(ctx *cli.Context) error { path = ctx.GlobalString(utils.DataDirFlag.Name) } if path != "" { - if ctx.GlobalBool(utils.LegacyTestnetFlag.Name) || ctx.GlobalBool(utils.RopstenFlag.Name) { + if ctx.GlobalBool(utils.RopstenFlag.Name) { // Maintain compatibility with older Geth configurations storing the // Ropsten database in `testnet` instead of `ropsten`. legacyPath := filepath.Join(path, "testnet") diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 93f9e3d0d9..c3f41b187c 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -53,7 +53,7 @@ func TestConsoleWelcome(t *testing.T) { coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" // Start a geth console, make sure it's cleaned up and terminate the console - geth := runMinimalGeth(t, "--etherbase", coinbase, "console") + geth := runMinimalGeth(t, "--miner.etherbase", coinbase, "console") // Gather all the infos the welcome message needs to contain geth.SetTemplateFunc("goos", func() string { return runtime.GOOS }) @@ -100,7 +100,7 @@ func TestAttachWelcome(t *testing.T) { p := trulyRandInt(1024, 65533) // Yeah, sometimes this will fail, sorry :P httpPort = strconv.Itoa(p) wsPort = strconv.Itoa(p + 1) - geth := runMinimalGeth(t, "--etherbase", "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182", + geth := runMinimalGeth(t, "--miner.etherbase", "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182", "--ipcpath", ipc, "--http", "--http.port", httpPort, "--ws", "--ws.port", wsPort) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b0316c5409..d48bfdd42f 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -61,8 +61,6 @@ var ( utils.UnlockedAccountFlag, utils.PasswordFileFlag, utils.BootnodesFlag, - utils.LegacyBootnodesV4Flag, - utils.LegacyBootnodesV5Flag, utils.DataDirFlag, utils.AncientFlag, utils.MinFreeDiskSpaceFlag, @@ -96,11 +94,9 @@ var ( utils.SnapshotFlag, utils.TxLookupLimitFlag, utils.LightServeFlag, - utils.LegacyLightServFlag, utils.LightIngressFlag, utils.LightEgressFlag, utils.LightMaxPeersFlag, - utils.LegacyLightPeersFlag, utils.LightNoPruneFlag, utils.LightKDFFlag, utils.UltraLightServersFlag, @@ -122,17 +118,12 @@ var ( utils.MaxPendingPeersFlag, utils.MiningEnabledFlag, utils.MinerThreadsFlag, - utils.LegacyMinerThreadsFlag, utils.MinerNotifyFlag, utils.MinerGasTargetFlag, - utils.LegacyMinerGasTargetFlag, utils.MinerGasLimitFlag, utils.MinerGasPriceFlag, - utils.LegacyMinerGasPriceFlag, utils.MinerEtherbaseFlag, - utils.LegacyMinerEtherbaseFlag, utils.MinerExtraDataFlag, - utils.LegacyMinerExtraDataFlag, utils.MinerRecommitIntervalFlag, utils.MinerNoVerfiyFlag, utils.NATFlag, @@ -145,7 +136,6 @@ var ( utils.MainnetFlag, utils.DeveloperFlag, utils.DeveloperPeriodFlag, - utils.LegacyTestnetFlag, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, @@ -158,9 +148,7 @@ var ( utils.FakePoWFlag, utils.NoCompactionFlag, utils.GpoBlocksFlag, - utils.LegacyGpoBlocksFlag, utils.GpoPercentileFlag, - utils.LegacyGpoPercentileFlag, utils.GpoMaxGasPriceFlag, utils.EWASMInterpreterFlag, utils.EVMInterpreterFlag, @@ -178,22 +166,18 @@ var ( utils.LegacyRPCPortFlag, utils.LegacyRPCCORSDomainFlag, utils.LegacyRPCVirtualHostsFlag, + utils.LegacyRPCApiFlag, utils.GraphQLEnabledFlag, utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, utils.HTTPApiFlag, utils.HTTPPathPrefixFlag, - utils.LegacyRPCApiFlag, utils.WSEnabledFlag, utils.WSListenAddrFlag, - utils.LegacyWSListenAddrFlag, utils.WSPortFlag, - utils.LegacyWSPortFlag, utils.WSApiFlag, - utils.LegacyWSApiFlag, utils.WSAllowedOriginsFlag, utils.WSPathPrefixFlag, - utils.LegacyWSAllowedOriginsFlag, utils.IPCDisabledFlag, utils.IPCPathFlag, utils.InsecureUnlockAllowedFlag, @@ -267,7 +251,6 @@ func init() { app.Flags = append(app.Flags, rpcFlags...) app.Flags = append(app.Flags, consoleFlags...) app.Flags = append(app.Flags, debug.Flags...) - app.Flags = append(app.Flags, debug.DeprecatedFlags...) app.Flags = append(app.Flags, whisperFlags...) app.Flags = append(app.Flags, metricsFlags...) @@ -293,11 +276,6 @@ func main() { func prepare(ctx *cli.Context) { // If we're running a known preset, log it for convenience. switch { - case ctx.GlobalIsSet(utils.LegacyTestnetFlag.Name): - log.Info("Starting Geth on Ropsten testnet...") - log.Warn("The --testnet flag is ambiguous! Please specify one of --goerli, --rinkeby, or --ropsten.") - log.Warn("The generic --testnet flag is deprecated and will be removed in the future!") - case ctx.GlobalIsSet(utils.RopstenFlag.Name): log.Info("Starting Geth on Ropsten testnet...") @@ -316,7 +294,7 @@ func prepare(ctx *cli.Context) { // If we're a full node on mainnet without --cache specified, bump default cache allowance if ctx.GlobalString(utils.SyncModeFlag.Name) != "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) && !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) { // Make sure we're not on any supported preconfigured testnet either - if !ctx.GlobalIsSet(utils.LegacyTestnetFlag.Name) && !ctx.GlobalIsSet(utils.RopstenFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) { + if !ctx.GlobalIsSet(utils.RopstenFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) { // Nope, we're really on mainnet. Bump that cache up! log.Info("Bumping default cache on mainnet", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 4096) ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(4096)) @@ -461,19 +439,11 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { if !ok { utils.Fatalf("Ethereum service not running: %v", err) } - // Set the gas price to the limits from the CLI and start mining gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) - if ctx.GlobalIsSet(utils.LegacyMinerGasPriceFlag.Name) && !ctx.GlobalIsSet(utils.MinerGasPriceFlag.Name) { - gasprice = utils.GlobalBig(ctx, utils.LegacyMinerGasPriceFlag.Name) - } ethBackend.TxPool().SetGasPrice(gasprice) // start mining threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name) - if ctx.GlobalIsSet(utils.LegacyMinerThreadsFlag.Name) && !ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) { - threads = ctx.GlobalInt(utils.LegacyMinerThreadsFlag.Name) - log.Warn("The flag --minerthreads is deprecated and will be removed in the future, please use --miner.threads") - } if err := ethBackend.StartMining(threads); err != nil { utils.Fatalf("Failed to start mining: %v", err) } diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go index 79b892c59b..527c38a657 100644 --- a/cmd/geth/run_test.go +++ b/cmd/geth/run_test.go @@ -75,7 +75,7 @@ func runGeth(t *testing.T, args ...string) *testgeth { if i < len(args)-1 { tt.Datadir = args[i+1] } - case "--etherbase": + case "--miner.etherbase": if i < len(args)-1 { tt.Etherbase = args[i+1] } diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 6805a42585..b59c530c27 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -60,7 +60,6 @@ var ( utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.LegacyTestnetFlag, utils.CacheTrieJournalFlag, utils.BloomFilterSizeFlag, }, @@ -90,7 +89,6 @@ the trie clean cache with default directory will be deleted. utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.LegacyTestnetFlag, }, Description: ` geth snapshot verify-state @@ -110,7 +108,6 @@ In other words, this command does the snapshot to trie conversion. utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.LegacyTestnetFlag, }, Description: ` geth snapshot traverse-state @@ -132,7 +129,6 @@ It's also usable without snapshot enabled. utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.LegacyTestnetFlag, }, Description: ` geth snapshot traverse-rawstate diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 55e934d31b..cae388c1d3 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -161,8 +161,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{ Name: "NETWORKING", Flags: []cli.Flag{ utils.BootnodesFlag, - utils.LegacyBootnodesV4Flag, - utils.LegacyBootnodesV5Flag, utils.DNSDiscoveryFlag, utils.ListenPortFlag, utils.MaxPeersFlag, @@ -223,7 +221,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ }, { Name: "ALIASED (deprecated)", - Flags: append([]cli.Flag{ + Flags: []cli.Flag{ utils.NoUSBFlag, utils.LegacyRPCEnabledFlag, utils.LegacyRPCListenAddrFlag, @@ -231,15 +229,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.LegacyRPCCORSDomainFlag, utils.LegacyRPCVirtualHostsFlag, utils.LegacyRPCApiFlag, - utils.LegacyWSListenAddrFlag, - utils.LegacyWSPortFlag, - utils.LegacyWSAllowedOriginsFlag, - utils.LegacyWSApiFlag, - utils.LegacyGpoBlocksFlag, - utils.LegacyGpoPercentileFlag, - utils.LegacyGraphQLListenAddrFlag, - utils.LegacyGraphQLPortFlag, - }, debug.DeprecatedFlags...), + }, }, { Name: "MISC", diff --git a/cmd/puppeth/module_node.go b/cmd/puppeth/module_node.go index 5d9ef46523..3ea96870d4 100644 --- a/cmd/puppeth/module_node.go +++ b/cmd/puppeth/module_node.go @@ -94,7 +94,7 @@ func deployNode(client *sshClient, network string, bootnodes []string, config *n lightFlag := "" if config.peersLight > 0 { - lightFlag = fmt.Sprintf("--lightpeers=%d --lightserv=50", config.peersLight) + lightFlag = fmt.Sprintf("--light.maxpeers=%d --light.serve=50", config.peersLight) } dockerfile := new(bytes.Buffer) template.Must(template.New("").Parse(nodeDockerfile)).Execute(dockerfile, map[string]interface{}{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0555acfc75..817041c589 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -765,13 +765,9 @@ var ( // then a subdirectory of the specified datadir will be used. func MakeDataDir(ctx *cli.Context) string { if path := ctx.GlobalString(DataDirFlag.Name); path != "" { - if ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name) { + if ctx.GlobalBool(RopstenFlag.Name) { // Maintain compatibility with older Geth configurations storing the // Ropsten database in `testnet` instead of `ropsten`. - legacyPath := filepath.Join(path, "testnet") - if _, err := os.Stat(legacyPath); !os.IsNotExist(err) { - return legacyPath - } return filepath.Join(path, "ropsten") } if ctx.GlobalBool(RinkebyFlag.Name) { @@ -827,13 +823,9 @@ func setNodeUserIdent(ctx *cli.Context, cfg *node.Config) { func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { urls := params.MainnetBootnodes switch { - case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(LegacyBootnodesV4Flag.Name): - if ctx.GlobalIsSet(LegacyBootnodesV4Flag.Name) { - urls = SplitAndTrim(ctx.GlobalString(LegacyBootnodesV4Flag.Name)) - } else { - urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) - } - case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): + case ctx.GlobalIsSet(BootnodesFlag.Name): + urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) + case ctx.GlobalBool(RopstenFlag.Name): urls = params.RopstenBootnodes case ctx.GlobalBool(RinkebyFlag.Name): urls = params.RinkebyBootnodes @@ -863,12 +855,8 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { urls := params.V5Bootnodes switch { - case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(LegacyBootnodesV5Flag.Name): - if ctx.GlobalIsSet(LegacyBootnodesV5Flag.Name) { - urls = SplitAndTrim(ctx.GlobalString(LegacyBootnodesV5Flag.Name)) - } else { - urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) - } + case ctx.GlobalIsSet(BootnodesFlag.Name): + urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) case cfg.BootstrapNodesV5 != nil: return // already set, don't apply defaults. } @@ -921,11 +909,11 @@ func SplitAndTrim(input string) (ret []string) { // command line flags, returning empty if the HTTP endpoint is disabled. func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalBool(LegacyRPCEnabledFlag.Name) && cfg.HTTPHost == "" { - log.Warn("The flag --rpc is deprecated and will be removed in the future, please use --http") + log.Warn("The flag --rpc is deprecated and will be removed June 2021, please use --http") cfg.HTTPHost = "127.0.0.1" if ctx.GlobalIsSet(LegacyRPCListenAddrFlag.Name) { cfg.HTTPHost = ctx.GlobalString(LegacyRPCListenAddrFlag.Name) - log.Warn("The flag --rpcaddr is deprecated and will be removed in the future, please use --http.addr") + log.Warn("The flag --rpcaddr is deprecated and will be removed June 2021, please use --http.addr") } } if ctx.GlobalBool(HTTPEnabledFlag.Name) && cfg.HTTPHost == "" { @@ -937,7 +925,7 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LegacyRPCPortFlag.Name) { cfg.HTTPPort = ctx.GlobalInt(LegacyRPCPortFlag.Name) - log.Warn("The flag --rpcport is deprecated and will be removed in the future, please use --http.port") + log.Warn("The flag --rpcport is deprecated and will be removed June 2021, please use --http.port") } if ctx.GlobalIsSet(HTTPPortFlag.Name) { cfg.HTTPPort = ctx.GlobalInt(HTTPPortFlag.Name) @@ -945,7 +933,7 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LegacyRPCCORSDomainFlag.Name) { cfg.HTTPCors = SplitAndTrim(ctx.GlobalString(LegacyRPCCORSDomainFlag.Name)) - log.Warn("The flag --rpccorsdomain is deprecated and will be removed in the future, please use --http.corsdomain") + log.Warn("The flag --rpccorsdomain is deprecated and will be removed June 2021, please use --http.corsdomain") } if ctx.GlobalIsSet(HTTPCORSDomainFlag.Name) { cfg.HTTPCors = SplitAndTrim(ctx.GlobalString(HTTPCORSDomainFlag.Name)) @@ -953,7 +941,7 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LegacyRPCApiFlag.Name) { cfg.HTTPModules = SplitAndTrim(ctx.GlobalString(LegacyRPCApiFlag.Name)) - log.Warn("The flag --rpcapi is deprecated and will be removed in the future, please use --http.api") + log.Warn("The flag --rpcapi is deprecated and will be removed June 2021, please use --http.api") } if ctx.GlobalIsSet(HTTPApiFlag.Name) { cfg.HTTPModules = SplitAndTrim(ctx.GlobalString(HTTPApiFlag.Name)) @@ -961,7 +949,7 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LegacyRPCVirtualHostsFlag.Name) { cfg.HTTPVirtualHosts = SplitAndTrim(ctx.GlobalString(LegacyRPCVirtualHostsFlag.Name)) - log.Warn("The flag --rpcvhosts is deprecated and will be removed in the future, please use --http.vhosts") + log.Warn("The flag --rpcvhosts is deprecated and will be removed June 2021, please use --http.vhosts") } if ctx.GlobalIsSet(HTTPVirtualHostsFlag.Name) { cfg.HTTPVirtualHosts = SplitAndTrim(ctx.GlobalString(HTTPVirtualHostsFlag.Name)) @@ -991,34 +979,18 @@ func setGraphQL(ctx *cli.Context, cfg *node.Config) { func setWS(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalBool(WSEnabledFlag.Name) && cfg.WSHost == "" { cfg.WSHost = "127.0.0.1" - if ctx.GlobalIsSet(LegacyWSListenAddrFlag.Name) { - cfg.WSHost = ctx.GlobalString(LegacyWSListenAddrFlag.Name) - log.Warn("The flag --wsaddr is deprecated and will be removed in the future, please use --ws.addr") - } if ctx.GlobalIsSet(WSListenAddrFlag.Name) { cfg.WSHost = ctx.GlobalString(WSListenAddrFlag.Name) } } - if ctx.GlobalIsSet(LegacyWSPortFlag.Name) { - cfg.WSPort = ctx.GlobalInt(LegacyWSPortFlag.Name) - log.Warn("The flag --wsport is deprecated and will be removed in the future, please use --ws.port") - } if ctx.GlobalIsSet(WSPortFlag.Name) { cfg.WSPort = ctx.GlobalInt(WSPortFlag.Name) } - if ctx.GlobalIsSet(LegacyWSAllowedOriginsFlag.Name) { - cfg.WSOrigins = SplitAndTrim(ctx.GlobalString(LegacyWSAllowedOriginsFlag.Name)) - log.Warn("The flag --wsorigins is deprecated and will be removed in the future, please use --ws.origins") - } if ctx.GlobalIsSet(WSAllowedOriginsFlag.Name) { cfg.WSOrigins = SplitAndTrim(ctx.GlobalString(WSAllowedOriginsFlag.Name)) } - if ctx.GlobalIsSet(LegacyWSApiFlag.Name) { - cfg.WSModules = SplitAndTrim(ctx.GlobalString(LegacyWSApiFlag.Name)) - log.Warn("The flag --wsapi is deprecated and will be removed in the future, please use --ws.api") - } if ctx.GlobalIsSet(WSApiFlag.Name) { cfg.WSModules = SplitAndTrim(ctx.GlobalString(WSApiFlag.Name)) } @@ -1042,10 +1014,6 @@ func setIPC(ctx *cli.Context, cfg *node.Config) { // setLes configures the les server and ultra light client settings from the command line flags. func setLes(ctx *cli.Context, cfg *ethconfig.Config) { - if ctx.GlobalIsSet(LegacyLightServFlag.Name) { - cfg.LightServ = ctx.GlobalInt(LegacyLightServFlag.Name) - log.Warn("The flag --lightserv is deprecated and will be removed in the future, please use --light.serve") - } if ctx.GlobalIsSet(LightServeFlag.Name) { cfg.LightServ = ctx.GlobalInt(LightServeFlag.Name) } @@ -1055,10 +1023,6 @@ func setLes(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.GlobalIsSet(LightEgressFlag.Name) { cfg.LightEgress = ctx.GlobalInt(LightEgressFlag.Name) } - if ctx.GlobalIsSet(LegacyLightPeersFlag.Name) { - cfg.LightPeers = ctx.GlobalInt(LegacyLightPeersFlag.Name) - log.Warn("The flag --lightpeers is deprecated and will be removed in the future, please use --light.maxpeers") - } if ctx.GlobalIsSet(LightMaxPeersFlag.Name) { cfg.LightPeers = ctx.GlobalInt(LightMaxPeersFlag.Name) } @@ -1122,13 +1086,8 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error // setEtherbase retrieves the etherbase either from the directly specified // command line flags or from the keystore if CLI indexed. func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *ethconfig.Config) { - // Extract the current etherbase, new flag overriding legacy one + // Extract the current etherbase var etherbase string - if ctx.GlobalIsSet(LegacyMinerEtherbaseFlag.Name) { - etherbase = ctx.GlobalString(LegacyMinerEtherbaseFlag.Name) - log.Warn("The flag --etherbase is deprecated and will be removed in the future, please use --miner.etherbase") - - } if ctx.GlobalIsSet(MinerEtherbaseFlag.Name) { etherbase = ctx.GlobalString(MinerEtherbaseFlag.Name) } @@ -1172,27 +1131,24 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { setBootstrapNodesV5(ctx, cfg) lightClient := ctx.GlobalString(SyncModeFlag.Name) == "light" - lightServer := (ctx.GlobalInt(LegacyLightServFlag.Name) != 0 || ctx.GlobalInt(LightServeFlag.Name) != 0) + lightServer := (ctx.GlobalInt(LightServeFlag.Name) != 0) - lightPeers := ctx.GlobalInt(LegacyLightPeersFlag.Name) - if ctx.GlobalIsSet(LightMaxPeersFlag.Name) { - lightPeers = ctx.GlobalInt(LightMaxPeersFlag.Name) - } - if lightClient && !ctx.GlobalIsSet(LegacyLightPeersFlag.Name) && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) { + lightPeers := ctx.GlobalInt(LightMaxPeersFlag.Name) + if lightClient && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) { // dynamic default - for clients we use 1/10th of the default for servers lightPeers /= 10 } if ctx.GlobalIsSet(MaxPeersFlag.Name) { cfg.MaxPeers = ctx.GlobalInt(MaxPeersFlag.Name) - if lightServer && !ctx.GlobalIsSet(LegacyLightPeersFlag.Name) && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) { + if lightServer && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) { cfg.MaxPeers += lightPeers } } else { if lightServer { cfg.MaxPeers += lightPeers } - if lightClient && (ctx.GlobalIsSet(LegacyLightPeersFlag.Name) || ctx.GlobalIsSet(LightMaxPeersFlag.Name)) && cfg.MaxPeers < lightPeers { + if lightClient && ctx.GlobalIsSet(LightMaxPeersFlag.Name) && cfg.MaxPeers < lightPeers { cfg.MaxPeers = lightPeers } } @@ -1297,7 +1253,7 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { cfg.DataDir = ctx.GlobalString(DataDirFlag.Name) case ctx.GlobalBool(DeveloperFlag.Name): cfg.DataDir = "" // unless explicitly requested, use memory databases - case (ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name)) && cfg.DataDir == node.DefaultDataDir(): + case ctx.GlobalBool(RopstenFlag.Name) && cfg.DataDir == node.DefaultDataDir(): // Maintain compatibility with older Geth configurations storing the // Ropsten database in `testnet` instead of `ropsten`. legacyPath := filepath.Join(node.DefaultDataDir(), "testnet") @@ -1307,6 +1263,8 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { } else { cfg.DataDir = filepath.Join(node.DefaultDataDir(), "ropsten") } + + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "ropsten") case ctx.GlobalBool(RinkebyFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby") case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): @@ -1323,17 +1281,9 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) { cfg.Blocks = ethconfig.LightClientGPO.Blocks cfg.Percentile = ethconfig.LightClientGPO.Percentile } - if ctx.GlobalIsSet(LegacyGpoBlocksFlag.Name) { - cfg.Blocks = ctx.GlobalInt(LegacyGpoBlocksFlag.Name) - log.Warn("The flag --gpoblocks is deprecated and will be removed in the future, please use --gpo.blocks") - } if ctx.GlobalIsSet(GpoBlocksFlag.Name) { cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name) } - if ctx.GlobalIsSet(LegacyGpoPercentileFlag.Name) { - cfg.Percentile = ctx.GlobalInt(LegacyGpoPercentileFlag.Name) - log.Warn("The flag --gpopercentile is deprecated and will be removed in the future, please use --gpo.percentile") - } if ctx.GlobalIsSet(GpoPercentileFlag.Name) { cfg.Percentile = ctx.GlobalInt(GpoPercentileFlag.Name) } @@ -1416,27 +1366,15 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) { if ctx.GlobalIsSet(MinerNotifyFlag.Name) { cfg.Notify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",") } - if ctx.GlobalIsSet(LegacyMinerExtraDataFlag.Name) { - cfg.ExtraData = []byte(ctx.GlobalString(LegacyMinerExtraDataFlag.Name)) - log.Warn("The flag --extradata is deprecated and will be removed in the future, please use --miner.extradata") - } if ctx.GlobalIsSet(MinerExtraDataFlag.Name) { cfg.ExtraData = []byte(ctx.GlobalString(MinerExtraDataFlag.Name)) } - if ctx.GlobalIsSet(LegacyMinerGasTargetFlag.Name) { - cfg.GasFloor = ctx.GlobalUint64(LegacyMinerGasTargetFlag.Name) - log.Warn("The flag --targetgaslimit is deprecated and will be removed in the future, please use --miner.gastarget") - } if ctx.GlobalIsSet(MinerGasTargetFlag.Name) { cfg.GasFloor = ctx.GlobalUint64(MinerGasTargetFlag.Name) } if ctx.GlobalIsSet(MinerGasLimitFlag.Name) { cfg.GasCeil = ctx.GlobalUint64(MinerGasLimitFlag.Name) } - if ctx.GlobalIsSet(LegacyMinerGasPriceFlag.Name) { - cfg.GasPrice = GlobalBig(ctx, LegacyMinerGasPriceFlag.Name) - log.Warn("The flag --gasprice is deprecated and will be removed in the future, please use --miner.gasprice") - } if ctx.GlobalIsSet(MinerGasPriceFlag.Name) { cfg.GasPrice = GlobalBig(ctx, MinerGasPriceFlag.Name) } @@ -1525,11 +1463,11 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, MainnetFlag, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag) - CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light") + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag) + CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) - if (ctx.GlobalIsSet(LegacyLightServFlag.Name) || ctx.GlobalIsSet(LightServeFlag.Name)) && ctx.GlobalIsSet(TxLookupLimitFlag.Name) { + if ctx.GlobalIsSet(LightServeFlag.Name) && ctx.GlobalIsSet(TxLookupLimitFlag.Name) { log.Warn("LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited") } var ks *keystore.KeyStore @@ -1644,7 +1582,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } cfg.Genesis = core.DefaultGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) - case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): + case ctx.GlobalBool(RopstenFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 3 } @@ -1710,7 +1648,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } chaindb.Close() } - if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(LegacyMinerGasPriceFlag.Name) { + if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) { cfg.Miner.GasPrice = big.NewInt(1) } default: @@ -1849,7 +1787,7 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { func MakeGenesis(ctx *cli.Context) *core.Genesis { var genesis *core.Genesis switch { - case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): + case ctx.GlobalBool(RopstenFlag.Name): genesis = core.DefaultRopstenGenesisBlock() case ctx.GlobalBool(RinkebyFlag.Name): genesis = core.DefaultRinkebyGenesisBlock() diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go index ff45ab9094..fb5fde6576 100644 --- a/cmd/utils/flags_legacy.go +++ b/cmd/utils/flags_legacy.go @@ -20,7 +20,6 @@ import ( "fmt" "strings" - "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/node" "gopkg.in/urfave/cli.v1" ) @@ -34,143 +33,39 @@ var ShowDeprecated = cli.Command{ Description: "Show flags that have been deprecated and will soon be removed", } -var DeprecatedFlags = []cli.Flag{ - LegacyTestnetFlag, - LegacyLightServFlag, - LegacyLightPeersFlag, - LegacyMinerThreadsFlag, - LegacyMinerGasTargetFlag, - LegacyMinerGasPriceFlag, - LegacyMinerEtherbaseFlag, - LegacyMinerExtraDataFlag, -} +var DeprecatedFlags = []cli.Flag{} var ( - // (Deprecated April 2018) - LegacyMinerThreadsFlag = cli.IntFlag{ - Name: "minerthreads", - Usage: "Number of CPU threads to use for mining (deprecated, use --miner.threads)", - Value: 0, - } - LegacyMinerGasTargetFlag = cli.Uint64Flag{ - Name: "targetgaslimit", - Usage: "Target gas floor for mined blocks (deprecated, use --miner.gastarget)", - Value: ethconfig.Defaults.Miner.GasFloor, - } - LegacyMinerGasPriceFlag = BigFlag{ - Name: "gasprice", - Usage: "Minimum gas price for mining a transaction (deprecated, use --miner.gasprice)", - Value: ethconfig.Defaults.Miner.GasPrice, - } - LegacyMinerEtherbaseFlag = cli.StringFlag{ - Name: "etherbase", - Usage: "Public address for block mining rewards (default = first account, deprecated, use --miner.etherbase)", - Value: "0", - } - LegacyMinerExtraDataFlag = cli.StringFlag{ - Name: "extradata", - Usage: "Block extra data set by the miner (default = client version, deprecated, use --miner.extradata)", - } - - // (Deprecated June 2019) - LegacyLightServFlag = cli.IntFlag{ - Name: "lightserv", - Usage: "Maximum percentage of time allowed for serving LES requests (deprecated, use --light.serve)", - Value: ethconfig.Defaults.LightServ, - } - LegacyLightPeersFlag = cli.IntFlag{ - Name: "lightpeers", - Usage: "Maximum number of light clients to serve, or light servers to attach to (deprecated, use --light.maxpeers)", - Value: ethconfig.Defaults.LightPeers, - } - - // (Deprecated April 2020) - LegacyTestnetFlag = cli.BoolFlag{ // TODO(q9f): Remove after Ropsten is discontinued. - Name: "testnet", - Usage: "Pre-configured test network (Deprecated: Please choose one of --goerli, --rinkeby, or --ropsten.)", - } - // (Deprecated May 2020, shown in aliased flags section) LegacyRPCEnabledFlag = cli.BoolFlag{ Name: "rpc", - Usage: "Enable the HTTP-RPC server (deprecated, use --http)", + Usage: "Enable the HTTP-RPC server (deprecated and will be removed June 2021, use --http)", } LegacyRPCListenAddrFlag = cli.StringFlag{ Name: "rpcaddr", - Usage: "HTTP-RPC server listening interface (deprecated, use --http.addr)", + Usage: "HTTP-RPC server listening interface (deprecated and will be removed June 2021, use --http.addr)", Value: node.DefaultHTTPHost, } LegacyRPCPortFlag = cli.IntFlag{ Name: "rpcport", - Usage: "HTTP-RPC server listening port (deprecated, use --http.port)", + Usage: "HTTP-RPC server listening port (deprecated and will be removed June 2021, use --http.port)", Value: node.DefaultHTTPPort, } LegacyRPCCORSDomainFlag = cli.StringFlag{ Name: "rpccorsdomain", - Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced) (deprecated, use --http.corsdomain)", + Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced) (deprecated and will be removed June 2021, use --http.corsdomain)", Value: "", } LegacyRPCVirtualHostsFlag = cli.StringFlag{ Name: "rpcvhosts", - Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (deprecated, use --http.vhosts)", + Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (deprecated and will be removed June 2021, use --http.vhosts)", Value: strings.Join(node.DefaultConfig.HTTPVirtualHosts, ","), } LegacyRPCApiFlag = cli.StringFlag{ Name: "rpcapi", - Usage: "API's offered over the HTTP-RPC interface (deprecated, use --http.api)", - Value: "", - } - LegacyWSListenAddrFlag = cli.StringFlag{ - Name: "wsaddr", - Usage: "WS-RPC server listening interface (deprecated, use --ws.addr)", - Value: node.DefaultWSHost, - } - LegacyWSPortFlag = cli.IntFlag{ - Name: "wsport", - Usage: "WS-RPC server listening port (deprecated, use --ws.port)", - Value: node.DefaultWSPort, - } - LegacyWSApiFlag = cli.StringFlag{ - Name: "wsapi", - Usage: "API's offered over the WS-RPC interface (deprecated, use --ws.api)", + Usage: "API's offered over the HTTP-RPC interface (deprecated and will be removed June 2021, use --http.api)", Value: "", } - LegacyWSAllowedOriginsFlag = cli.StringFlag{ - Name: "wsorigins", - Usage: "Origins from which to accept websockets requests (deprecated, use --ws.origins)", - Value: "", - } - LegacyGpoBlocksFlag = cli.IntFlag{ - Name: "gpoblocks", - Usage: "Number of recent blocks to check for gas prices (deprecated, use --gpo.blocks)", - Value: ethconfig.Defaults.GPO.Blocks, - } - LegacyGpoPercentileFlag = cli.IntFlag{ - Name: "gpopercentile", - Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices (deprecated, use --gpo.percentile)", - Value: ethconfig.Defaults.GPO.Percentile, - } - LegacyBootnodesV4Flag = cli.StringFlag{ - Name: "bootnodesv4", - Usage: "Comma separated enode URLs for P2P v4 discovery bootstrap (light server, full nodes) (deprecated, use --bootnodes)", - Value: "", - } - LegacyBootnodesV5Flag = cli.StringFlag{ - Name: "bootnodesv5", - Usage: "Comma separated enode URLs for P2P v5 discovery bootstrap (light server, light nodes) (deprecated, use --bootnodes)", - Value: "", - } - - // (Deprecated July 2020, shown in aliased flags section) - LegacyGraphQLListenAddrFlag = cli.StringFlag{ - Name: "graphql.addr", - Usage: "GraphQL server listening interface (deprecated, graphql can only be enabled on the HTTP-RPC server endpoint, use --graphql)", - } - LegacyGraphQLPortFlag = cli.IntFlag{ - Name: "graphql.port", - Usage: "GraphQL server listening port (deprecated, graphql can only be enabled on the HTTP-RPC server endpoint, use --graphql)", - Value: node.DefaultHTTPPort, - } ) // showDeprecated displays deprecated flags that will be soon removed from the codebase. @@ -179,8 +74,7 @@ func showDeprecated(*cli.Context) { fmt.Println("The following flags are deprecated and will be removed in the future!") fmt.Println("--------------------------------------------------------------------") fmt.Println() - - for _, flag := range DeprecatedFlags { - fmt.Println(flag.String()) - } + // TODO remove when there are newly deprecated flags + fmt.Println("no deprecated flags to show at this time") + fmt.Println() } diff --git a/internal/debug/flags.go b/internal/debug/flags.go index fc4ea9f518..2c92b19de6 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -90,30 +90,6 @@ var ( Name: "trace", Usage: "Write execution trace to the given file", } - // (Deprecated April 2020) - legacyPprofPortFlag = cli.IntFlag{ - Name: "pprofport", - Usage: "pprof HTTP server listening port (deprecated, use --pprof.port)", - Value: 6060, - } - legacyPprofAddrFlag = cli.StringFlag{ - Name: "pprofaddr", - Usage: "pprof HTTP server listening interface (deprecated, use --pprof.addr)", - Value: "127.0.0.1", - } - legacyMemprofilerateFlag = cli.IntFlag{ - Name: "memprofilerate", - Usage: "Turn on memory profiling with the given rate (deprecated, use --pprof.memprofilerate)", - Value: runtime.MemProfileRate, - } - legacyBlockprofilerateFlag = cli.IntFlag{ - Name: "blockprofilerate", - Usage: "Turn on block profiling with the given rate (deprecated, use --pprof.blockprofilerate)", - } - legacyCpuprofileFlag = cli.StringFlag{ - Name: "cpuprofile", - Usage: "Write CPU profile to the given file (deprecated, use --pprof.cpuprofile)", - } ) // Flags holds all command-line flags required for debugging. @@ -123,12 +99,9 @@ var Flags = []cli.Flag{ blockprofilerateFlag, cpuprofileFlag, traceFlag, } -var DeprecatedFlags = []cli.Flag{ - legacyPprofPortFlag, legacyPprofAddrFlag, legacyMemprofilerateFlag, - legacyBlockprofilerateFlag, legacyCpuprofileFlag, -} - -var glogger *log.GlogHandler +var ( + glogger *log.GlogHandler +) func init() { glogger = log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) @@ -159,16 +132,8 @@ func Setup(ctx *cli.Context) error { log.Root().SetHandler(glogger) // profiling, tracing - if ctx.GlobalIsSet(legacyMemprofilerateFlag.Name) { - runtime.MemProfileRate = ctx.GlobalInt(legacyMemprofilerateFlag.Name) - log.Warn("The flag --memprofilerate is deprecated and will be removed in the future, please use --pprof.memprofilerate") - } runtime.MemProfileRate = ctx.GlobalInt(memprofilerateFlag.Name) - if ctx.GlobalIsSet(legacyBlockprofilerateFlag.Name) { - Handler.SetBlockProfileRate(ctx.GlobalInt(legacyBlockprofilerateFlag.Name)) - log.Warn("The flag --blockprofilerate is deprecated and will be removed in the future, please use --pprof.blockprofilerate") - } Handler.SetBlockProfileRate(ctx.GlobalInt(blockprofilerateFlag.Name)) if traceFile := ctx.GlobalString(traceFlag.Name); traceFile != "" { @@ -182,26 +147,12 @@ func Setup(ctx *cli.Context) error { return err } } - if cpuFile := ctx.GlobalString(legacyCpuprofileFlag.Name); cpuFile != "" { - log.Warn("The flag --cpuprofile is deprecated and will be removed in the future, please use --pprof.cpuprofile") - if err := Handler.StartCPUProfile(cpuFile); err != nil { - return err - } - } // pprof server if ctx.GlobalBool(pprofFlag.Name) { listenHost := ctx.GlobalString(pprofAddrFlag.Name) - if ctx.GlobalIsSet(legacyPprofAddrFlag.Name) && !ctx.GlobalIsSet(pprofAddrFlag.Name) { - listenHost = ctx.GlobalString(legacyPprofAddrFlag.Name) - log.Warn("The flag --pprofaddr is deprecated and will be removed in the future, please use --pprof.addr") - } port := ctx.GlobalInt(pprofPortFlag.Name) - if ctx.GlobalIsSet(legacyPprofPortFlag.Name) && !ctx.GlobalIsSet(pprofPortFlag.Name) { - port = ctx.GlobalInt(legacyPprofPortFlag.Name) - log.Warn("The flag --pprofport is deprecated and will be removed in the future, please use --pprof.port") - } address := fmt.Sprintf("%s:%d", listenHost, port) // This context value ("metrics.addr") represents the utils.MetricsHTTPFlag.Name. From b2b5c82acaa89387960805d53359629e854814bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Feb 2021 12:56:18 +0200 Subject: [PATCH 216/235] eth/protocols/snap: lower abortion and resumption logs to debug --- eth/protocols/snap/sync.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index c31e4a5dae..1cfdef15bd 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -1595,7 +1595,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { // is interrupted and resumed later. However, *do* update the // previous root hash. if subtasks, ok := res.task.SubTasks[res.hashes[i]]; ok { - log.Error("Resuming large storage retrieval", "account", res.hashes[i], "root", account.Root) + log.Debug("Resuming large storage retrieval", "account", res.hashes[i], "root", account.Root) for _, subtask := range subtasks { subtask.root = account.Root } @@ -1614,7 +1614,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { // now we have to live with that. for hash := range res.task.SubTasks { if _, ok := resumed[hash]; !ok { - log.Error("Aborting suspended storage retrieval", "account", hash) + log.Debug("Aborting suspended storage retrieval", "account", hash) delete(res.task.SubTasks, hash) } } From 378e961d857e02a1ce032727da08dfebf2d96cac Mon Sep 17 00:00:00 2001 From: gary rong Date: Thu, 25 Feb 2021 20:55:07 +0800 Subject: [PATCH 217/235] cmd, eth, les: enable serving light clients when non-synced (#22250) This PR adds a more CLI flag, so that the les-server can serve light clients even the local node is not synced yet. This functionality is needed in some testing environments(e.g. hive). After launching the les server, no more blocks will be imported so the node is always marked as "non-synced". --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 7 +++++++ eth/ethconfig/config.go | 1 + eth/ethconfig/gen_config.go | 6 ++++++ les/server.go | 6 +++++- 6 files changed, 21 insertions(+), 1 deletion(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index d48bfdd42f..b4c622ce2e 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -102,6 +102,7 @@ var ( utils.UltraLightServersFlag, utils.UltraLightFractionFlag, utils.UltraLightOnlyAnnounceFlag, + utils.LightNoSyncServeFlag, utils.WhitelistFlag, utils.BloomFilterSizeFlag, utils.CacheFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index cae388c1d3..24215f55a8 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -68,6 +68,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.UltraLightFractionFlag, utils.UltraLightOnlyAnnounceFlag, utils.LightNoPruneFlag, + utils.LightNoSyncServeFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 817041c589..9fff183a14 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -269,6 +269,10 @@ var ( Name: "light.nopruning", Usage: "Disable ancient light chain data pruning", } + LightNoSyncServeFlag = cli.BoolFlag{ + Name: "light.nosyncserve", + Usage: "Enables serving light clients before syncing", + } // Ethash settings EthashCacheDirFlag = DirectoryFlag{ Name: "ethash.cachedir", @@ -1042,6 +1046,9 @@ func setLes(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.GlobalIsSet(LightNoPruneFlag.Name) { cfg.LightNoPrune = ctx.GlobalBool(LightNoPruneFlag.Name) } + if ctx.GlobalIsSet(LightNoSyncServeFlag.Name) { + cfg.LightNoSyncServe = ctx.GlobalBool(LightNoSyncServeFlag.Name) + } } // MakeDatabaseHandles raises out the number of allowed file handles per process diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index e192e4d333..841dc5e9e1 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -140,6 +140,7 @@ type Config struct { LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers LightPeers int `toml:",omitempty"` // Maximum number of LES client peers LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning + LightNoSyncServe bool `toml:",omitempty"` // Whether to serve light clients before syncing SyncFromCheckpoint bool `toml:",omitempty"` // Whether to sync the header chain from the configured checkpoint // Ultra Light client options diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 5814b81b09..ca93b2ad00 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -31,6 +31,7 @@ func (c Config) MarshalTOML() (interface{}, error) { LightEgress int `toml:",omitempty"` LightPeers int `toml:",omitempty"` LightNoPrune bool `toml:",omitempty"` + LightNoSyncServe bool `toml:",omitempty"` SyncFromCheckpoint bool `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"` UltraLightFraction int `toml:",omitempty"` @@ -74,6 +75,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.LightEgress = c.LightEgress enc.LightPeers = c.LightPeers enc.LightNoPrune = c.LightNoPrune + enc.LightNoSyncServe = c.LightNoSyncServe enc.SyncFromCheckpoint = c.SyncFromCheckpoint enc.UltraLightServers = c.UltraLightServers enc.UltraLightFraction = c.UltraLightFraction @@ -121,6 +123,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { LightEgress *int `toml:",omitempty"` LightPeers *int `toml:",omitempty"` LightNoPrune *bool `toml:",omitempty"` + LightNoSyncServe *bool `toml:",omitempty"` SyncFromCheckpoint *bool `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"` UltraLightFraction *int `toml:",omitempty"` @@ -195,6 +198,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.LightNoPrune != nil { c.LightNoPrune = *dec.LightNoPrune } + if dec.LightNoSyncServe != nil { + c.LightNoSyncServe = *dec.LightNoSyncServe + } if dec.SyncFromCheckpoint != nil { c.SyncFromCheckpoint = *dec.SyncFromCheckpoint } diff --git a/les/server.go b/les/server.go index e34647f290..359784cf7d 100644 --- a/les/server.go +++ b/les/server.go @@ -118,7 +118,11 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les threadsIdle: threads, p2pSrv: node.Server(), } - srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), e.Synced) + issync := e.Synced + if config.LightNoSyncServe { + issync = func() bool { return true } + } + srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), issync) srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config) srv.oracle = srv.setupOracle(node, e.BlockChain().Genesis().Hash(), config) From 7a3c890009535bc3b87b01d9af19566e654be9da Mon Sep 17 00:00:00 2001 From: gary rong Date: Thu, 25 Feb 2021 21:24:04 +0800 Subject: [PATCH 218/235] les, light: improve txstatus retrieval (#22349) Transaction unindexing will be enabled by default as of 1.10, which causes tx status retrieval will be broken without this PR. This PR introduces a retry mechanism in TxStatus retrieval. --- les/client.go | 2 +- les/fetcher_test.go | 35 ++++- les/handler_test.go | 218 +++++++++++++++++++++-------- les/odr.go | 101 +++++++++++++- les/odr_requests.go | 5 +- les/odr_test.go | 192 ++++++++++++++++++++++++- les/peer.go | 12 +- les/pruner_test.go | 29 ++-- les/request_test.go | 9 +- les/sync_test.go | 56 ++++++-- les/test_helper.go | 334 ++++++++++++++++++++++++++------------------ les/ulc_test.go | 15 +- light/odr.go | 1 + light/odr_util.go | 9 +- 14 files changed, 776 insertions(+), 242 deletions(-) diff --git a/les/client.go b/les/client.go index faaf6095e8..053118df5a 100644 --- a/les/client.go +++ b/les/client.go @@ -122,7 +122,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.getTimeout) leth.relay = newLesTxRelay(peers, leth.retriever) - leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.retriever) + leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.peers, leth.retriever) leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations, config.LightNoPrune) leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune) leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer) diff --git a/les/fetcher_test.go b/les/fetcher_test.go index a9e6e6835e..d3a74d25c2 100644 --- a/les/fetcher_test.go +++ b/les/fetcher_test.go @@ -66,7 +66,12 @@ func TestSequentialAnnouncementsLes2(t *testing.T) { testSequentialAnnouncements func TestSequentialAnnouncementsLes3(t *testing.T) { testSequentialAnnouncements(t, 3) } func testSequentialAnnouncements(t *testing.T, protocol int) { - s, c, teardown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + s, c, teardown := newClientServerEnv(t, netconfig) defer teardown() // Create connected peer pair. @@ -101,7 +106,12 @@ func TestGappedAnnouncementsLes2(t *testing.T) { testGappedAnnouncements(t, 2) } func TestGappedAnnouncementsLes3(t *testing.T) { testGappedAnnouncements(t, 3) } func testGappedAnnouncements(t *testing.T, protocol int) { - s, c, teardown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + s, c, teardown := newClientServerEnv(t, netconfig) defer teardown() // Create connected peer pair. @@ -183,7 +193,13 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { ids = append(ids, n.String()) } } - _, c, teardown := newClientServerEnv(t, 0, protocol, nil, ids, 60, false, false, true) + netconfig := testnetConfig{ + protocol: protocol, + nopruning: true, + ulcServers: ids, + ulcFraction: 60, + } + _, c, teardown := newClientServerEnv(t, netconfig) defer teardown() defer func() { for i := 0; i < len(teardowns); i++ { @@ -233,8 +249,17 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { check([]uint64{10}, 10, func() { <-newHead }) // Sync the whole chain. } -func TestInvalidAnnounces(t *testing.T) { - s, c, teardown := newClientServerEnv(t, 4, lpv3, nil, nil, 0, false, false, true) +func TestInvalidAnnouncesLES2(t *testing.T) { testInvalidAnnounces(t, lpv2) } +func TestInvalidAnnouncesLES3(t *testing.T) { testInvalidAnnounces(t, lpv3) } +func TestInvalidAnnouncesLES4(t *testing.T) { testInvalidAnnounces(t, lpv4) } + +func testInvalidAnnounces(t *testing.T, protocol int) { + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + s, c, teardown := newClientServerEnv(t, netconfig) defer teardown() // Create connected peer pair. diff --git a/les/handler_test.go b/les/handler_test.go index e251f4503b..d1dbee6bdf 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -52,9 +52,16 @@ func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) } func TestGetBlockHeadersLes4(t *testing.T) { testGetBlockHeaders(t, 4) } func testGetBlockHeaders(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, downloader.MaxHeaderFetch+15, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: downloader.MaxHeaderFetch + 15, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() bc := server.handler.blockchain // Create a "random" unknown hash for testing @@ -169,8 +176,8 @@ func testGetBlockHeaders(t *testing.T, protocol int) { // Send the hash request and verify the response reqID++ - sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, tt.query) - if err := expectResponse(server.peer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil { + sendRequest(rawPeer.app, GetBlockHeadersMsg, reqID, tt.query) + if err := expectResponse(rawPeer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil { t.Errorf("test %d: headers mismatch: %v", i, err) } } @@ -182,9 +189,17 @@ func TestGetBlockBodiesLes3(t *testing.T) { testGetBlockBodies(t, 3) } func TestGetBlockBodiesLes4(t *testing.T) { testGetBlockBodies(t, 4) } func testGetBlockBodies(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, downloader.MaxBlockFetch+15, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: downloader.MaxHeaderFetch + 15, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain // Create a batch of tests for various scenarios @@ -247,8 +262,8 @@ func testGetBlockBodies(t *testing.T, protocol int) { reqID++ // Send the hash request and verify the response - sendRequest(server.peer.app, GetBlockBodiesMsg, reqID, hashes) - if err := expectResponse(server.peer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil { + sendRequest(rawPeer.app, GetBlockBodiesMsg, reqID, hashes) + if err := expectResponse(rawPeer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil { t.Errorf("test %d: bodies mismatch: %v", i, err) } } @@ -261,8 +276,17 @@ func TestGetCodeLes4(t *testing.T) { testGetCode(t, 4) } func testGetCode(t *testing.T, protocol int) { // Assemble the test environment - server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain var codereqs []*CodeReq @@ -279,8 +303,8 @@ func testGetCode(t *testing.T, protocol int) { } } - sendRequest(server.peer.app, GetCodeMsg, 42, codereqs) - if err := expectResponse(server.peer.app, CodeMsg, 42, testBufLimit, codes); err != nil { + sendRequest(rawPeer.app, GetCodeMsg, 42, codereqs) + if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, codes); err != nil { t.Errorf("codes mismatch: %v", err) } } @@ -291,8 +315,17 @@ func TestGetStaleCodeLes3(t *testing.T) { testGetStaleCode(t, 3) } func TestGetStaleCodeLes4(t *testing.T) { testGetStaleCode(t, 4) } func testGetStaleCode(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: core.TriesInMemory + 4, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain check := func(number uint64, expected [][]byte) { @@ -300,8 +333,8 @@ func testGetStaleCode(t *testing.T, protocol int) { BHash: bc.GetHeaderByNumber(number).Hash(), AccKey: crypto.Keccak256(testContractAddr[:]), } - sendRequest(server.peer.app, GetCodeMsg, 42, []*CodeReq{req}) - if err := expectResponse(server.peer.app, CodeMsg, 42, testBufLimit, expected); err != nil { + sendRequest(rawPeer.app, GetCodeMsg, 42, []*CodeReq{req}) + if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, expected); err != nil { t.Errorf("codes mismatch: %v", err) } } @@ -317,9 +350,17 @@ func TestGetReceiptLes4(t *testing.T) { testGetReceipt(t, 4) } func testGetReceipt(t *testing.T, protocol int) { // Assemble the test environment - server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain // Collect the hashes to request, and the response to expect @@ -332,8 +373,8 @@ func testGetReceipt(t *testing.T, protocol int) { receipts = append(receipts, rawdb.ReadRawReceipts(server.db, block.Hash(), block.NumberU64())) } // Send the hash request and verify the response - sendRequest(server.peer.app, GetReceiptsMsg, 42, hashes) - if err := expectResponse(server.peer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil { + sendRequest(rawPeer.app, GetReceiptsMsg, 42, hashes) + if err := expectResponse(rawPeer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil { t.Errorf("receipts mismatch: %v", err) } } @@ -345,9 +386,17 @@ func TestGetProofsLes4(t *testing.T) { testGetProofs(t, 4) } func testGetProofs(t *testing.T, protocol int) { // Assemble the test environment - server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain var proofreqs []ProofReq @@ -368,8 +417,8 @@ func testGetProofs(t *testing.T, protocol int) { } } // Send the proof request and verify the response - sendRequest(server.peer.app, GetProofsV2Msg, 42, proofreqs) - if err := expectResponse(server.peer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil { + sendRequest(rawPeer.app, GetProofsV2Msg, 42, proofreqs) + if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil { t.Errorf("proofs mismatch: %v", err) } } @@ -380,8 +429,17 @@ func TestGetStaleProofLes3(t *testing.T) { testGetStaleProof(t, 3) } func TestGetStaleProofLes4(t *testing.T) { testGetStaleProof(t, 4) } func testGetStaleProof(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: core.TriesInMemory + 4, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain check := func(number uint64, wantOK bool) { @@ -393,7 +451,7 @@ func testGetStaleProof(t *testing.T, protocol int) { BHash: header.Hash(), Key: account, } - sendRequest(server.peer.app, GetProofsV2Msg, 42, []*ProofReq{req}) + sendRequest(rawPeer.app, GetProofsV2Msg, 42, []*ProofReq{req}) var expected []rlp.RawValue if wantOK { @@ -402,7 +460,7 @@ func testGetStaleProof(t *testing.T, protocol int) { t.Prove(account, 0, proofsV2) expected = proofsV2.NodeList() } - if err := expectResponse(server.peer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil { + if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil { t.Errorf("codes mismatch: %v", err) } } @@ -417,20 +475,30 @@ func TestGetCHTProofsLes3(t *testing.T) { testGetCHTProofs(t, 3) } func TestGetCHTProofsLes4(t *testing.T) { testGetCHTProofs(t, 4) } func testGetCHTProofs(t *testing.T, protocol int) { - config := light.TestServerIndexerConfig - - waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - for { - cs, _, _ := cIndexer.Sections() - if cs >= 1 { - break + var ( + config = light.TestServerIndexerConfig + waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + cs, _, _ := cIndexer.Sections() + if cs >= 1 { + break + } + time.Sleep(10 * time.Millisecond) } - time.Sleep(10 * time.Millisecond) } - } - server, tearDown := newServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, false, true, 0) + netconfig = testnetConfig{ + blocks: int(config.ChtSize + config.ChtConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + ) + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain // Assemble the proofs from the different protocols @@ -454,8 +522,8 @@ func testGetCHTProofs(t *testing.T, protocol int) { AuxReq: htAuxHeader, }} // Send the proof request and verify the response - sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, requestsV2) - if err := expectResponse(server.peer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil { + sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requestsV2) + if err := expectResponse(rawPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil { t.Errorf("proofs mismatch: %v", err) } } @@ -466,20 +534,30 @@ func TestGetBloombitsProofsLes4(t *testing.T) { testGetBloombitsProofs(t, 4) } // Tests that bloombits proofs can be correctly retrieved. func testGetBloombitsProofs(t *testing.T, protocol int) { - config := light.TestServerIndexerConfig - - waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - for { - bts, _, _ := btIndexer.Sections() - if bts >= 1 { - break + var ( + config = light.TestServerIndexerConfig + waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + bts, _, _ := btIndexer.Sections() + if bts >= 1 { + break + } + time.Sleep(10 * time.Millisecond) } - time.Sleep(10 * time.Millisecond) } - } - server, tearDown := newServerEnv(t, int(config.BloomTrieSize+config.BloomTrieConfirms), protocol, waitIndexers, false, true, 0) + netconfig = testnetConfig{ + blocks: int(config.BloomTrieSize + config.BloomTrieConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + ) + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain // Request and verify each bit of the bloom bits proofs @@ -503,20 +581,28 @@ func testGetBloombitsProofs(t *testing.T, protocol int) { trie.Prove(key, 0, &proofs.Proofs) // Send the proof request and verify the response - sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, requests) - if err := expectResponse(server.peer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil { + sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requests) + if err := expectResponse(rawPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil { t.Errorf("bit %d: proofs mismatch: %v", bit, err) } } } -func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, 2) } -func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, 3) } -func TestTransactionStatusLes4(t *testing.T) { testTransactionStatus(t, 4) } +func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, lpv2) } +func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, lpv3) } +func TestTransactionStatusLes4(t *testing.T) { testTransactionStatus(t, lpv4) } func testTransactionStatus(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, 0, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + server.handler.addTxsSync = true chain := server.handler.blockchain @@ -526,11 +612,11 @@ func testTransactionStatus(t *testing.T, protocol int) { test := func(tx *types.Transaction, send bool, expStatus light.TxStatus) { reqID++ if send { - sendRequest(server.peer.app, SendTxV2Msg, reqID, types.Transactions{tx}) + sendRequest(rawPeer.app, SendTxV2Msg, reqID, types.Transactions{tx}) } else { - sendRequest(server.peer.app, GetTxStatusMsg, reqID, []common.Hash{tx.Hash()}) + sendRequest(rawPeer.app, GetTxStatusMsg, reqID, []common.Hash{tx.Hash()}) } - if err := expectResponse(server.peer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil { + if err := expectResponse(rawPeer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil { t.Errorf("transaction status mismatch") } } @@ -572,7 +658,7 @@ func testTransactionStatus(t *testing.T, protocol int) { t.Fatalf("pending count mismatch: have %d, want 1", pending) } // Discard new block announcement - msg, _ := server.peer.app.ReadMsg() + msg, _ := rawPeer.app.ReadMsg() msg.Discard() // check if their status is included now @@ -597,7 +683,7 @@ func testTransactionStatus(t *testing.T, protocol int) { t.Fatalf("pending count mismatch: have %d, want 3", pending) } // Discard new block announcement - msg, _ = server.peer.app.ReadMsg() + msg, _ = rawPeer.app.ReadMsg() msg.Discard() // check if their status is pending again @@ -605,11 +691,23 @@ func testTransactionStatus(t *testing.T, protocol int) { test(tx2, false, light.TxStatus{Status: core.TxStatusPending}) } -func TestStopResumeLes3(t *testing.T) { - server, tearDown := newServerEnv(t, 0, 3, nil, true, true, testBufLimit/10) +func TestStopResumeLES3(t *testing.T) { testStopResume(t, lpv3) } +func TestStopResumeLES4(t *testing.T) { testStopResume(t, lpv4) } + +func testStopResume(t *testing.T, protocol int) { + netconfig := testnetConfig{ + protocol: protocol, + simClock: true, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() server.handler.server.costTracker.testing = true + server.handler.server.costTracker.testCostList = testCostList(testBufLimit / 10) + + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() var ( reqID uint64 @@ -619,14 +717,14 @@ func TestStopResumeLes3(t *testing.T) { header := server.handler.blockchain.CurrentHeader() req := func() { reqID++ - sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1}) + sendRequest(rawPeer.app, GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1}) } for i := 1; i <= 5; i++ { // send requests while we still have enough buffer and expect a response for expBuf >= testCost { req() expBuf -= testCost - if err := expectResponse(server.peer.app, BlockHeadersMsg, reqID, expBuf, []*types.Header{header}); err != nil { + if err := expectResponse(rawPeer.app, BlockHeadersMsg, reqID, expBuf, []*types.Header{header}); err != nil { t.Errorf("expected response and failed: %v", err) } } @@ -636,7 +734,7 @@ func TestStopResumeLes3(t *testing.T) { req() c-- } - if err := p2p.ExpectMsg(server.peer.app, StopMsg, nil); err != nil { + if err := p2p.ExpectMsg(rawPeer.app, StopMsg, nil); err != nil { t.Errorf("expected StopMsg and failed: %v", err) } // wait until the buffer is recharged by half of the limit @@ -645,7 +743,7 @@ func TestStopResumeLes3(t *testing.T) { // expect a ResumeMsg with the partially recharged buffer value expBuf += testBufRecharge * wait - if err := p2p.ExpectMsg(server.peer.app, ResumeMsg, expBuf); err != nil { + if err := p2p.ExpectMsg(rawPeer.app, ResumeMsg, expBuf); err != nil { t.Errorf("expected ResumeMsg and failed: %v", err) } } diff --git a/les/odr.go b/les/odr.go index 2c36f512de..d45c6a1a5d 100644 --- a/les/odr.go +++ b/les/odr.go @@ -18,6 +18,7 @@ package les import ( "context" + "sort" "time" "github.com/ethereum/go-ethereum/common/mclock" @@ -31,14 +32,16 @@ type LesOdr struct { db ethdb.Database indexerConfig *light.IndexerConfig chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer + peers *serverPeerSet retriever *retrieveManager stop chan struct{} } -func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, retriever *retrieveManager) *LesOdr { +func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, peers *serverPeerSet, retriever *retrieveManager) *LesOdr { return &LesOdr{ db: db, indexerConfig: config, + peers: peers, retriever: retriever, stop: make(chan struct{}), } @@ -98,7 +101,101 @@ type Msg struct { Obj interface{} } -// Retrieve tries to fetch an object from the LES network. +// peerByTxHistory is a heap.Interface implementation which can sort +// the peerset by transaction history. +type peerByTxHistory []*serverPeer + +func (h peerByTxHistory) Len() int { return len(h) } +func (h peerByTxHistory) Less(i, j int) bool { + if h[i].txHistory == txIndexUnlimited { + return false + } + if h[j].txHistory == txIndexUnlimited { + return true + } + return h[i].txHistory < h[j].txHistory +} +func (h peerByTxHistory) Swap(i, j int) { h[i], h[j] = h[j], h[i] } + +const ( + maxTxStatusRetry = 3 // The maximum retrys will be made for tx status request. + maxTxStatusCandidates = 5 // The maximum les servers the tx status requests will be sent to. +) + +// RetrieveTxStatus retrieves the transaction status from the LES network. +// There is no guarantee in the LES protocol that the mined transaction will +// be retrieved back for sure because of different reasons(the transaction +// is unindexed, the malicous server doesn't reply it deliberately, etc). +// Therefore, unretrieved transactions(UNKNOWN) will receive a certain number +// of retries, thus giving a weak guarantee. +func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequest) error { + // Sort according to the transaction history supported by the peer and + // select the peers with longest history. + var ( + retries int + peers []*serverPeer + missing = len(req.Hashes) + result = make([]light.TxStatus, len(req.Hashes)) + canSend = make(map[string]bool) + ) + for _, peer := range odr.peers.allPeers() { + if peer.txHistory == txIndexDisabled { + continue + } + peers = append(peers, peer) + } + sort.Sort(sort.Reverse(peerByTxHistory(peers))) + for i := 0; i < maxTxStatusCandidates && i < len(peers); i++ { + canSend[peers[i].id] = true + } + // Send out the request and assemble the result. + for { + if retries >= maxTxStatusRetry || len(canSend) == 0 { + break + } + var ( + // Deep copy the request, so that the partial result won't be mixed. + req = &TxStatusRequest{Hashes: req.Hashes} + id = genReqID() + distreq = &distReq{ + getCost: func(dp distPeer) uint64 { return req.GetCost(dp.(*serverPeer)) }, + canSend: func(dp distPeer) bool { return canSend[dp.(*serverPeer).id] }, + request: func(dp distPeer) func() { + p := dp.(*serverPeer) + p.fcServer.QueuedRequest(id, req.GetCost(p)) + delete(canSend, p.id) + return func() { req.Request(id, p) } + }, + } + ) + if err := odr.retriever.retrieve(ctx, id, distreq, func(p distPeer, msg *Msg) error { return req.Validate(odr.db, msg) }, odr.stop); err != nil { + return err + } + // Collect the response and assemble them to the final result. + // All the response is not verifiable, so always pick the first + // one we get. + for index, status := range req.Status { + if result[index].Status != core.TxStatusUnknown { + continue + } + if status.Status == core.TxStatusUnknown { + continue + } + result[index], missing = status, missing-1 + } + // Abort the procedure if all the status are retrieved + if missing == 0 { + break + } + retries += 1 + } + req.Status = result + return nil +} + +// Retrieve tries to fetch an object from the LES network. It's a common API +// for most of the LES requests except for the TxStatusRequest which needs +// the additional retry mechanism. // If the network retrieval was successful, it stores the object in local db. func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) { lreq := LesRequest(req) diff --git a/les/odr_requests.go b/les/odr_requests.go index 711c54b40f..d548fb1ee0 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -487,7 +487,7 @@ func (r *TxStatusRequest) GetCost(peer *serverPeer) uint64 { // CanSend tells if a certain peer is suitable for serving the given request func (r *TxStatusRequest) CanSend(peer *serverPeer) bool { - return peer.serveTxLookup + return peer.txHistory != txIndexDisabled } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) @@ -496,13 +496,12 @@ func (r *TxStatusRequest) Request(reqID uint64, peer *serverPeer) error { return peer.requestTxStatus(reqID, r.Hashes) } -// Valid processes an ODR request reply message from the LES network +// Validate processes an ODR request reply message from the LES network // returns true and stores results in memory if the message was a valid reply // to the request (implementation of LesOdrRequest) func (r *TxStatusRequest) Validate(db ethdb.Database, msg *Msg) error { log.Debug("Validating transaction status", "count", len(r.Hashes)) - // Ensure we have a correct message with a single block body if msg.MsgType != MsgTxStatus { return errInvalidMessageType } diff --git a/les/odr_test.go b/les/odr_test.go index 2a70a296c8..a43382e05e 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -19,7 +19,10 @@ package les import ( "bytes" "context" + "crypto/rand" + "fmt" "math/big" + "reflect" "testing" "time" @@ -190,7 +193,13 @@ func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainCon // testOdr tests odr requests whose validation guaranteed by block headers. func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) { // Assemble the test environment - server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true, true) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + connect: true, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() // Ensure the client has synced all necessary data. @@ -246,3 +255,184 @@ func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn od test(5) } } + +func TestGetTxStatusFromUnindexedPeersLES4(t *testing.T) { testGetTxStatusFromUnindexedPeers(t, lpv4) } + +func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) { + var ( + blocks = 8 + netconfig = testnetConfig{ + blocks: blocks, + protocol: protocol, + nopruning: true, + } + ) + server, client, tearDown := newClientServerEnv(t, netconfig) + defer tearDown() + + // Iterate the chain, create the tx indexes locally + var ( + testHash common.Hash + testStatus light.TxStatus + + txs = make(map[common.Hash]*types.Transaction) // Transaction objects set + blockNumbers = make(map[common.Hash]uint64) // Transaction hash to block number mappings + blockHashes = make(map[common.Hash]common.Hash) // Transaction hash to block hash mappings + intraIndex = make(map[common.Hash]uint64) // Transaction intra-index in block + ) + for number := uint64(1); number < server.backend.Blockchain().CurrentBlock().NumberU64(); number++ { + block := server.backend.Blockchain().GetBlockByNumber(number) + if block == nil { + t.Fatalf("Failed to retrieve block %d", number) + } + for index, tx := range block.Transactions() { + txs[tx.Hash()] = tx + blockNumbers[tx.Hash()] = number + blockHashes[tx.Hash()] = block.Hash() + intraIndex[tx.Hash()] = uint64(index) + + if testHash == (common.Hash{}) { + testHash = tx.Hash() + testStatus = light.TxStatus{ + Status: core.TxStatusIncluded, + Lookup: &rawdb.LegacyTxLookupEntry{ + BlockHash: block.Hash(), + BlockIndex: block.NumberU64(), + Index: uint64(index), + }, + } + } + } + } + // serveMsg processes incoming GetTxStatusMsg and sends the response back. + serveMsg := func(peer *testPeer, txLookup uint64) error { + msg, err := peer.app.ReadMsg() + if err != nil { + return err + } + if msg.Code != GetTxStatusMsg { + return fmt.Errorf("message code mismatch: got %d, expected %d", msg.Code, GetTxStatusMsg) + } + var r GetTxStatusPacket + if err := msg.Decode(&r); err != nil { + return err + } + stats := make([]light.TxStatus, len(r.Hashes)) + for i, hash := range r.Hashes { + number, exist := blockNumbers[hash] + if !exist { + continue // Filter out unknown transactions + } + min := uint64(blocks) - txLookup + if txLookup != txIndexUnlimited && (txLookup == txIndexDisabled || number < min) { + continue // Filter out unindexed transactions + } + stats[i].Status = core.TxStatusIncluded + stats[i].Lookup = &rawdb.LegacyTxLookupEntry{ + BlockHash: blockHashes[hash], + BlockIndex: number, + Index: intraIndex[hash], + } + } + data, _ := rlp.EncodeToBytes(stats) + reply := &reply{peer.app, TxStatusMsg, r.ReqID, data} + reply.send(testBufLimit) + return nil + } + + var testspecs = []struct { + peers int + txLookups []uint64 + txs []common.Hash + results []light.TxStatus + }{ + // Retrieve mined transaction from the empty peerset + { + peers: 0, + txLookups: []uint64{}, + txs: []common.Hash{testHash}, + results: []light.TxStatus{{}}, + }, + // Retrieve unknown transaction from the full peers + { + peers: 3, + txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, + txs: []common.Hash{randomHash()}, + results: []light.TxStatus{{}}, + }, + // Retrieve mined transaction from the full peers + { + peers: 3, + txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, + txs: []common.Hash{testHash}, + results: []light.TxStatus{testStatus}, + }, + // Retrieve mixed transactions from the full peers + { + peers: 3, + txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, + txs: []common.Hash{randomHash(), testHash}, + results: []light.TxStatus{{}, testStatus}, + }, + // Retrieve mixed transactions from unindexed peer(but the target is still available) + { + peers: 3, + txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2}, + txs: []common.Hash{randomHash(), testHash}, + results: []light.TxStatus{{}, testStatus}, + }, + // Retrieve mixed transactions from unindexed peer(but the target is not available) + { + peers: 3, + txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2}, + txs: []common.Hash{randomHash(), testHash}, + results: []light.TxStatus{{}, {}}, + }, + } + for _, testspec := range testspecs { + // Create a bunch of server peers with different tx history + var ( + serverPeers []*testPeer + closeFns []func() + ) + for i := 0; i < testspec.peers; i++ { + peer, closePeer, _ := client.newRawPeer(t, fmt.Sprintf("server-%d", i), protocol, testspec.txLookups[i]) + serverPeers = append(serverPeers, peer) + closeFns = append(closeFns, closePeer) + + // Create a one-time routine for serving message + go func(i int, peer *testPeer) { + serveMsg(peer, testspec.txLookups[i]) + }(i, peer) + } + + // Send out the GetTxStatus requests, compare the result with + // expected value. + r := &light.TxStatusRequest{Hashes: testspec.txs} + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + err := client.handler.backend.odr.RetrieveTxStatus(ctx, r) + if err != nil { + t.Errorf("Failed to retrieve tx status %v", err) + } else { + if !reflect.DeepEqual(testspec.results, r.Status) { + t.Errorf("Result mismatch, diff") + } + } + + // Close all connected peers and start the next round + for _, closeFn := range closeFns { + closeFn() + } + } +} + +// randomHash generates a random blob of data and returns it as a hash. +func randomHash() common.Hash { + var hash common.Hash + if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil { + panic(err) + } + return hash +} diff --git a/les/peer.go b/les/peer.go index 479b4034bc..0361167ee1 100644 --- a/les/peer.go +++ b/les/peer.go @@ -341,7 +341,7 @@ type serverPeer struct { onlyAnnounce bool // The flag whether the server sends announcement only. chainSince, chainRecent uint64 // The range of chain server peer can serve. stateSince, stateRecent uint64 // The range of state server peer can serve. - serveTxLookup bool // The server peer can serve tx lookups. + txHistory uint64 // The length of available tx history, 0 means all, 1 means disabled // Advertised checkpoint fields checkpointNumber uint64 // The block height which the checkpoint is registered. @@ -634,13 +634,13 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter if err := recv.get("recentTxLookup", &recentTx); err != nil { return err } - // Note: in the current version we only consider the tx index service useful - // if it is unlimited. This can be made configurable in the future. - p.serveTxLookup = recentTx == txIndexUnlimited + p.txHistory = uint64(recentTx) } else { - p.serveTxLookup = true + // The weak assumption is held here that legacy les server(les2,3) + // has unlimited transaction history. The les serving in these legacy + // versions is disabled if the transaction is unindexed. + p.txHistory = txIndexUnlimited } - if p.onlyAnnounce && !p.trusted { return errResp(ErrUselessPeer, "peer cannot serve requests") } diff --git a/les/pruner_test.go b/les/pruner_test.go index 62b4e9a950..c6f198c088 100644 --- a/les/pruner_test.go +++ b/les/pruner_test.go @@ -28,19 +28,26 @@ import ( ) func TestLightPruner(t *testing.T) { - config := light.TestClientIndexerConfig - - waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - for { - cs, _, _ := cIndexer.Sections() - bts, _, _ := btIndexer.Sections() - if cs >= 3 && bts >= 3 { - break + var ( + waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + cs, _, _ := cIndexer.Sections() + bts, _, _ := btIndexer.Sections() + if cs >= 3 && bts >= 3 { + break + } + time.Sleep(10 * time.Millisecond) } - time.Sleep(10 * time.Millisecond) } - } - server, client, tearDown := newClientServerEnv(t, int(3*config.ChtSize+config.ChtConfirms), 2, waitIndexers, nil, 0, false, true, false) + config = light.TestClientIndexerConfig + netconfig = testnetConfig{ + blocks: int(3*config.ChtSize + config.ChtConfirms), + protocol: 3, + indexFn: waitIndexers, + connect: true, + } + ) + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() // checkDB iterates the chain with given prefix, resolves the block number diff --git a/les/request_test.go b/les/request_test.go index b054fd88df..c65405e375 100644 --- a/les/request_test.go +++ b/les/request_test.go @@ -83,7 +83,14 @@ func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrReq func testAccess(t *testing.T, protocol int, fn accessTestFn) { // Assemble the test environment - server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true, true) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + indexFn: nil, + connect: true, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() // Ensure the client has synced all necessary data. diff --git a/les/sync_test.go b/les/sync_test.go index 64e7283663..d3bb90df02 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -31,15 +31,15 @@ import ( ) // Test light syncing which will download all headers from genesis. -func TestLightSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 0) } +func TestLightSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 0) } // Test legacy checkpoint syncing which will download tail headers // based on a hardcoded checkpoint. -func TestLegacyCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 1) } +func TestLegacyCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 1) } // Test checkpoint syncing which will download tail headers based // on a verified checkpoint. -func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 2) } +func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 2) } func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { config := light.TestServerIndexerConfig @@ -55,7 +55,13 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { } } // Generate 128+1 blocks (totally 1 CHT section) - server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: int(config.ChtSize + config.ChtConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() expected := config.ChtSize + config.ChtConfirms @@ -78,7 +84,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { // Register the assembled checkpoint into oracle. header := server.backend.Blockchain().CurrentHeader() - data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) + data := append([]byte{0x19, 0x00}, append(oracleAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337)) @@ -128,10 +134,10 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { } } -func TestMissOracleBackend(t *testing.T) { testMissOracleBackend(t, true) } -func TestMissOracleBackendNoCheckpoint(t *testing.T) { testMissOracleBackend(t, false) } +func TestMissOracleBackendLES3(t *testing.T) { testMissOracleBackend(t, true, lpv3) } +func TestMissOracleBackendNoCheckpointLES3(t *testing.T) { testMissOracleBackend(t, false, lpv3) } -func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { +func testMissOracleBackend(t *testing.T, hasCheckpoint bool, protocol int) { config := light.TestServerIndexerConfig waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { @@ -145,7 +151,13 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { } } // Generate 128+1 blocks (totally 1 CHT section) - server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: int(config.ChtSize + config.ChtConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() expected := config.ChtSize + config.ChtConfirms @@ -160,7 +172,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { // Register the assembled checkpoint into oracle. header := server.backend.Blockchain().CurrentHeader() - data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) + data := append([]byte{0x19, 0x00}, append(oracleAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337)) @@ -220,7 +232,9 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { } } -func TestSyncFromConfiguredCheckpoint(t *testing.T) { +func TestSyncFromConfiguredCheckpointLES3(t *testing.T) { testSyncFromConfiguredCheckpoint(t, lpv3) } + +func testSyncFromConfiguredCheckpoint(t *testing.T, protocol int) { config := light.TestServerIndexerConfig waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { @@ -234,7 +248,13 @@ func TestSyncFromConfiguredCheckpoint(t *testing.T) { } } // Generate 256+1 blocks (totally 2 CHT sections) - server, client, tearDown := newClientServerEnv(t, int(2*config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: int(2*config.ChtSize + config.ChtConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() // Configure the local checkpoint(the first section) @@ -296,7 +316,9 @@ func TestSyncFromConfiguredCheckpoint(t *testing.T) { } } -func TestSyncAll(t *testing.T) { +func TestSyncAll(t *testing.T) { testSyncAll(t, lpv3) } + +func testSyncAll(t *testing.T, protocol int) { config := light.TestServerIndexerConfig waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { @@ -310,7 +332,13 @@ func TestSyncAll(t *testing.T) { } } // Generate 256+1 blocks (totally 2 CHT sections) - server, client, tearDown := newClientServerEnv(t, int(2*config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: int(2*config.ChtSize + config.ChtConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() client.handler.backend.config.SyncFromCheckpoint = true diff --git a/les/test_helper.go b/les/test_helper.go index 6f93c8a48f..39313b1e3b 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -14,8 +14,9 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// This file contains some shares testing functionality, common to multiple -// different files and modules being tested. +// This file contains some shares testing functionality, common to multiple +// different files and modules being tested. Client based network and Server +// based network can be created easily with available APIs. package les @@ -68,10 +69,10 @@ var ( testEventEmitterCode = common.Hex2Bytes("60606040523415600e57600080fd5b7f57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e60405160405180910390a160358060476000396000f3006060604052600080fd00a165627a7a723058203f727efcad8b5811f8cb1fc2620ce5e8c63570d697aef968172de296ea3994140029") - // Checkpoint registrar relative - registrarAddr common.Address - signerKey, _ = crypto.GenerateKey() - signerAddr = crypto.PubkeyToAddress(signerKey.PublicKey) + // Checkpoint oracle relative fields + oracleAddr common.Address + signerKey, _ = crypto.GenerateKey() + signerAddr = crypto.PubkeyToAddress(signerKey.PublicKey) ) var ( @@ -112,14 +113,23 @@ func prepare(n int, backend *backends.SimulatedBackend) { for i := 0; i < n; i++ { switch i { case 0: + // Builtin-block + // number: 1 + // txs: 2 + // deploy checkpoint contract auth, _ := bind.NewKeyedTransactorWithChainID(bankKey, big.NewInt(1337)) - registrarAddr, _, _, _ = contract.DeployCheckpointOracle(auth, backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) + oracleAddr, _, _, _ = contract.DeployCheckpointOracle(auth, backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) + // bankUser transfers some ether to user1 nonce, _ := backend.PendingNonceAt(ctx, bankAddr) tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10000), params.TxGas, nil, nil), signer, bankKey) backend.SendTransaction(ctx, tx) case 1: + // Builtin-block + // number: 2 + // txs: 4 + bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) userNonce1, _ := backend.PendingNonceAt(ctx, userAddr1) @@ -140,6 +150,10 @@ func prepare(n int, backend *backends.SimulatedBackend) { tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(0), testEventEmitterCode), signer, userKey1) backend.SendTransaction(ctx, tx4) case 2: + // Builtin-block + // number: 3 + // txs: 2 + // bankUser transfer some ether to signer bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) tx1, _ := types.SignTx(types.NewTransaction(bankNonce, signerAddr, big.NewInt(1000000000), params.TxGas, nil, nil), signer, bankKey) @@ -150,6 +164,10 @@ func prepare(n int, backend *backends.SimulatedBackend) { tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, nil, data), signer, bankKey) backend.SendTransaction(ctx, tx2) case 3: + // Builtin-block + // number: 4 + // txs: 1 + // invoke test contract bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") @@ -310,45 +328,61 @@ type testPeer struct { app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side } -// newTestPeer creates a new peer registered at the given protocol manager. -func newTestPeer(t *testing.T, name string, version int, handler *serverHandler, shake bool, testCost uint64) (*testPeer, <-chan error) { - // Create a message pipe to communicate through - app, net := p2p.MsgPipe() - - // Generate a random id and create the peer - var id enode.ID - rand.Read(id[:]) - peer := newClientPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) +// handshakeWithServer executes the handshake with the remote server peer. +func (p *testPeer) handshakeWithServer(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID) { + // It only works for the simulated client peer + if p.cpeer == nil { + t.Fatal("handshake for client peer only") + } + var sendList keyValueList + sendList = sendList.add("protocolVersion", uint64(p.cpeer.version)) + sendList = sendList.add("networkId", uint64(NetworkId)) + sendList = sendList.add("headTd", td) + sendList = sendList.add("headHash", head) + sendList = sendList.add("headNum", headNum) + sendList = sendList.add("genesisHash", genesis) + if p.cpeer.version >= lpv4 { + sendList = sendList.add("forkID", &forkID) + } + if err := p2p.ExpectMsg(p.app, StatusMsg, nil); err != nil { + t.Fatalf("status recv: %v", err) + } + if err := p2p.Send(p.app, StatusMsg, sendList); err != nil { + t.Fatalf("status send: %v", err) + } +} - // Start the peer on a new thread - errCh := make(chan error, 1) - go func() { - select { - case <-handler.closeCh: - errCh <- p2p.DiscQuitting - case errCh <- handler.handle(peer): - } - }() - tp := &testPeer{ - app: app, - net: net, - cpeer: peer, +// handshakeWithClient executes the handshake with the remote client peer. +func (p *testPeer) handshakeWithClient(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, costList RequestCostList, recentTxLookup uint64) { + // It only works for the simulated client peer + if p.speer == nil { + t.Fatal("handshake for server peer only") } - // Execute any implicitly requested handshakes and return - if shake { - // Customize the cost table if required. - if testCost != 0 { - handler.server.costTracker.testCostList = testCostList(testCost) - } - var ( - genesis = handler.blockchain.Genesis() - head = handler.blockchain.CurrentHeader() - td = handler.blockchain.GetTd(head.Hash(), head.Number.Uint64()) - ) - forkID := forkid.NewID(handler.blockchain.Config(), genesis.Hash(), head.Number.Uint64()) - tp.handshake(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID, testCostList(testCost)) + var sendList keyValueList + sendList = sendList.add("protocolVersion", uint64(p.speer.version)) + sendList = sendList.add("networkId", uint64(NetworkId)) + sendList = sendList.add("headTd", td) + sendList = sendList.add("headHash", head) + sendList = sendList.add("headNum", headNum) + sendList = sendList.add("genesisHash", genesis) + sendList = sendList.add("serveHeaders", nil) + sendList = sendList.add("serveChainSince", uint64(0)) + sendList = sendList.add("serveStateSince", uint64(0)) + sendList = sendList.add("serveRecentState", uint64(core.TriesInMemory-4)) + sendList = sendList.add("txRelay", nil) + sendList = sendList.add("flowControl/BL", testBufLimit) + sendList = sendList.add("flowControl/MRR", testBufRecharge) + sendList = sendList.add("flowControl/MRC", costList) + if p.speer.version >= lpv4 { + sendList = sendList.add("forkID", &forkID) + sendList = sendList.add("recentTxLookup", recentTxLookup) + } + if err := p2p.ExpectMsg(p.app, StatusMsg, nil); err != nil { + t.Fatalf("status recv: %v", err) + } + if err := p2p.Send(p.app, StatusMsg, sendList); err != nil { + t.Fatalf("status send: %v", err) } - return tp, errCh } // close terminates the local side of the peer, notifying the remote protocol @@ -402,48 +436,9 @@ func newTestPeerPair(name string, version int, server *serverHandler, client *cl return &testPeer{cpeer: peer1, net: net, app: app}, &testPeer{speer: peer2, net: app, app: net}, nil } -// handshake simulates a trivial handshake that expects the same state from the -// remote side as we are simulating locally. -func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, costList RequestCostList) { - var expList keyValueList - expList = expList.add("protocolVersion", uint64(p.cpeer.version)) - expList = expList.add("networkId", uint64(NetworkId)) - expList = expList.add("headTd", td) - expList = expList.add("headHash", head) - expList = expList.add("headNum", headNum) - expList = expList.add("genesisHash", genesis) - if p.cpeer.version >= lpv4 { - expList = expList.add("forkID", &forkID) - } - sendList := make(keyValueList, len(expList)) - copy(sendList, expList) - expList = expList.add("serveHeaders", nil) - expList = expList.add("serveChainSince", uint64(0)) - expList = expList.add("serveStateSince", uint64(0)) - expList = expList.add("serveRecentState", uint64(core.TriesInMemory-4)) - expList = expList.add("txRelay", nil) - if p.cpeer.version >= lpv4 { - expList = expList.add("recentTxLookup", uint64(0)) - } - expList = expList.add("flowControl/BL", testBufLimit) - expList = expList.add("flowControl/MRR", testBufRecharge) - expList = expList.add("flowControl/MRC", costList) - - if err := p2p.ExpectMsg(p.app, StatusMsg, expList); err != nil { - t.Fatalf("status recv: %v", err) - } - if err := p2p.Send(p.app, StatusMsg, sendList); err != nil { - t.Fatalf("status send: %v", err) - } - p.cpeer.fcParams = flowcontrol.ServerParams{ - BufLimit: testBufLimit, - MinRecharge: testBufRecharge, - } -} - type indexerCallback func(*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer) -// testClient represents a client for testing with necessary auxiliary fields. +// testClient represents a client object for testing with necessary auxiliary fields. type testClient struct { clock mclock.Clock db ethdb.Database @@ -455,7 +450,58 @@ type testClient struct { bloomTrieIndexer *core.ChainIndexer } -// testServer represents a server for testing with necessary auxiliary fields. +// newRawPeer creates a new server peer connects to the server and do the handshake. +func (client *testClient) newRawPeer(t *testing.T, name string, version int, recentTxLookup uint64) (*testPeer, func(), <-chan error) { + // Create a message pipe to communicate through + app, net := p2p.MsgPipe() + + // Generate a random id and create the peer + var id enode.ID + rand.Read(id[:]) + peer := newServerPeer(version, NetworkId, false, p2p.NewPeer(id, name, nil), net) + + // Start the peer on a new thread + errCh := make(chan error, 1) + go func() { + select { + case <-client.handler.closeCh: + errCh <- p2p.DiscQuitting + case errCh <- client.handler.handle(peer): + } + }() + tp := &testPeer{ + app: app, + net: net, + speer: peer, + } + var ( + genesis = client.handler.backend.blockchain.Genesis() + head = client.handler.backend.blockchain.CurrentHeader() + td = client.handler.backend.blockchain.GetTd(head.Hash(), head.Number.Uint64()) + ) + forkID := forkid.NewID(client.handler.backend.blockchain.Config(), genesis.Hash(), head.Number.Uint64()) + tp.handshakeWithClient(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID, testCostList(0), recentTxLookup) // disable flow control by default + + // Ensure the connection is established or exits when any error occurs + for { + select { + case <-errCh: + return nil, nil, nil + default: + } + if atomic.LoadUint32(&peer.serving) == 1 { + break + } + time.Sleep(50 * time.Millisecond) + } + closePeer := func() { + tp.speer.close() + tp.close() + } + return tp, closePeer, errCh +} + +// testServer represents a server object for testing with necessary auxiliary fields. type testServer struct { clock mclock.Clock backend *backends.SimulatedBackend @@ -468,89 +514,109 @@ type testServer struct { bloomTrieIndexer *core.ChainIndexer } -func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, simClock bool, newPeer bool, testCost uint64) (*testServer, func()) { - db := rawdb.NewMemoryDatabase() - indexers := testIndexers(db, nil, light.TestServerIndexerConfig, true) +// newRawPeer creates a new client peer connects to the server and do the handshake. +func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*testPeer, func(), <-chan error) { + // Create a message pipe to communicate through + app, net := p2p.MsgPipe() - var clock mclock.Clock = &mclock.System{} - if simClock { - clock = &mclock.Simulated{} - } - handler, b := newTestServerHandler(blocks, indexers, db, clock) + // Generate a random id and create the peer + var id enode.ID + rand.Read(id[:]) + peer := newClientPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) - var peer *testPeer - if newPeer { - peer, _ = newTestPeer(t, "peer", protocol, handler, true, testCost) + // Start the peer on a new thread + errCh := make(chan error, 1) + go func() { + select { + case <-server.handler.closeCh: + errCh <- p2p.DiscQuitting + case errCh <- server.handler.handle(peer): + } + }() + tp := &testPeer{ + app: app, + net: net, + cpeer: peer, } + var ( + genesis = server.handler.blockchain.Genesis() + head = server.handler.blockchain.CurrentHeader() + td = server.handler.blockchain.GetTd(head.Hash(), head.Number.Uint64()) + ) + forkID := forkid.NewID(server.handler.blockchain.Config(), genesis.Hash(), head.Number.Uint64()) + tp.handshakeWithServer(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID) - cIndexer, bIndexer, btIndexer := indexers[0], indexers[1], indexers[2] - cIndexer.Start(handler.blockchain) - bIndexer.Start(handler.blockchain) - - // Wait until indexers generate enough index data. - if callback != nil { - callback(cIndexer, bIndexer, btIndexer) - } - server := &testServer{ - clock: clock, - backend: b, - db: db, - peer: peer, - handler: handler, - chtIndexer: cIndexer, - bloomIndexer: bIndexer, - bloomTrieIndexer: btIndexer, - } - teardown := func() { - if newPeer { - peer.close() - peer.cpeer.close() - b.Close() + // Ensure the connection is established or exits when any error occurs + for { + select { + case <-errCh: + return nil, nil, nil + default: } - cIndexer.Close() - bIndexer.Close() + if atomic.LoadUint32(&peer.serving) == 1 { + break + } + time.Sleep(50 * time.Millisecond) + } + closePeer := func() { + tp.cpeer.close() + tp.close() } - return server, teardown + return tp, closePeer, errCh } -func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, ulcServers []string, ulcFraction int, simClock bool, connect bool, disablePruning bool) (*testServer, *testClient, func()) { - sdb, cdb := rawdb.NewMemoryDatabase(), rawdb.NewMemoryDatabase() - speers := newServerPeerSet() +// testnetConfig wraps all the configurations for testing network. +type testnetConfig struct { + blocks int + protocol int + indexFn indexerCallback + ulcServers []string + ulcFraction int + simClock bool + connect bool + nopruning bool +} +func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testClient, func()) { + var ( + sdb = rawdb.NewMemoryDatabase() + cdb = rawdb.NewMemoryDatabase() + speers = newServerPeerSet() + ) var clock mclock.Clock = &mclock.System{} - if simClock { + if config.simClock { clock = &mclock.Simulated{} } dist := newRequestDistributor(speers, clock) rm := newRetrieveManager(speers, dist, func() time.Duration { return time.Millisecond * 500 }) - odr := NewLesOdr(cdb, light.TestClientIndexerConfig, rm) + odr := NewLesOdr(cdb, light.TestClientIndexerConfig, speers, rm) sindexers := testIndexers(sdb, nil, light.TestServerIndexerConfig, true) - cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig, disablePruning) + cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig, config.nopruning) scIndexer, sbIndexer, sbtIndexer := sindexers[0], sindexers[1], sindexers[2] ccIndexer, cbIndexer, cbtIndexer := cIndexers[0], cIndexers[1], cIndexers[2] odr.SetIndexers(ccIndexer, cbIndexer, cbtIndexer) - server, b := newTestServerHandler(blocks, sindexers, sdb, clock) - client := newTestClientHandler(b, odr, cIndexers, cdb, speers, ulcServers, ulcFraction) + server, b := newTestServerHandler(config.blocks, sindexers, sdb, clock) + client := newTestClientHandler(b, odr, cIndexers, cdb, speers, config.ulcServers, config.ulcFraction) scIndexer.Start(server.blockchain) sbIndexer.Start(server.blockchain) ccIndexer.Start(client.backend.blockchain) cbIndexer.Start(client.backend.blockchain) - if callback != nil { - callback(scIndexer, sbIndexer, sbtIndexer) + if config.indexFn != nil { + config.indexFn(scIndexer, sbIndexer, sbtIndexer) } var ( err error speer, cpeer *testPeer ) - if connect { + if config.connect { done := make(chan struct{}) client.syncEnd = func(_ *types.Header) { close(done) } - cpeer, speer, err = newTestPeerPair("peer", protocol, server, client) + cpeer, speer, err = newTestPeerPair("peer", config.protocol, server, client) if err != nil { t.Fatalf("Failed to connect testing peers %v", err) } @@ -580,7 +646,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer bloomTrieIndexer: cbtIndexer, } teardown := func() { - if connect { + if config.connect { speer.close() cpeer.close() cpeer.cpeer.close() diff --git a/les/ulc_test.go b/les/ulc_test.go index 657b13db2c..d7308fa593 100644 --- a/les/ulc_test.go +++ b/les/ulc_test.go @@ -126,7 +126,12 @@ func connect(server *serverHandler, serverId enode.ID, client *clientHandler, pr // newTestServerPeer creates server peer. func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *enode.Node, func()) { - s, teardown := newServerEnv(t, blocks, protocol, nil, false, false, 0) + netconfig := testnetConfig{ + blocks: blocks, + protocol: protocol, + nopruning: true, + } + s, _, teardown := newClientServerEnv(t, netconfig) key, err := crypto.GenerateKey() if err != nil { t.Fatal("generate key err:", err) @@ -138,6 +143,12 @@ func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *en // newTestLightPeer creates node with light sync mode func newTestLightPeer(t *testing.T, protocol int, ulcServers []string, ulcFraction int) (*testClient, func()) { - _, c, teardown := newClientServerEnv(t, 0, protocol, nil, ulcServers, ulcFraction, false, false, true) + netconfig := testnetConfig{ + protocol: protocol, + ulcServers: ulcServers, + ulcFraction: ulcFraction, + nopruning: true, + } + _, c, teardown := newClientServerEnv(t, netconfig) return c, teardown } diff --git a/light/odr.go b/light/odr.go index bb243f9152..9521dd53e8 100644 --- a/light/odr.go +++ b/light/odr.go @@ -42,6 +42,7 @@ type OdrBackend interface { BloomTrieIndexer() *core.ChainIndexer BloomIndexer() *core.ChainIndexer Retrieve(ctx context.Context, req OdrRequest) error + RetrieveTxStatus(ctx context.Context, req *TxStatusRequest) error IndexerConfig() *IndexerConfig } diff --git a/light/odr_util.go b/light/odr_util.go index ec2d1e6491..bbbcdbce21 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -269,10 +269,15 @@ func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint return result, nil } -// GetTransaction retrieves a canonical transaction by hash and also returns its position in the chain +// GetTransaction retrieves a canonical transaction by hash and also returns +// its position in the chain. There is no guarantee in the LES protocol that +// the mined transaction will be retrieved back for sure because of different +// reasons(the transaction is unindexed, the malicous server doesn't reply it +// deliberately, etc). Therefore, unretrieved transactions will receive a certain +// number of retrys, thus giving a weak guarantee. func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { r := &TxStatusRequest{Hashes: []common.Hash{txHash}} - if err := odr.Retrieve(ctx, r); err != nil || r.Status[0].Status != core.TxStatusIncluded { + if err := odr.RetrieveTxStatus(ctx, r); err != nil || r.Status[0].Status != core.TxStatusIncluded { return nil, common.Hash{}, 0, 0, err } pos := r.Status[0].Lookup From bbfb1e4008a359a8b57ec654330c0e674623e52f Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Thu, 25 Feb 2021 07:26:57 -0700 Subject: [PATCH 219/235] all: add support for EIP-2718, EIP-2930 transactions (#21502) This adds support for EIP-2718 typed transactions as well as EIP-2930 access list transactions (tx type 1). These EIPs are scheduled for the Berlin fork. There very few changes to existing APIs in core/types, and several new APIs to deal with access list transactions. In particular, there are two new constructor functions for transactions: types.NewTx and types.SignNewTx. Since the canonical encoding of typed transactions is not RLP-compatible, Transaction now has new methods for encoding and decoding: MarshalBinary and UnmarshalBinary. The existing EIP-155 signer does not support the new transaction types. All code dealing with transaction signatures should be updated to use the newer EIP-2930 signer. To make this easier for future updates, we have added new constructor functions for types.Signer: types.LatestSigner and types.LatestSignerForChainID. This change also adds support for the YoloV3 testnet. Co-authored-by: Martin Holst Swende Co-authored-by: Felix Lange Co-authored-by: Ryan Schneider --- accounts/abi/bind/auth.go | 4 +- accounts/abi/bind/backends/simulated.go | 25 +- accounts/keystore/keystore.go | 17 +- accounts/scwallet/wallet.go | 2 +- accounts/usbwallet/trezor.go | 2 + cmd/clef/main.go | 2 +- cmd/evm/README.md | 17 +- cmd/evm/internal/t8ntool/execution.go | 37 +- cmd/evm/internal/t8ntool/flags.go | 5 + cmd/evm/internal/t8ntool/transition.go | 135 +++++-- cmd/evm/main.go | 1 + cmd/evm/testdata/8/alloc.json | 11 + cmd/evm/testdata/8/env.json | 7 + cmd/evm/testdata/8/readme.md | 63 ++++ cmd/evm/testdata/8/txs.json | 58 +++ cmd/geth/main.go | 7 +- cmd/geth/usage.go | 3 +- cmd/utils/flags.go | 4 +- core/bench_test.go | 2 +- core/blockchain_test.go | 102 +++++- core/error.go | 10 +- core/genesis.go | 6 +- core/genesis_alloc.go | 3 +- core/state/statedb.go | 26 ++ core/state_prefetcher.go | 33 +- core/state_processor.go | 39 +- core/state_transition.go | 18 +- core/tx_pool.go | 12 +- core/types/access_list_tx.go | 115 ++++++ core/types/block.go | 21 +- core/types/block_test.go | 62 +++- core/types/derive_sha.go | 58 --- core/types/gen_access_tuple.go | 43 +++ core/types/gen_receipt_json.go | 6 + core/types/gen_tx_json.go | 101 ----- core/types/hashing.go | 112 ++++++ core/types/hashing_test.go | 212 +++++++++++ core/types/legacy_tx.go | 111 ++++++ core/types/receipt.go | 101 ++++- core/types/receipt_test.go | 76 +++- core/types/transaction.go | 466 ++++++++++++++---------- core/types/transaction_marshalling.go | 187 ++++++++++ core/types/transaction_signing.go | 259 +++++++++++-- core/types/transaction_test.go | 276 ++++++++++++-- core/vm/interface.go | 1 + core/vm/runtime/runtime.go | 18 +- eth/downloader/downloader.go | 2 +- eth/gasprice/gasprice_test.go | 2 +- eth/tracers/api.go | 5 +- eth/tracers/tracer.go | 2 +- ethclient/ethclient.go | 3 +- ethclient/ethclient_test.go | 2 +- ethclient/signer.go | 3 + graphql/graphql.go | 9 +- interfaces.go | 2 + internal/ethapi/api.go | 169 +++++---- internal/guide/guide_test.go | 4 +- les/benchmark.go | 2 +- les/odr_test.go | 4 +- light/odr_test.go | 2 +- light/txpool.go | 10 +- miner/worker.go | 7 +- miner/worker_test.go | 19 +- params/config.go | 6 +- params/protocol_params.go | 31 +- signer/core/api.go | 5 +- tests/state_test_util.go | 18 +- tests/transaction_test_util.go | 2 +- trie/stacktrie_test.go | 170 --------- 69 files changed, 2446 insertions(+), 909 deletions(-) create mode 100644 cmd/evm/testdata/8/alloc.json create mode 100644 cmd/evm/testdata/8/env.json create mode 100644 cmd/evm/testdata/8/readme.md create mode 100644 cmd/evm/testdata/8/txs.json create mode 100644 core/types/access_list_tx.go delete mode 100644 core/types/derive_sha.go create mode 100644 core/types/gen_access_tuple.go delete mode 100644 core/types/gen_tx_json.go create mode 100644 core/types/hashing.go create mode 100644 core/types/hashing_test.go create mode 100644 core/types/legacy_tx.go create mode 100644 core/types/transaction_marshalling.go diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index 1190772676..b8065e8488 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -120,7 +120,7 @@ func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accou if chainID == nil { return nil, ErrNoChainID } - signer := types.NewEIP155Signer(chainID) + signer := types.LatestSignerForChainID(chainID) return &TransactOpts{ From: account.Address, Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { @@ -143,7 +143,7 @@ func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*Tr if chainID == nil { return nil, ErrNoChainID } - signer := types.NewEIP155Signer(chainID) + signer := types.LatestSignerForChainID(chainID) return &TransactOpts{ From: keyAddr, Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 8be364d08f..d6d525eae1 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -559,7 +559,10 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa b.mu.Lock() defer b.mu.Unlock() - sender, err := types.Sender(types.NewEIP155Signer(b.config.ChainID), tx) + // Check transaction validity. + block := b.blockchain.CurrentBlock() + signer := types.MakeSigner(b.blockchain.Config(), block.Number()) + sender, err := types.Sender(signer, tx) if err != nil { panic(fmt.Errorf("invalid transaction: %v", err)) } @@ -568,7 +571,8 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)) } - blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + // Include tx in chain. + blocks, _ := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { for _, tx := range b.pendingBlock.Transactions() { block.AddTxWithChain(b.blockchain, tx) } @@ -707,14 +711,15 @@ type callMsg struct { ethereum.CallMsg } -func (m callMsg) From() common.Address { return m.CallMsg.From } -func (m callMsg) Nonce() uint64 { return 0 } -func (m callMsg) CheckNonce() bool { return false } -func (m callMsg) To() *common.Address { return m.CallMsg.To } -func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } -func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } -func (m callMsg) Value() *big.Int { return m.CallMsg.Value } -func (m callMsg) Data() []byte { return m.CallMsg.Data } +func (m callMsg) From() common.Address { return m.CallMsg.From } +func (m callMsg) Nonce() uint64 { return 0 } +func (m callMsg) CheckNonce() bool { return false } +func (m callMsg) To() *common.Address { return m.CallMsg.To } +func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } +func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } +func (m callMsg) Value() *big.Int { return m.CallMsg.Value } +func (m callMsg) Data() []byte { return m.CallMsg.Data } +func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList } // filterBackend implements filters.Backend to support filtering for logs without // taking bloom-bits acceleration structures into account. diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index 9d5e2cf6a2..88dcfbeb69 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -283,11 +283,9 @@ func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *b if !found { return nil, ErrLocked } - // Depending on the presence of the chain ID, sign with EIP155 or homestead - if chainID != nil { - return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey) - } - return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey) + // Depending on the presence of the chain ID, sign with 2718 or homestead + signer := types.LatestSignerForChainID(chainID) + return types.SignTx(tx, signer, unlockedKey.PrivateKey) } // SignHashWithPassphrase signs hash if the private key matching the given address @@ -310,12 +308,9 @@ func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, return nil, err } defer zeroKey(key.PrivateKey) - - // Depending on the presence of the chain ID, sign with EIP155 or homestead - if chainID != nil { - return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey) - } - return types.SignTx(tx, types.HomesteadSigner{}, key.PrivateKey) + // Depending on the presence of the chain ID, sign with or without replay protection. + signer := types.LatestSignerForChainID(chainID) + return types.SignTx(tx, signer, key.PrivateKey) } // Unlock unlocks the given account indefinitely. diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index 6476646d7f..b4d229bc0b 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -699,7 +699,7 @@ func (w *Wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock // the account in a keystore). func (w *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { - signer := types.NewEIP155Signer(chainID) + signer := types.LatestSignerForChainID(chainID) hash := signer.Hash(tx) sig, err := w.signHash(account, hash[:]) if err != nil { diff --git a/accounts/usbwallet/trezor.go b/accounts/usbwallet/trezor.go index 1892097baf..0546458c47 100644 --- a/accounts/usbwallet/trezor.go +++ b/accounts/usbwallet/trezor.go @@ -255,9 +255,11 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction if chainID == nil { signer = new(types.HomesteadSigner) } else { + // Trezor backend does not support typed transactions yet. signer = types.NewEIP155Signer(chainID) signature[64] -= byte(chainID.Uint64()*2 + 35) } + // Inject the final signature into the transaction and sanity check the sender signed, err := tx.WithSignature(signer, signature) if err != nil { diff --git a/cmd/clef/main.go b/cmd/clef/main.go index dbbb410fb6..8befce88dc 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -1106,7 +1106,7 @@ func GenDoc(ctx *cli.Context) { rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed") var tx types.Transaction - rlp.DecodeBytes(rlpdata, &tx) + tx.UnmarshalBinary(rlpdata) add("OnApproved - SignTransactionResult", desc, ðapi.SignTransactionResult{Raw: rlpdata, Tx: &tx}) } diff --git a/cmd/evm/README.md b/cmd/evm/README.md index 8f0848bde8..7742ccbbb7 100644 --- a/cmd/evm/README.md +++ b/cmd/evm/README.md @@ -30,7 +30,7 @@ Command line params that has to be supported are --trace.nomemory Disable full memory dump in traces --trace.nostack Disable stack output in traces --trace.noreturndata Disable return data output in traces - --output.basedir value Specifies where output files are placed. Will be created if it does not exist. (default: ".") + --output.basedir value Specifies where output files are placed. Will be created if it does not exist. --output.alloc alloc Determines where to put the alloc of the post-state. `stdout` - into the stdout output `stderr` - into the stderr output @@ -237,10 +237,10 @@ Example where blockhashes are provided: cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2 ``` ``` -{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"PUSH1","error":""} -{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"BLOCKHASH","error":""} -{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"STOP","error":""} -{"output":"","gasUsed":"0x17","time":112885} +{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"PUSH1","error":""} +{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"BLOCKHASH","error":""} +{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"STOP","error":""} +{"output":"","gasUsed":"0x17","time":142709} ``` In this example, the caller has not provided the required blockhash: @@ -256,9 +256,9 @@ Error code: 4 Another thing that can be done, is to chain invocations: ``` ./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.alloc=stdout | ./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json -INFO [08-03|15:25:15.168] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" -INFO [08-03|15:25:15.169] rejected tx index=0 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" -INFO [08-03|15:25:15.169] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" +INFO [01-21|22:41:22.963] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" +INFO [01-21|22:41:22.966] rejected tx index=0 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" +INFO [01-21|22:41:22.967] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" ``` What happened here, is that we first applied two identical transactions, so the second one was rejected. @@ -267,4 +267,3 @@ the same two transactions: this time, both failed due to too low nonce. In order to meaningfully chain invocations, one would need to provide meaningful new `env`, otherwise the actual blocknumber (exposed to the EVM) would not increase. - diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 525723e3cb..c3f1b16efc 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -143,19 +143,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, vmConfig.Debug = (tracer != nil) statedb.Prepare(tx.Hash(), blockHash, txIndex) txContext := core.NewEVMTxContext(msg) - - evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) - if chainConfig.IsYoloV3(vmContext.BlockNumber) { - statedb.AddAddressToAccessList(msg.From()) - if dst := msg.To(); dst != nil { - statedb.AddAddressToAccessList(*dst) - // If it's a create-tx, the destination will be added inside evm.create - } - for _, addr := range evm.ActivePrecompiles() { - statedb.AddAddressToAccessList(addr) - } - } snapshot := statedb.Snapshot() + evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) + // (ret []byte, usedGas uint64, failed bool, err error) msgResult, err := core.ApplyMessage(evm, msg, gaspool) if err != nil { @@ -169,7 +159,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, return nil, nil, NewError(ErrorMissingBlockhash, hashError) } gasUsed += msgResult.UsedGas - // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx + + // Receipt: { var root []byte if chainConfig.IsByzantium(vmContext.BlockNumber) { @@ -178,22 +169,32 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, root = statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)).Bytes() } - receipt := types.NewReceipt(root, msgResult.Failed(), gasUsed) + // Create a new receipt for the transaction, storing the intermediate root and + // gas used by the tx. + receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: gasUsed} + if msgResult.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } receipt.TxHash = tx.Hash() receipt.GasUsed = msgResult.UsedGas - // if the transaction created a contract, store the creation address in the receipt. + + // If the transaction created a contract, store the creation address in the receipt. if msg.To() == nil { receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } - // Set the receipt logs and create a bloom for filtering + + // Set the receipt logs and create the bloom filter. receipt.Logs = statedb.GetLogs(tx.Hash()) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) - // These three are non-consensus fields + // These three are non-consensus fields: //receipt.BlockHash - //receipt.BlockNumber = + //receipt.BlockNumber receipt.TransactionIndex = uint(txIndex) receipts = append(receipts, receipt) } + txIndex++ } statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)) diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go index 424156ba82..a599462cc6 100644 --- a/cmd/evm/internal/t8ntool/flags.go +++ b/cmd/evm/internal/t8ntool/flags.go @@ -47,6 +47,11 @@ var ( Usage: "Specifies where output files are placed. Will be created if it does not exist.", Value: "", } + OutputBodyFlag = cli.StringFlag{ + Name: "output.body", + Usage: "If set, the RLP of the transactions (block body) will be written to this file.", + Value: "", + } OutputAllocFlag = cli.StringFlag{ Name: "output.alloc", Usage: "Determines where to put the `alloc` of the post-state.\n" + diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 5119ed5fb7..fedcd12435 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -17,6 +17,7 @@ package t8ntool import ( + "crypto/ecdsa" "encoding/json" "fmt" "io/ioutil" @@ -25,12 +26,15 @@ import ( "path" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests" "gopkg.in/urfave/cli.v1" ) @@ -64,9 +68,9 @@ func (n *NumberedError) Code() int { } type input struct { - Alloc core.GenesisAlloc `json:"alloc,omitempty"` - Env *stEnv `json:"env,omitempty"` - Txs types.Transactions `json:"txs,omitempty"` + Alloc core.GenesisAlloc `json:"alloc,omitempty"` + Env *stEnv `json:"env,omitempty"` + Txs []*txWithKey `json:"txs,omitempty"` } func Main(ctx *cli.Context) error { @@ -135,7 +139,7 @@ func Main(ctx *cli.Context) error { txStr = ctx.String(InputTxsFlag.Name) inputData = &input{} ) - + // Figure out the prestate alloc if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector { decoder := json.NewDecoder(os.Stdin) decoder.Decode(inputData) @@ -151,7 +155,9 @@ func Main(ctx *cli.Context) error { return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling alloc-file: %v", err)) } } + prestate.Pre = inputData.Alloc + // Set the block environment if envStr != stdinSelector { inFile, err := os.Open(envStr) if err != nil { @@ -165,26 +171,8 @@ func Main(ctx *cli.Context) error { } inputData.Env = &env } - - if txStr != stdinSelector { - inFile, err := os.Open(txStr) - if err != nil { - return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) - } - defer inFile.Close() - decoder := json.NewDecoder(inFile) - var txs types.Transactions - if err := decoder.Decode(&txs); err != nil { - return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling txs-file: %v", err)) - } - inputData.Txs = txs - } - - prestate.Pre = inputData.Alloc prestate.Env = *inputData.Env - txs = inputData.Txs - // Iterate over all the tests, run them and aggregate the results vmConfig := vm.Config{ Tracer: tracer, Debug: (tracer != nil), @@ -200,17 +188,105 @@ func Main(ctx *cli.Context) error { // Set the chain id chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) + var txsWithKeys []*txWithKey + if txStr != stdinSelector { + inFile, err := os.Open(txStr) + if err != nil { + return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) + } + defer inFile.Close() + decoder := json.NewDecoder(inFile) + if err := decoder.Decode(&txsWithKeys); err != nil { + return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling txs-file: %v", err)) + } + } else { + txsWithKeys = inputData.Txs + } + // We may have to sign the transactions. + signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number))) + + if txs, err = signUnsignedTransactions(txsWithKeys, signer); err != nil { + return NewError(ErrorJson, fmt.Errorf("Failed signing transactions: %v", err)) + } + + // Iterate over all the tests, run them and aggregate the results + // Run the test and aggregate the result state, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer) if err != nil { return err } + body, _ := rlp.EncodeToBytes(txs) // Dump the excution result - //postAlloc := state.DumpGenesisFormat(false, false, false) collector := make(Alloc) state.DumpToCollector(collector, false, false, false, nil, -1) - return dispatchOutput(ctx, baseDir, result, collector) + return dispatchOutput(ctx, baseDir, result, collector, body) + +} +// txWithKey is a helper-struct, to allow us to use the types.Transaction along with +// a `secretKey`-field, for input +type txWithKey struct { + key *ecdsa.PrivateKey + tx *types.Transaction +} + +func (t *txWithKey) UnmarshalJSON(input []byte) error { + // Read the secretKey, if present + type sKey struct { + Key *common.Hash `json:"secretKey"` + } + var key sKey + if err := json.Unmarshal(input, &key); err != nil { + return err + } + if key.Key != nil { + k := key.Key.Hex()[2:] + if ecdsaKey, err := crypto.HexToECDSA(k); err != nil { + return err + } else { + t.key = ecdsaKey + } + } + // Now, read the transaction itself + var tx types.Transaction + if err := json.Unmarshal(input, &tx); err != nil { + return err + } + t.tx = &tx + return nil +} + +// signUnsignedTransactions converts the input txs to canonical transactions. +// +// The transactions can have two forms, either +// 1. unsigned or +// 2. signed +// For (1), r, s, v, need so be zero, and the `secretKey` needs to be set. +// If so, we sign it here and now, with the given `secretKey` +// If the condition above is not met, then it's considered a signed transaction. +// +// To manage this, we read the transactions twice, first trying to read the secretKeys, +// and secondly to read them with the standard tx json format +func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Transactions, error) { + var signedTxs []*types.Transaction + for i, txWithKey := range txs { + tx := txWithKey.tx + key := txWithKey.key + v, r, s := tx.RawSignatureValues() + if key != nil && v.BitLen()+r.BitLen()+s.BitLen() == 0 { + // This transaction needs to be signed + signed, err := types.SignTx(tx, signer, key) + if err != nil { + return nil, NewError(ErrorJson, fmt.Errorf("Tx %d: failed to sign tx: %v", i, err)) + } + signedTxs = append(signedTxs, signed) + } else { + // Already signed + signedTxs = append(signedTxs, tx) + } + } + return signedTxs, nil } type Alloc map[common.Address]core.GenesisAccount @@ -241,15 +317,17 @@ func saveFile(baseDir, filename string, data interface{}) error { if err != nil { return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) } - if err = ioutil.WriteFile(path.Join(baseDir, filename), b, 0644); err != nil { + location := path.Join(baseDir, filename) + if err = ioutil.WriteFile(location, b, 0644); err != nil { return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err)) } + log.Info("Wrote file", "file", location) return nil } // dispatchOutput writes the output data to either stderr or stdout, or to the specified // files -func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc) error { +func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes) error { stdOutObject := make(map[string]interface{}) stdErrObject := make(map[string]interface{}) dispatch := func(baseDir, fName, name string, obj interface{}) error { @@ -258,6 +336,8 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a stdOutObject[name] = obj case "stderr": stdErrObject[name] = obj + case "": + // don't save default: // save to file if err := saveFile(baseDir, fName, obj); err != nil { return err @@ -271,6 +351,9 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil { return err } + if err := dispatch(baseDir, ctx.String(OutputBodyFlag.Name), "body", body); err != nil { + return err + } if len(stdOutObject) > 0 { b, err := json.MarshalIndent(stdOutObject, "", " ") if err != nil { diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 35c672142d..8a3e4e0ea2 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -149,6 +149,7 @@ var stateTransitionCommand = cli.Command{ t8ntool.OutputBasedir, t8ntool.OutputAllocFlag, t8ntool.OutputResultFlag, + t8ntool.OutputBodyFlag, t8ntool.InputAllocFlag, t8ntool.InputEnvFlag, t8ntool.InputTxsFlag, diff --git a/cmd/evm/testdata/8/alloc.json b/cmd/evm/testdata/8/alloc.json new file mode 100644 index 0000000000..1d1b5f86c6 --- /dev/null +++ b/cmd/evm/testdata/8/alloc.json @@ -0,0 +1,11 @@ +{ + "0x000000000000000000000000000000000000aaaa": { + "balance": "0x03", + "code": "0x5854505854", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x100000", + "nonce": "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/8/env.json b/cmd/evm/testdata/8/env.json new file mode 100644 index 0000000000..8b91934724 --- /dev/null +++ b/cmd/evm/testdata/8/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x1000000000", + "currentNumber": "0x1000000", + "currentTimestamp": "0x04" +} \ No newline at end of file diff --git a/cmd/evm/testdata/8/readme.md b/cmd/evm/testdata/8/readme.md new file mode 100644 index 0000000000..778fc6151a --- /dev/null +++ b/cmd/evm/testdata/8/readme.md @@ -0,0 +1,63 @@ +## EIP-2930 testing + +This test contains testcases for EIP-2930, which uses transactions with access lists. + +### Prestate + +The alloc portion contains one contract (`0x000000000000000000000000000000000000aaaa`), containing the +following code: `0x5854505854`: `PC ;SLOAD; POP; PC; SLOAD`. + +Essentialy, this contract does `SLOAD(0)` and `SLOAD(3)`. + +The alloc also contains some funds on `0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b`. + +## Transactions + +There are three transactions, each invokes the contract above. + +1. ACL-transaction, which contains some non-used slots +2. Regular transaction +3. ACL-transaction, which contains the slots `1` and `3` in `0x000000000000000000000000000000000000aaaa` + +## Execution + +Running it yields: +``` +dir=./testdata/8 && ./evm t8n --state.fork=Berlin --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --trace && cat trace-* | grep SLOAD +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x47c86","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x49cf6","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x494be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} + +``` + +Simlarly, we can provide the input transactions via `stdin` instead of as file: + +``` +dir=./testdata/8 \ + && cat $dir/txs.json | jq "{txs: .}" \ + | ./evm t8n --state.fork=Berlin \ + --input.alloc=$dir/alloc.json \ + --input.txs=stdin \ + --input.env=$dir/env.json \ + --trace \ + && cat trace-* | grep SLOAD + +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x47c86","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x49cf6","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x494be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +``` + +If we try to execute it on older rules: +``` +dir=./testdata/8 && ./evm t8n --state.fork=Istanbul --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json +INFO [01-21|23:21:51.265] rejected tx index=0 hash="d2818d…6ab3da" error="tx type not supported" +INFO [01-21|23:21:51.265] rejected tx index=1 hash="26ea00…81c01b" from=0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B error="nonce too high: address 0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B, tx: 1 state: 0" +INFO [01-21|23:21:51.265] rejected tx index=2 hash="698d01…369cee" error="tx type not supported" +``` +Number `1` and `3` are not applicable, and therefore number `2` has wrong nonce. \ No newline at end of file diff --git a/cmd/evm/testdata/8/txs.json b/cmd/evm/testdata/8/txs.json new file mode 100644 index 0000000000..35142ba234 --- /dev/null +++ b/cmd/evm/testdata/8/txs.json @@ -0,0 +1,58 @@ +[ + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "chainId": "0x1", + "input": "0x", + "nonce": "0x0", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x1", + "type" : "0x1", + "accessList": [ + {"address": "0x0000000000000000000000000000000000000000", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ], + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "input": "0x", + "nonce": "0x1", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x2", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "chainId": "0x1", + "input": "0x", + "nonce": "0x2", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x1", + "type" : "0x1", + "accessList": [ + {"address": "0x000000000000000000000000000000000000aaaa", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ] + } + ], + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b4c622ce2e..a3af44f6c4 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -140,9 +140,7 @@ var ( utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - // YOLOv3 is not yet complete! - // TODO: enable this once 2718/2930 is added - //utils.YoloV3Flag, + utils.YoloV3Flag, utils.VMEnableDebugFlag, utils.NetworkIdFlag, utils.EthStatsURLFlag, @@ -286,6 +284,9 @@ func prepare(ctx *cli.Context) { case ctx.GlobalIsSet(utils.GoerliFlag.Name): log.Info("Starting Geth on Görli testnet...") + case ctx.GlobalIsSet(utils.YoloV3Flag.Name): + log.Info("Starting Geth on YOLOv3 testnet...") + case ctx.GlobalIsSet(utils.DeveloperFlag.Name): log.Info("Starting Geth in ephemeral dev mode...") diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 24215f55a8..6935adabc7 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -44,8 +44,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.MainnetFlag, utils.GoerliFlag, utils.RinkebyFlag, - // TODO: Re-enable this when 2718/2930 is added - //utils.YoloV3Flag, + utils.YoloV3Flag, utils.RopstenFlag, utils.SyncModeFlag, utils.ExitWhenSyncedFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 9fff183a14..57547d0c49 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1277,7 +1277,7 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli") case ctx.GlobalBool(YoloV3Flag.Name) && cfg.DataDir == node.DefaultDataDir(): - cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v2") + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v3") } } @@ -1609,7 +1609,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) case ctx.GlobalBool(YoloV3Flag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { - cfg.NetworkId = new(big.Int).SetBytes([]byte("yolov3")).Uint64() // "yolov3" + cfg.NetworkId = new(big.Int).SetBytes([]byte("yolov3x")).Uint64() // "yolov3x" } cfg.Genesis = core.DefaultYoloV3GenesisBlock() case ctx.GlobalBool(DeveloperFlag.Name): diff --git a/core/bench_test.go b/core/bench_test.go index 0f4cabd837..85653ea5db 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -85,7 +85,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { return func(i int, gen *BlockGen) { toaddr := common.Address{} data := make([]byte, nbytes) - gas, _ := IntrinsicGas(data, false, false, false) + gas, _ := IntrinsicGas(data, nil, false, false, false) tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, nil, data), types.HomesteadSigner{}, benchRootKey) gen.AddTx(tx) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index c33e7321ec..3e4757f8b6 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -600,7 +600,7 @@ func TestFastVsFullChains(t *testing.T) { Alloc: GenesisAlloc{address: {Balance: funds}}, } genesis = gspec.MustCommit(gendb) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, 1024, func(i int, block *BlockGen) { block.SetCoinbase(common.Address{0x00}) @@ -839,7 +839,7 @@ func TestChainTxReorgs(t *testing.T) { }, } genesis = gspec.MustCommit(db) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) // Create two transactions shared between the chains: @@ -944,7 +944,7 @@ func TestLogReorgs(t *testing.T) { code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}} genesis = gspec.MustCommit(db) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) @@ -998,7 +998,7 @@ func TestLogRebirth(t *testing.T) { db = rawdb.NewMemoryDatabase() gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}} genesis = gspec.MustCommit(db) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) engine = ethash.NewFaker() blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil) ) @@ -1062,7 +1062,7 @@ func TestSideLogRebirth(t *testing.T) { db = rawdb.NewMemoryDatabase() gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}} genesis = gspec.MustCommit(db) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) ) @@ -1135,7 +1135,7 @@ func TestReorgSideEvent(t *testing.T) { Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}, } genesis = gspec.MustCommit(db) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) @@ -1295,7 +1295,7 @@ func TestEIP155Transition(t *testing.T) { } block.AddTx(tx) - tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainID)) + tx, err = basicTx(types.LatestSigner(gspec.Config)) if err != nil { t.Fatal(err) } @@ -1307,7 +1307,7 @@ func TestEIP155Transition(t *testing.T) { } block.AddTx(tx) - tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainID)) + tx, err = basicTx(types.LatestSigner(gspec.Config)) if err != nil { t.Fatal(err) } @@ -1345,7 +1345,7 @@ func TestEIP155Transition(t *testing.T) { } ) if i == 0 { - tx, err = basicTx(types.NewEIP155Signer(big.NewInt(2))) + tx, err = basicTx(types.LatestSigner(config)) if err != nil { t.Fatal(err) } @@ -1385,7 +1385,7 @@ func TestEIP161AccountRemoval(t *testing.T) { var ( tx *types.Transaction err error - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) switch i { case 0: @@ -2078,7 +2078,7 @@ func TestTransactionIndices(t *testing.T) { funds = big.NewInt(1000000000) gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}} genesis = gspec.MustCommit(gendb) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) height := uint64(128) blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), func(i int, block *BlockGen) { @@ -2205,7 +2205,7 @@ func TestSkipStaleTxIndicesInFastSync(t *testing.T) { funds = big.NewInt(1000000000) gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}} genesis = gspec.MustCommit(gendb) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) height := uint64(128) blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), func(i int, block *BlockGen) { @@ -3030,3 +3030,81 @@ func TestInitThenFailCreateContract(t *testing.T) { } } } + +// TestEIP2718Transition tests that an EIP-2718 transaction will be accepted +// after the fork block has passed. This is verified by sending an EIP-2930 +// access list transaction, which specifies a single slot access, and then +// checking that the gas usage of a hot SLOAD and a cold SLOAD are calculated +// correctly. +func TestEIP2718Transition(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + + // Generate a canonical chain to act as the main dataset + engine = ethash.NewFaker() + db = rawdb.NewMemoryDatabase() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + gspec = &Genesis{ + Config: params.YoloV3ChainConfig, + Alloc: GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAA sloads 0x00 and 0x01 + aa: { + Code: []byte{ + byte(vm.PC), + byte(vm.PC), + byte(vm.SLOAD), + byte(vm.SLOAD), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + genesis = gspec.MustCommit(db) + ) + + blocks, _ := GenerateChain(gspec.Config, genesis, engine, db, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + + // One transaction to 0xAAAA + signer := types.LatestSigner(gspec.Config) + tx, _ := types.SignNewTx(key, signer, &types.AccessListTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &aa, + Gas: 30000, + GasPrice: big.NewInt(1), + AccessList: types.AccessList{{ + Address: aa, + StorageKeys: []common.Hash{{0}}, + }}, + }) + b.AddTx(tx) + }) + + // Import the canonical chain + diskdb := rawdb.NewMemoryDatabase() + gspec.MustCommit(diskdb) + + chain, err := NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + block := chain.GetBlockByNumber(1) + + // Expected gas is intrinsic + 2 * pc + hot load + cold load, since only one load is in the access list + expected := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas + vm.GasQuickStep*2 + vm.WarmStorageReadCostEIP2929 + vm.ColdSloadCostEIP2929 + if block.GasUsed() != expected { + t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expected, block.GasUsed()) + + } +} diff --git a/core/error.go b/core/error.go index 5a28be7e1c..197dd81567 100644 --- a/core/error.go +++ b/core/error.go @@ -16,7 +16,11 @@ package core -import "errors" +import ( + "errors" + + "github.com/ethereum/go-ethereum/core/types" +) var ( // ErrKnownBlock is returned when a block to import is already known locally. @@ -63,4 +67,8 @@ var ( // ErrIntrinsicGas is returned if the transaction is specified to use less gas // than required to start the invocation. ErrIntrinsicGas = errors.New("intrinsic gas too low") + + // ErrTxTypeNotSupported is returned if a transaction is not supported in the + // current network configuration. + ErrTxTypeNotSupported = types.ErrTxTypeNotSupported ) diff --git a/core/genesis.go b/core/genesis.go index 462aec45bd..6bcc50b050 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -377,11 +377,11 @@ func DefaultGoerliGenesisBlock() *Genesis { } func DefaultYoloV3GenesisBlock() *Genesis { - // Full genesis: https://gist.github.com/holiman/b2c32a05ff2e2712e11c0787d987d46f + // Full genesis: https://gist.github.com/holiman/c6ed9269dce28304ad176314caa75e97 return &Genesis{ Config: params.YoloV3ChainConfig, - Timestamp: 0x60117f8b, - ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000008a37866fd3627c9205a37c8685666f32ec07bb1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + Timestamp: 0x6027dd2e, + ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000001041afbcb359d5a8dc58c15b2ff51354ff8a217d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), GasLimit: 0x47b760, Difficulty: big.NewInt(1), Alloc: decodePrealloc(yoloV3AllocData), diff --git a/core/genesis_alloc.go b/core/genesis_alloc.go index 6eecbbf0e8..5b0e933d7a 100644 --- a/core/genesis_alloc.go +++ b/core/genesis_alloc.go @@ -25,5 +25,4 @@ const mainnetAllocData = "\xfa\x04]X\u0793\r\x83b\x011\x8e\u0189\x9agT\x06\x908' const ropstenAllocData = "\xf9\x03\xa4\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x80\xc2\v\x80\xc2\f\x80\xc2\r\x80\xc2\x0e\x80\xc2\x0f\x80\xc2\x10\x80\xc2\x11\x80\xc2\x12\x80\xc2\x13\x80\xc2\x14\x80\xc2\x15\x80\xc2\x16\x80\xc2\x17\x80\xc2\x18\x80\xc2\x19\x80\xc2\x1a\x80\xc2\x1b\x80\xc2\x1c\x80\xc2\x1d\x80\xc2\x1e\x80\xc2\x1f\x80\xc2 \x80\xc2!\x80\xc2\"\x80\xc2#\x80\xc2$\x80\xc2%\x80\xc2&\x80\xc2'\x80\xc2(\x80\xc2)\x80\xc2*\x80\xc2+\x80\xc2,\x80\xc2-\x80\xc2.\x80\xc2/\x80\xc20\x80\xc21\x80\xc22\x80\xc23\x80\xc24\x80\xc25\x80\xc26\x80\xc27\x80\xc28\x80\xc29\x80\xc2:\x80\xc2;\x80\xc2<\x80\xc2=\x80\xc2>\x80\xc2?\x80\xc2@\x80\xc2A\x80\xc2B\x80\xc2C\x80\xc2D\x80\xc2E\x80\xc2F\x80\xc2G\x80\xc2H\x80\xc2I\x80\xc2J\x80\xc2K\x80\xc2L\x80\xc2M\x80\xc2N\x80\xc2O\x80\xc2P\x80\xc2Q\x80\xc2R\x80\xc2S\x80\xc2T\x80\xc2U\x80\xc2V\x80\xc2W\x80\xc2X\x80\xc2Y\x80\xc2Z\x80\xc2[\x80\xc2\\\x80\xc2]\x80\xc2^\x80\xc2_\x80\xc2`\x80\xc2a\x80\xc2b\x80\xc2c\x80\xc2d\x80\xc2e\x80\xc2f\x80\xc2g\x80\xc2h\x80\xc2i\x80\xc2j\x80\xc2k\x80\xc2l\x80\xc2m\x80\xc2n\x80\xc2o\x80\xc2p\x80\xc2q\x80\xc2r\x80\xc2s\x80\xc2t\x80\xc2u\x80\xc2v\x80\xc2w\x80\xc2x\x80\xc2y\x80\xc2z\x80\xc2{\x80\xc2|\x80\xc2}\x80\xc2~\x80\xc2\u007f\x80\u00c1\x80\x80\u00c1\x81\x80\u00c1\x82\x80\u00c1\x83\x80\u00c1\x84\x80\u00c1\x85\x80\u00c1\x86\x80\u00c1\x87\x80\u00c1\x88\x80\u00c1\x89\x80\u00c1\x8a\x80\u00c1\x8b\x80\u00c1\x8c\x80\u00c1\x8d\x80\u00c1\x8e\x80\u00c1\x8f\x80\u00c1\x90\x80\u00c1\x91\x80\u00c1\x92\x80\u00c1\x93\x80\u00c1\x94\x80\u00c1\x95\x80\u00c1\x96\x80\u00c1\x97\x80\u00c1\x98\x80\u00c1\x99\x80\u00c1\x9a\x80\u00c1\x9b\x80\u00c1\x9c\x80\u00c1\x9d\x80\u00c1\x9e\x80\u00c1\x9f\x80\u00c1\xa0\x80\u00c1\xa1\x80\u00c1\xa2\x80\u00c1\xa3\x80\u00c1\xa4\x80\u00c1\xa5\x80\u00c1\xa6\x80\u00c1\xa7\x80\u00c1\xa8\x80\u00c1\xa9\x80\u00c1\xaa\x80\u00c1\xab\x80\u00c1\xac\x80\u00c1\xad\x80\u00c1\xae\x80\u00c1\xaf\x80\u00c1\xb0\x80\u00c1\xb1\x80\u00c1\xb2\x80\u00c1\xb3\x80\u00c1\xb4\x80\u00c1\xb5\x80\u00c1\xb6\x80\u00c1\xb7\x80\u00c1\xb8\x80\u00c1\xb9\x80\u00c1\xba\x80\u00c1\xbb\x80\u00c1\xbc\x80\u00c1\xbd\x80\u00c1\xbe\x80\u00c1\xbf\x80\u00c1\xc0\x80\u00c1\xc1\x80\u00c1\u0080\u00c1\u00c0\u00c1\u0100\u00c1\u0140\u00c1\u0180\u00c1\u01c0\u00c1\u0200\u00c1\u0240\u00c1\u0280\u00c1\u02c0\u00c1\u0300\u00c1\u0340\u00c1\u0380\u00c1\u03c0\u00c1\u0400\u00c1\u0440\u00c1\u0480\u00c1\u04c0\u00c1\u0500\u00c1\u0540\u00c1\u0580\u00c1\u05c0\u00c1\u0600\u00c1\u0640\u00c1\u0680\u00c1\u06c0\u00c1\u0700\u00c1\u0740\u00c1\u0780\u00c1\u07c0\u00c1\xe0\x80\u00c1\xe1\x80\u00c1\xe2\x80\u00c1\xe3\x80\u00c1\xe4\x80\u00c1\xe5\x80\u00c1\xe6\x80\u00c1\xe7\x80\u00c1\xe8\x80\u00c1\xe9\x80\u00c1\xea\x80\u00c1\xeb\x80\u00c1\xec\x80\u00c1\xed\x80\u00c1\xee\x80\u00c1\xef\x80\u00c1\xf0\x80\u00c1\xf1\x80\u00c1\xf2\x80\u00c1\xf3\x80\u00c1\xf4\x80\u00c1\xf5\x80\u00c1\xf6\x80\u00c1\xf7\x80\u00c1\xf8\x80\u00c1\xf9\x80\u00c1\xfa\x80\u00c1\xfb\x80\u00c1\xfc\x80\u00c1\xfd\x80\u00c1\xfe\x80\u00c1\xff\x80\u3507KT\xa8\xbd\x15)f\xd6?pk\xae\x1f\xfe\xb0A\x19!\xe5\x8d\f\x9f,\x9c\xd0Ft\xed\xea@\x00\x00\x00" const rinkebyAllocData = "\xf9\x03\xb7\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x941\xb9\x8d\x14\x00{\xde\xe67)\x80\x86\x98\x8a\v\xbd1\x18E#\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" const goerliAllocData = "\xf9\x04\x06\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xe0\x94L*\xe4\x82Y5\x05\xf0\x16<\xde\xfc\a>\x81\xc6<\xdaA\a\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xa8\xe8\xf1G2e\x8eKQ\xe8q\x191\x05:\x8ai\xba\xf2\xb1\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe1\x94\u0665\x17\x9f\t\x1d\x85\x05\x1d<\x98'\x85\xef\xd1E\\\uc199\x8b\bE\x95\x16\x14\x01HJ\x00\x00\x00\xe1\x94\u08bdBX\xd2v\x887\xba\xa2j(\xfeq\xdc\a\x9f\x84\u01cbJG\xe3\xc1$H\xf4\xad\x00\x00\x00" -const yoloV3AllocData = "\xf9\x05\x01\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x94\x0e\x89\xe2\xae\xdb\x1c\xfc\u06d4$\xd4\x1a\x1f!\x8fA2s\x81r\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94`\xad\xc0\xf8\x9aA\xaf#|\xe75T\xed\xe1p\xd73\xec\x14\xe0\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94y\x9d2\x9e_X4\x19\x16|\xd7\"\x96$\x85\x92n3\x8fJ\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94|\xf5\xb7\x9b\xfe)\x1ag\xab\x02\xb3\x93\xe4V\xcc\xc4\xc2f\xf7S\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8a\x8e\xaf\xb1\xcfb\xbf\xbe\xb1t\x17i\xda\xe1\xa9\xddG\x99a\x92\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8b\xa1\xf1\tU\x1b\xd42\x800\x12dZ\xc16\xdd\xd6M\xbar\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xb0*.\xda\x1b1\u007f\xbd\x16v\x01(\x83k\n\u015bV\x0e\x9d\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - +const yoloV3AllocData = "\xf9\x05o\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x94\x0e\x89\xe2\xae\xdb\x1c\xfc\u06d4$\xd4\x1a\x1f!\x8fA2s\x81r\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x10A\xaf\xbc\xb3Y\u0568\xdcX\xc1[/\xf5\x13T\xff\x8a!}\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94`\xad\xc0\xf8\x9aA\xaf#|\xe75T\xed\xe1p\xd73\xec\x14\xe0\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94y\x9d2\x9e_X4\x19\x16|\xd7\"\x96$\x85\x92n3\x8fJ\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94|\xf5\xb7\x9b\xfe)\x1ag\xab\x02\xb3\x93\xe4V\xcc\xc4\xc2f\xf7S\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8a\x8e\xaf\xb1\xcfb\xbf\xbe\xb1t\x17i\xda\xe1\xa9\xddG\x99a\x92\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8b\xa1\xf1\tU\x1b\xd42\x800\x12dZ\xc16\xdd\xd6M\xbar\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xb0*.\xda\x1b1\u007f\xbd\x16v\x01(\x83k\n\u015bV\x0e\x9d\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xdf\n\x88\xb2\xb6\x8cg7\x13\xa8\xec\x82`\x03go'.5s\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" diff --git a/core/state/statedb.go b/core/state/statedb.go index 8fd066e24f..2e5d6e47c8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -983,6 +983,32 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { return root, err } +// PrepareAccessList handles the preparatory steps for executing a state transition with +// regards to both EIP-2929 and EIP-2930: +// +// - Add sender to access list (2929) +// - Add destination to access list (2929) +// - Add precompiles to access list (2929) +// - Add the contents of the optional tx access list (2930) +// +// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number. +func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { + s.AddAddressToAccessList(sender) + if dst != nil { + s.AddAddressToAccessList(*dst) + // If it's a create-tx, the destination will be added inside evm.create + } + for _, addr := range precompiles { + s.AddAddressToAccessList(addr) + } + for _, el := range list { + s.AddAddressToAccessList(el.Address) + for _, key := range el.StorageKeys { + s.AddSlotToAccessList(el.Address, key) + } + } +} + // AddAddressToAccessList adds the given address to the access list func (s *StateDB) AddAddressToAccessList(addr common.Address) { if s.accessList.AddAddress(addr) { diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 6fa52c2d9b..05394321f7 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -19,7 +19,6 @@ package core import ( "sync/atomic" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -50,8 +49,11 @@ func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine conse // only goal is to pre-cache transaction signatures and state trie nodes. func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *uint32) { var ( - header = block.Header() - gaspool = new(GasPool).AddGas(block.GasLimit()) + header = block.Header() + gaspool = new(GasPool).AddGas(block.GasLimit()) + blockContext = NewEVMBlockContext(header, p.bc, nil) + evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) + signer = types.MakeSigner(p.config, header.Number) ) // Iterate over and process the individual transactions byzantium := p.config.IsByzantium(block.Number()) @@ -60,9 +62,13 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c if interrupt != nil && atomic.LoadUint32(interrupt) == 1 { return } - // Block precaching permitted to continue, execute the transaction + // Convert the transaction into an executable message and pre-cache its sender + msg, err := tx.AsMessage(signer) + if err != nil { + return // Also invalid block, bail out + } statedb.Prepare(tx.Hash(), block.Hash(), i) - if err := precacheTransaction(p.config, p.bc, nil, gaspool, statedb, header, tx, cfg); err != nil { + if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil { return // Ugh, something went horribly wrong, bail out } // If we're pre-byzantium, pre-load trie nodes for the intermediate root @@ -79,17 +85,10 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c // precacheTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. -func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gaspool *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, cfg vm.Config) error { - // Convert the transaction into an executable message and pre-cache its sender - msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) - if err != nil { - return err - } - // Create the EVM and execute the transaction - context := NewEVMBlockContext(header, bc, author) - txContext := NewEVMTxContext(msg) - vm := vm.NewEVM(context, txContext, statedb, config, cfg) - - _, err = ApplyMessage(vm, msg, gaspool) +func precacheTransaction(msg types.Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM) error { + // Update the evm with the new transaction context. + evm.Reset(NewEVMTxContext(msg), statedb) + // Add addresses to access list if applicable + _, err := ApplyMessage(evm, msg, gaspool) return err } diff --git a/core/state_processor.go b/core/state_processor.go index c5b0bb1609..40a953f0d4 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -90,28 +90,17 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { - // Create a new context to be used in the EVM environment + // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) - // Add addresses to access list if applicable - if config.IsYoloV3(header.Number) { - statedb.AddAddressToAccessList(msg.From()) - if dst := msg.To(); dst != nil { - statedb.AddAddressToAccessList(*dst) - // If it's a create-tx, the destination will be added inside evm.create - } - for _, addr := range evm.ActivePrecompiles() { - statedb.AddAddressToAccessList(addr) - } - } - - // Update the evm with the new transaction context. evm.Reset(txContext, statedb) - // Apply the transaction to the current state (included in the env) + + // Apply the transaction to the current state (included in the env). result, err := ApplyMessage(evm, msg, gp) if err != nil { return nil, err } - // Update the state with pending changes + + // Update the state with pending changes. var root []byte if config.IsByzantium(header.Number) { statedb.Finalise(true) @@ -120,22 +109,28 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon } *usedGas += result.UsedGas - // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx - // based on the eip phase, we're passing whether the root touch-delete accounts. - receipt := types.NewReceipt(root, result.Failed(), *usedGas) + // Create a new receipt for the transaction, storing the intermediate root and gas used + // by the tx. + receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas} + if result.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } receipt.TxHash = tx.Hash() receipt.GasUsed = result.UsedGas - // if the transaction created a contract, store the creation address in the receipt. + + // If the transaction created a contract, store the creation address in the receipt. if msg.To() == nil { receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } - // Set the receipt logs and create a bloom for filtering + + // Set the receipt logs and create the bloom filter. receipt.Logs = statedb.GetLogs(tx.Hash()) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) receipt.BlockHash = statedb.BlockHash() receipt.BlockNumber = header.Number receipt.TransactionIndex = uint(statedb.TxIndex()) - return receipt, err } diff --git a/core/state_transition.go b/core/state_transition.go index a8d1936c1f..0d589a6119 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -22,6 +22,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" ) @@ -67,6 +68,7 @@ type Message interface { Nonce() uint64 CheckNonce() bool Data() []byte + AccessList() types.AccessList } // ExecutionResult includes all output after executing given evm @@ -105,10 +107,10 @@ func (result *ExecutionResult) Revert() []byte { } // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. -func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 bool) (uint64, error) { +func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool) (uint64, error) { // Set the starting gas for the raw transaction var gas uint64 - if contractCreation && isHomestead { + if isContractCreation && isHomestead { gas = params.TxGasContractCreation } else { gas = params.TxGas @@ -138,6 +140,10 @@ func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 boo } gas += z * params.TxDataZeroGas } + if accessList != nil { + gas += uint64(len(accessList)) * params.TxAccessListAddressGas + gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas + } return gas, nil } @@ -238,7 +244,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { contractCreation := msg.To() == nil // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul) + gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, istanbul) if err != nil { return nil, err } @@ -251,6 +257,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) { return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex()) } + + // Set up the initial access list. + if st.evm.ChainConfig().IsYoloV3(st.evm.Context.BlockNumber) { + st.state.PrepareAccessList(msg.From(), msg.To(), st.evm.ActivePrecompiles(), msg.AccessList()) + } + var ( ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err diff --git a/core/tx_pool.go b/core/tx_pool.go index 36546cde75..28ac822131 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -229,6 +229,7 @@ type TxPool struct { mu sync.RWMutex istanbul bool // Fork indicator whether we are in the istanbul stage. + eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions. currentState *state.StateDB // Current state in the blockchain head pendingNonces *txNoncer // Pending state tracking virtual nonces @@ -268,7 +269,7 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block config: config, chainconfig: chainconfig, chain: chain, - signer: types.NewEIP155Signer(chainconfig.ChainID), + signer: types.LatestSigner(chainconfig), pending: make(map[common.Address]*txList), queue: make(map[common.Address]*txList), beats: make(map[common.Address]time.Time), @@ -522,6 +523,10 @@ func (pool *TxPool) local() map[common.Address]types.Transactions { // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { + // Accept only legacy transactions until EIP-2718/2930 activates. + if !pool.eip2718 && tx.Type() != types.LegacyTxType { + return ErrTxTypeNotSupported + } // Reject transactions over defined size to prevent DOS attacks if uint64(tx.Size()) > txMaxSize { return ErrOversizedData @@ -535,7 +540,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { if pool.currentMaxGas < tx.Gas() { return ErrGasLimit } - // Make sure the transaction is signed properly + // Make sure the transaction is signed properly. from, err := types.Sender(pool.signer, tx) if err != nil { return ErrInvalidSender @@ -554,7 +559,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { return ErrInsufficientFunds } // Ensure the transaction has more gas than the basic tx fee. - intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, true, pool.istanbul) + intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul) if err != nil { return err } @@ -1199,6 +1204,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // Update all fork indicator by next pending block number. next := new(big.Int).Add(newHead.Number, big.NewInt(1)) pool.istanbul = pool.chainconfig.IsIstanbul(next) + pool.eip2718 = pool.chainconfig.IsYoloV3(next) } // promoteExecutables moves transactions that have become processable from the diff --git a/core/types/access_list_tx.go b/core/types/access_list_tx.go new file mode 100644 index 0000000000..65ee95adf6 --- /dev/null +++ b/core/types/access_list_tx.go @@ -0,0 +1,115 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +//go:generate gencodec -type AccessTuple -out gen_access_tuple.go + +// AccessList is an EIP-2930 access list. +type AccessList []AccessTuple + +// AccessTuple is the element type of an access list. +type AccessTuple struct { + Address common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` +} + +// StorageKeys returns the total number of storage keys in the access list. +func (al AccessList) StorageKeys() int { + sum := 0 + for _, tuple := range al { + sum += len(tuple.StorageKeys) + } + return sum +} + +// AccessListTx is the data of EIP-2930 access list transactions. +type AccessListTx struct { + ChainID *big.Int // destination chain ID + Nonce uint64 // nonce of sender account + GasPrice *big.Int // wei per gas + Gas uint64 // gas limit + To *common.Address `rlp:"nil"` // nil means contract creation + Value *big.Int // wei amount + Data []byte // contract invocation input data + AccessList AccessList // EIP-2930 access list + V, R, S *big.Int // signature values +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *AccessListTx) copy() TxData { + cpy := &AccessListTx{ + Nonce: tx.Nonce, + To: tx.To, // TODO: copy pointed-to address + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + Value: new(big.Int), + ChainID: new(big.Int), + GasPrice: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + copy(cpy.AccessList, tx.AccessList) + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasPrice != nil { + cpy.GasPrice.Set(tx.GasPrice) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// accessors for innerTx. + +func (tx *AccessListTx) txType() byte { return AccessListTxType } +func (tx *AccessListTx) chainID() *big.Int { return tx.ChainID } +func (tx *AccessListTx) protected() bool { return true } +func (tx *AccessListTx) accessList() AccessList { return tx.AccessList } +func (tx *AccessListTx) data() []byte { return tx.Data } +func (tx *AccessListTx) gas() uint64 { return tx.Gas } +func (tx *AccessListTx) gasPrice() *big.Int { return tx.GasPrice } +func (tx *AccessListTx) value() *big.Int { return tx.Value } +func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } +func (tx *AccessListTx) to() *common.Address { return tx.To } + +func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *AccessListTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s +} diff --git a/core/types/block.go b/core/types/block.go index 8096ebb755..553db003bb 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -23,15 +23,12 @@ import ( "io" "math/big" "reflect" - "sync" "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" ) var ( @@ -131,22 +128,6 @@ func (h *Header) SanityCheck() error { return nil } -// hasherPool holds LegacyKeccak hashers. -var hasherPool = sync.Pool{ - New: func() interface{} { - return sha3.NewLegacyKeccak256() - }, -} - -func rlpHash(x interface{}) (h common.Hash) { - sha := hasherPool.Get().(crypto.KeccakState) - defer hasherPool.Put(sha) - sha.Reset() - rlp.Encode(sha, x) - sha.Read(h[:]) - return h -} - // EmptyBody returns true if there is no additional 'body' to complete the header // that is: no transactions and no uncles. func (h *Header) EmptyBody() bool { @@ -221,7 +202,7 @@ type storageblock struct { // The values of TxHash, UncleHash, ReceiptHash and Bloom in header // are ignored and set to values derived from the given txs, uncles // and receipts. -func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher Hasher) *Block { +func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block { b := &Block{header: CopyHeader(header), td: new(big.Int)} // TODO: panic if len(txs) != len(receipts) diff --git a/core/types/block_test.go b/core/types/block_test.go index 4dfdcf9545..63904f882c 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -59,6 +59,66 @@ func TestBlockEncoding(t *testing.T) { tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100")) check("len(Transactions)", len(block.Transactions()), 1) check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash()) + ourBlockEnc, err := rlp.EncodeToBytes(&block) + if err != nil { + t.Fatal("encode error: ", err) + } + if !bytes.Equal(ourBlockEnc, blockEnc) { + t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc) + } +} + +func TestEIP2718BlockEncoding(t *testing.T) { + blockEnc := common.FromHex("f90319f90211a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a0e6e49996c7ec59f7a23d22b83239a60151512c65613bf84a0d7da336399ebc4aa0cafe75574d59780665a97fbfd11365c7545aa8f1abf4e5e12e8243334ef7286bb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000820200832fefd882a410845506eb0796636f6f6c65737420626c6f636b206f6e20636861696ea0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f90101f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b89e01f89b01800a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a03dbacc8d0259f2508625e97fdfc57cd85fdd16e5821bc2c10bdd1a52649e8335a0476e10695b183a87b0aa292a7f4b78ef0c3fbe62aa2c42c84e1d9c3da159ef14c0") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { + t.Fatal("decode error: ", err) + } + + check := func(f string, got, want interface{}) { + if !reflect.DeepEqual(got, want) { + t.Errorf("%s mismatch: got %v, want %v", f, got, want) + } + } + check("Difficulty", block.Difficulty(), big.NewInt(131072)) + check("GasLimit", block.GasLimit(), uint64(3141592)) + check("GasUsed", block.GasUsed(), uint64(42000)) + check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1")) + check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498")) + check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017")) + check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4)) + check("Time", block.Time(), uint64(1426516743)) + check("Size", block.Size(), common.StorageSize(len(blockEnc))) + + // Create legacy tx. + to := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") + tx1 := NewTx(&LegacyTx{ + Nonce: 0, + To: &to, + Value: big.NewInt(10), + Gas: 50000, + GasPrice: big.NewInt(10), + }) + sig := common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100") + tx1, _ = tx1.WithSignature(HomesteadSigner{}, sig) + + // Create ACL tx. + addr := common.HexToAddress("0x0000000000000000000000000000000000000001") + tx2 := NewTx(&AccessListTx{ + ChainID: big.NewInt(1), + Nonce: 0, + To: &to, + Gas: 123457, + GasPrice: big.NewInt(10), + AccessList: AccessList{{Address: addr, StorageKeys: []common.Hash{{0}}}}, + }) + sig2 := common.Hex2Bytes("3dbacc8d0259f2508625e97fdfc57cd85fdd16e5821bc2c10bdd1a52649e8335476e10695b183a87b0aa292a7f4b78ef0c3fbe62aa2c42c84e1d9c3da159ef1401") + tx2, _ = tx2.WithSignature(NewEIP2930Signer(big.NewInt(1)), sig2) + + check("len(Transactions)", len(block.Transactions()), 2) + check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash()) + check("Transactions[1].Hash", block.Transactions()[1].Hash(), tx2.Hash()) + check("Transactions[1].Type()", block.Transactions()[1].Type(), uint8(AccessListTxType)) ourBlockEnc, err := rlp.EncodeToBytes(&block) if err != nil { @@ -121,7 +181,7 @@ func makeBenchBlock() *Block { key, _ = crypto.GenerateKey() txs = make([]*Transaction, 70) receipts = make([]*Receipt, len(txs)) - signer = NewEIP155Signer(params.TestChainConfig.ChainID) + signer = LatestSigner(params.TestChainConfig) uncles = make([]*Header, 3) ) header := &Header{ diff --git a/core/types/derive_sha.go b/core/types/derive_sha.go deleted file mode 100644 index 51a10f3f3d..0000000000 --- a/core/types/derive_sha.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package types - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" -) - -type DerivableList interface { - Len() int - GetRlp(i int) []byte -} - -// Hasher is the tool used to calculate the hash of derivable list. -type Hasher interface { - Reset() - Update([]byte, []byte) - Hash() common.Hash -} - -func DeriveSha(list DerivableList, hasher Hasher) common.Hash { - hasher.Reset() - - // StackTrie requires values to be inserted in increasing - // hash order, which is not the order that `list` provides - // hashes in. This insertion sequence ensures that the - // order is correct. - - var buf []byte - for i := 1; i < list.Len() && i <= 0x7f; i++ { - buf = rlp.AppendUint64(buf[:0], uint64(i)) - hasher.Update(buf, list.GetRlp(i)) - } - if list.Len() > 0 { - buf = rlp.AppendUint64(buf[:0], 0) - hasher.Update(buf, list.GetRlp(0)) - } - for i := 0x80; i < list.Len(); i++ { - buf = rlp.AppendUint64(buf[:0], uint64(i)) - hasher.Update(buf, list.GetRlp(i)) - } - return hasher.Hash() -} diff --git a/core/types/gen_access_tuple.go b/core/types/gen_access_tuple.go new file mode 100644 index 0000000000..fc48a84cc0 --- /dev/null +++ b/core/types/gen_access_tuple.go @@ -0,0 +1,43 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common" +) + +// MarshalJSON marshals as JSON. +func (a AccessTuple) MarshalJSON() ([]byte, error) { + type AccessTuple struct { + Address common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` + } + var enc AccessTuple + enc.Address = a.Address + enc.StorageKeys = a.StorageKeys + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (a *AccessTuple) UnmarshalJSON(input []byte) error { + type AccessTuple struct { + Address *common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` + } + var dec AccessTuple + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Address == nil { + return errors.New("missing required field 'address' for AccessTuple") + } + a.Address = *dec.Address + if dec.StorageKeys == nil { + return errors.New("missing required field 'storageKeys' for AccessTuple") + } + a.StorageKeys = dec.StorageKeys + return nil +} diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go index 790ed65b58..bb892f85be 100644 --- a/core/types/gen_receipt_json.go +++ b/core/types/gen_receipt_json.go @@ -16,6 +16,7 @@ var _ = (*receiptMarshaling)(nil) // MarshalJSON marshals as JSON. func (r Receipt) MarshalJSON() ([]byte, error) { type Receipt struct { + Type hexutil.Uint64 `json:"type,omitempty"` PostState hexutil.Bytes `json:"root"` Status hexutil.Uint64 `json:"status"` CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` @@ -29,6 +30,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { TransactionIndex hexutil.Uint `json:"transactionIndex"` } var enc Receipt + enc.Type = hexutil.Uint64(r.Type) enc.PostState = r.PostState enc.Status = hexutil.Uint64(r.Status) enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed) @@ -46,6 +48,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (r *Receipt) UnmarshalJSON(input []byte) error { type Receipt struct { + Type *hexutil.Uint64 `json:"type,omitempty"` PostState *hexutil.Bytes `json:"root"` Status *hexutil.Uint64 `json:"status"` CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` @@ -62,6 +65,9 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Type != nil { + r.Type = uint8(*dec.Type) + } if dec.PostState != nil { r.PostState = *dec.PostState } diff --git a/core/types/gen_tx_json.go b/core/types/gen_tx_json.go deleted file mode 100644 index e676058ecc..0000000000 --- a/core/types/gen_tx_json.go +++ /dev/null @@ -1,101 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package types - -import ( - "encoding/json" - "errors" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var _ = (*txdataMarshaling)(nil) - -// MarshalJSON marshals as JSON. -func (t txdata) MarshalJSON() ([]byte, error) { - type txdata struct { - AccountNonce hexutil.Uint64 `json:"nonce" gencodec:"required"` - Price *hexutil.Big `json:"gasPrice" gencodec:"required"` - GasLimit hexutil.Uint64 `json:"gas" gencodec:"required"` - Recipient *common.Address `json:"to" rlp:"nil"` - Amount *hexutil.Big `json:"value" gencodec:"required"` - Payload hexutil.Bytes `json:"input" gencodec:"required"` - V *hexutil.Big `json:"v" gencodec:"required"` - R *hexutil.Big `json:"r" gencodec:"required"` - S *hexutil.Big `json:"s" gencodec:"required"` - Hash *common.Hash `json:"hash" rlp:"-"` - } - var enc txdata - enc.AccountNonce = hexutil.Uint64(t.AccountNonce) - enc.Price = (*hexutil.Big)(t.Price) - enc.GasLimit = hexutil.Uint64(t.GasLimit) - enc.Recipient = t.Recipient - enc.Amount = (*hexutil.Big)(t.Amount) - enc.Payload = t.Payload - enc.V = (*hexutil.Big)(t.V) - enc.R = (*hexutil.Big)(t.R) - enc.S = (*hexutil.Big)(t.S) - enc.Hash = t.Hash - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (t *txdata) UnmarshalJSON(input []byte) error { - type txdata struct { - AccountNonce *hexutil.Uint64 `json:"nonce" gencodec:"required"` - Price *hexutil.Big `json:"gasPrice" gencodec:"required"` - GasLimit *hexutil.Uint64 `json:"gas" gencodec:"required"` - Recipient *common.Address `json:"to" rlp:"nil"` - Amount *hexutil.Big `json:"value" gencodec:"required"` - Payload *hexutil.Bytes `json:"input" gencodec:"required"` - V *hexutil.Big `json:"v" gencodec:"required"` - R *hexutil.Big `json:"r" gencodec:"required"` - S *hexutil.Big `json:"s" gencodec:"required"` - Hash *common.Hash `json:"hash" rlp:"-"` - } - var dec txdata - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.AccountNonce == nil { - return errors.New("missing required field 'nonce' for txdata") - } - t.AccountNonce = uint64(*dec.AccountNonce) - if dec.Price == nil { - return errors.New("missing required field 'gasPrice' for txdata") - } - t.Price = (*big.Int)(dec.Price) - if dec.GasLimit == nil { - return errors.New("missing required field 'gas' for txdata") - } - t.GasLimit = uint64(*dec.GasLimit) - if dec.Recipient != nil { - t.Recipient = dec.Recipient - } - if dec.Amount == nil { - return errors.New("missing required field 'value' for txdata") - } - t.Amount = (*big.Int)(dec.Amount) - if dec.Payload == nil { - return errors.New("missing required field 'input' for txdata") - } - t.Payload = *dec.Payload - if dec.V == nil { - return errors.New("missing required field 'v' for txdata") - } - t.V = (*big.Int)(dec.V) - if dec.R == nil { - return errors.New("missing required field 'r' for txdata") - } - t.R = (*big.Int)(dec.R) - if dec.S == nil { - return errors.New("missing required field 's' for txdata") - } - t.S = (*big.Int)(dec.S) - if dec.Hash != nil { - t.Hash = dec.Hash - } - return nil -} diff --git a/core/types/hashing.go b/core/types/hashing.go new file mode 100644 index 0000000000..71efb25a9a --- /dev/null +++ b/core/types/hashing.go @@ -0,0 +1,112 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "bytes" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" +) + +// hasherPool holds LegacyKeccak256 hashers for rlpHash. +var hasherPool = sync.Pool{ + New: func() interface{} { return sha3.NewLegacyKeccak256() }, +} + +// deriveBufferPool holds temporary encoder buffers for DeriveSha and TX encoding. +var encodeBufferPool = sync.Pool{ + New: func() interface{} { return new(bytes.Buffer) }, +} + +func rlpHash(x interface{}) (h common.Hash) { + sha := hasherPool.Get().(crypto.KeccakState) + defer hasherPool.Put(sha) + sha.Reset() + rlp.Encode(sha, x) + sha.Read(h[:]) + return h +} + +// prefixedRlpHash writes the prefix into the hasher before rlp-encoding the +// given interface. It's used for typed transactions. +func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { + sha := hasherPool.Get().(crypto.KeccakState) + defer hasherPool.Put(sha) + sha.Reset() + sha.Write([]byte{prefix}) + rlp.Encode(sha, x) + sha.Read(h[:]) + return h +} + +// TrieHasher is the tool used to calculate the hash of derivable list. +// This is internal, do not use. +type TrieHasher interface { + Reset() + Update([]byte, []byte) + Hash() common.Hash +} + +// DerivableList is the input to DeriveSha. +// It is implemented by the 'Transactions' and 'Receipts' types. +// This is internal, do not use these methods. +type DerivableList interface { + Len() int + EncodeIndex(int, *bytes.Buffer) +} + +func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { + buf.Reset() + list.EncodeIndex(i, buf) + // It's really unfortunate that we need to do perform this copy. + // StackTrie holds onto the values until Hash is called, so the values + // written to it must not alias. + return common.CopyBytes(buf.Bytes()) +} + +// DeriveSha creates the tree hashes of transactions and receipts in a block header. +func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash { + hasher.Reset() + + valueBuf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(valueBuf) + + // StackTrie requires values to be inserted in increasing hash order, which is not the + // order that `list` provides hashes in. This insertion sequence ensures that the + // order is correct. + var indexBuf []byte + for i := 1; i < list.Len() && i <= 0x7f; i++ { + indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) + value := encodeForDerive(list, i, valueBuf) + hasher.Update(indexBuf, value) + } + if list.Len() > 0 { + indexBuf = rlp.AppendUint64(indexBuf[:0], 0) + value := encodeForDerive(list, 0, valueBuf) + hasher.Update(indexBuf, value) + } + for i := 0x80; i < list.Len(); i++ { + indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) + value := encodeForDerive(list, i, valueBuf) + hasher.Update(indexBuf, value) + } + return hasher.Hash() +} diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go new file mode 100644 index 0000000000..a948b10ef6 --- /dev/null +++ b/core/types/hashing_test.go @@ -0,0 +1,212 @@ +package types_test + +import ( + "bytes" + "fmt" + "io" + "math/big" + mrand "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +func TestDeriveSha(t *testing.T) { + txs, err := genTxs(0) + if err != nil { + t.Fatal(err) + } + for len(txs) < 1000 { + exp := types.DeriveSha(txs, new(trie.Trie)) + got := types.DeriveSha(txs, trie.NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) + } + newTxs, err := genTxs(uint64(len(txs) + 1)) + if err != nil { + t.Fatal(err) + } + txs = append(txs, newTxs...) + } +} + +// TestEIP2718DeriveSha tests that the input to the DeriveSha function is correct. +func TestEIP2718DeriveSha(t *testing.T) { + for _, tc := range []struct { + rlpData string + exp string + }{ + { + rlpData: "0xb8a701f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9", + exp: "01 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n80 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n", + }, + } { + d := &hashToHumanReadable{} + var t1, t2 types.Transaction + rlp.DecodeBytes(common.FromHex(tc.rlpData), &t1) + rlp.DecodeBytes(common.FromHex(tc.rlpData), &t2) + txs := types.Transactions{&t1, &t2} + types.DeriveSha(txs, d) + if tc.exp != string(d.data) { + t.Fatalf("Want\n%v\nhave:\n%v", tc.exp, string(d.data)) + } + } +} + +func BenchmarkDeriveSha200(b *testing.B) { + txs, err := genTxs(200) + if err != nil { + b.Fatal(err) + } + var exp common.Hash + var got common.Hash + b.Run("std_trie", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + exp = types.DeriveSha(txs, new(trie.Trie)) + } + }) + + b.Run("stack_trie", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + got = types.DeriveSha(txs, trie.NewStackTrie(nil)) + } + }) + if got != exp { + b.Errorf("got %x exp %x", got, exp) + } +} + +func TestFuzzDeriveSha(t *testing.T) { + // increase this for longer runs -- it's set to quite low for travis + rndSeed := mrand.Int() + for i := 0; i < 10; i++ { + seed := rndSeed + i + exp := types.DeriveSha(newDummy(i), new(trie.Trie)) + got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + printList(newDummy(seed)) + t.Fatalf("seed %d: got %x exp %x", seed, got, exp) + } + } +} + +// TestDerivableList contains testcases found via fuzzing +func TestDerivableList(t *testing.T) { + type tcase []string + tcs := []tcase{ + { + "0xc041", + }, + { + "0xf04cf757812428b0763112efb33b6f4fad7deb445e", + "0xf04cf757812428b0763112efb33b6f4fad7deb445e", + }, + { + "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", + "0x6cd850eca0a7ac46bb1748d7b9cb88aa3bd21c57d852c28198ad8fa422c4595032e88a4494b4778b36b944fe47a52b8c5cd312910139dfcb4147ab8e972cc456bcb063f25dd78f54c4d34679e03142c42c662af52947d45bdb6e555751334ace76a5080ab5a0256a1d259855dfc5c0b8023b25befbb13fd3684f9f755cbd3d63544c78ee2001452dd54633a7593ade0b183891a0a4e9c7844e1254005fbe592b1b89149a502c24b6e1dca44c158aebedf01beae9c30cabe16a", + "0x14abd5c47c0be87b0454596baad2", + "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", + }, + } + for i, tc := range tcs[1:] { + exp := types.DeriveSha(flatList(tc), new(trie.Trie)) + got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + t.Fatalf("case %d: got %x exp %x", i, got, exp) + } + } +} + +func genTxs(num uint64) (types.Transactions, error) { + key, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + if err != nil { + return nil, err + } + var addr = crypto.PubkeyToAddress(key.PublicKey) + newTx := func(i uint64) (*types.Transaction, error) { + signer := types.NewEIP155Signer(big.NewInt(18)) + utx := types.NewTransaction(i, addr, new(big.Int), 0, new(big.Int).SetUint64(10000000), nil) + tx, err := types.SignTx(utx, signer, key) + return tx, err + } + var txs types.Transactions + for i := uint64(0); i < num; i++ { + tx, err := newTx(i) + if err != nil { + return nil, err + } + txs = append(txs, tx) + } + return txs, nil +} + +type dummyDerivableList struct { + len int + seed int +} + +func newDummy(seed int) *dummyDerivableList { + d := &dummyDerivableList{} + src := mrand.NewSource(int64(seed)) + // don't use lists longer than 4K items + d.len = int(src.Int63() & 0x0FFF) + d.seed = seed + return d +} + +func (d *dummyDerivableList) Len() int { + return d.len +} + +func (d *dummyDerivableList) EncodeIndex(i int, w *bytes.Buffer) { + src := mrand.NewSource(int64(d.seed + i)) + // max item size 256, at least 1 byte per item + size := 1 + src.Int63()&0x00FF + io.CopyN(w, mrand.New(src), size) +} + +func printList(l types.DerivableList) { + fmt.Printf("list length: %d\n", l.Len()) + fmt.Printf("{\n") + for i := 0; i < l.Len(); i++ { + var buf bytes.Buffer + l.EncodeIndex(i, &buf) + fmt.Printf("\"0x%x\",\n", buf.Bytes()) + } + fmt.Printf("},\n") +} + +type flatList []string + +func (f flatList) Len() int { + return len(f) +} +func (f flatList) EncodeIndex(i int, w *bytes.Buffer) { + w.Write(hexutil.MustDecode(f[i])) +} + +type hashToHumanReadable struct { + data []byte +} + +func (d *hashToHumanReadable) Reset() { + d.data = make([]byte, 0) +} + +func (d *hashToHumanReadable) Update(i []byte, i2 []byte) { + l := fmt.Sprintf("%x %x\n", i, i2) + d.data = append(d.data, []byte(l)...) +} + +func (d *hashToHumanReadable) Hash() common.Hash { + return common.Hash{} +} diff --git a/core/types/legacy_tx.go b/core/types/legacy_tx.go new file mode 100644 index 0000000000..41ad44f379 --- /dev/null +++ b/core/types/legacy_tx.go @@ -0,0 +1,111 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// LegacyTx is the transaction data of regular Ethereum transactions. +type LegacyTx struct { + Nonce uint64 // nonce of sender account + GasPrice *big.Int // wei per gas + Gas uint64 // gas limit + To *common.Address `rlp:"nil"` // nil means contract creation + Value *big.Int // wei amount + Data []byte // contract invocation input data + V, R, S *big.Int // signature values +} + +// NewTransaction creates an unsigned legacy transaction. +// Deprecated: use NewTx instead. +func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { + return NewTx(&LegacyTx{ + Nonce: nonce, + To: &to, + Value: amount, + Gas: gasLimit, + GasPrice: gasPrice, + Data: data, + }) +} + +// NewContractCreation creates an unsigned legacy transaction. +// Deprecated: use NewTx instead. +func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { + return NewTx(&LegacyTx{ + Nonce: nonce, + Value: amount, + Gas: gasLimit, + GasPrice: gasPrice, + Data: data, + }) +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *LegacyTx) copy() TxData { + cpy := &LegacyTx{ + Nonce: tx.Nonce, + To: tx.To, // TODO: copy pointed-to address + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are initialized below. + Value: new(big.Int), + GasPrice: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.GasPrice != nil { + cpy.GasPrice.Set(tx.GasPrice) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// accessors for innerTx. + +func (tx *LegacyTx) txType() byte { return LegacyTxType } +func (tx *LegacyTx) chainID() *big.Int { return deriveChainId(tx.V) } +func (tx *LegacyTx) accessList() AccessList { return nil } +func (tx *LegacyTx) data() []byte { return tx.Data } +func (tx *LegacyTx) gas() uint64 { return tx.Gas } +func (tx *LegacyTx) gasPrice() *big.Int { return tx.GasPrice } +func (tx *LegacyTx) value() *big.Int { return tx.Value } +func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } +func (tx *LegacyTx) to() *common.Address { return tx.To } + +func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *LegacyTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.V, tx.R, tx.S = v, r, s +} diff --git a/core/types/receipt.go b/core/types/receipt.go index a96c7525ef..48f4aef06a 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -38,6 +38,8 @@ var ( receiptStatusSuccessfulRLP = []byte{0x01} ) +var errEmptyTypedReceipt = errors.New("empty typed receipt bytes") + const ( // ReceiptStatusFailed is the status code of a transaction if execution failed. ReceiptStatusFailed = uint64(0) @@ -49,6 +51,7 @@ const ( // Receipt represents the results of a transaction. type Receipt struct { // Consensus fields: These fields are defined by the Yellow Paper + Type uint8 `json:"type,omitempty"` PostState []byte `json:"root"` Status uint64 `json:"status"` CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"` @@ -69,6 +72,7 @@ type Receipt struct { } type receiptMarshaling struct { + Type hexutil.Uint64 PostState hexutil.Bytes Status hexutil.Uint64 CumulativeGasUsed hexutil.Uint64 @@ -114,8 +118,13 @@ type v3StoredReceiptRLP struct { } // NewReceipt creates a barebone transaction receipt, copying the init fields. +// Deprecated: create receipts using a struct literal instead. func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt { - r := &Receipt{PostState: common.CopyBytes(root), CumulativeGasUsed: cumulativeGasUsed} + r := &Receipt{ + Type: LegacyTxType, + PostState: common.CopyBytes(root), + CumulativeGasUsed: cumulativeGasUsed, + } if failed { r.Status = ReceiptStatusFailed } else { @@ -127,21 +136,65 @@ func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt { // EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt // into an RLP stream. If no post state is present, byzantium fork is assumed. func (r *Receipt) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs}) + data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} + if r.Type == LegacyTxType { + return rlp.Encode(w, data) + } + // It's an EIP-2718 typed TX receipt. + if r.Type != AccessListTxType { + return ErrTxTypeNotSupported + } + buf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(buf) + buf.Reset() + buf.WriteByte(r.Type) + if err := rlp.Encode(buf, data); err != nil { + return err + } + return rlp.Encode(w, buf.Bytes()) } // DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt // from an RLP stream. func (r *Receipt) DecodeRLP(s *rlp.Stream) error { - var dec receiptRLP - if err := s.Decode(&dec); err != nil { - return err - } - if err := r.setStatus(dec.PostStateOrStatus); err != nil { + kind, _, err := s.Kind() + switch { + case err != nil: return err + case kind == rlp.List: + // It's a legacy receipt. + var dec receiptRLP + if err := s.Decode(&dec); err != nil { + return err + } + r.Type = LegacyTxType + return r.setFromRLP(dec) + case kind == rlp.String: + // It's an EIP-2718 typed tx receipt. + b, err := s.Bytes() + if err != nil { + return err + } + if len(b) == 0 { + return errEmptyTypedReceipt + } + r.Type = b[0] + if r.Type == AccessListTxType { + var dec receiptRLP + if err := rlp.DecodeBytes(b[1:], &dec); err != nil { + return err + } + return r.setFromRLP(dec) + } + return ErrTxTypeNotSupported + default: + return rlp.ErrExpectedList } - r.CumulativeGasUsed, r.Bloom, r.Logs = dec.CumulativeGasUsed, dec.Bloom, dec.Logs - return nil +} + +func (r *Receipt) setFromRLP(data receiptRLP) error { + r.CumulativeGasUsed, r.Bloom, r.Logs = data.CumulativeGasUsed, data.Bloom, data.Logs + return r.setStatus(data.PostStateOrStatus) } func (r *Receipt) setStatus(postStateOrStatus []byte) error { @@ -172,7 +225,6 @@ func (r *Receipt) statusEncoding() []byte { // to approximate and limit the memory consumption of various caches. func (r *Receipt) Size() common.StorageSize { size := common.StorageSize(unsafe.Sizeof(*r)) + common.StorageSize(len(r.PostState)) - size += common.StorageSize(len(r.Logs)) * common.StorageSize(unsafe.Sizeof(Log{})) for _, log := range r.Logs { size += common.StorageSize(len(log.Topics)*common.HashLength + len(log.Data)) @@ -277,19 +329,27 @@ func decodeV3StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { return nil } -// Receipts is a wrapper around a Receipt array to implement DerivableList. +// Receipts implements DerivableList for receipts. type Receipts []*Receipt // Len returns the number of receipts in this list. -func (r Receipts) Len() int { return len(r) } - -// GetRlp returns the RLP encoding of one receipt from the list. -func (r Receipts) GetRlp(i int) []byte { - bytes, err := rlp.EncodeToBytes(r[i]) - if err != nil { - panic(err) +func (rs Receipts) Len() int { return len(rs) } + +// EncodeIndex encodes the i'th receipt to w. +func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { + r := rs[i] + data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} + switch r.Type { + case LegacyTxType: + rlp.Encode(w, data) + case AccessListTxType: + w.WriteByte(AccessListTxType) + rlp.Encode(w, data) + default: + // For unsupported types, write nothing. Since this is for + // DeriveSha, the error will be caught matching the derived hash + // to the block. } - return bytes } // DeriveFields fills the receipts with their computed fields based on consensus @@ -302,7 +362,8 @@ func (r Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, num return errors.New("transaction and receipt count mismatch") } for i := 0; i < len(r); i++ { - // The transaction hash can be retrieved from the transaction itself + // The transaction type and hash can be retrieved from the transaction itself + r[i].Type = txs[i].Type() r[i].TxHash = txs[i].Hash() // block location fields diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 806b3dd2ab..22a316c237 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -29,6 +29,15 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +func TestDecodeEmptyTypedReceipt(t *testing.T) { + input := []byte{0x80} + var r Receipt + err := rlp.DecodeBytes(input, &r) + if err != errEmptyTypedReceipt { + t.Fatal("wrong error:", err) + } +} + func TestLegacyReceiptDecoding(t *testing.T) { tests := []struct { name string @@ -154,9 +163,29 @@ func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) { // Tests that receipt data can be correctly derived from the contextual infos func TestDeriveFields(t *testing.T) { // Create a few transactions to have receipts for + to2 := common.HexToAddress("0x2") + to3 := common.HexToAddress("0x3") txs := Transactions{ - NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil), - NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil), + NewTx(&LegacyTx{ + Nonce: 1, + Value: big.NewInt(1), + Gas: 1, + GasPrice: big.NewInt(1), + }), + NewTx(&LegacyTx{ + To: &to2, + Nonce: 2, + Value: big.NewInt(2), + Gas: 2, + GasPrice: big.NewInt(2), + }), + NewTx(&AccessListTx{ + To: &to3, + Nonce: 3, + Value: big.NewInt(3), + Gas: 3, + GasPrice: big.NewInt(3), + }), } // Create the corresponding receipts receipts := Receipts{ @@ -182,6 +211,18 @@ func TestDeriveFields(t *testing.T) { ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}), GasUsed: 2, }, + &Receipt{ + Type: AccessListTxType, + PostState: common.Hash{3}.Bytes(), + CumulativeGasUsed: 6, + Logs: []*Log{ + {Address: common.BytesToAddress([]byte{0x33})}, + {Address: common.BytesToAddress([]byte{0x03, 0x33})}, + }, + TxHash: txs[2].Hash(), + ContractAddress: common.BytesToAddress([]byte{0x03, 0x33, 0x33}), + GasUsed: 3, + }, } // Clear all the computed fields and re-derive them number := big.NewInt(1) @@ -196,6 +237,9 @@ func TestDeriveFields(t *testing.T) { logIndex := uint(0) for i := range receipts { + if receipts[i].Type != txs[i].Type() { + t.Errorf("receipts[%d].Type = %d, want %d", i, receipts[i].Type, txs[i].Type()) + } if receipts[i].TxHash != txs[i].Hash() { t.Errorf("receipts[%d].TxHash = %s, want %s", i, receipts[i].TxHash.String(), txs[i].Hash().String()) } @@ -243,6 +287,34 @@ func TestDeriveFields(t *testing.T) { } } +// TestTypedReceiptEncodingDecoding reproduces a flaw that existed in the receipt +// rlp decoder, which failed due to a shadowing error. +func TestTypedReceiptEncodingDecoding(t *testing.T) { + var payload = common.FromHex("f9043eb9010c01f90108018262d4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010c01f901080182cd14b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f901090183013754b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f90109018301a194b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0") + check := func(bundle []*Receipt) { + t.Helper() + for i, receipt := range bundle { + if got, want := receipt.Type, uint8(1); got != want { + t.Fatalf("bundle %d: got %x, want %x", i, got, want) + } + } + } + { + var bundle []*Receipt + rlp.DecodeBytes(payload, &bundle) + check(bundle) + } + { + var bundle []*Receipt + r := bytes.NewReader(payload) + s := rlp.NewStream(r, uint64(len(payload))) + if err := s.Decode(&bundle); err != nil { + t.Fatal(err) + } + check(bundle) + } +} + func clearComputedFieldsOnReceipts(t *testing.T, receipts Receipts) { t.Helper() diff --git a/core/types/transaction.go b/core/types/transaction.go index 177bfbb70b..49127630ae 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -17,6 +17,7 @@ package types import ( + "bytes" "container/heap" "errors" "io" @@ -25,20 +26,28 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) -//go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go - var ( - ErrInvalidSig = errors.New("invalid transaction v, r, s values") + ErrInvalidSig = errors.New("invalid transaction v, r, s values") + ErrUnexpectedProtection = errors.New("transaction type does not supported EIP-155 protected signatures") + ErrInvalidTxType = errors.New("transaction type not valid in this context") + ErrTxTypeNotSupported = errors.New("transaction type not supported") + errEmptyTypedTx = errors.New("empty typed transaction bytes") +) + +// Transaction types. +const ( + LegacyTxType = iota + AccessListTxType ) +// Transaction is an Ethereum transaction. type Transaction struct { - data txdata // Consensus contents of a transaction - time time.Time // Time first seen locally (spam avoidance) + inner TxData // Consensus contents of a transaction + time time.Time // Time first seen locally (spam avoidance) // caches hash atomic.Value @@ -46,170 +55,266 @@ type Transaction struct { from atomic.Value } -type txdata struct { - AccountNonce uint64 `json:"nonce" gencodec:"required"` - Price *big.Int `json:"gasPrice" gencodec:"required"` - GasLimit uint64 `json:"gas" gencodec:"required"` - Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation - Amount *big.Int `json:"value" gencodec:"required"` - Payload []byte `json:"input" gencodec:"required"` +// NewTx creates a new transaction. +func NewTx(inner TxData) *Transaction { + tx := new(Transaction) + tx.setDecoded(inner.copy(), 0) + return tx +} + +// TxData is the underlying data of a transaction. +// +// This is implemented by LegacyTx and AccessListTx. +type TxData interface { + txType() byte // returns the type ID + copy() TxData // creates a deep copy and initializes all fields - // Signature values - V *big.Int `json:"v" gencodec:"required"` - R *big.Int `json:"r" gencodec:"required"` - S *big.Int `json:"s" gencodec:"required"` + chainID() *big.Int + accessList() AccessList + data() []byte + gas() uint64 + gasPrice() *big.Int + value() *big.Int + nonce() uint64 + to() *common.Address - // This is only used when marshaling to JSON. - Hash *common.Hash `json:"hash" rlp:"-"` + rawSignatureValues() (v, r, s *big.Int) + setSignatureValues(chainID, v, r, s *big.Int) } -type txdataMarshaling struct { - AccountNonce hexutil.Uint64 - Price *hexutil.Big - GasLimit hexutil.Uint64 - Amount *hexutil.Big - Payload hexutil.Bytes - V *hexutil.Big - R *hexutil.Big - S *hexutil.Big +// EncodeRLP implements rlp.Encoder +func (tx *Transaction) EncodeRLP(w io.Writer) error { + if tx.Type() == LegacyTxType { + return rlp.Encode(w, tx.inner) + } + // It's an EIP-2718 typed TX envelope. + buf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(buf) + buf.Reset() + if err := tx.encodeTyped(buf); err != nil { + return err + } + return rlp.Encode(w, buf.Bytes()) } -func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { - return newTransaction(nonce, &to, amount, gasLimit, gasPrice, data) +// encodeTyped writes the canonical encoding of a typed transaction to w. +func (tx *Transaction) encodeTyped(w *bytes.Buffer) error { + w.WriteByte(tx.Type()) + return rlp.Encode(w, tx.inner) } -func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { - return newTransaction(nonce, nil, amount, gasLimit, gasPrice, data) +// MarshalBinary returns the canonical encoding of the transaction. +// For legacy transactions, it returns the RLP encoding. For EIP-2718 typed +// transactions, it returns the type and payload. +func (tx *Transaction) MarshalBinary() ([]byte, error) { + if tx.Type() == LegacyTxType { + return rlp.EncodeToBytes(tx.inner) + } + var buf bytes.Buffer + err := tx.encodeTyped(&buf) + return buf.Bytes(), err } -func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { - if len(data) > 0 { - data = common.CopyBytes(data) +// DecodeRLP implements rlp.Decoder +func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { + kind, size, err := s.Kind() + switch { + case err != nil: + return err + case kind == rlp.List: + // It's a legacy transaction. + var inner LegacyTx + err := s.Decode(&inner) + if err == nil { + tx.setDecoded(&inner, int(rlp.ListSize(size))) + } + return err + case kind == rlp.String: + // It's an EIP-2718 typed TX envelope. + var b []byte + if b, err = s.Bytes(); err != nil { + return err + } + inner, err := tx.decodeTyped(b) + if err == nil { + tx.setDecoded(inner, len(b)) + } + return err + default: + return rlp.ErrExpectedList } - d := txdata{ - AccountNonce: nonce, - Recipient: to, - Payload: data, - Amount: new(big.Int), - GasLimit: gasLimit, - Price: new(big.Int), - V: new(big.Int), - R: new(big.Int), - S: new(big.Int), +} + +// UnmarshalBinary decodes the canonical encoding of transactions. +// It supports legacy RLP transactions and EIP2718 typed transactions. +func (tx *Transaction) UnmarshalBinary(b []byte) error { + if len(b) > 0 && b[0] > 0x7f { + // It's a legacy transaction. + var data LegacyTx + err := rlp.DecodeBytes(b, &data) + if err != nil { + return err + } + tx.setDecoded(&data, len(b)) + return nil } - if amount != nil { - d.Amount.Set(amount) + // It's an EIP2718 typed transaction envelope. + inner, err := tx.decodeTyped(b) + if err != nil { + return err } - if gasPrice != nil { - d.Price.Set(gasPrice) + tx.setDecoded(inner, len(b)) + return nil +} + +// decodeTyped decodes a typed transaction from the canonical format. +func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { + if len(b) == 0 { + return nil, errEmptyTypedTx } - return &Transaction{ - data: d, - time: time.Now(), + switch b[0] { + case AccessListTxType: + var inner AccessListTx + err := rlp.DecodeBytes(b[1:], &inner) + return &inner, err + default: + return nil, ErrTxTypeNotSupported } } -// ChainId returns which chain id this transaction was signed for (if at all) -func (tx *Transaction) ChainId() *big.Int { - return deriveChainId(tx.data.V) +// setDecoded sets the inner transaction and size after decoding. +func (tx *Transaction) setDecoded(inner TxData, size int) { + tx.inner = inner + tx.time = time.Now() + if size > 0 { + tx.size.Store(common.StorageSize(size)) + } } -// Protected returns whether the transaction is protected from replay protection. -func (tx *Transaction) Protected() bool { - return isProtectedV(tx.data.V) +func sanityCheckSignature(v *big.Int, r *big.Int, s *big.Int, maybeProtected bool) error { + if isProtectedV(v) && !maybeProtected { + return ErrUnexpectedProtection + } + + var plainV byte + if isProtectedV(v) { + chainID := deriveChainId(v).Uint64() + plainV = byte(v.Uint64() - 35 - 2*chainID) + } else if maybeProtected { + // Only EIP-155 signatures can be optionally protected. Since + // we determined this v value is not protected, it must be a + // raw 27 or 28. + plainV = byte(v.Uint64() - 27) + } else { + // If the signature is not optionally protected, we assume it + // must already be equal to the recovery id. + plainV = byte(v.Uint64()) + } + if !crypto.ValidateSignatureValues(plainV, r, s, false) { + return ErrInvalidSig + } + + return nil } func isProtectedV(V *big.Int) bool { if V.BitLen() <= 8 { v := V.Uint64() - return v != 27 && v != 28 + return v != 27 && v != 28 && v != 1 && v != 0 } // anything not 27 or 28 is considered protected return true } -// EncodeRLP implements rlp.Encoder -func (tx *Transaction) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, &tx.data) -} - -// DecodeRLP implements rlp.Decoder -func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { - _, size, _ := s.Kind() - err := s.Decode(&tx.data) - if err == nil { - tx.size.Store(common.StorageSize(rlp.ListSize(size))) - tx.time = time.Now() +// Protected says whether the transaction is replay-protected. +func (tx *Transaction) Protected() bool { + switch tx := tx.inner.(type) { + case *LegacyTx: + return tx.V != nil && isProtectedV(tx.V) + default: + return true } - return err } -// MarshalJSON encodes the web3 RPC transaction format. -func (tx *Transaction) MarshalJSON() ([]byte, error) { - hash := tx.Hash() - data := tx.data - data.Hash = &hash - return data.MarshalJSON() +// Type returns the transaction type. +func (tx *Transaction) Type() uint8 { + return tx.inner.txType() } -// UnmarshalJSON decodes the web3 RPC transaction format. -func (tx *Transaction) UnmarshalJSON(input []byte) error { - var dec txdata - if err := dec.UnmarshalJSON(input); err != nil { - return err - } - withSignature := dec.V.Sign() != 0 || dec.R.Sign() != 0 || dec.S.Sign() != 0 - if withSignature { - var V byte - if isProtectedV(dec.V) { - chainID := deriveChainId(dec.V).Uint64() - V = byte(dec.V.Uint64() - 35 - 2*chainID) - } else { - V = byte(dec.V.Uint64() - 27) - } - if !crypto.ValidateSignatureValues(V, dec.R, dec.S, false) { - return ErrInvalidSig - } - } - *tx = Transaction{ - data: dec, - time: time.Now(), - } - return nil +// ChainId returns the EIP155 chain ID of the transaction. The return value will always be +// non-nil. For legacy transactions which are not replay-protected, the return value is +// zero. +func (tx *Transaction) ChainId() *big.Int { + return tx.inner.chainID() } -func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) } -func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit } -func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) } -func (tx *Transaction) GasPriceCmp(other *Transaction) int { - return tx.data.Price.Cmp(other.data.Price) -} -func (tx *Transaction) GasPriceIntCmp(other *big.Int) int { - return tx.data.Price.Cmp(other) -} -func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) } -func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce } -func (tx *Transaction) CheckNonce() bool { return true } +// Data returns the input data of the transaction. +func (tx *Transaction) Data() []byte { return tx.inner.data() } + +// AccessList returns the access list of the transaction. +func (tx *Transaction) AccessList() AccessList { return tx.inner.accessList() } + +// Gas returns the gas limit of the transaction. +func (tx *Transaction) Gas() uint64 { return tx.inner.gas() } + +// GasPrice returns the gas price of the transaction. +func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.inner.gasPrice()) } + +// Value returns the ether amount of the transaction. +func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.inner.value()) } + +// Nonce returns the sender account nonce of the transaction. +func (tx *Transaction) Nonce() uint64 { return tx.inner.nonce() } // To returns the recipient address of the transaction. -// It returns nil if the transaction is a contract creation. +// For contract-creation transactions, To returns nil. func (tx *Transaction) To() *common.Address { - if tx.data.Recipient == nil { + // Copy the pointed-to address. + ito := tx.inner.to() + if ito == nil { return nil } - to := *tx.data.Recipient - return &to + cpy := *ito + return &cpy +} + +// Cost returns gas * gasPrice + value. +func (tx *Transaction) Cost() *big.Int { + total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) + total.Add(total, tx.Value()) + return total +} + +// RawSignatureValues returns the V, R, S signature values of the transaction. +// The return values should not be modified by the caller. +func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { + return tx.inner.rawSignatureValues() +} + +// GasPriceCmp compares the gas prices of two transactions. +func (tx *Transaction) GasPriceCmp(other *Transaction) int { + return tx.inner.gasPrice().Cmp(other.GasPrice()) } -// Hash hashes the RLP encoding of tx. -// It uniquely identifies the transaction. +// GasPriceIntCmp compares the gas price of the transaction against the given price. +func (tx *Transaction) GasPriceIntCmp(other *big.Int) int { + return tx.inner.gasPrice().Cmp(other) +} + +// Hash returns the transaction hash. func (tx *Transaction) Hash() common.Hash { if hash := tx.hash.Load(); hash != nil { return hash.(common.Hash) } - v := rlpHash(tx) - tx.hash.Store(v) - return v + + var h common.Hash + if tx.Type() == LegacyTxType { + h = rlpHash(tx.inner) + } else { + h = prefixedRlpHash(tx.Type(), tx.inner) + } + tx.hash.Store(h) + return h } // Size returns the true RLP encoded storage size of the transaction, either by @@ -219,32 +324,11 @@ func (tx *Transaction) Size() common.StorageSize { return size.(common.StorageSize) } c := writeCounter(0) - rlp.Encode(&c, &tx.data) + rlp.Encode(&c, &tx.inner) tx.size.Store(common.StorageSize(c)) return common.StorageSize(c) } -// AsMessage returns the transaction as a core.Message. -// -// AsMessage requires a signer to derive the sender. -// -// XXX Rename message to something less arbitrary? -func (tx *Transaction) AsMessage(s Signer) (Message, error) { - msg := Message{ - nonce: tx.data.AccountNonce, - gasLimit: tx.data.GasLimit, - gasPrice: new(big.Int).Set(tx.data.Price), - to: tx.data.Recipient, - amount: tx.data.Amount, - data: tx.data.Payload, - checkNonce: true, - } - - var err error - msg.from, err = Sender(s, tx) - return msg, err -} - // WithSignature returns a new transaction with the given signature. // This signature needs to be in the [R || S || V] format where V is 0 or 1. func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) { @@ -252,40 +336,27 @@ func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, e if err != nil { return nil, err } - cpy := &Transaction{ - data: tx.data, - time: tx.time, - } - cpy.data.R, cpy.data.S, cpy.data.V = r, s, v - return cpy, nil + cpy := tx.inner.copy() + cpy.setSignatureValues(signer.ChainID(), v, r, s) + return &Transaction{inner: cpy, time: tx.time}, nil } -// Cost returns amount + gasprice * gaslimit. -func (tx *Transaction) Cost() *big.Int { - total := new(big.Int).Mul(tx.data.Price, new(big.Int).SetUint64(tx.data.GasLimit)) - total.Add(total, tx.data.Amount) - return total -} - -// RawSignatureValues returns the V, R, S signature values of the transaction. -// The return values should not be modified by the caller. -func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { - return tx.data.V, tx.data.R, tx.data.S -} - -// Transactions is a Transaction slice type for basic sorting. +// Transactions implements DerivableList for transactions. type Transactions []*Transaction // Len returns the length of s. func (s Transactions) Len() int { return len(s) } -// Swap swaps the i'th and the j'th element in s. -func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// GetRlp implements Rlpable and returns the i'th element of s in rlp. -func (s Transactions) GetRlp(i int) []byte { - enc, _ := rlp.EncodeToBytes(s[i]) - return enc +// EncodeIndex encodes the i'th transaction to w. Note that this does not check for errors +// because we assume that *Transaction will only ever contain valid txs that were either +// constructed by decoding or via public API in this package. +func (s Transactions) EncodeIndex(i int, w *bytes.Buffer) { + tx := s[i] + if tx.Type() == LegacyTxType { + rlp.Encode(w, tx.inner) + } else { + tx.encodeTyped(w) + } } // TxDifference returns a new set which is the difference between a and b. @@ -312,7 +383,7 @@ func TxDifference(a, b Transactions) Transactions { type TxByNonce Transactions func (s TxByNonce) Len() int { return len(s) } -func (s TxByNonce) Less(i, j int) bool { return s[i].data.AccountNonce < s[j].data.AccountNonce } +func (s TxByNonce) Less(i, j int) bool { return s[i].Nonce() < s[j].Nonce() } func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // TxByPriceAndTime implements both the sort and the heap interface, making it useful @@ -323,7 +394,7 @@ func (s TxByPriceAndTime) Len() int { return len(s) } func (s TxByPriceAndTime) Less(i, j int) bool { // If the prices are equal, use the time the transaction was first seen for // deterministic sorting - cmp := s[i].data.Price.Cmp(s[j].data.Price) + cmp := s[i].GasPrice().Cmp(s[j].GasPrice()) if cmp == 0 { return s[i].time.Before(s[j].time) } @@ -361,13 +432,13 @@ func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transa // Initialize a price and received time based heap with the head transactions heads := make(TxByPriceAndTime, 0, len(txs)) for from, accTxs := range txs { - heads = append(heads, accTxs[0]) // Ensure the sender address is from the signer - acc, _ := Sender(signer, accTxs[0]) - txs[acc] = accTxs[1:] - if from != acc { + if acc, _ := Sender(signer, accTxs[0]); acc != from { delete(txs, from) + continue } + heads = append(heads, accTxs[0]) + txs[from] = accTxs[1:] } heap.Init(&heads) @@ -416,10 +487,11 @@ type Message struct { gasLimit uint64 gasPrice *big.Int data []byte + accessList AccessList checkNonce bool } -func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool) Message { +func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, accessList AccessList, checkNonce bool) Message { return Message{ from: from, to: to, @@ -428,15 +500,35 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b gasLimit: gasLimit, gasPrice: gasPrice, data: data, + accessList: accessList, checkNonce: checkNonce, } } -func (m Message) From() common.Address { return m.from } -func (m Message) To() *common.Address { return m.to } -func (m Message) GasPrice() *big.Int { return m.gasPrice } -func (m Message) Value() *big.Int { return m.amount } -func (m Message) Gas() uint64 { return m.gasLimit } -func (m Message) Nonce() uint64 { return m.nonce } -func (m Message) Data() []byte { return m.data } -func (m Message) CheckNonce() bool { return m.checkNonce } +// AsMessage returns the transaction as a core.Message. +func (tx *Transaction) AsMessage(s Signer) (Message, error) { + msg := Message{ + nonce: tx.Nonce(), + gasLimit: tx.Gas(), + gasPrice: new(big.Int).Set(tx.GasPrice()), + to: tx.To(), + amount: tx.Value(), + data: tx.Data(), + accessList: tx.AccessList(), + checkNonce: true, + } + + var err error + msg.from, err = Sender(s, tx) + return msg, err +} + +func (m Message) From() common.Address { return m.from } +func (m Message) To() *common.Address { return m.to } +func (m Message) GasPrice() *big.Int { return m.gasPrice } +func (m Message) Value() *big.Int { return m.amount } +func (m Message) Gas() uint64 { return m.gasLimit } +func (m Message) Nonce() uint64 { return m.nonce } +func (m Message) Data() []byte { return m.data } +func (m Message) AccessList() AccessList { return m.accessList } +func (m Message) CheckNonce() bool { return m.checkNonce } diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go new file mode 100644 index 0000000000..184a17d5b5 --- /dev/null +++ b/core/types/transaction_marshalling.go @@ -0,0 +1,187 @@ +package types + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// txJSON is the JSON representation of transactions. +type txJSON struct { + Type hexutil.Uint64 `json:"type"` + + // Common transaction fields: + Nonce *hexutil.Uint64 `json:"nonce"` + GasPrice *hexutil.Big `json:"gasPrice"` + Gas *hexutil.Uint64 `json:"gas"` + Value *hexutil.Big `json:"value"` + Data *hexutil.Bytes `json:"input"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` + To *common.Address `json:"to"` + + // Access list transaction fields: + ChainID *hexutil.Big `json:"chainId,omitempty"` + AccessList *AccessList `json:"accessList,omitempty"` + + // Only used for encoding: + Hash common.Hash `json:"hash"` +} + +// MarshalJSON marshals as JSON with a hash. +func (t *Transaction) MarshalJSON() ([]byte, error) { + var enc txJSON + // These are set for all tx types. + enc.Hash = t.Hash() + enc.Type = hexutil.Uint64(t.Type()) + + // Other fields are set conditionally depending on tx type. + switch tx := t.inner.(type) { + case *LegacyTx: + enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) + enc.Gas = (*hexutil.Uint64)(&tx.Gas) + enc.GasPrice = (*hexutil.Big)(tx.GasPrice) + enc.Value = (*hexutil.Big)(tx.Value) + enc.Data = (*hexutil.Bytes)(&tx.Data) + enc.To = t.To() + enc.V = (*hexutil.Big)(tx.V) + enc.R = (*hexutil.Big)(tx.R) + enc.S = (*hexutil.Big)(tx.S) + case *AccessListTx: + enc.ChainID = (*hexutil.Big)(tx.ChainID) + enc.AccessList = &tx.AccessList + enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) + enc.Gas = (*hexutil.Uint64)(&tx.Gas) + enc.GasPrice = (*hexutil.Big)(tx.GasPrice) + enc.Value = (*hexutil.Big)(tx.Value) + enc.Data = (*hexutil.Bytes)(&tx.Data) + enc.To = t.To() + enc.V = (*hexutil.Big)(tx.V) + enc.R = (*hexutil.Big)(tx.R) + enc.S = (*hexutil.Big)(tx.S) + } + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (t *Transaction) UnmarshalJSON(input []byte) error { + var dec txJSON + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + + // Decode / verify fields according to transaction type. + var inner TxData + switch dec.Type { + case LegacyTxType: + var itx LegacyTx + inner = &itx + if dec.To != nil { + itx.To = dec.To + } + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + if dec.GasPrice == nil { + return errors.New("missing required field 'gasPrice' in transaction") + } + itx.GasPrice = (*big.Int)(dec.GasPrice) + if dec.Gas == nil { + return errors.New("missing required field 'gas' in transaction") + } + itx.Gas = uint64(*dec.Gas) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + if dec.Data == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Data + if dec.V == nil { + return errors.New("missing required field 'v' in transaction") + } + itx.V = (*big.Int)(dec.V) + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.R = (*big.Int)(dec.R) + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.S = (*big.Int)(dec.S) + withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 + if withSignature { + if err := sanityCheckSignature(itx.V, itx.R, itx.S, true); err != nil { + return err + } + } + + case AccessListTxType: + var itx AccessListTx + inner = &itx + // Access list is optional for now. + if dec.AccessList != nil { + itx.AccessList = *dec.AccessList + } + if dec.ChainID == nil { + return errors.New("missing required field 'chainId' in transaction") + } + itx.ChainID = (*big.Int)(dec.ChainID) + if dec.To != nil { + itx.To = dec.To + } + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + if dec.GasPrice == nil { + return errors.New("missing required field 'gasPrice' in transaction") + } + itx.GasPrice = (*big.Int)(dec.GasPrice) + if dec.Gas == nil { + return errors.New("missing required field 'gas' in transaction") + } + itx.Gas = uint64(*dec.Gas) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + if dec.Data == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Data + if dec.V == nil { + return errors.New("missing required field 'v' in transaction") + } + itx.V = (*big.Int)(dec.V) + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.R = (*big.Int)(dec.R) + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.S = (*big.Int)(dec.S) + withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 + if withSignature { + if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { + return err + } + } + + default: + return ErrTxTypeNotSupported + } + + // Now set the inner transaction. + t.setDecoded(inner, 0) + + // TODO: check hash here? + return nil +} diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 842fedbd03..1645369b4f 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -27,9 +27,7 @@ import ( "github.com/ethereum/go-ethereum/params" ) -var ( - ErrInvalidChainId = errors.New("invalid chain id for signer") -) +var ErrInvalidChainId = errors.New("invalid chain id for signer") // sigCache is used to cache the derived sender and contains // the signer used to derive it. @@ -42,6 +40,8 @@ type sigCache struct { func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { var signer Signer switch { + case config.IsYoloV3(blockNumber): + signer = NewEIP2930Signer(config.ChainID) case config.IsEIP155(blockNumber): signer = NewEIP155Signer(config.ChainID) case config.IsHomestead(blockNumber): @@ -52,7 +52,40 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { return signer } -// SignTx signs the transaction using the given signer and private key +// LatestSigner returns the 'most permissive' Signer available for the given chain +// configuration. Specifically, this enables support of EIP-155 replay protection and +// EIP-2930 access list transactions when their respective forks are scheduled to occur at +// any block number in the chain config. +// +// Use this in transaction-handling code where the current block number is unknown. If you +// have the current block number available, use MakeSigner instead. +func LatestSigner(config *params.ChainConfig) Signer { + if config.ChainID != nil { + if config.YoloV3Block != nil { + return NewEIP2930Signer(config.ChainID) + } + if config.EIP155Block != nil { + return NewEIP155Signer(config.ChainID) + } + } + return HomesteadSigner{} +} + +// LatestSignerForChainID returns the 'most permissive' Signer available. Specifically, +// this enables support for EIP-155 replay protection and all implemented EIP-2718 +// transaction types if chainID is non-nil. +// +// Use this in transaction-handling code where the current block number and fork +// configuration are unknown. If you have a ChainConfig, use LatestSigner instead. +// If you have a ChainConfig and know the current block number, use MakeSigner instead. +func LatestSignerForChainID(chainID *big.Int) Signer { + if chainID == nil { + return HomesteadSigner{} + } + return NewEIP2930Signer(chainID) +} + +// SignTx signs the transaction using the given signer and private key. func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) { h := s.Hash(tx) sig, err := crypto.Sign(h[:], prv) @@ -62,6 +95,27 @@ func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, err return tx.WithSignature(s, sig) } +// SignNewTx creates a transaction and signs it. +func SignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) (*Transaction, error) { + tx := NewTx(txdata) + h := s.Hash(tx) + sig, err := crypto.Sign(h[:], prv) + if err != nil { + return nil, err + } + return tx.WithSignature(s, sig) +} + +// MustSignNewTx creates a transaction and signs it. +// This panics if the transaction cannot be signed. +func MustSignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) *Transaction { + tx, err := SignNewTx(prv, s, txdata) + if err != nil { + panic(err) + } + return tx +} + // Sender returns the address derived from the signature (V, R, S) using secp256k1 // elliptic curve and an error if it failed deriving or upon an incorrect // signature. @@ -88,21 +142,128 @@ func Sender(signer Signer, tx *Transaction) (common.Address, error) { return addr, nil } -// Signer encapsulates transaction signature handling. Note that this interface is not a -// stable API and may change at any time to accommodate new protocol rules. +// Signer encapsulates transaction signature handling. The name of this type is slightly +// misleading because Signers don't actually sign, they're just for validating and +// processing of signatures. +// +// Note that this interface is not a stable API and may change at any time to accommodate +// new protocol rules. type Signer interface { // Sender returns the sender address of the transaction. Sender(tx *Transaction) (common.Address, error) + // SignatureValues returns the raw R, S, V values corresponding to the // given signature. SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) - // Hash returns the hash to be signed. + ChainID() *big.Int + + // Hash returns 'signature hash', i.e. the transaction hash that is signed by the + // private key. This hash does not uniquely identify the transaction. Hash(tx *Transaction) common.Hash + // Equal returns true if the given signer is the same as the receiver. Equal(Signer) bool } -// EIP155Transaction implements Signer using the EIP155 rules. +type eip2930Signer struct{ EIP155Signer } + +// NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions, +// EIP-155 replay protected transactions, and legacy Homestead transactions. +func NewEIP2930Signer(chainId *big.Int) Signer { + return eip2930Signer{NewEIP155Signer(chainId)} +} + +func (s eip2930Signer) ChainID() *big.Int { + return s.chainId +} + +func (s eip2930Signer) Equal(s2 Signer) bool { + x, ok := s2.(eip2930Signer) + return ok && x.chainId.Cmp(s.chainId) == 0 +} + +func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) { + V, R, S := tx.RawSignatureValues() + switch tx.Type() { + case LegacyTxType: + if !tx.Protected() { + return HomesteadSigner{}.Sender(tx) + } + V = new(big.Int).Sub(V, s.chainIdMul) + V.Sub(V, big8) + case AccessListTxType: + // ACL txs are defined to use 0 and 1 as their recovery id, add + // 27 to become equivalent to unprotected Homestead signatures. + V = new(big.Int).Add(V, big.NewInt(27)) + default: + return common.Address{}, ErrTxTypeNotSupported + } + if tx.ChainId().Cmp(s.chainId) != 0 { + return common.Address{}, ErrInvalidChainId + } + return recoverPlain(s.Hash(tx), R, S, V, true) +} + +func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + switch txdata := tx.inner.(type) { + case *LegacyTx: + R, S, V = decodeSignature(sig) + if s.chainId.Sign() != 0 { + V = big.NewInt(int64(sig[64] + 35)) + V.Add(V, s.chainIdMul) + } + case *AccessListTx: + // Check that chain ID of tx matches the signer. We also accept ID zero here, + // because it indicates that the chain ID was not specified in the tx. + if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 { + return nil, nil, nil, ErrInvalidChainId + } + R, S, _ = decodeSignature(sig) + V = big.NewInt(int64(sig[64])) + default: + return nil, nil, nil, ErrTxTypeNotSupported + } + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s eip2930Signer) Hash(tx *Transaction) common.Hash { + switch tx.Type() { + case LegacyTxType: + return rlpHash([]interface{}{ + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + s.chainId, uint(0), uint(0), + }) + case AccessListTxType: + return prefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + }) + default: + // This _should_ not happen, but in case someone sends in a bad + // json struct via RPC, it's probably more prudent to return an + // empty hash instead of killing the node with a panic + //panic("Unsupported transaction type: %d", tx.typ) + return common.Hash{} + } +} + +// EIP155Signer implements Signer using the EIP-155 rules. This accepts transactions which +// are replay-protected as well as unprotected homestead transactions. type EIP155Signer struct { chainId, chainIdMul *big.Int } @@ -117,6 +278,10 @@ func NewEIP155Signer(chainId *big.Int) EIP155Signer { } } +func (s EIP155Signer) ChainID() *big.Int { + return s.chainId +} + func (s EIP155Signer) Equal(s2 Signer) bool { eip155, ok := s2.(EIP155Signer) return ok && eip155.chainId.Cmp(s.chainId) == 0 @@ -125,24 +290,28 @@ func (s EIP155Signer) Equal(s2 Signer) bool { var big8 = big.NewInt(8) func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != LegacyTxType { + return common.Address{}, ErrTxTypeNotSupported + } if !tx.Protected() { return HomesteadSigner{}.Sender(tx) } if tx.ChainId().Cmp(s.chainId) != 0 { return common.Address{}, ErrInvalidChainId } - V := new(big.Int).Sub(tx.data.V, s.chainIdMul) + V, R, S := tx.RawSignatureValues() + V = new(big.Int).Sub(V, s.chainIdMul) V.Sub(V, big8) - return recoverPlain(s.Hash(tx), tx.data.R, tx.data.S, V, true) + return recoverPlain(s.Hash(tx), R, S, V, true) } // SignatureValues returns signature values. This signature // needs to be in the [R || S || V] format where V is 0 or 1. func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { - R, S, V, err = HomesteadSigner{}.SignatureValues(tx, sig) - if err != nil { - return nil, nil, nil, err + if tx.Type() != LegacyTxType { + return nil, nil, nil, ErrTxTypeNotSupported } + R, S, V = decodeSignature(sig) if s.chainId.Sign() != 0 { V = big.NewInt(int64(sig[64] + 35)) V.Add(V, s.chainIdMul) @@ -154,12 +323,12 @@ func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big // It does not uniquely identify the transaction. func (s EIP155Signer) Hash(tx *Transaction) common.Hash { return rlpHash([]interface{}{ - tx.data.AccountNonce, - tx.data.Price, - tx.data.GasLimit, - tx.data.Recipient, - tx.data.Amount, - tx.data.Payload, + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), s.chainId, uint(0), uint(0), }) } @@ -168,6 +337,10 @@ func (s EIP155Signer) Hash(tx *Transaction) common.Hash { // homestead rules. type HomesteadSigner struct{ FrontierSigner } +func (s HomesteadSigner) ChainID() *big.Int { + return nil +} + func (s HomesteadSigner) Equal(s2 Signer) bool { _, ok := s2.(HomesteadSigner) return ok @@ -180,25 +353,39 @@ func (hs HomesteadSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v } func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) { - return recoverPlain(hs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, true) + if tx.Type() != LegacyTxType { + return common.Address{}, ErrTxTypeNotSupported + } + v, r, s := tx.RawSignatureValues() + return recoverPlain(hs.Hash(tx), r, s, v, true) } type FrontierSigner struct{} +func (s FrontierSigner) ChainID() *big.Int { + return nil +} + func (s FrontierSigner) Equal(s2 Signer) bool { _, ok := s2.(FrontierSigner) return ok } +func (fs FrontierSigner) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != LegacyTxType { + return common.Address{}, ErrTxTypeNotSupported + } + v, r, s := tx.RawSignatureValues() + return recoverPlain(fs.Hash(tx), r, s, v, false) +} + // SignatureValues returns signature values. This signature // needs to be in the [R || S || V] format where V is 0 or 1. func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) { - if len(sig) != crypto.SignatureLength { - panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)) + if tx.Type() != LegacyTxType { + return nil, nil, nil, ErrTxTypeNotSupported } - r = new(big.Int).SetBytes(sig[:32]) - s = new(big.Int).SetBytes(sig[32:64]) - v = new(big.Int).SetBytes([]byte{sig[64] + 27}) + r, s, v = decodeSignature(sig) return r, s, v, nil } @@ -206,17 +393,23 @@ func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v * // It does not uniquely identify the transaction. func (fs FrontierSigner) Hash(tx *Transaction) common.Hash { return rlpHash([]interface{}{ - tx.data.AccountNonce, - tx.data.Price, - tx.data.GasLimit, - tx.data.Recipient, - tx.data.Amount, - tx.data.Payload, + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), }) } -func (fs FrontierSigner) Sender(tx *Transaction) (common.Address, error) { - return recoverPlain(fs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, false) +func decodeSignature(sig []byte) (r, s, v *big.Int) { + if len(sig) != crypto.SignatureLength { + panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)) + } + r = new(big.Int).SetBytes(sig[:32]) + s = new(big.Int).SetBytes(sig[32:64]) + v = new(big.Int).SetBytes([]byte{sig[64] + 27}) + return r, s, v } func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) { diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 159cb0c4c4..3cece9c235 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -20,7 +20,9 @@ import ( "bytes" "crypto/ecdsa" "encoding/json" + "fmt" "math/big" + "reflect" "testing" "time" @@ -32,6 +34,8 @@ import ( // The values in those tests are from the Transaction Tests // at github.com/ethereum/tests. var ( + testAddr = common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b") + emptyTx = NewTransaction( 0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), @@ -41,7 +45,7 @@ var ( rightvrsTx, _ = NewTransaction( 3, - common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b"), + testAddr, big.NewInt(10), 2000, big.NewInt(1), @@ -50,8 +54,32 @@ var ( HomesteadSigner{}, common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"), ) + + emptyEip2718Tx = NewTx(&AccessListTx{ + ChainID: big.NewInt(1), + Nonce: 3, + To: &testAddr, + Value: big.NewInt(10), + Gas: 25000, + GasPrice: big.NewInt(1), + Data: common.FromHex("5544"), + }) + + signedEip2718Tx, _ = emptyEip2718Tx.WithSignature( + NewEIP2930Signer(big.NewInt(1)), + common.Hex2Bytes("c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b266032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d3752101"), + ) ) +func TestDecodeEmptyTypedTx(t *testing.T) { + input := []byte{0x80} + var tx Transaction + err := rlp.DecodeBytes(input, &tx) + if err != errEmptyTypedTx { + t.Fatal("wrong error:", err) + } +} + func TestTransactionSigHash(t *testing.T) { var homestead HomesteadSigner if homestead.Hash(emptyTx) != common.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") { @@ -73,10 +101,121 @@ func TestTransactionEncode(t *testing.T) { } } +func TestEIP2718TransactionSigHash(t *testing.T) { + s := NewEIP2930Signer(big.NewInt(1)) + if s.Hash(emptyEip2718Tx) != common.HexToHash("49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3") { + t.Errorf("empty EIP-2718 transaction hash mismatch, got %x", s.Hash(emptyEip2718Tx)) + } + if s.Hash(signedEip2718Tx) != common.HexToHash("49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3") { + t.Errorf("signed EIP-2718 transaction hash mismatch, got %x", s.Hash(signedEip2718Tx)) + } +} + +// This test checks signature operations on access list transactions. +func TestEIP2930Signer(t *testing.T) { + + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + keyAddr = crypto.PubkeyToAddress(key.PublicKey) + signer1 = NewEIP2930Signer(big.NewInt(1)) + signer2 = NewEIP2930Signer(big.NewInt(2)) + tx0 = NewTx(&AccessListTx{Nonce: 1}) + tx1 = NewTx(&AccessListTx{ChainID: big.NewInt(1), Nonce: 1}) + tx2, _ = SignNewTx(key, signer2, &AccessListTx{ChainID: big.NewInt(2), Nonce: 1}) + ) + + tests := []struct { + tx *Transaction + signer Signer + wantSignerHash common.Hash + wantSenderErr error + wantSignErr error + wantHash common.Hash // after signing + }{ + { + tx: tx0, + signer: signer1, + wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"), + wantSenderErr: ErrInvalidChainId, + wantHash: common.HexToHash("1ccd12d8bbdb96ea391af49a35ab641e219b2dd638dea375f2bc94dd290f2549"), + }, + { + tx: tx1, + signer: signer1, + wantSenderErr: ErrInvalidSig, + wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"), + wantHash: common.HexToHash("1ccd12d8bbdb96ea391af49a35ab641e219b2dd638dea375f2bc94dd290f2549"), + }, + { + // This checks what happens when trying to sign an unsigned tx for the wrong chain. + tx: tx1, + signer: signer2, + wantSenderErr: ErrInvalidChainId, + wantSignerHash: common.HexToHash("367967247499343401261d718ed5aa4c9486583e4d89251afce47f4a33c33362"), + wantSignErr: ErrInvalidChainId, + }, + { + // This checks what happens when trying to re-sign a signed tx for the wrong chain. + tx: tx2, + signer: signer1, + wantSenderErr: ErrInvalidChainId, + wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"), + wantSignErr: ErrInvalidChainId, + }, + } + + for i, test := range tests { + sigHash := test.signer.Hash(test.tx) + if sigHash != test.wantSignerHash { + t.Errorf("test %d: wrong sig hash: got %x, want %x", i, sigHash, test.wantSignerHash) + } + sender, err := Sender(test.signer, test.tx) + if err != test.wantSenderErr { + t.Errorf("test %d: wrong Sender error %q", i, err) + } + if err == nil && sender != keyAddr { + t.Errorf("test %d: wrong sender address %x", i, sender) + } + signedTx, err := SignTx(test.tx, test.signer, key) + if err != test.wantSignErr { + t.Fatalf("test %d: wrong SignTx error %q", i, err) + } + if signedTx != nil { + if signedTx.Hash() != test.wantHash { + t.Errorf("test %d: wrong tx hash after signing: got %x, want %x", i, signedTx.Hash(), test.wantHash) + } + } + } +} + +func TestEIP2718TransactionEncode(t *testing.T) { + // RLP representation + { + have, err := rlp.EncodeToBytes(signedEip2718Tx) + if err != nil { + t.Fatalf("encode error: %v", err) + } + want := common.FromHex("b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521") + if !bytes.Equal(have, want) { + t.Errorf("encoded RLP mismatch, got %x", have) + } + } + // Binary representation + { + have, err := signedEip2718Tx.MarshalBinary() + if err != nil { + t.Fatalf("encode error: %v", err) + } + want := common.FromHex("01f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521") + if !bytes.Equal(have, want) { + t.Errorf("encoded RLP mismatch, got %x", have) + } + } +} + func decodeTx(data []byte) (*Transaction, error) { var tx Transaction t, err := &tx, rlp.Decode(bytes.NewReader(data), &tx) - return t, err } @@ -219,50 +358,125 @@ func TestTransactionTimeSort(t *testing.T) { } } -// TestTransactionJSON tests serializing/de-serializing to/from JSON. -func TestTransactionJSON(t *testing.T) { +// TestTransactionCoding tests serializing/de-serializing to/from rlp and JSON. +func TestTransactionCoding(t *testing.T) { key, err := crypto.GenerateKey() if err != nil { t.Fatalf("could not generate key: %v", err) } - signer := NewEIP155Signer(common.Big1) - - transactions := make([]*Transaction, 0, 50) - for i := uint64(0); i < 25; i++ { - var tx *Transaction - switch i % 2 { + var ( + signer = NewEIP2930Signer(common.Big1) + addr = common.HexToAddress("0x0000000000000000000000000000000000000001") + recipient = common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") + accesses = AccessList{{Address: addr, StorageKeys: []common.Hash{{0}}}} + ) + for i := uint64(0); i < 500; i++ { + var txdata TxData + switch i % 5 { case 0: - tx = NewTransaction(i, common.Address{1}, common.Big0, 1, common.Big2, []byte("abcdef")) + // Legacy tx. + txdata = &LegacyTx{ + Nonce: i, + To: &recipient, + Gas: 1, + GasPrice: big.NewInt(2), + Data: []byte("abcdef"), + } case 1: - tx = NewContractCreation(i, common.Big0, 1, common.Big2, []byte("abcdef")) + // Legacy tx contract creation. + txdata = &LegacyTx{ + Nonce: i, + Gas: 1, + GasPrice: big.NewInt(2), + Data: []byte("abcdef"), + } + case 2: + // Tx with non-zero access list. + txdata = &AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + To: &recipient, + Gas: 123457, + GasPrice: big.NewInt(10), + AccessList: accesses, + Data: []byte("abcdef"), + } + case 3: + // Tx with empty access list. + txdata = &AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + To: &recipient, + Gas: 123457, + GasPrice: big.NewInt(10), + Data: []byte("abcdef"), + } + case 4: + // Contract creation with access list. + txdata = &AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + Gas: 123457, + GasPrice: big.NewInt(10), + AccessList: accesses, + } } - transactions = append(transactions, tx) - - signedTx, err := SignTx(tx, signer, key) + tx, err := SignNewTx(key, signer, txdata) if err != nil { t.Fatalf("could not sign transaction: %v", err) } - - transactions = append(transactions, signedTx) - } - - for _, tx := range transactions { - data, err := json.Marshal(tx) + // RLP + parsedTx, err := encodeDecodeBinary(tx) if err != nil { - t.Fatalf("json.Marshal failed: %v", err) + t.Fatal(err) } + assertEqual(parsedTx, tx) - var parsedTx *Transaction - if err := json.Unmarshal(data, &parsedTx); err != nil { - t.Fatalf("json.Unmarshal failed: %v", err) + // JSON + parsedTx, err = encodeDecodeJSON(tx) + if err != nil { + t.Fatal(err) } + assertEqual(parsedTx, tx) + } +} - // compare nonce, price, gaslimit, recipient, amount, payload, V, R, S - if tx.Hash() != parsedTx.Hash() { - t.Errorf("parsed tx differs from original tx, want %v, got %v", tx, parsedTx) - } - if tx.ChainId().Cmp(parsedTx.ChainId()) != 0 { - t.Errorf("invalid chain id, want %d, got %d", tx.ChainId(), parsedTx.ChainId()) +func encodeDecodeJSON(tx *Transaction) (*Transaction, error) { + data, err := json.Marshal(tx) + if err != nil { + return nil, fmt.Errorf("json encoding failed: %v", err) + } + var parsedTx = &Transaction{} + if err := json.Unmarshal(data, &parsedTx); err != nil { + return nil, fmt.Errorf("json decoding failed: %v", err) + } + return parsedTx, nil +} + +func encodeDecodeBinary(tx *Transaction) (*Transaction, error) { + data, err := tx.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("rlp encoding failed: %v", err) + } + var parsedTx = &Transaction{} + if err := parsedTx.UnmarshalBinary(data); err != nil { + return nil, fmt.Errorf("rlp decoding failed: %v", err) + } + return parsedTx, nil +} + +func assertEqual(orig *Transaction, cpy *Transaction) error { + // compare nonce, price, gaslimit, recipient, amount, payload, V, R, S + if want, got := orig.Hash(), cpy.Hash(); want != got { + return fmt.Errorf("parsed tx differs from original tx, want %v, got %v", want, got) + } + if want, got := orig.ChainId(), cpy.ChainId(); want.Cmp(got) != 0 { + return fmt.Errorf("invalid chain id, want %d, got %d", want, got) + } + if orig.AccessList() != nil { + if !reflect.DeepEqual(orig.AccessList(), cpy.AccessList()) { + return fmt.Errorf("access list wrong!") } } + return nil } diff --git a/core/vm/interface.go b/core/vm/interface.go index fb5bbca48f..ad9b05d666 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -57,6 +57,7 @@ type StateDB interface { // is defined according to EIP161 (balance = nonce = code = 0). Empty(common.Address) bool + PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) AddressInAccessList(addr common.Address) bool SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 7cdb4ebd22..c97bdc420c 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -114,11 +114,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { sender = vm.AccountRef(cfg.Origin) ) if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { - cfg.State.AddAddressToAccessList(cfg.Origin) - cfg.State.AddAddressToAccessList(address) - for _, addr := range vmenv.ActivePrecompiles() { - cfg.State.AddAddressToAccessList(addr) - } + cfg.State.PrepareAccessList(cfg.Origin, &address, vmenv.ActivePrecompiles(), nil) } cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. @@ -150,10 +146,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { sender = vm.AccountRef(cfg.Origin) ) if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { - cfg.State.AddAddressToAccessList(cfg.Origin) - for _, addr := range vmenv.ActivePrecompiles() { - cfg.State.AddAddressToAccessList(addr) - } + cfg.State.PrepareAccessList(cfg.Origin, nil, vmenv.ActivePrecompiles(), nil) } // Call the code with the given configuration. @@ -177,12 +170,9 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er vmenv := NewEnv(cfg) sender := cfg.State.GetOrNewStateObject(cfg.Origin) + statedb := cfg.State if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { - cfg.State.AddAddressToAccessList(cfg.Origin) - cfg.State.AddAddressToAccessList(address) - for _, addr := range vmenv.ActivePrecompiles() { - cfg.State.AddAddressToAccessList(addr) - } + statedb.PrepareAccessList(cfg.Origin, &address, vmenv.ActivePrecompiles(), nil) } // Call the code with the given configuration. diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 416a387e31..5ddd2f9848 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -1387,7 +1387,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) case err == nil: peer.log.Trace("Delivered new batch of data", "type", kind, "count", packet.Stats()) default: - peer.log.Trace("Failed to deliver retrieved data", "type", kind, "err", err) + peer.log.Debug("Failed to deliver retrieved data", "type", kind, "err", err) } } // Blocks assembled, try to update the progress diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 89caeeb45b..4fd2df10e2 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -63,7 +63,7 @@ func newTestBackend(t *testing.T) *testBackend { Config: params.TestChainConfig, Alloc: core.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}}, } - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) engine := ethash.NewFaker() db := rawdb.NewMemoryDatabase() diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 1bf59552c9..bd995d08a9 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -753,14 +753,15 @@ func (api *API) traceTx(ctx context.Context, message core.Message, vmctx vm.Bloc default: tracer = vm.NewStructLogger(config.LogConfig) } + // Run the transaction with tracing enabled. vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer}) - result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) } - // Depending on the tracer type, format and return the output + + // Depending on the tracer type, format and return the output. switch tracer := tracer.(type) { case *vm.StructLogger: // If the result contains a revert reason, return it. diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index dba7ce87cf..80775caa8e 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -556,7 +556,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost if data, ok := jst.ctx["input"].([]byte); ok { input = data } - intrinsicGas, err := core.IntrinsicGas(input, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul) + intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul) if err != nil { return err } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 8dc34a835e..a17696356c 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" ) @@ -521,7 +520,7 @@ func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64 // If the transaction was a contract creation use the TransactionReceipt method to get the // contract address after the transaction has been mined. func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error { - data, err := rlp.EncodeToBytes(tx) + data, err := tx.MarshalBinary() if err != nil { return err } diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 8b175ee066..9a5a45e34f 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -560,7 +560,7 @@ func sendTransaction(ec *Client) error { } // Create transaction tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) - signer := types.NewEIP155Signer(chainID) + signer := types.LatestSignerForChainID(chainID) signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) if err != nil { return err diff --git a/ethclient/signer.go b/ethclient/signer.go index 74a93f1e2f..9de020b352 100644 --- a/ethclient/signer.go +++ b/ethclient/signer.go @@ -51,6 +51,9 @@ func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) return s.addr, nil } +func (s *senderFromServer) ChainID() *big.Int { + panic("can't sign with senderFromServer") +} func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash { panic("can't sign with senderFromServer") } diff --git a/graphql/graphql.go b/graphql/graphql.go index 5dc0f723d3..2374beb8e1 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -33,7 +33,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" ) @@ -246,12 +245,8 @@ func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account, if err != nil || tx == nil { return nil, err } - var signer types.Signer = types.HomesteadSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } + signer := types.LatestSigner(t.backend.ChainConfig()) from, _ := types.Sender(signer, tx) - return &Account{ backend: t.backend, address: from, @@ -1022,7 +1017,7 @@ func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Has func (r *Resolver) SendRawTransaction(ctx context.Context, args struct{ Data hexutil.Bytes }) (common.Hash, error) { tx := new(types.Transaction) - if err := rlp.DecodeBytes(args.Data, tx); err != nil { + if err := tx.UnmarshalBinary(args.Data); err != nil { return common.Hash{}, err } hash, err := ethapi.SubmitTransaction(ctx, r.backend, tx) diff --git a/interfaces.go b/interfaces.go index 1ff31f96b6..afcdc17e58 100644 --- a/interfaces.go +++ b/interfaces.go @@ -119,6 +119,8 @@ type CallMsg struct { GasPrice *big.Int // wei <-> gas exchange ratio Value *big.Int // amount of wei sent along with the call Data []byte // input data, usually an ABI-encoded contract method invocation + + AccessList types.AccessList // EIP-2930 access list. } // A ContractCaller provides contract calls, essentially transactions that are executed by diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 52b4f5f506..622063cf64 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -410,7 +410,7 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs log.Warn("Failed transaction sign attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err) return nil, err } - data, err := rlp.EncodeToBytes(signed) + data, err := signed.MarshalBinary() if err != nil { return nil, err } @@ -748,12 +748,13 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.A // CallArgs represents the arguments for a call. type CallArgs struct { - From *common.Address `json:"from"` - To *common.Address `json:"to"` - Gas *hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Value *hexutil.Big `json:"value"` - Data *hexutil.Bytes `json:"data"` + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Value *hexutil.Big `json:"value"` + Data *hexutil.Bytes `json:"data"` + AccessList *types.AccessList `json:"accessList"` } // ToMessage converts CallArgs to the Message type used by the core evm @@ -780,18 +781,20 @@ func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message { if args.GasPrice != nil { gasPrice = args.GasPrice.ToInt() } - value := new(big.Int) if args.Value != nil { value = args.Value.ToInt() } - var data []byte if args.Data != nil { data = *args.Data } + var accessList types.AccessList + if args.AccessList != nil { + accessList = *args.AccessList + } - msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false) + msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, accessList, false) return msg } @@ -869,13 +872,13 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo evm.Cancel() }() - // Setup the gas pool (also for unmetered requests) - // and apply the message. + // Execute the message. gp := new(core.GasPool).AddGas(math.MaxUint64) result, err := core.ApplyMessage(evm, msg, gp) if err := vmError(); err != nil { return nil, err } + // If the timer caused an abort, return an appropriate error message if evm.Cancelled() { return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) @@ -1200,33 +1203,43 @@ func (s *PublicBlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Bloc // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction type RPCTransaction struct { - BlockHash *common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From common.Address `json:"from"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Hash common.Hash `json:"hash"` - Input hexutil.Bytes `json:"input"` - Nonce hexutil.Uint64 `json:"nonce"` - To *common.Address `json:"to"` - TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` - Value *hexutil.Big `json:"value"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` + BlockHash *common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Hash common.Hash `json:"hash"` + Input hexutil.Bytes `json:"input"` + Nonce hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` + Value *hexutil.Big `json:"value"` + Type hexutil.Uint64 `json:"type"` + Accesses *types.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` } // newRPCTransaction returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { - var signer types.Signer = types.FrontierSigner{} + // Determine the signer. For replay-protected transactions, use the most permissive + // signer, because we assume that signers are backwards-compatible with old + // transactions. For non-protected transactions, the homestead signer signer is used + // because the return value of ChainId is zero for those transactions. + var signer types.Signer if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) + signer = types.LatestSignerForChainID(tx.ChainId()) + } else { + signer = types.HomesteadSigner{} } + from, _ := types.Sender(signer, tx) v, r, s := tx.RawSignatureValues() - result := &RPCTransaction{ + Type: hexutil.Uint64(tx.Type()), From: from, Gas: hexutil.Uint64(tx.Gas()), GasPrice: (*hexutil.Big)(tx.GasPrice()), @@ -1244,6 +1257,11 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } + if tx.Type() == types.AccessListTxType { + al := tx.AccessList() + result.Accesses = &al + result.ChainID = (*hexutil.Big)(tx.ChainId()) + } return result } @@ -1267,7 +1285,7 @@ func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) hexutil.By if index >= uint64(len(txs)) { return nil } - blob, _ := rlp.EncodeToBytes(txs[index]) + blob, _ := txs[index].MarshalBinary() return blob } @@ -1285,11 +1303,15 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa type PublicTransactionPoolAPI struct { b Backend nonceLock *AddrLocker + signer types.Signer } // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransactionPoolAPI { - return &PublicTransactionPoolAPI{b, nonceLock} + // The signer used by the API should always be the 'latest' known one because we expect + // signers to be backwards-compatible with old transactions. + signer := types.LatestSigner(b.ChainConfig()) + return &PublicTransactionPoolAPI{b, nonceLock, signer} } // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. @@ -1394,7 +1416,7 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context, } } // Serialize to RLP and return - return rlp.EncodeToBytes(tx) + return tx.MarshalBinary() } // GetTransactionReceipt returns the transaction receipt for the given transaction hash. @@ -1412,10 +1434,9 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha } receipt := receipts[index] - var signer types.Signer = types.FrontierSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } + // Derive the sender. + bigblock := new(big.Int).SetUint64(blockNumber) + signer := types.MakeSigner(s.b.ChainConfig(), bigblock) from, _ := types.Sender(signer, tx) fields := map[string]interface{}{ @@ -1430,6 +1451,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha "contractAddress": nil, "logs": receipt.Logs, "logsBloom": receipt.Bloom, + "type": hexutil.Uint(tx.Type()), } // Assign receipt status or post state. @@ -1473,9 +1495,13 @@ type SendTxArgs struct { // newer name and should be preferred by clients. Data *hexutil.Bytes `json:"data"` Input *hexutil.Bytes `json:"input"` + + // For non-legacy transactions + AccessList *types.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` } -// setDefaults is a helper function that fills in default values for unspecified tx fields. +// setDefaults fills in default values for unspecified tx fields. func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { if args.GasPrice == nil { price, err := b.SuggestPrice(ctx) @@ -1509,6 +1535,7 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { return errors.New(`contract creation without any data provided`) } } + // Estimate the gas usage if necessary. if args.Gas == nil { // For backwards-compatibility reason, we try both input and data @@ -1518,11 +1545,12 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { input = args.Data } callArgs := CallArgs{ - From: &args.From, // From shouldn't be nil - To: args.To, - GasPrice: args.GasPrice, - Value: args.Value, - Data: input, + From: &args.From, // From shouldn't be nil + To: args.To, + GasPrice: args.GasPrice, + Value: args.Value, + Data: input, + AccessList: args.AccessList, } pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap()) @@ -1532,9 +1560,15 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { args.Gas = &estimated log.Trace("Estimate gas usage automatically", "gas", args.Gas) } + if args.ChainID == nil { + id := (*hexutil.Big)(b.ChainConfig().ChainID) + args.ChainID = id + } return nil } +// toTransaction converts the arguments to a transaction. +// This assumes that setDefaults has been called. func (args *SendTxArgs) toTransaction() *types.Transaction { var input []byte if args.Input != nil { @@ -1542,10 +1576,30 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { } else if args.Data != nil { input = *args.Data } - if args.To == nil { - return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input) + + var data types.TxData + if args.AccessList == nil { + data = &types.LegacyTx{ + To: args.To, + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasPrice: (*big.Int)(args.GasPrice), + Value: (*big.Int)(args.Value), + Data: input, + } + } else { + data = &types.AccessListTx{ + To: args.To, + ChainID: (*big.Int)(args.ChainID), + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasPrice: (*big.Int)(args.GasPrice), + Value: (*big.Int)(args.Value), + Data: input, + AccessList: *args.AccessList, + } } - return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input) + return types.NewTx(data) } // SubmitTransaction is a helper function that submits tx to txPool and logs a message. @@ -1619,7 +1673,7 @@ func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args Sen } // Assemble the transaction and obtain rlp tx := args.toTransaction() - data, err := rlp.EncodeToBytes(tx) + data, err := tx.MarshalBinary() if err != nil { return nil, err } @@ -1628,9 +1682,9 @@ func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args Sen // SendRawTransaction will add the signed transaction to the transaction pool. // The sender is responsible for signing the transaction and using the correct nonce. -func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) { +func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) { tx := new(types.Transaction) - if err := rlp.DecodeBytes(encodedTx, tx); err != nil { + if err := tx.UnmarshalBinary(input); err != nil { return common.Hash{}, err } return SubmitTransaction(ctx, s.b, tx) @@ -1691,7 +1745,7 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen if err != nil { return nil, err } - data, err := rlp.EncodeToBytes(tx) + data, err := tx.MarshalBinary() if err != nil { return nil, err } @@ -1713,11 +1767,7 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, err } transactions := make([]*RPCTransaction, 0, len(pending)) for _, tx := range pending { - var signer types.Signer = types.HomesteadSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } - from, _ := types.Sender(signer, tx) + from, _ := types.Sender(s.signer, tx) if _, exists := accounts[from]; exists { transactions = append(transactions, newRPCPendingTransaction(tx)) } @@ -1754,13 +1804,9 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr return common.Hash{}, err } for _, p := range pending { - var signer types.Signer = types.HomesteadSigner{} - if p.Protected() { - signer = types.NewEIP155Signer(p.ChainId()) - } - wantSigHash := signer.Hash(matchTx) - - if pFrom, err := types.Sender(signer, p); err == nil && pFrom == sendArgs.From && signer.Hash(p) == wantSigHash { + wantSigHash := s.signer.Hash(matchTx) + pFrom, err := types.Sender(s.signer, p) + if err == nil && pFrom == sendArgs.From && s.signer.Hash(p) == wantSigHash { // Match. Re-sign and send the transaction. if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 { sendArgs.GasPrice = gasPrice @@ -1778,7 +1824,6 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr return signedTx.Hash(), nil } } - return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash()) } diff --git a/internal/guide/guide_test.go b/internal/guide/guide_test.go index 9c7ad16d18..abc48e0e4b 100644 --- a/internal/guide/guide_test.go +++ b/internal/guide/guide_test.go @@ -31,6 +31,7 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -75,7 +76,8 @@ func TestAccountManagement(t *testing.T) { if err != nil { t.Fatalf("Failed to create signer account: %v", err) } - tx, chain := new(types.Transaction), big.NewInt(1) + tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil) + chain := big.NewInt(1) // Sign a transaction with a single authorization if _, err := ks.SignTxWithPassphrase(signer, "Signer password", tx, chain); err != nil { diff --git a/les/benchmark.go b/les/benchmark.go index 6255c1049e..757822a6b3 100644 --- a/les/benchmark.go +++ b/les/benchmark.go @@ -171,7 +171,7 @@ type benchmarkTxSend struct { func (b *benchmarkTxSend) init(h *serverHandler, count int) error { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) - signer := types.NewEIP155Signer(big.NewInt(18)) + signer := types.LatestSigner(h.server.chainConfig) b.txs = make(types.Transactions, count) for i := range b.txs { diff --git a/les/odr_test.go b/les/odr_test.go index a43382e05e..0c75014d49 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -135,7 +135,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai from := statedb.GetOrNewStateObject(bankAddr) from.SetBalance(math.MaxBig256) - msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} + msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, nil, false)} context := core.NewEVMBlockContext(header, bc, nil) txContext := core.NewEVMTxContext(msg) @@ -150,7 +150,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai header := lc.GetHeaderByHash(bhash) state := light.NewState(ctx, header, lc.Odr()) state.SetBalance(bankAddr, math.MaxBig256) - msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} + msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, nil, false)} context := core.NewEVMBlockContext(header, lc, nil) txContext := core.NewEVMTxContext(msg) vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{}) diff --git a/light/odr_test.go b/light/odr_test.go index cb22334fdc..0fc45b8734 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -194,7 +194,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain // Perform read-only call. st.SetBalance(testBankAddress, math.MaxBig256) - msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, false)} + msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, nil, false)} txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(header, chain, nil) vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{}) diff --git a/light/txpool.go b/light/txpool.go index 2831de5a65..bf5f9ff583 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -32,7 +32,6 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" ) const ( @@ -69,6 +68,7 @@ type TxPool struct { clearIdx uint64 // earliest block nr that can contain mined tx info istanbul bool // Fork indicator whether we are in the istanbul stage. + eip2718 bool // Fork indicator whether we are in the eip2718 stage. } // TxRelayBackend provides an interface to the mechanism that forwards transacions @@ -90,7 +90,7 @@ type TxRelayBackend interface { func NewTxPool(config *params.ChainConfig, chain *LightChain, relay TxRelayBackend) *TxPool { pool := &TxPool{ config: config, - signer: types.NewEIP155Signer(config.ChainID), + signer: types.LatestSigner(config), nonce: make(map[common.Address]uint64), pending: make(map[common.Hash]*types.Transaction), mined: make(map[common.Hash][]*types.Transaction), @@ -314,6 +314,7 @@ func (pool *TxPool) setNewHead(head *types.Header) { // Update fork indicator by next pending block number next := new(big.Int).Add(head.Number, big.NewInt(1)) pool.istanbul = pool.config.IsIstanbul(next) + pool.eip2718 = pool.config.IsYoloV3(next) } // Stop stops the light transaction pool @@ -381,7 +382,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error } // Should supply enough intrinsic gas - gas, err := core.IntrinsicGas(tx.Data(), tx.To() == nil, true, pool.istanbul) + gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul) if err != nil { return err } @@ -430,8 +431,7 @@ func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error { func (pool *TxPool) Add(ctx context.Context, tx *types.Transaction) error { pool.mu.Lock() defer pool.mu.Unlock() - - data, err := rlp.EncodeToBytes(tx) + data, err := tx.MarshalBinary() if err != nil { return err } diff --git a/miner/worker.go b/miner/worker.go index e81d50e46e..2cee6af0c3 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -654,7 +654,7 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { state.StartPrefetcher("miner") env := &environment{ - signer: types.NewEIP155Signer(w.chainConfig.ChainID), + signer: types.MakeSigner(w.chainConfig, header.Number), state: state, ancestors: mapset.NewSet(), family: mapset.NewSet(), @@ -829,6 +829,11 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin w.current.tcount++ txs.Shift() + case errors.Is(err, core.ErrTxTypeNotSupported): + // Pop the unsupported transaction without shifting in the next from the account + log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type()) + txs.Pop() + default: // Strange error, discard the transaction and get the next in line (note, the // nonce-too-high clause will prevent us from executing in vain). diff --git a/miner/worker_test.go b/miner/worker_test.go index a5c558ba5f..0fe62316e1 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -81,10 +81,25 @@ func init() { Period: 10, Epoch: 30000, } - tx1, _ := types.SignTx(types.NewTransaction(0, testUserAddress, big.NewInt(1000), params.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey) + + signer := types.LatestSigner(params.TestChainConfig) + tx1 := types.MustSignNewTx(testBankKey, signer, &types.AccessListTx{ + ChainID: params.TestChainConfig.ChainID, + Nonce: 0, + To: &testUserAddress, + Value: big.NewInt(1000), + Gas: params.TxGas, + }) pendingTxs = append(pendingTxs, tx1) - tx2, _ := types.SignTx(types.NewTransaction(1, testUserAddress, big.NewInt(1000), params.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey) + + tx2 := types.MustSignNewTx(testBankKey, signer, &types.LegacyTx{ + Nonce: 1, + To: &testUserAddress, + Value: big.NewInt(1000), + Gas: params.TxGas, + }) newTxs = append(newTxs, tx2) + rand.Seed(time.Now().UnixNano()) } diff --git a/params/config.go b/params/config.go index 0dbf592d19..929bdbeb94 100644 --- a/params/config.go +++ b/params/config.go @@ -215,7 +215,7 @@ var ( // YoloV3ChainConfig contains the chain parameters to run a node on the YOLOv3 test network. YoloV3ChainConfig = &ChainConfig{ - ChainID: big.NewInt(133519467574834), + ChainID: new(big.Int).SetBytes([]byte("yolov3x")), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: true, @@ -246,9 +246,9 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), nil, new(EthashConfig), nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) diff --git a/params/protocol_params.go b/params/protocol_params.go index ffb901ec3d..88f1a06e12 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -60,20 +60,23 @@ const ( JumpdestGas uint64 = 1 // Once per JUMPDEST operation. EpochDuration uint64 = 30000 // Duration between proof-of-work epochs. - CreateDataGas uint64 = 200 // - CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack. - ExpGas uint64 = 10 // Once per EXP instruction - LogGas uint64 = 375 // Per LOG* operation. - CopyGas uint64 = 3 // - StackLimit uint64 = 1024 // Maximum size of VM stack allowed. - TierStepGas uint64 = 0 // Once per operation, for a selection of them. - LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. - CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. - Create2Gas uint64 = 32000 // Once per CREATE2 operation - SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. - MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. - TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. - TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) + CreateDataGas uint64 = 200 // + CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack. + ExpGas uint64 = 10 // Once per EXP instruction + LogGas uint64 = 375 // Per LOG* operation. + CopyGas uint64 = 3 // + StackLimit uint64 = 1024 // Maximum size of VM stack allowed. + TierStepGas uint64 = 0 // Once per operation, for a selection of them. + LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. + CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. + Create2Gas uint64 = 32000 // Once per CREATE2 operation + SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. + MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. + + TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. + TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) + TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list + TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list // These have been changed during the course of the chain CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. diff --git a/signer/core/api.go b/signer/core/api.go index 07e206a74c..968dcfb2ed 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -33,7 +33,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/signer/storage" ) @@ -574,11 +573,11 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth return nil, err } - rlpdata, err := rlp.EncodeToBytes(signedTx) + data, err := signedTx.MarshalBinary() if err != nil { return nil, err } - response := ethapi.SignTransactionResult{Raw: rlpdata, Tx: signedTx} + response := ethapi.SignTransactionResult{Raw: data, Tx: signedTx} // Finally, send the signed tx to the UI api.UI.OnApprovedTx(response) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 53c7d955be..03dc01459c 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -182,27 +182,21 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh if err != nil { return nil, nil, common.Hash{}, err } + + // Prepare the EVM. txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash evm := vm.NewEVM(context, txContext, statedb, config, vmconfig) - if config.IsYoloV3(context.BlockNumber) { - statedb.AddAddressToAccessList(msg.From()) - if dst := msg.To(); dst != nil { - statedb.AddAddressToAccessList(*dst) - // If it's a create-tx, the destination will be added inside evm.create - } - for _, addr := range evm.ActivePrecompiles() { - statedb.AddAddressToAccessList(addr) - } - } + // Execute the message. + snapshot := statedb.Snapshot() gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()) - snapshot := statedb.Snapshot() if _, err := core.ApplyMessage(evm, msg, gaspool); err != nil { statedb.RevertToSnapshot(snapshot) } + // Commit block statedb.Commit(config.IsEIP158(block.Number())) // Add 0-value mining reward. This only makes a difference in the cases @@ -300,7 +294,7 @@ func (tx *stTransaction) toMessage(ps stPostState) (core.Message, error) { return nil, fmt.Errorf("invalid tx data %q", dataHex) } - msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, true) + msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, nil, true) return msg, nil } diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index aea90535c3..82ee01de15 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -55,7 +55,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error { return nil, nil, err } // Intrinsic gas - requiredGas, err := core.IntrinsicGas(tx.Data(), tx.To() == nil, isHomestead, isIstanbul) + requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul) if err != nil { return nil, nil, err } diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go index d4488b4029..29706f2e9d 100644 --- a/trie/stacktrie_test.go +++ b/trie/stacktrie_test.go @@ -1,16 +1,9 @@ package trie import ( - "bytes" - "fmt" - "math/big" - mrand "math/rand" "testing" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb/memorydb" ) @@ -78,169 +71,6 @@ func TestValLength56(t *testing.T) { } } -func genTxs(num uint64) (types.Transactions, error) { - key, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - if err != nil { - return nil, err - } - var addr = crypto.PubkeyToAddress(key.PublicKey) - newTx := func(i uint64) (*types.Transaction, error) { - signer := types.NewEIP155Signer(big.NewInt(18)) - tx, err := types.SignTx(types.NewTransaction(i, addr, new(big.Int), 0, new(big.Int).SetUint64(10000000), nil), signer, key) - return tx, err - } - var txs types.Transactions - for i := uint64(0); i < num; i++ { - tx, err := newTx(i) - if err != nil { - return nil, err - } - txs = append(txs, tx) - } - return txs, nil -} - -func TestDeriveSha(t *testing.T) { - txs, err := genTxs(0) - if err != nil { - t.Fatal(err) - } - for len(txs) < 1000 { - exp := types.DeriveSha(txs, newEmpty()) - got := types.DeriveSha(txs, NewStackTrie(nil)) - if !bytes.Equal(got[:], exp[:]) { - t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) - } - newTxs, err := genTxs(uint64(len(txs) + 1)) - if err != nil { - t.Fatal(err) - } - txs = append(txs, newTxs...) - } -} - -func BenchmarkDeriveSha200(b *testing.B) { - txs, err := genTxs(200) - if err != nil { - b.Fatal(err) - } - var exp common.Hash - var got common.Hash - b.Run("std_trie", func(b *testing.B) { - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - exp = types.DeriveSha(txs, newEmpty()) - } - }) - - b.Run("stack_trie", func(b *testing.B) { - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - got = types.DeriveSha(txs, NewStackTrie(nil)) - } - }) - if got != exp { - b.Errorf("got %x exp %x", got, exp) - } -} - -type dummyDerivableList struct { - len int - seed int -} - -func newDummy(seed int) *dummyDerivableList { - d := &dummyDerivableList{} - src := mrand.NewSource(int64(seed)) - // don't use lists longer than 4K items - d.len = int(src.Int63() & 0x0FFF) - d.seed = seed - return d -} - -func (d *dummyDerivableList) Len() int { - return d.len -} - -func (d *dummyDerivableList) GetRlp(i int) []byte { - src := mrand.NewSource(int64(d.seed + i)) - // max item size 256, at least 1 byte per item - size := 1 + src.Int63()&0x00FF - data := make([]byte, size) - _, err := mrand.New(src).Read(data) - if err != nil { - panic(err) - } - return data -} - -func printList(l types.DerivableList) { - fmt.Printf("list length: %d\n", l.Len()) - fmt.Printf("{\n") - for i := 0; i < l.Len(); i++ { - v := l.GetRlp(i) - fmt.Printf("\"0x%x\",\n", v) - } - fmt.Printf("},\n") -} - -func TestFuzzDeriveSha(t *testing.T) { - // increase this for longer runs -- it's set to quite low for travis - rndSeed := mrand.Int() - for i := 0; i < 10; i++ { - seed := rndSeed + i - exp := types.DeriveSha(newDummy(i), newEmpty()) - got := types.DeriveSha(newDummy(i), NewStackTrie(nil)) - if !bytes.Equal(got[:], exp[:]) { - printList(newDummy(seed)) - t.Fatalf("seed %d: got %x exp %x", seed, got, exp) - } - } -} - -type flatList struct { - rlpvals []string -} - -func newFlatList(rlpvals []string) *flatList { - return &flatList{rlpvals} -} -func (f *flatList) Len() int { - return len(f.rlpvals) -} -func (f *flatList) GetRlp(i int) []byte { - return hexutil.MustDecode(f.rlpvals[i]) -} - -// TestDerivableList contains testcases found via fuzzing -func TestDerivableList(t *testing.T) { - type tcase []string - tcs := []tcase{ - { - "0xc041", - }, - { - "0xf04cf757812428b0763112efb33b6f4fad7deb445e", - "0xf04cf757812428b0763112efb33b6f4fad7deb445e", - }, - { - "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", - "0x6cd850eca0a7ac46bb1748d7b9cb88aa3bd21c57d852c28198ad8fa422c4595032e88a4494b4778b36b944fe47a52b8c5cd312910139dfcb4147ab8e972cc456bcb063f25dd78f54c4d34679e03142c42c662af52947d45bdb6e555751334ace76a5080ab5a0256a1d259855dfc5c0b8023b25befbb13fd3684f9f755cbd3d63544c78ee2001452dd54633a7593ade0b183891a0a4e9c7844e1254005fbe592b1b89149a502c24b6e1dca44c158aebedf01beae9c30cabe16a", - "0x14abd5c47c0be87b0454596baad2", - "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", - }, - } - for i, tc := range tcs[1:] { - exp := types.DeriveSha(newFlatList(tc), newEmpty()) - got := types.DeriveSha(newFlatList(tc), NewStackTrie(nil)) - if !bytes.Equal(got[:], exp[:]) { - t.Fatalf("case %d: got %x exp %x", i, got, exp) - } - } -} - // TestUpdateSmallNodes tests a case where the leaves are small (both key and value), // which causes a lot of node-within-node. This case was found via fuzzing. func TestUpdateSmallNodes(t *testing.T) { From de9465f991916e183a504ce79988c6cef544f7f1 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 25 Feb 2021 18:36:01 +0100 Subject: [PATCH 220/235] cmd/devp2p: add eth66 test suite (#22363) Co-authored-by: Martin Holst Swende --- cmd/devp2p/README.md | 12 +- cmd/devp2p/internal/ethtest/eth66_suite.go | 382 ++++++++++++++++++ .../internal/ethtest/eth66_suiteHelpers.go | 270 +++++++++++++ cmd/devp2p/internal/ethtest/suite.go | 39 +- cmd/devp2p/internal/ethtest/transaction.go | 29 +- cmd/devp2p/internal/ethtest/types.go | 8 +- cmd/devp2p/rlpxcmd.go | 7 +- go.mod | 1 + 8 files changed, 721 insertions(+), 27 deletions(-) create mode 100644 cmd/devp2p/internal/ethtest/eth66_suite.go create mode 100644 cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md index ca0b852fda..e934ee25c9 100644 --- a/cmd/devp2p/README.md +++ b/cmd/devp2p/README.md @@ -100,7 +100,17 @@ Then, run the following command, replacing `` with the enode of the geth ``` Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again. - + +#### Eth66 Test Suite + +The Eth66 test suite is also a conformance test suite for the eth 66 protocol version specifically. +To run the eth66 protocol test suite, initialize a geth node as described above and run the following command, +replacing `` with the enode of the geth node: + + ``` + devp2p rlpx eth66-test cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json +``` + [eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md [dns-tutorial]: https://geth.ethereum.org/docs/developers/dns-discovery-setup [discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go new file mode 100644 index 0000000000..644fed61eb --- /dev/null +++ b/cmd/devp2p/internal/ethtest/eth66_suite.go @@ -0,0 +1,382 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" +) + +// TestStatus_66 attempts to connect to the given node and exchange +// a status message with it on the eth66 protocol, and then check to +// make sure the chain head is correct. +func (s *Suite) TestStatus_66(t *utesting.T) { + conn := s.dial66(t) + // get protoHandshake + conn.handshake(t) + // get status + switch msg := conn.statusExchange66(t, s.chain).(type) { + case *Status: + status := *msg + if status.ProtocolVersion != uint32(66) { + t.Fatalf("mismatch in version: wanted 66, got %d", status.ProtocolVersion) + } + t.Logf("got status message: %s", pretty.Sdump(msg)) + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +// TestGetBlockHeaders_66 tests whether the given node can respond to +// an eth66 `GetBlockHeaders` request and that the response is accurate. +func (s *Suite) TestGetBlockHeaders_66(t *utesting.T) { + conn := s.setupConnection66(t) + // get block headers + req := ð.GetBlockHeadersPacket66{ + RequestId: 3, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 2, + Skip: 1, + Reverse: false, + }, + } + // write message + headers := s.getBlockHeaders66(t, conn, req, req.RequestId) + // check for correct headers + headersMatch(t, s.chain, headers) +} + +// TestSimultaneousRequests_66 sends two simultaneous `GetBlockHeader` requests +// with different request IDs and checks to make sure the node responds with the correct +// headers per request. +func (s *Suite) TestSimultaneousRequests_66(t *utesting.T) { + // create two connections + conn1, conn2 := s.setupConnection66(t), s.setupConnection66(t) + // create two requests + req1 := ð.GetBlockHeadersPacket66{ + RequestId: 111, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 2, + Skip: 1, + Reverse: false, + }, + } + req2 := ð.GetBlockHeadersPacket66{ + RequestId: 222, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 4, + Skip: 1, + Reverse: false, + }, + } + // wait for headers for first request + headerChan := make(chan BlockHeaders, 1) + go func(headers chan BlockHeaders) { + headers <- s.getBlockHeaders66(t, conn1, req1, req1.RequestId) + }(headerChan) + // check headers of second request + headersMatch(t, s.chain, s.getBlockHeaders66(t, conn2, req2, req2.RequestId)) + // check headers of first request + headersMatch(t, s.chain, <-headerChan) +} + +// TestBroadcast_66 tests whether a block announcement is correctly +// propagated to the given node's peer(s) on the eth66 protocol. +func (s *Suite) TestBroadcast_66(t *utesting.T) { + sendConn, receiveConn := s.setupConnection66(t), s.setupConnection66(t) + nextBlock := len(s.chain.blocks) + blockAnnouncement := &NewBlock{ + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + } + s.testAnnounce66(t, sendConn, receiveConn, blockAnnouncement) + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) + // wait for client to update its chain + if err := receiveConn.waitForBlock66(s.chain.Head()); err != nil { + t.Fatal(err) + } +} + +// TestGetBlockBodies_66 tests whether the given node can respond to +// a `GetBlockBodies` request and that the response is accurate over +// the eth66 protocol. +func (s *Suite) TestGetBlockBodies_66(t *utesting.T) { + conn := s.setupConnection66(t) + // create block bodies request + id := uint64(55) + req := ð.GetBlockBodiesPacket66{ + RequestId: id, + GetBlockBodiesPacket: eth.GetBlockBodiesPacket{ + s.chain.blocks[54].Hash(), + s.chain.blocks[75].Hash(), + }, + } + if err := conn.write66(req, GetBlockBodies{}.Code()); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + reqID, msg := conn.readAndServe66(s.chain, timeout) + switch msg := msg.(type) { + case BlockBodies: + if reqID != req.RequestId { + t.Fatalf("request ID mismatch: wanted %d, got %d", req.RequestId, reqID) + } + t.Logf("received %d block bodies", len(msg)) + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +// TestLargeAnnounce_66 tests the announcement mechanism with a large block. +func (s *Suite) TestLargeAnnounce_66(t *utesting.T) { + nextBlock := len(s.chain.blocks) + blocks := []*NewBlock{ + { + Block: largeBlock(), + TD: s.fullChain.TD(nextBlock + 1), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: largeNumber(2), + }, + { + Block: largeBlock(), + TD: largeNumber(2), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + }, + } + + for i, blockAnnouncement := range blocks[0:3] { + t.Logf("Testing malicious announcement: %v\n", i) + sendConn := s.setupConnection66(t) + if err := sendConn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // Invalid announcement, check that peer disconnected + switch msg := sendConn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + break + default: + t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) + } + } + // Test the last block as a valid block + sendConn := s.setupConnection66(t) + receiveConn := s.setupConnection66(t) + s.testAnnounce66(t, sendConn, receiveConn, blocks[3]) + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) + // wait for client to update its chain + if err := receiveConn.waitForBlock66(s.fullChain.blocks[nextBlock]); err != nil { + t.Fatal(err) + } +} + +// TestMaliciousHandshake_66 tries to send malicious data during the handshake. +func (s *Suite) TestMaliciousHandshake_66(t *utesting.T) { + conn := s.dial66(t) + // write hello to client + pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] + handshakes := []*Hello{ + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 66}, + }, + ID: pub0, + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + {Name: "eth", Version: 66}, + }, + ID: append(pub0, byte(0)), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + {Name: "eth", Version: 66}, + }, + ID: append(pub0, pub0...), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + {Name: "eth", Version: 66}, + }, + ID: largeBuffer(2), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 66}, + }, + ID: largeBuffer(2), + }, + } + for i, handshake := range handshakes { + t.Logf("Testing malicious handshake %v\n", i) + // Init the handshake + if err := conn.Write(handshake); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // check that the peer disconnected + timeout := 20 * time.Second + // Discard one hello + for i := 0; i < 2; i++ { + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + case *Hello: + // Hello's are sent concurrently, so ignore them + continue + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } + } + // Dial for the next round + conn = s.dial66(t) + } +} + +// TestMaliciousStatus_66 sends a status package with a large total difficulty. +func (s *Suite) TestMaliciousStatus_66(t *utesting.T) { + conn := s.dial66(t) + // get protoHandshake + conn.handshake(t) + status := &Status{ + ProtocolVersion: uint32(66), + NetworkID: s.chain.chainConfig.ChainID.Uint64(), + TD: largeNumber(2), + Head: s.chain.blocks[s.chain.Len()-1].Hash(), + Genesis: s.chain.blocks[0].Hash(), + ForkID: s.chain.ForkID(), + } + // get status + switch msg := conn.statusExchange(t, s.chain, status).(type) { + case *Status: + t.Logf("%+v\n", msg) + default: + t.Fatalf("expected status, got: %#v ", msg) + } + // wait for disconnect + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + return + default: + t.Fatalf("expected disconnect, got: %s", pretty.Sdump(msg)) + } +} + +func (s *Suite) TestTransaction_66(t *utesting.T) { + tests := []*types.Transaction{ + getNextTxFromChain(t, s), + unknownTx(t, s), + } + for i, tx := range tests { + t.Logf("Testing tx propagation: %v\n", i) + sendSuccessfulTx66(t, s, tx) + } +} + +func (s *Suite) TestMaliciousTx_66(t *utesting.T) { + tests := []*types.Transaction{ + getOldTxFromChain(t, s), + invalidNonceTx(t, s), + hugeAmount(t, s), + hugeGasPrice(t, s), + hugeData(t, s), + } + for i, tx := range tests { + t.Logf("Testing malicious tx propagation: %v\n", i) + sendFailingTx66(t, s, tx) + } +} + +// TestZeroRequestID_66 checks that a request ID of zero is still handled +// by the node. +func (s *Suite) TestZeroRequestID_66(t *utesting.T) { + conn := s.setupConnection66(t) + req := ð.GetBlockHeadersPacket66{ + RequestId: 0, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Number: 0, + }, + Amount: 2, + }, + } + headersMatch(t, s.chain, s.getBlockHeaders66(t, conn, req, req.RequestId)) +} + +// TestSameRequestID_66 sends two requests with the same request ID +// concurrently to a single node. +func (s *Suite) TestSameRequestID_66(t *utesting.T) { + conn := s.setupConnection66(t) + // create two separate requests with same ID + reqID := uint64(1234) + req1 := ð.GetBlockHeadersPacket66{ + RequestId: reqID, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Number: 0, + }, + Amount: 2, + }, + } + req2 := ð.GetBlockHeadersPacket66{ + RequestId: reqID, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Number: 33, + }, + Amount: 2, + }, + } + // send requests concurrently + go func() { + headersMatch(t, s.chain, s.getBlockHeaders66(t, conn, req2, reqID)) + }() + // check response from first request + headersMatch(t, s.chain, s.getBlockHeaders66(t, conn, req1, reqID)) +} diff --git a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go new file mode 100644 index 0000000000..b7fa1dce26 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go @@ -0,0 +1,270 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "fmt" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/assert" +) + +func (c *Conn) statusExchange66(t *utesting.T, chain *Chain) Message { + status := &Status{ + ProtocolVersion: uint32(66), + NetworkID: chain.chainConfig.ChainID.Uint64(), + TD: chain.TD(chain.Len()), + Head: chain.blocks[chain.Len()-1].Hash(), + Genesis: chain.blocks[0].Hash(), + ForkID: chain.ForkID(), + } + return c.statusExchange(t, chain, status) +} + +func (s *Suite) dial66(t *utesting.T) *Conn { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + conn.caps = append(conn.caps, p2p.Cap{Name: "eth", Version: 66}) + return conn +} + +func (c *Conn) write66(req eth.Packet, code int) error { + payload, err := rlp.EncodeToBytes(req) + if err != nil { + return err + } + _, err = c.Conn.Write(uint64(code), payload) + return err +} + +func (c *Conn) read66() (uint64, Message) { + code, rawData, _, err := c.Conn.Read() + if err != nil { + return 0, errorf("could not read from connection: %v", err) + } + + var msg Message + + switch int(code) { + case (Hello{}).Code(): + msg = new(Hello) + + case (Ping{}).Code(): + msg = new(Ping) + case (Pong{}).Code(): + msg = new(Pong) + case (Disconnect{}).Code(): + msg = new(Disconnect) + case (Status{}).Code(): + msg = new(Status) + case (GetBlockHeaders{}).Code(): + ethMsg := new(eth.GetBlockHeadersPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, GetBlockHeaders(*ethMsg.GetBlockHeadersPacket) + case (BlockHeaders{}).Code(): + ethMsg := new(eth.BlockHeadersPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, BlockHeaders(ethMsg.BlockHeadersPacket) + case (GetBlockBodies{}).Code(): + ethMsg := new(eth.GetBlockBodiesPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, GetBlockBodies(ethMsg.GetBlockBodiesPacket) + case (BlockBodies{}).Code(): + ethMsg := new(eth.BlockBodiesPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, BlockBodies(ethMsg.BlockBodiesPacket) + case (NewBlock{}).Code(): + msg = new(NewBlock) + case (NewBlockHashes{}).Code(): + msg = new(NewBlockHashes) + case (Transactions{}).Code(): + msg = new(Transactions) + case (NewPooledTransactionHashes{}).Code(): + msg = new(NewPooledTransactionHashes) + default: + msg = errorf("invalid message code: %d", code) + } + + if msg != nil { + if err := rlp.DecodeBytes(rawData, msg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return 0, msg + } + return 0, errorf("invalid message: %s", string(rawData)) +} + +// ReadAndServe serves GetBlockHeaders requests while waiting +// on another message from the node. +func (c *Conn) readAndServe66(chain *Chain, timeout time.Duration) (uint64, Message) { + start := time.Now() + for time.Since(start) < timeout { + timeout := time.Now().Add(10 * time.Second) + c.SetReadDeadline(timeout) + + reqID, msg := c.read66() + + switch msg := msg.(type) { + case *Ping: + c.Write(&Pong{}) + case *GetBlockHeaders: + headers, err := chain.GetHeaders(*msg) + if err != nil { + return 0, errorf("could not get headers for inbound header request: %v", err) + } + + if err := c.Write(headers); err != nil { + return 0, errorf("could not write to connection: %v", err) + } + default: + return reqID, msg + } + } + return 0, errorf("no message received within %v", timeout) +} + +func (s *Suite) setupConnection66(t *utesting.T) *Conn { + // create conn + sendConn := s.dial66(t) + sendConn.handshake(t) + sendConn.statusExchange66(t, s.chain) + return sendConn +} + +func (s *Suite) testAnnounce66(t *utesting.T, sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) { + // Announce the block. + if err := sendConn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + s.waitAnnounce66(t, receiveConn, blockAnnouncement) +} + +func (s *Suite) waitAnnounce66(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) { + timeout := 20 * time.Second + _, msg := conn.readAndServe66(s.chain, timeout) + switch msg := msg.(type) { + case *NewBlock: + t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) + assert.Equal(t, + blockAnnouncement.Block.Header(), msg.Block.Header(), + "wrong block header in announcement", + ) + assert.Equal(t, + blockAnnouncement.TD, msg.TD, + "wrong TD in announcement", + ) + case *NewBlockHashes: + blockHashes := *msg + t.Logf("received NewBlockHashes message: %s", pretty.Sdump(blockHashes)) + assert.Equal(t, blockAnnouncement.Block.Hash(), blockHashes[0].Hash, + "wrong block hash in announcement", + ) + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +// waitForBlock66 waits for confirmation from the client that it has +// imported the given block. +func (c *Conn) waitForBlock66(block *types.Block) error { + defer c.SetReadDeadline(time.Time{}) + + timeout := time.Now().Add(20 * time.Second) + c.SetReadDeadline(timeout) + for { + req := eth.GetBlockHeadersPacket66{ + RequestId: 54, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: block.Hash(), + }, + Amount: 1, + }, + } + if err := c.write66(req, GetBlockHeaders{}.Code()); err != nil { + return err + } + + reqID, msg := c.read66() + // check message + switch msg := msg.(type) { + case BlockHeaders: + // check request ID + if reqID != req.RequestId { + return fmt.Errorf("request ID mismatch: wanted %d, got %d", req.RequestId, reqID) + } + if len(msg) > 0 { + return nil + } + time.Sleep(100 * time.Millisecond) + default: + return fmt.Errorf("invalid message: %s", pretty.Sdump(msg)) + } + } +} + +func sendSuccessfulTx66(t *utesting.T, s *Suite, tx *types.Transaction) { + sendConn := s.setupConnection66(t) + sendSuccessfulTxWithConn(t, s, tx, sendConn) +} + +func sendFailingTx66(t *utesting.T, s *Suite, tx *types.Transaction) { + sendConn, recvConn := s.setupConnection66(t), s.setupConnection66(t) + sendFailingTxWithConns(t, s, tx, sendConn, recvConn) +} + +func (s *Suite) getBlockHeaders66(t *utesting.T, conn *Conn, req eth.Packet, expectedID uint64) BlockHeaders { + if err := conn.write66(req, GetBlockHeaders{}.Code()); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // check block headers response + reqID, msg := conn.readAndServe66(s.chain, timeout) + + switch msg := msg.(type) { + case BlockHeaders: + if reqID != expectedID { + t.Fatalf("request ID mismatch: wanted %d, got %d", expectedID, reqID) + } + return msg + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + return nil + } +} + +func headersMatch(t *utesting.T, chain *Chain, headers BlockHeaders) { + for _, header := range headers { + num := header.Number.Uint64() + t.Logf("received header (%d): %s", num, pretty.Sdump(header.Hash())) + assert.Equal(t, chain.blocks[int(num)].Header(), header) + } +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index edf7bb7e31..48010b90dd 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -53,29 +53,47 @@ type Suite struct { // NewSuite creates and returns a new eth-test suite that can // be used to test the given node against the given blockchain // data. -func NewSuite(dest *enode.Node, chainfile string, genesisfile string) *Suite { +func NewSuite(dest *enode.Node, chainfile string, genesisfile string) (*Suite, error) { chain, err := loadChain(chainfile, genesisfile) if err != nil { - panic(err) + return nil, err } return &Suite{ Dest: dest, chain: chain.Shorten(1000), fullChain: chain, - } + }, nil } -func (s *Suite) AllTests() []utesting.Test { +func (s *Suite) EthTests() []utesting.Test { return []utesting.Test{ + // status {Name: "Status", Fn: s.TestStatus}, + {Name: "Status_66", Fn: s.TestStatus_66}, + // get block headers {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, - {Name: "Broadcast", Fn: s.TestBroadcast}, + {Name: "GetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66}, + {Name: "TestSimultaneousRequests_66", Fn: s.TestSimultaneousRequests_66}, + {Name: "TestSameRequestID_66", Fn: s.TestSameRequestID_66}, + {Name: "TestZeroRequestID_66", Fn: s.TestZeroRequestID_66}, + // get block bodies {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, + {Name: "GetBlockBodies_66", Fn: s.TestGetBlockBodies_66}, + // broadcast + {Name: "Broadcast", Fn: s.TestBroadcast}, + {Name: "Broadcast_66", Fn: s.TestBroadcast_66}, {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, + {Name: "TestLargeAnnounce_66", Fn: s.TestLargeAnnounce_66}, + // malicious handshakes + status {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, + {Name: "TestMaliciousHandshake_66", Fn: s.TestMaliciousHandshake_66}, + {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus}, + // test transactions {Name: "TestTransactions", Fn: s.TestTransaction}, + {Name: "TestTransactions_66", Fn: s.TestTransaction_66}, {Name: "TestMaliciousTransactions", Fn: s.TestMaliciousTx}, + {Name: "TestMaliciousTransactions_66", Fn: s.TestMaliciousTx_66}, } } @@ -161,7 +179,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { headers := *msg for _, header := range headers { num := header.Number.Uint64() - t.Logf("received header (%d): %s", num, pretty.Sdump(header)) + t.Logf("received header (%d): %s", num, pretty.Sdump(header.Hash())) assert.Equal(t, s.chain.blocks[int(num)].Header(), header) } default: @@ -386,20 +404,23 @@ func (s *Suite) setupConnection(t *utesting.T) *Conn { // returning the created Conn if successful. func (s *Suite) dial() (*Conn, error) { var conn Conn - + // dial fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) if err != nil { return nil, err } conn.Conn = rlpx.NewConn(fd, s.Dest.Pubkey()) - // do encHandshake conn.ourKey, _ = crypto.GenerateKey() _, err = conn.Handshake(conn.ourKey) if err != nil { return nil, err } - + // set default p2p capabilities + conn.caps = []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + } return &conn, nil } diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go index effcc3af29..21aa221e8b 100644 --- a/cmd/devp2p/internal/ethtest/transaction.go +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -30,6 +30,10 @@ var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c666 func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { sendConn := s.setupConnection(t) + sendSuccessfulTxWithConn(t, s, tx, sendConn) +} + +func sendSuccessfulTxWithConn(t *utesting.T, s *Suite, tx *types.Transaction, sendConn *Conn) { t.Logf("sending tx: %v %v %v\n", tx.Hash().String(), tx.GasPrice(), tx.Gas()) // Send the transaction if err := sendConn.Write(&Transactions{tx}); err != nil { @@ -41,20 +45,21 @@ func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { case *Transactions: recTxs := *msg - if len(recTxs) < 1 { - t.Fatalf("received transactions do not match send: %v", recTxs) - } - if tx.Hash() != recTxs[len(recTxs)-1].Hash() { - t.Fatalf("received transactions do not match send: got %v want %v", recTxs, tx) + for _, gotTx := range recTxs { + if gotTx.Hash() == tx.Hash() { + // Ok + return + } } + t.Fatalf("missing transaction: got %v missing %v", recTxs, tx.Hash()) case *NewPooledTransactionHashes: txHashes := *msg - if len(txHashes) < 1 { - t.Fatalf("received transactions do not match send: %v", txHashes) - } - if tx.Hash() != txHashes[len(txHashes)-1] { - t.Fatalf("wrong announcement received, wanted %v got %v", tx, txHashes) + for _, gotHash := range txHashes { + if gotHash == tx.Hash() { + return + } } + t.Fatalf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash()) default: t.Fatalf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg)) } @@ -62,6 +67,10 @@ func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { func sendFailingTx(t *utesting.T, s *Suite, tx *types.Transaction) { sendConn, recvConn := s.setupConnection(t), s.setupConnection(t) + sendFailingTxWithConns(t, s, tx, sendConn, recvConn) +} + +func sendFailingTxWithConns(t *utesting.T, s *Suite, tx *types.Transaction, sendConn, recvConn *Conn) { // Wait for a transaction announcement switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { case *NewPooledTransactionHashes: diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index b901f50700..734adff366 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -125,6 +125,7 @@ type Conn struct { *rlpx.Conn ourKey *ecdsa.PrivateKey ethProtocolVersion uint + caps []p2p.Cap } func (c *Conn) Read() Message { @@ -221,11 +222,8 @@ func (c *Conn) handshake(t *utesting.T) Message { pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] ourHandshake := &Hello{ Version: 5, - Caps: []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, - }, - ID: pub0, + Caps: c.caps, + ID: pub0, } if err := c.Write(ourHandshake); err != nil { t.Fatalf("could not write to connection: %v", err) diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go index d90eb4687c..ac92818aa4 100644 --- a/cmd/devp2p/rlpxcmd.go +++ b/cmd/devp2p/rlpxcmd.go @@ -94,6 +94,9 @@ func rlpxEthTest(ctx *cli.Context) error { if ctx.NArg() < 3 { exit("missing path to chain.rlp as command-line argument") } - suite := ethtest.NewSuite(getNodeArg(ctx), ctx.Args()[1], ctx.Args()[2]) - return runTests(ctx, suite.AllTests()) + suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args()[1], ctx.Args()[2]) + if err != nil { + exit(err) + } + return runTests(ctx, suite.EthTests()) } diff --git a/go.mod b/go.mod index cddce4e0d8..a1099c52cc 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c golang.org/x/text v0.3.3 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 From dc109cce26da8a93f74a998f9dd7fc2ac0ab98d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Thu, 25 Feb 2021 21:08:34 +0100 Subject: [PATCH 221/235] les: move server pool to les/vflux/client (#22377) * les: move serverPool to les/vflux/client * les: add metrics * les: moved ValueTracker inside ServerPool * les: protect against node registration before server pool is started * les/vflux/client: fixed tests * les: make peer registration safe --- internal/web3ext/web3ext.go | 14 +- les/client.go | 38 +---- les/client_handler.go | 14 ++ les/peer.go | 9 +- les/vflux/client/queueiterator_test.go | 11 -- les/{ => vflux/client}/serverpool.go | 173 +++++++++++++--------- les/{ => vflux/client}/serverpool_test.go | 64 ++++---- les/vflux/client/valuetracker.go | 77 +++++----- les/vflux/client/valuetracker_test.go | 4 +- 9 files changed, 206 insertions(+), 198 deletions(-) rename les/{ => vflux/client}/serverpool.go (76%) rename les/{ => vflux/client}/serverpool_test.go (86%) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 77954bbbf0..6fcf4b8380 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -33,7 +33,7 @@ var Modules = map[string]string{ "swarmfs": SwarmfsJs, "txpool": TxpoolJs, "les": LESJs, - "lespay": LESPayJs, + "vflux": VfluxJs, } const ChequebookJs = ` @@ -877,24 +877,24 @@ web3._extend({ }); ` -const LESPayJs = ` +const VfluxJs = ` web3._extend({ - property: 'lespay', + property: 'vflux', methods: [ new web3._extend.Method({ name: 'distribution', - call: 'lespay_distribution', + call: 'vflux_distribution', params: 2 }), new web3._extend.Method({ name: 'timeout', - call: 'lespay_timeout', + call: 'vflux_timeout', params: 2 }), new web3._extend.Method({ name: 'value', - call: 'lespay_value', + call: 'vflux_value', params: 2 }), ], @@ -902,7 +902,7 @@ web3._extend({ [ new web3._extend.Property({ name: 'requestStats', - getter: 'lespay_requestStats' + getter: 'vflux_requestStats' }), ] }); diff --git a/les/client.go b/les/client.go index 053118df5a..e20519fd91 100644 --- a/les/client.go +++ b/les/client.go @@ -57,8 +57,7 @@ type LightEthereum struct { handler *clientHandler txPool *light.TxPool blockchain *light.LightChain - serverPool *serverPool - valueTracker *vfc.ValueTracker + serverPool *vfc.ServerPool dialCandidates enode.Iterator pruner *pruner @@ -109,17 +108,14 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), - valueTracker: vfc.NewValueTracker(lesDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), p2pServer: stack.Server(), p2pConfig: &stack.Config().P2P, } - peers.subscribe((*vtSubscription)(leth.valueTracker)) - leth.serverPool = newServerPool(lesDb, []byte("serverpool:"), leth.valueTracker, time.Second, nil, &mclock.System{}, config.UltraLightServers) - peers.subscribe(leth.serverPool) - leth.dialCandidates = leth.serverPool.dialIterator + leth.serverPool, leth.dialCandidates = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, nil, &mclock.System{}, config.UltraLightServers, requestList) + leth.serverPool.AddMetrics(suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge, sessionValueMeter, serverDialedMeter) - leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.getTimeout) + leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.GetTimeout) leth.relay = newLesTxRelay(peers, leth.retriever) leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.peers, leth.retriever) @@ -193,23 +189,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { return leth, nil } -// vtSubscription implements serverPeerSubscriber -type vtSubscription vfc.ValueTracker - -// registerPeer implements serverPeerSubscriber -func (v *vtSubscription) registerPeer(p *serverPeer) { - vt := (*vfc.ValueTracker)(v) - p.setValueTracker(vt, vt.Register(p.ID())) - p.updateVtParams() -} - -// unregisterPeer implements serverPeerSubscriber -func (v *vtSubscription) unregisterPeer(p *serverPeer) { - vt := (*vfc.ValueTracker)(v) - vt.Unregister(p.ID()) - p.setValueTracker(nil, nil) -} - type LightDummyAPI struct{} // Etherbase is the address that mining rewards will be send to @@ -266,7 +245,7 @@ func (s *LightEthereum) APIs() []rpc.API { }, { Namespace: "vflux", Version: "1.0", - Service: vfc.NewPrivateClientAPI(s.valueTracker), + Service: s.serverPool.API(), Public: false, }, }...) @@ -302,8 +281,8 @@ func (s *LightEthereum) Start() error { if err != nil { return err } - s.serverPool.addSource(discovery) - s.serverPool.start() + s.serverPool.AddSource(discovery) + s.serverPool.Start() // Start bloom request workers. s.wg.Add(bloomServiceThreads) s.startBloomHandlers(params.BloomBitsBlocksClient) @@ -316,8 +295,7 @@ func (s *LightEthereum) Start() error { // Ethereum protocol. func (s *LightEthereum) Stop() error { close(s.closeCh) - s.serverPool.stop() - s.valueTracker.Stop() + s.serverPool.Stop() s.peers.close() s.reqDist.close() s.odr.Stop() diff --git a/les/client_handler.go b/les/client_handler.go index 6cd786cda0..f8e9edc9fe 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -114,11 +114,25 @@ func (h *clientHandler) handle(p *serverPeer) error { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err } + // Register peer with the server pool + if h.backend.serverPool != nil { + if nvt, err := h.backend.serverPool.RegisterNode(p.Node()); err == nil { + p.setValueTracker(nvt) + p.updateVtParams() + defer func() { + p.setValueTracker(nil) + h.backend.serverPool.UnregisterNode(p.Node()) + }() + } else { + return err + } + } // Register the peer locally if err := h.backend.peers.register(p); err != nil { p.Log().Error("Light Ethereum peer registration failed", "err", err) return err } + serverConnectionGauge.Update(int64(h.backend.peers.len())) connectedAt := mclock.Now() diff --git a/les/peer.go b/les/peer.go index 0361167ee1..78019b1d87 100644 --- a/les/peer.go +++ b/les/peer.go @@ -349,7 +349,6 @@ type serverPeer struct { fcServer *flowcontrol.ServerNode // Client side mirror token bucket. vtLock sync.Mutex - valueTracker *vfc.ValueTracker nodeValueTracker *vfc.NodeValueTracker sentReqs map[uint64]sentReqEntry @@ -676,9 +675,8 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter // setValueTracker sets the value tracker references for connected servers. Note that the // references should be removed upon disconnection by setValueTracker(nil, nil). -func (p *serverPeer) setValueTracker(vt *vfc.ValueTracker, nvt *vfc.NodeValueTracker) { +func (p *serverPeer) setValueTracker(nvt *vfc.NodeValueTracker) { p.vtLock.Lock() - p.valueTracker = vt p.nodeValueTracker = nvt if nvt != nil { p.sentReqs = make(map[uint64]sentReqEntry) @@ -705,7 +703,7 @@ func (p *serverPeer) updateVtParams() { } } } - p.valueTracker.UpdateCosts(p.nodeValueTracker, reqCosts) + p.nodeValueTracker.UpdateCosts(reqCosts) } // sentReqEntry remembers sent requests and their sending times @@ -732,7 +730,6 @@ func (p *serverPeer) answeredRequest(id uint64) { } e, ok := p.sentReqs[id] delete(p.sentReqs, id) - vt := p.valueTracker nvt := p.nodeValueTracker p.vtLock.Unlock() if !ok { @@ -752,7 +749,7 @@ func (p *serverPeer) answeredRequest(id uint64) { vtReqs[1] = vfc.ServedRequest{ReqType: uint32(m.rest), Amount: e.amount - 1} } dt := time.Duration(mclock.Now() - e.at) - vt.Served(nvt, vtReqs[:reqCount], dt) + nvt.Served(vtReqs[:reqCount], dt) } // clientPeer represents each node to which the les server is connected. diff --git a/les/vflux/client/queueiterator_test.go b/les/vflux/client/queueiterator_test.go index a74301c7d3..400d978e19 100644 --- a/les/vflux/client/queueiterator_test.go +++ b/les/vflux/client/queueiterator_test.go @@ -26,17 +26,6 @@ import ( "github.com/ethereum/go-ethereum/p2p/nodestate" ) -func testNodeID(i int) enode.ID { - return enode.ID{42, byte(i % 256), byte(i / 256)} -} - -func testNodeIndex(id enode.ID) int { - if id[0] != 42 { - return -1 - } - return int(id[1]) + int(id[2])*256 -} - func testNode(i int) *enode.Node { return enode.SignNull(new(enr.Record), testNodeID(i)) } diff --git a/les/serverpool.go b/les/vflux/client/serverpool.go similarity index 76% rename from les/serverpool.go rename to les/vflux/client/serverpool.go index 977579988e..95f7246091 100644 --- a/les/serverpool.go +++ b/les/vflux/client/serverpool.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package les +package client import ( "errors" @@ -27,8 +27,8 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/utils" - vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nodestate" @@ -50,31 +50,34 @@ const ( maxQueryFails = 100 // number of consecutive UDP query failures before we print a warning ) -// serverPool provides a node iterator for dial candidates. The output is a mix of newly discovered +// ServerPool provides a node iterator for dial candidates. The output is a mix of newly discovered // nodes, a weighted random selection of known (previously valuable) nodes and trusted/paid nodes. -type serverPool struct { +type ServerPool struct { clock mclock.Clock unixTime func() int64 db ethdb.KeyValueStore - ns *nodestate.NodeStateMachine - vt *vfc.ValueTracker - mixer *enode.FairMix - mixSources []enode.Iterator - dialIterator enode.Iterator - validSchemes enr.IdentityScheme - trustedURLs []string - fillSet *vfc.FillSet - queryFails uint32 + ns *nodestate.NodeStateMachine + vt *ValueTracker + mixer *enode.FairMix + mixSources []enode.Iterator + dialIterator enode.Iterator + validSchemes enr.IdentityScheme + trustedURLs []string + fillSet *FillSet + started, queryFails uint32 timeoutLock sync.RWMutex timeout time.Duration - timeWeights vfc.ResponseTimeWeights + timeWeights ResponseTimeWeights timeoutRefreshed mclock.AbsTime + + suggestedTimeoutGauge, totalValueGauge metrics.Gauge + sessionValueMeter metrics.Meter } // nodeHistory keeps track of dial costs which determine node weight together with the -// service value calculated by vfc.ValueTracker. +// service value calculated by ValueTracker. type nodeHistory struct { dialCost utils.ExpiredValue redialWaitStart, redialWaitEnd int64 // unix time (seconds) @@ -91,18 +94,18 @@ type nodeHistoryEnc struct { type queryFunc func(*enode.Node) int var ( - serverPoolSetup = &nodestate.Setup{Version: 1} - sfHasValue = serverPoolSetup.NewPersistentFlag("hasValue") - sfQueried = serverPoolSetup.NewFlag("queried") - sfCanDial = serverPoolSetup.NewFlag("canDial") - sfDialing = serverPoolSetup.NewFlag("dialed") - sfWaitDialTimeout = serverPoolSetup.NewFlag("dialTimeout") - sfConnected = serverPoolSetup.NewFlag("connected") - sfRedialWait = serverPoolSetup.NewFlag("redialWait") - sfAlwaysConnect = serverPoolSetup.NewFlag("alwaysConnect") + clientSetup = &nodestate.Setup{Version: 1} + sfHasValue = clientSetup.NewPersistentFlag("hasValue") + sfQueried = clientSetup.NewFlag("queried") + sfCanDial = clientSetup.NewFlag("canDial") + sfDialing = clientSetup.NewFlag("dialed") + sfWaitDialTimeout = clientSetup.NewFlag("dialTimeout") + sfConnected = clientSetup.NewFlag("connected") + sfRedialWait = clientSetup.NewFlag("redialWait") + sfAlwaysConnect = clientSetup.NewFlag("alwaysConnect") sfDisableSelection = nodestate.MergeFlags(sfQueried, sfCanDial, sfDialing, sfConnected, sfRedialWait) - sfiNodeHistory = serverPoolSetup.NewPersistentField("nodeHistory", reflect.TypeOf(nodeHistory{}), + sfiNodeHistory = clientSetup.NewPersistentField("nodeHistory", reflect.TypeOf(nodeHistory{}), func(field interface{}) ([]byte, error) { if n, ok := field.(nodeHistory); ok { ne := nodeHistoryEnc{ @@ -126,25 +129,25 @@ var ( return n, err }, ) - sfiNodeWeight = serverPoolSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0))) - sfiConnectedStats = serverPoolSetup.NewField("connectedStats", reflect.TypeOf(vfc.ResponseTimeStats{})) + sfiNodeWeight = clientSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0))) + sfiConnectedStats = clientSetup.NewField("connectedStats", reflect.TypeOf(ResponseTimeStats{})) ) // newServerPool creates a new server pool -func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *vfc.ValueTracker, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string) *serverPool { - s := &serverPool{ +func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) { + s := &ServerPool{ db: db, clock: clock, unixTime: func() int64 { return time.Now().Unix() }, validSchemes: enode.ValidSchemes, trustedURLs: trustedURLs, - vt: vt, - ns: nodestate.NewNodeStateMachine(db, []byte(string(dbKey)+"ns:"), clock, serverPoolSetup), + vt: NewValueTracker(db, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), + ns: nodestate.NewNodeStateMachine(db, []byte(string(dbKey)+"ns:"), clock, clientSetup), } s.recalTimeout() s.mixer = enode.NewFairMix(mixTimeout) - knownSelector := vfc.NewWrsIterator(s.ns, sfHasValue, sfDisableSelection, sfiNodeWeight) - alwaysConnect := vfc.NewQueueIterator(s.ns, sfAlwaysConnect, sfDisableSelection, true, nil) + knownSelector := NewWrsIterator(s.ns, sfHasValue, sfDisableSelection, sfiNodeWeight) + alwaysConnect := NewQueueIterator(s.ns, sfAlwaysConnect, sfDisableSelection, true, nil) s.mixSources = append(s.mixSources, knownSelector) s.mixSources = append(s.mixSources, alwaysConnect) @@ -166,14 +169,30 @@ func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *vfc.ValueTracker, m } }) - s.ns.AddLogMetrics(sfHasValue, sfDisableSelection, "selectable", nil, nil, serverSelectableGauge) - s.ns.AddLogMetrics(sfDialing, nodestate.Flags{}, "dialed", serverDialedMeter, nil, nil) - s.ns.AddLogMetrics(sfConnected, nodestate.Flags{}, "connected", nil, nil, serverConnectedGauge) - return s + return s, s.dialIterator +} + +// AddMetrics adds metrics to the server pool. Should be called before Start(). +func (s *ServerPool) AddMetrics( + suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge metrics.Gauge, + sessionValueMeter, serverDialedMeter metrics.Meter) { + + s.suggestedTimeoutGauge = suggestedTimeoutGauge + s.totalValueGauge = totalValueGauge + s.sessionValueMeter = sessionValueMeter + if serverSelectableGauge != nil { + s.ns.AddLogMetrics(sfHasValue, sfDisableSelection, "selectable", nil, nil, serverSelectableGauge) + } + if serverDialedMeter != nil { + s.ns.AddLogMetrics(sfDialing, nodestate.Flags{}, "dialed", serverDialedMeter, nil, nil) + } + if serverConnectedGauge != nil { + s.ns.AddLogMetrics(sfConnected, nodestate.Flags{}, "connected", nil, nil, serverConnectedGauge) + } } -// addSource adds a node discovery source to the server pool (should be called before start) -func (s *serverPool) addSource(source enode.Iterator) { +// AddSource adds a node discovery source to the server pool (should be called before start) +func (s *ServerPool) AddSource(source enode.Iterator) { if source != nil { s.mixSources = append(s.mixSources, source) } @@ -182,8 +201,8 @@ func (s *serverPool) addSource(source enode.Iterator) { // addPreNegFilter installs a node filter mechanism that performs a pre-negotiation query. // Nodes that are filtered out and does not appear on the output iterator are put back // into redialWait state. -func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enode.Iterator { - s.fillSet = vfc.NewFillSet(s.ns, input, sfQueried) +func (s *ServerPool) addPreNegFilter(input enode.Iterator, query queryFunc) enode.Iterator { + s.fillSet = NewFillSet(s.ns, input, sfQueried) s.ns.SubscribeState(sfQueried, func(n *enode.Node, oldState, newState nodestate.Flags) { if newState.Equals(sfQueried) { fails := atomic.LoadUint32(&s.queryFails) @@ -221,7 +240,7 @@ func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enod }() } }) - return vfc.NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) { + return NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) { if waiting { s.fillSet.SetTarget(preNegLimit) } else { @@ -231,7 +250,7 @@ func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enod } // start starts the server pool. Note that NodeStateMachine should be started first. -func (s *serverPool) start() { +func (s *ServerPool) Start() { s.ns.Start() for _, iter := range s.mixSources { // add sources to mixer at startup because the mixer instantly tries to read them @@ -261,10 +280,11 @@ func (s *serverPool) start() { } }) }) + atomic.StoreUint32(&s.started, 1) } // stop stops the server pool -func (s *serverPool) stop() { +func (s *ServerPool) Stop() { s.dialIterator.Close() if s.fillSet != nil { s.fillSet.Close() @@ -276,32 +296,34 @@ func (s *serverPool) stop() { }) }) s.ns.Stop() + s.vt.Stop() } // registerPeer implements serverPeerSubscriber -func (s *serverPool) registerPeer(p *serverPeer) { - s.ns.SetState(p.Node(), sfConnected, sfDialing.Or(sfWaitDialTimeout), 0) - nvt := s.vt.Register(p.ID()) - s.ns.SetField(p.Node(), sfiConnectedStats, nvt.RtStats()) - p.setValueTracker(s.vt, nvt) - p.updateVtParams() +func (s *ServerPool) RegisterNode(node *enode.Node) (*NodeValueTracker, error) { + if atomic.LoadUint32(&s.started) == 0 { + return nil, errors.New("server pool not started yet") + } + s.ns.SetState(node, sfConnected, sfDialing.Or(sfWaitDialTimeout), 0) + nvt := s.vt.Register(node.ID()) + s.ns.SetField(node, sfiConnectedStats, nvt.RtStats()) + return nvt, nil } // unregisterPeer implements serverPeerSubscriber -func (s *serverPool) unregisterPeer(p *serverPeer) { +func (s *ServerPool) UnregisterNode(node *enode.Node) { s.ns.Operation(func() { - s.setRedialWait(p.Node(), dialCost, dialWaitStep) - s.ns.SetStateSub(p.Node(), nodestate.Flags{}, sfConnected, 0) - s.ns.SetFieldSub(p.Node(), sfiConnectedStats, nil) + s.setRedialWait(node, dialCost, dialWaitStep) + s.ns.SetStateSub(node, nodestate.Flags{}, sfConnected, 0) + s.ns.SetFieldSub(node, sfiConnectedStats, nil) }) - s.vt.Unregister(p.ID()) - p.setValueTracker(nil, nil) + s.vt.Unregister(node.ID()) } // recalTimeout calculates the current recommended timeout. This value is used by // the client as a "soft timeout" value. It also affects the service value calculation // of individual nodes. -func (s *serverPool) recalTimeout() { +func (s *ServerPool) recalTimeout() { // Use cached result if possible, avoid recalculating too frequently. s.timeoutLock.RLock() refreshed := s.timeoutRefreshed @@ -330,17 +352,21 @@ func (s *serverPool) recalTimeout() { s.timeoutLock.Lock() if s.timeout != timeout { s.timeout = timeout - s.timeWeights = vfc.TimeoutWeights(s.timeout) + s.timeWeights = TimeoutWeights(s.timeout) - suggestedTimeoutGauge.Update(int64(s.timeout / time.Millisecond)) - totalValueGauge.Update(int64(rts.Value(s.timeWeights, s.vt.StatsExpFactor()))) + if s.suggestedTimeoutGauge != nil { + s.suggestedTimeoutGauge.Update(int64(s.timeout / time.Millisecond)) + } + if s.totalValueGauge != nil { + s.totalValueGauge.Update(int64(rts.Value(s.timeWeights, s.vt.StatsExpFactor()))) + } } s.timeoutRefreshed = now s.timeoutLock.Unlock() } -// getTimeout returns the recommended request timeout. -func (s *serverPool) getTimeout() time.Duration { +// GetTimeout returns the recommended request timeout. +func (s *ServerPool) GetTimeout() time.Duration { s.recalTimeout() s.timeoutLock.RLock() defer s.timeoutLock.RUnlock() @@ -349,7 +375,7 @@ func (s *serverPool) getTimeout() time.Duration { // getTimeoutAndWeight returns the recommended request timeout as well as the // response time weight which is necessary to calculate service value. -func (s *serverPool) getTimeoutAndWeight() (time.Duration, vfc.ResponseTimeWeights) { +func (s *ServerPool) getTimeoutAndWeight() (time.Duration, ResponseTimeWeights) { s.recalTimeout() s.timeoutLock.RLock() defer s.timeoutLock.RUnlock() @@ -358,7 +384,7 @@ func (s *serverPool) getTimeoutAndWeight() (time.Duration, vfc.ResponseTimeWeigh // addDialCost adds the given amount of dial cost to the node history and returns the current // amount of total dial cost -func (s *serverPool) addDialCost(n *nodeHistory, amount int64) uint64 { +func (s *ServerPool) addDialCost(n *nodeHistory, amount int64) uint64 { logOffset := s.vt.StatsExpirer().LogOffset(s.clock.Now()) if amount > 0 { n.dialCost.Add(amount, logOffset) @@ -371,7 +397,7 @@ func (s *serverPool) addDialCost(n *nodeHistory, amount int64) uint64 { } // serviceValue returns the service value accumulated in this session and in total -func (s *serverPool) serviceValue(node *enode.Node) (sessionValue, totalValue float64) { +func (s *ServerPool) serviceValue(node *enode.Node) (sessionValue, totalValue float64) { nvt := s.vt.GetNode(node.ID()) if nvt == nil { return 0, 0 @@ -381,11 +407,13 @@ func (s *serverPool) serviceValue(node *enode.Node) (sessionValue, totalValue fl expFactor := s.vt.StatsExpFactor() totalValue = currentStats.Value(timeWeights, expFactor) - if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(vfc.ResponseTimeStats); ok { + if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(ResponseTimeStats); ok { diff := currentStats diff.SubStats(&connStats) sessionValue = diff.Value(timeWeights, expFactor) - sessionValueMeter.Mark(int64(sessionValue)) + if s.sessionValueMeter != nil { + s.sessionValueMeter.Mark(int64(sessionValue)) + } } return } @@ -393,7 +421,7 @@ func (s *serverPool) serviceValue(node *enode.Node) (sessionValue, totalValue fl // updateWeight calculates the node weight and updates the nodeWeight field and the // hasValue flag. It also saves the node state if necessary. // Note: this function should run inside a NodeStateMachine operation -func (s *serverPool) updateWeight(node *enode.Node, totalValue float64, totalDialCost uint64) { +func (s *ServerPool) updateWeight(node *enode.Node, totalValue float64, totalDialCost uint64) { weight := uint64(totalValue * nodeWeightMul / float64(totalDialCost)) if weight >= nodeWeightThreshold { s.ns.SetStateSub(node, sfHasValue, nodestate.Flags{}, 0) @@ -415,7 +443,7 @@ func (s *serverPool) updateWeight(node *enode.Node, totalValue float64, totalDia // to the minimum. // Note: node weight is also recalculated and updated by this function. // Note 2: this function should run inside a NodeStateMachine operation -func (s *serverPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep float64) { +func (s *ServerPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep float64) { n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory) sessionValue, totalValue := s.serviceValue(node) totalDialCost := s.addDialCost(&n, addDialCost) @@ -481,9 +509,14 @@ func (s *serverPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep // This function should be called during startup and shutdown only, otherwise setRedialWait // will keep the weights updated as the underlying statistics are adjusted. // Note: this function should run inside a NodeStateMachine operation -func (s *serverPool) calculateWeight(node *enode.Node) { +func (s *ServerPool) calculateWeight(node *enode.Node) { n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory) _, totalValue := s.serviceValue(node) totalDialCost := s.addDialCost(&n, 0) s.updateWeight(node, totalValue, totalDialCost) } + +// API returns the vflux client API +func (s *ServerPool) API() *PrivateClientAPI { + return NewPrivateClientAPI(s.vt) +} diff --git a/les/serverpool_test.go b/les/vflux/client/serverpool_test.go similarity index 86% rename from les/serverpool_test.go rename to les/vflux/client/serverpool_test.go index 5c8ae56f6c..3af3db95bc 100644 --- a/les/serverpool_test.go +++ b/les/vflux/client/serverpool_test.go @@ -14,10 +14,11 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package les +package client import ( "math/rand" + "strconv" "sync/atomic" "testing" "time" @@ -25,8 +26,6 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/memorydb" - vfc "github.com/ethereum/go-ethereum/les/vflux/client" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" ) @@ -50,13 +49,13 @@ func testNodeIndex(id enode.ID) int { return int(id[1]) + int(id[2])*256 } -type serverPoolTest struct { +type ServerPoolTest struct { db ethdb.KeyValueStore clock *mclock.Simulated quit chan struct{} preNeg, preNegFail bool - vt *vfc.ValueTracker - sp *serverPool + vt *ValueTracker + sp *ServerPool input enode.Iterator testNodes []spTestNode trusted []string @@ -71,15 +70,15 @@ type spTestNode struct { connectCycles, waitCycles int nextConnCycle, totalConn int connected, service bool - peer *serverPeer + node *enode.Node } -func newServerPoolTest(preNeg, preNegFail bool) *serverPoolTest { +func newServerPoolTest(preNeg, preNegFail bool) *ServerPoolTest { nodes := make([]*enode.Node, spTestNodes) for i := range nodes { nodes[i] = enode.SignNull(&enr.Record{}, testNodeID(i)) } - return &serverPoolTest{ + return &ServerPoolTest{ clock: &mclock.Simulated{}, db: memorydb.New(), input: enode.CycleNodes(nodes), @@ -89,7 +88,7 @@ func newServerPoolTest(preNeg, preNegFail bool) *serverPoolTest { } } -func (s *serverPoolTest) beginWait() { +func (s *ServerPoolTest) beginWait() { // ensure that dialIterator and the maximal number of pre-neg queries are not all stuck in a waiting state for atomic.AddInt32(&s.waitCount, 1) > preNegLimit { atomic.AddInt32(&s.waitCount, -1) @@ -97,16 +96,16 @@ func (s *serverPoolTest) beginWait() { } } -func (s *serverPoolTest) endWait() { +func (s *ServerPoolTest) endWait() { atomic.AddInt32(&s.waitCount, -1) atomic.AddInt32(&s.waitEnded, 1) } -func (s *serverPoolTest) addTrusted(i int) { +func (s *ServerPoolTest) addTrusted(i int) { s.trusted = append(s.trusted, enode.SignNull(&enr.Record{}, testNodeID(i)).String()) } -func (s *serverPoolTest) start() { +func (s *ServerPoolTest) start() { var testQuery queryFunc if s.preNeg { testQuery = func(node *enode.Node) int { @@ -144,13 +143,17 @@ func (s *serverPoolTest) start() { } } - s.vt = vfc.NewValueTracker(s.db, s.clock, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)) - s.sp = newServerPool(s.db, []byte("serverpool:"), s.vt, 0, testQuery, s.clock, s.trusted) - s.sp.addSource(s.input) + requestList := make([]RequestInfo, testReqTypes) + for i := range requestList { + requestList[i] = RequestInfo{Name: "testreq" + strconv.Itoa(i), InitAmount: 1, InitValue: 1} + } + + s.sp, _ = NewServerPool(s.db, []byte("sp:"), 0, testQuery, s.clock, s.trusted, requestList) + s.sp.AddSource(s.input) s.sp.validSchemes = enode.ValidSchemesForTesting s.sp.unixTime = func() int64 { return int64(s.clock.Now()) / int64(time.Second) } s.disconnect = make(map[int][]int) - s.sp.start() + s.sp.Start() s.quit = make(chan struct{}) go func() { last := int32(-1) @@ -170,31 +173,30 @@ func (s *serverPoolTest) start() { }() } -func (s *serverPoolTest) stop() { +func (s *ServerPoolTest) stop() { close(s.quit) - s.sp.stop() - s.vt.Stop() + s.sp.Stop() for i := range s.testNodes { n := &s.testNodes[i] if n.connected { n.totalConn += s.cycle } n.connected = false - n.peer = nil + n.node = nil n.nextConnCycle = 0 } s.conn, s.servedConn = 0, 0 } -func (s *serverPoolTest) run() { +func (s *ServerPoolTest) run() { for count := spTestLength; count > 0; count-- { if dcList := s.disconnect[s.cycle]; dcList != nil { for _, idx := range dcList { n := &s.testNodes[idx] - s.sp.unregisterPeer(n.peer) + s.sp.UnregisterNode(n.node) n.totalConn += s.cycle n.connected = false - n.peer = nil + n.node = nil s.conn-- if n.service { s.servedConn-- @@ -221,10 +223,10 @@ func (s *serverPoolTest) run() { n.connected = true dc := s.cycle + n.connectCycles s.disconnect[dc] = append(s.disconnect[dc], idx) - n.peer = &serverPeer{peerCommons: peerCommons{Peer: p2p.NewPeer(id, "", nil)}} - s.sp.registerPeer(n.peer) + n.node = dial + nv, _ := s.sp.RegisterNode(n.node) if n.service { - s.vt.Served(s.vt.GetNode(id), []vfc.ServedRequest{{ReqType: 0, Amount: 100}}, 0) + nv.Served([]ServedRequest{{ReqType: 0, Amount: 100}}, 0) } } } @@ -234,7 +236,7 @@ func (s *serverPoolTest) run() { } } -func (s *serverPoolTest) setNodes(count, conn, wait int, service, trusted bool) (res []int) { +func (s *ServerPoolTest) setNodes(count, conn, wait int, service, trusted bool) (res []int) { for ; count > 0; count-- { idx := rand.Intn(spTestNodes) for s.testNodes[idx].connectCycles != 0 || s.testNodes[idx].connected { @@ -253,11 +255,11 @@ func (s *serverPoolTest) setNodes(count, conn, wait int, service, trusted bool) return } -func (s *serverPoolTest) resetNodes() { +func (s *ServerPoolTest) resetNodes() { for i, n := range s.testNodes { if n.connected { n.totalConn += s.cycle - s.sp.unregisterPeer(n.peer) + s.sp.UnregisterNode(n.node) } s.testNodes[i] = spTestNode{totalConn: n.totalConn} } @@ -266,7 +268,7 @@ func (s *serverPoolTest) resetNodes() { s.trusted = nil } -func (s *serverPoolTest) checkNodes(t *testing.T, nodes []int) { +func (s *ServerPoolTest) checkNodes(t *testing.T, nodes []int) { var sum int for _, idx := range nodes { n := &s.testNodes[idx] diff --git a/les/vflux/client/valuetracker.go b/les/vflux/client/valuetracker.go index 4e67b31d96..f5390d0920 100644 --- a/les/vflux/client/valuetracker.go +++ b/les/vflux/client/valuetracker.go @@ -45,6 +45,7 @@ var ( type NodeValueTracker struct { lock sync.Mutex + vt *ValueTracker rtStats, lastRtStats ResponseTimeStats lastTransfer mclock.AbsTime basket serverBasket @@ -52,15 +53,12 @@ type NodeValueTracker struct { reqValues *[]float64 } -// init initializes a NodeValueTracker. -// Note that the contents of the referenced reqValues slice will not change; a new -// reference is passed if the values are updated by ValueTracker. -func (nv *NodeValueTracker) init(now mclock.AbsTime, reqValues *[]float64) { - reqTypeCount := len(*reqValues) - nv.reqCosts = make([]uint64, reqTypeCount) - nv.lastTransfer = now - nv.reqValues = reqValues - nv.basket.init(reqTypeCount) +// UpdateCosts updates the node value tracker's request cost table +func (nv *NodeValueTracker) UpdateCosts(reqCosts []uint64) { + nv.vt.lock.Lock() + defer nv.vt.lock.Unlock() + + nv.updateCosts(reqCosts, &nv.vt.refBasket.reqValues, nv.vt.refBasket.reqValueFactor(reqCosts)) } // updateCosts updates the request cost table of the server. The request value factor @@ -97,6 +95,28 @@ func (nv *NodeValueTracker) transferStats(now mclock.AbsTime, transferRate float return nv.basket.transfer(-math.Expm1(-transferRate * float64(dt))), recentRtStats } +type ServedRequest struct { + ReqType, Amount uint32 +} + +// Served adds a served request to the node's statistics. An actual request may be composed +// of one or more request types (service vector indices). +func (nv *NodeValueTracker) Served(reqs []ServedRequest, respTime time.Duration) { + nv.vt.statsExpLock.RLock() + expFactor := nv.vt.statsExpFactor + nv.vt.statsExpLock.RUnlock() + + nv.lock.Lock() + defer nv.lock.Unlock() + + var value float64 + for _, r := range reqs { + nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor) + value += (*nv.reqValues)[r.ReqType] * float64(r.Amount) + } + nv.rtStats.Add(respTime, value, expFactor) +} + // RtStats returns the node's own response time distribution statistics func (nv *NodeValueTracker) RtStats() ResponseTimeStats { nv.lock.Lock() @@ -333,7 +353,12 @@ func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker { return nil } nv := vt.loadOrNewNode(id) - nv.init(vt.clock.Now(), &vt.refBasket.reqValues) + reqTypeCount := len(vt.refBasket.reqValues) + nv.reqCosts = make([]uint64, reqTypeCount) + nv.lastTransfer = vt.clock.Now() + nv.reqValues = &vt.refBasket.reqValues + nv.basket.init(reqTypeCount) + vt.connected[id] = nv return nv } @@ -364,7 +389,7 @@ func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker { if nv, ok := vt.connected[id]; ok { return nv } - nv := &NodeValueTracker{lastTransfer: vt.clock.Now()} + nv := &NodeValueTracker{vt: vt, lastTransfer: vt.clock.Now()} enc, err := vt.db.Get(append(vtNodeKey, id[:]...)) if err != nil { return nv @@ -425,14 +450,6 @@ func (vt *ValueTracker) saveNode(id enode.ID, nv *NodeValueTracker) { } } -// UpdateCosts updates the node value tracker's request cost table -func (vt *ValueTracker) UpdateCosts(nv *NodeValueTracker, reqCosts []uint64) { - vt.lock.Lock() - defer vt.lock.Unlock() - - nv.updateCosts(reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(reqCosts)) -} - // RtStats returns the global response time distribution statistics func (vt *ValueTracker) RtStats() ResponseTimeStats { vt.lock.Lock() @@ -464,28 +481,6 @@ func (vt *ValueTracker) periodicUpdate() { vt.saveToDb() } -type ServedRequest struct { - ReqType, Amount uint32 -} - -// Served adds a served request to the node's statistics. An actual request may be composed -// of one or more request types (service vector indices). -func (vt *ValueTracker) Served(nv *NodeValueTracker, reqs []ServedRequest, respTime time.Duration) { - vt.statsExpLock.RLock() - expFactor := vt.statsExpFactor - vt.statsExpLock.RUnlock() - - nv.lock.Lock() - defer nv.lock.Unlock() - - var value float64 - for _, r := range reqs { - nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor) - value += (*nv.reqValues)[r.ReqType] * float64(r.Amount) - } - nv.rtStats.Add(respTime, value, vt.statsExpFactor) -} - type RequestStatsItem struct { Name string ReqAmount, ReqValue float64 diff --git a/les/vflux/client/valuetracker_test.go b/les/vflux/client/valuetracker_test.go index ad398749e9..87a337be8d 100644 --- a/les/vflux/client/valuetracker_test.go +++ b/les/vflux/client/valuetracker_test.go @@ -64,7 +64,7 @@ func TestValueTracker(t *testing.T) { for j := range costList { costList[j] = uint64(baseCost * relPrices[j]) } - vt.UpdateCosts(nodes[i], costList) + nodes[i].UpdateCosts(costList) } for i := range nodes { nodes[i] = vt.Register(enode.ID{byte(i)}) @@ -77,7 +77,7 @@ func TestValueTracker(t *testing.T) { node := rand.Intn(testNodeCount) respTime := time.Duration((rand.Float64() + 1) * float64(time.Second) * float64(node+1) / testNodeCount) totalAmount[reqType] += uint64(reqAmount) - vt.Served(nodes[node], []ServedRequest{{uint32(reqType), uint32(reqAmount)}}, respTime) + nodes[node].Served([]ServedRequest{{uint32(reqType), uint32(reqAmount)}}, respTime) clock.Run(time.Second) } } else { From 092856267067dd78b527a773f5b240d5c9f5693a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Feb 2021 09:10:30 +0200 Subject: [PATCH 222/235] all: define Berlin hard fork spec --- cmd/geth/config.go | 5 ++- cmd/geth/main.go | 1 + cmd/puppeth/wizard_genesis.go | 4 ++ cmd/utils/flags.go | 4 ++ core/forkid/forkid_test.go | 60 ++++++++++++++------------ core/genesis.go | 7 ++++ core/state_transition.go | 2 +- core/tx_pool.go | 2 +- core/types/transaction_signing.go | 4 +- core/vm/contracts.go | 12 +++--- core/vm/evm.go | 10 ++--- core/vm/interpreter.go | 4 +- core/vm/jump_table.go | 15 ++++--- core/vm/runtime/runtime.go | 7 ++-- eth/backend.go | 2 +- eth/ethconfig/config.go | 3 ++ eth/tracers/api.go | 4 +- les/client.go | 2 +- light/txpool.go | 2 +- params/config.go | 70 ++++++++++++++++++------------- tests/init.go | 2 +- tests/state_test_util.go | 3 +- 22 files changed, 132 insertions(+), 93 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index c71d5eb653..c77c04c252 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -20,6 +20,7 @@ import ( "bufio" "errors" "fmt" + "math/big" "os" "reflect" "unicode" @@ -165,7 +166,9 @@ func checkWhisper(ctx *cli.Context) { // makeFullNode loads geth configuration and creates the Ethereum backend. func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { stack, cfg := makeConfigNode(ctx) - + if ctx.GlobalIsSet(utils.OverrideBerlinFlag.Name) { + cfg.Eth.OverrideBerlin = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideBerlinFlag.Name)) + } backend := utils.RegisterEthService(stack, &cfg.Eth) checkWhisper(ctx) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index a3af44f6c4..9afa031584 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -69,6 +69,7 @@ var ( utils.NoUSBFlag, utils.USBFlag, utils.SmartCardDaemonPathFlag, + utils.OverrideBerlinFlag, utils.EthashCacheDirFlag, utils.EthashCachesInMemoryFlag, utils.EthashCachesOnDiskFlag, diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index 78f63e1c7a..4f701fa1c3 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -235,6 +235,10 @@ func (w *wizard) manageGenesis() { fmt.Printf("Which block should Istanbul come into effect? (default = %v)\n", w.conf.Genesis.Config.IstanbulBlock) w.conf.Genesis.Config.IstanbulBlock = w.readDefaultBigInt(w.conf.Genesis.Config.IstanbulBlock) + fmt.Println() + fmt.Printf("Which block should Berlin come into effect? (default = %v)\n", w.conf.Genesis.Config.BerlinBlock) + w.conf.Genesis.Config.BerlinBlock = w.readDefaultBigInt(w.conf.Genesis.Config.BerlinBlock) + fmt.Println() fmt.Printf("Which block should YOLOv3 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV3Block) w.conf.Genesis.Config.YoloV3Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV3Block) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 57547d0c49..324a6d6a47 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -230,6 +230,10 @@ var ( Usage: "Megabytes of memory allocated to bloom-filter for pruning", Value: 2048, } + OverrideBerlinFlag = cli.Uint64Flag{ + Name: "override.berlin", + Usage: "Manually specify Berlin fork-block, overriding the bundled setting", + } // Light server and client settings LightServeFlag = cli.IntFlag{ Name: "light.serve", diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index 888b553475..a20598fa9d 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -43,24 +43,26 @@ func TestCreation(t *testing.T) { params.MainnetChainConfig, params.MainnetGenesisHash, []testcase{ - {0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced - {1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block - {1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block - {1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block - {1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block - {2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block - {2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block - {2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block - {2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block - {4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block - {4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block - {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block - {7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block - {9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block - {9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block - {9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block - {9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 0}}, // First Muir Glacier block - {10000000, ID{Hash: checksumToBytes(0xe029e991), Next: 0}}, // Future Muir Glacier block + {0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced + {1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block + {1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block + {1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block + {1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block + {2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block + {2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block + {2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block + {2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block + {4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block + {4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block + {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block + {7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block + {9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block + {9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block + {9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block + {9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block + {12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block + {12244000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // First Berlin block + {20000000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // Future Berlin block }, }, // Ropsten test cases @@ -80,8 +82,10 @@ func TestCreation(t *testing.T) { {6485845, ID{Hash: checksumToBytes(0xd6e2149b), Next: 6485846}}, // Last Petersburg block {6485846, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // First Istanbul block {7117116, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // Last Istanbul block - {7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}}, // First Muir Glacier block - {7500000, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}}, // Future + {7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // First Muir Glacier block + {9812188, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // Last Muir Glacier block + {9812189, ID{Hash: checksumToBytes(0xa157d377), Next: 0}}, // First Berlin block + {10000000, ID{Hash: checksumToBytes(0xa157d377), Next: 0}}, // Future Berlin block }, }, // Rinkeby test cases @@ -100,8 +104,10 @@ func TestCreation(t *testing.T) { {4321233, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // Last Constantinople block {4321234, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // First Petersburg block {5435344, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // Last Petersburg block - {5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}}, // First Istanbul block - {6000000, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}}, // Future Istanbul block + {5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // First Istanbul block + {8290927, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // Last Istanbul block + {8290928, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}}, // First Berlin block + {10000000, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}}, // Future Berlin block }, }, // Goerli test cases @@ -111,8 +117,10 @@ func TestCreation(t *testing.T) { []testcase{ {0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block {1561650, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Last Petersburg block - {1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}}, // First Istanbul block - {2000000, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}}, // Future Istanbul block + {1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // First Istanbul block + {4460643, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // Last Istanbul block + {4460644, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}}, // First Berlin block + {5000000, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}}, // Future Berlin block }, }, } @@ -185,11 +193,11 @@ func TestValidation(t *testing.T) { // Local is mainnet Petersburg, remote is Rinkeby Petersburg. {7987396, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale}, - // Local is mainnet Muir Glacier, far in the future. Remote announces Gopherium (non existing fork) + // Local is mainnet Berlin, far in the future. Remote announces Gopherium (non existing fork) // at some future block 88888888, for itself, but past block for local. Local is incompatible. // // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). - {88888888, ID{Hash: checksumToBytes(0xe029e991), Next: 88888888}, ErrLocalIncompatibleOrStale}, + {88888888, ID{Hash: checksumToBytes(0x0eb440f6), Next: 88888888}, ErrLocalIncompatibleOrStale}, // Local is mainnet Byzantium. Remote is also in Byzantium, but announces Gopherium (non existing // fork) at block 7279999, before Petersburg. Local is incompatible. diff --git a/core/genesis.go b/core/genesis.go index 6bcc50b050..e05e27fe17 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -153,6 +153,10 @@ func (e *GenesisMismatchError) Error() string { // // The returned chain configuration is never nil. func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) { + return SetupGenesisBlockWithOverride(db, genesis, nil) +} + +func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrideBerlin *big.Int) (*params.ChainConfig, common.Hash, error) { if genesis != nil && genesis.Config == nil { return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig } @@ -198,6 +202,9 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig } // Get the existing chain configuration. newcfg := genesis.configOrDefault(stored) + if overrideBerlin != nil { + newcfg.BerlinBlock = overrideBerlin + } if err := newcfg.CheckConfigForkOrder(); err != nil { return newcfg, common.Hash{}, err } diff --git a/core/state_transition.go b/core/state_transition.go index 0d589a6119..d511e40bd6 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -259,7 +259,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } // Set up the initial access list. - if st.evm.ChainConfig().IsYoloV3(st.evm.Context.BlockNumber) { + if st.evm.ChainConfig().IsBerlin(st.evm.Context.BlockNumber) { st.state.PrepareAccessList(msg.From(), msg.To(), st.evm.ActivePrecompiles(), msg.AccessList()) } diff --git a/core/tx_pool.go b/core/tx_pool.go index 28ac822131..4c1bd809fd 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -1204,7 +1204,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // Update all fork indicator by next pending block number. next := new(big.Int).Add(newHead.Number, big.NewInt(1)) pool.istanbul = pool.chainconfig.IsIstanbul(next) - pool.eip2718 = pool.chainconfig.IsYoloV3(next) + pool.eip2718 = pool.chainconfig.IsBerlin(next) } // promoteExecutables moves transactions that have become processable from the diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 1645369b4f..b4594cb90b 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -40,7 +40,7 @@ type sigCache struct { func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { var signer Signer switch { - case config.IsYoloV3(blockNumber): + case config.IsBerlin(blockNumber): signer = NewEIP2930Signer(config.ChainID) case config.IsEIP155(blockNumber): signer = NewEIP155Signer(config.ChainID) @@ -61,7 +61,7 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { // have the current block number available, use MakeSigner instead. func LatestSigner(config *params.ChainConfig) Signer { if config.ChainID != nil { - if config.YoloV3Block != nil { + if config.BerlinBlock != nil || config.YoloV3Block != nil { return NewEIP2930Signer(config.ChainID) } if config.EIP155Block != nil { diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 9ea19d38a7..4e99a51618 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -78,9 +78,9 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{9}): &blake2F{}, } -// PrecompiledContractsYoloV3 contains the default set of pre-compiled Ethereum -// contracts used in the Yolo v3 test release. -var PrecompiledContractsYoloV3 = map[common.Address]PrecompiledContract{ +// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum +// contracts used in the Berlin release. +var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, @@ -107,7 +107,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ } var ( - PrecompiledAddressesYoloV3 []common.Address + PrecompiledAddressesBerlin []common.Address PrecompiledAddressesIstanbul []common.Address PrecompiledAddressesByzantium []common.Address PrecompiledAddressesHomestead []common.Address @@ -123,8 +123,8 @@ func init() { for k := range PrecompiledContractsIstanbul { PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k) } - for k := range PrecompiledContractsYoloV3 { - PrecompiledAddressesYoloV3 = append(PrecompiledAddressesYoloV3, k) + for k := range PrecompiledContractsBerlin { + PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k) } } diff --git a/core/vm/evm.go b/core/vm/evm.go index 3617d77b12..7346d76e5b 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -46,8 +46,8 @@ type ( // configuration func (evm *EVM) ActivePrecompiles() []common.Address { switch { - case evm.chainRules.IsYoloV3: - return PrecompiledAddressesYoloV3 + case evm.chainRules.IsBerlin: + return PrecompiledAddressesBerlin case evm.chainRules.IsIstanbul: return PrecompiledAddressesIstanbul case evm.chainRules.IsByzantium: @@ -60,8 +60,8 @@ func (evm *EVM) ActivePrecompiles() []common.Address { func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { - case evm.chainRules.IsYoloV3: - precompiles = PrecompiledContractsYoloV3 + case evm.chainRules.IsBerlin: + precompiles = PrecompiledContractsBerlin case evm.chainRules.IsIstanbul: precompiles = PrecompiledContractsIstanbul case evm.chainRules.IsByzantium: @@ -446,7 +446,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, evm.StateDB.SetNonce(caller.Address(), nonce+1) // We add this to the access list _before_ taking a snapshot. Even if the creation fails, // the access-list change should not be rolled back - if evm.chainRules.IsYoloV3 { + if evm.chainRules.IsBerlin { evm.StateDB.AddAddressToAccessList(address) } // Ensure there's no existing contract already at the designated address diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 967db32780..0084b7d071 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -99,8 +99,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { if cfg.JumpTable[STOP] == nil { var jt JumpTable switch { - case evm.chainRules.IsYoloV3: - jt = yoloV3InstructionSet + case evm.chainRules.IsBerlin: + jt = berlinInstructionSet case evm.chainRules.IsIstanbul: jt = istanbulInstructionSet case evm.chainRules.IsConstantinople: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 954f657a94..d831f9300f 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -56,24 +56,23 @@ var ( byzantiumInstructionSet = newByzantiumInstructionSet() constantinopleInstructionSet = newConstantinopleInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet() - yoloV3InstructionSet = newYoloV3InstructionSet() + berlinInstructionSet = newBerlinInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. type JumpTable [256]*operation -// newYoloV3InstructionSet creates an instructionset containing -// - "EIP-2315: Simple Subroutines" -// - "EIP-2929: Gas cost increases for state access opcodes" -func newYoloV3InstructionSet() JumpTable { +// newBerlinInstructionSet returns the frontier, homestead, byzantium, +// contantinople, istanbul, petersburg and berlin instructions. +func newBerlinInstructionSet() JumpTable { instructionSet := newIstanbulInstructionSet() enable2315(&instructionSet) // Subroutines - https://eips.ethereum.org/EIPS/eip-2315 enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929 return instructionSet } -// newIstanbulInstructionSet returns the frontier, homestead -// byzantium, contantinople and petersburg instructions. +// newIstanbulInstructionSet returns the frontier, homestead, byzantium, +// contantinople, istanbul and petersburg instructions. func newIstanbulInstructionSet() JumpTable { instructionSet := newConstantinopleInstructionSet() @@ -84,7 +83,7 @@ func newIstanbulInstructionSet() JumpTable { return instructionSet } -// newConstantinopleInstructionSet returns the frontier, homestead +// newConstantinopleInstructionSet returns the frontier, homestead, // byzantium and contantinople instructions. func newConstantinopleInstructionSet() JumpTable { instructionSet := newByzantiumInstructionSet() diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index c97bdc420c..9cb69e1c76 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -65,6 +65,7 @@ func setDefaults(cfg *Config) { PetersburgBlock: new(big.Int), IstanbulBlock: new(big.Int), MuirGlacierBlock: new(big.Int), + BerlinBlock: new(big.Int), YoloV3Block: nil, } } @@ -113,7 +114,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsBerlin(vmenv.Context.BlockNumber) { cfg.State.PrepareAccessList(cfg.Origin, &address, vmenv.ActivePrecompiles(), nil) } cfg.State.CreateAccount(address) @@ -145,7 +146,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsBerlin(vmenv.Context.BlockNumber) { cfg.State.PrepareAccessList(cfg.Origin, nil, vmenv.ActivePrecompiles(), nil) } @@ -171,7 +172,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er sender := cfg.State.GetOrNewStateObject(cfg.Origin) statedb := cfg.State - if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsBerlin(vmenv.Context.BlockNumber) { statedb.PrepareAccessList(cfg.Origin, &address, vmenv.ActivePrecompiles(), nil) } diff --git a/eth/backend.go b/eth/backend.go index 044422763b..76ce5137f4 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -126,7 +126,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis) + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideBerlin) if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 841dc5e9e1..5d0eece067 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -198,6 +198,9 @@ type Config struct { // CheckpointOracle is the configuration for checkpoint oracle. CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` + + // Berlin block override (TODO: remove after the fork) + OverrideBerlin *big.Int `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain configuration. diff --git a/eth/tracers/api.go b/eth/tracers/api.go index bd995d08a9..61fe055689 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -581,8 +581,8 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block chainConfigCopy := new(params.ChainConfig) *chainConfigCopy = *chainConfig chainConfig = chainConfigCopy - if yolov3 := config.LogConfig.Overrides.YoloV3Block; yolov3 != nil { - chainConfig.YoloV3Block = yolov3 + if berlin := config.LogConfig.Overrides.BerlinBlock; berlin != nil { + chainConfig.BerlinBlock = berlin canon = false } } diff --git a/les/client.go b/les/client.go index e20519fd91..4d07f844f8 100644 --- a/les/client.go +++ b/les/client.go @@ -84,7 +84,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if err != nil { return nil, err } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis) + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideBerlin) if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat { return nil, genesisErr } diff --git a/light/txpool.go b/light/txpool.go index bf5f9ff583..1296389e3b 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -314,7 +314,7 @@ func (pool *TxPool) setNewHead(head *types.Header) { // Update fork indicator by next pending block number next := new(big.Int).Add(head.Number, big.NewInt(1)) pool.istanbul = pool.config.IsIstanbul(next) - pool.eip2718 = pool.config.IsYoloV3(next) + pool.eip2718 = pool.config.IsBerlin(next) } // Stop stops the light transaction pool diff --git a/params/config.go b/params/config.go index 929bdbeb94..8cbfffc03c 100644 --- a/params/config.go +++ b/params/config.go @@ -56,18 +56,19 @@ var ( // MainnetChainConfig is the chain parameters to run a node on the main network. MainnetChainConfig = &ChainConfig{ ChainID: big.NewInt(1), - HomesteadBlock: big.NewInt(1150000), - DAOForkBlock: big.NewInt(1920000), + HomesteadBlock: big.NewInt(1_150_000), + DAOForkBlock: big.NewInt(1_920_000), DAOForkSupport: true, - EIP150Block: big.NewInt(2463000), + EIP150Block: big.NewInt(2_463_000), EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), - EIP155Block: big.NewInt(2675000), - EIP158Block: big.NewInt(2675000), - ByzantiumBlock: big.NewInt(4370000), - ConstantinopleBlock: big.NewInt(7280000), - PetersburgBlock: big.NewInt(7280000), - IstanbulBlock: big.NewInt(9069000), - MuirGlacierBlock: big.NewInt(9200000), + EIP155Block: big.NewInt(2_675_000), + EIP158Block: big.NewInt(2_675_000), + ByzantiumBlock: big.NewInt(4_370_000), + ConstantinopleBlock: big.NewInt(7_280_000), + PetersburgBlock: big.NewInt(7_280_000), + IstanbulBlock: big.NewInt(9_069_000), + MuirGlacierBlock: big.NewInt(9_200_000), + BerlinBlock: big.NewInt(12_244_000), Ethash: new(EthashConfig), } @@ -102,11 +103,12 @@ var ( EIP150Hash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"), EIP155Block: big.NewInt(10), EIP158Block: big.NewInt(10), - ByzantiumBlock: big.NewInt(1700000), - ConstantinopleBlock: big.NewInt(4230000), - PetersburgBlock: big.NewInt(4939394), - IstanbulBlock: big.NewInt(6485846), - MuirGlacierBlock: big.NewInt(7117117), + ByzantiumBlock: big.NewInt(1_700_000), + ConstantinopleBlock: big.NewInt(4_230_000), + PetersburgBlock: big.NewInt(4_939_394), + IstanbulBlock: big.NewInt(6_485_846), + MuirGlacierBlock: big.NewInt(7_117_117), + BerlinBlock: big.NewInt(9_812_189), Ethash: new(EthashConfig), } @@ -141,11 +143,12 @@ var ( EIP150Hash: common.HexToHash("0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"), EIP155Block: big.NewInt(3), EIP158Block: big.NewInt(3), - ByzantiumBlock: big.NewInt(1035301), - ConstantinopleBlock: big.NewInt(3660663), - PetersburgBlock: big.NewInt(4321234), - IstanbulBlock: big.NewInt(5435345), + ByzantiumBlock: big.NewInt(1_035_301), + ConstantinopleBlock: big.NewInt(3_660_663), + PetersburgBlock: big.NewInt(4_321_234), + IstanbulBlock: big.NewInt(5_435_345), MuirGlacierBlock: nil, + BerlinBlock: big.NewInt(8_290_928), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -184,8 +187,9 @@ var ( ByzantiumBlock: big.NewInt(0), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(1561651), + IstanbulBlock: big.NewInt(1_561_651), MuirGlacierBlock: nil, + BerlinBlock: big.NewInt(4_460_644), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -227,6 +231,7 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: nil, + BerlinBlock: nil, // Don't enable Berlin directly, we're YOLOing it YoloV3Block: big.NewInt(0), Clique: &CliqueConfig{ Period: 15, @@ -239,16 +244,16 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), nil, new(EthashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -319,6 +324,7 @@ type ChainConfig struct { PetersburgBlock *big.Int `json:"petersburgBlock,omitempty"` // Petersburg switch block (nil = same as Constantinople) IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) + BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin) YoloV3Block *big.Int `json:"yoloV3Block,omitempty"` // YOLO v3: Gas repricings TODO @holiman add EIP references EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) @@ -358,7 +364,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v3: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, YOLO v3: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -371,6 +377,7 @@ func (c *ChainConfig) String() string { c.PetersburgBlock, c.IstanbulBlock, c.MuirGlacierBlock, + c.BerlinBlock, c.YoloV3Block, engine, ) @@ -428,9 +435,9 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool { return isForked(c.IstanbulBlock, num) } -// IsYoloV3 returns whether num is either equal to the YoloV3 fork block or greater. -func (c *ChainConfig) IsYoloV3(num *big.Int) bool { - return isForked(c.YoloV3Block, num) +// IsBerlin returns whether num is either equal to the Berlin fork block or greater. +func (c *ChainConfig) IsBerlin(num *big.Int) bool { + return isForked(c.BerlinBlock, num) || isForked(c.YoloV3Block, num) } // IsEWASM returns whether num represents a block number after the EWASM fork @@ -476,7 +483,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "petersburgBlock", block: c.PetersburgBlock}, {name: "istanbulBlock", block: c.IstanbulBlock}, {name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true}, - {name: "yoloV3Block", block: c.YoloV3Block}, + {name: "berlinBlock", block: c.BerlinBlock}, } { if lastFork.name != "" { // Next one must be higher number @@ -540,6 +547,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) { return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock) } + if isForkIncompatible(c.BerlinBlock, newcfg.BerlinBlock, head) { + return newCompatError("Berlin fork block", c.BerlinBlock, newcfg.BerlinBlock) + } if isForkIncompatible(c.YoloV3Block, newcfg.YoloV3Block, head) { return newCompatError("YOLOv3 fork block", c.YoloV3Block, newcfg.YoloV3Block) } @@ -613,7 +623,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool - IsYoloV3 bool + IsBerlin bool } // Rules ensures c's ChainID is not nil. @@ -632,6 +642,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsConstantinople: c.IsConstantinople(num), IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), - IsYoloV3: c.IsYoloV3(num), + IsBerlin: c.IsBerlin(num), } } diff --git a/tests/init.go b/tests/init.go index a2ef040786..67f706eb50 100644 --- a/tests/init.go +++ b/tests/init.go @@ -165,7 +165,7 @@ var Forks = map[string]*params.ChainConfig{ ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), - YoloV3Block: big.NewInt(0), + BerlinBlock: big.NewInt(0), }, } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 03dc01459c..99681baaf1 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -24,14 +24,13 @@ import ( "strconv" "strings" - "github.com/ethereum/go-ethereum/core/state/snapshot" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" From 27b31371d46bc932853cce36078b28a53088b2b2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 26 Feb 2021 13:40:35 +0100 Subject: [PATCH 223/235] rpc: add separate size limit for websocket (#22385) This makes the WebSocket message size limit independent of the limit used for HTTP requests. The new limit for WebSocket messages is 15MB. --- rpc/http_test.go | 25 +++++++++++++++++++++++++ rpc/testservice_test.go | 10 ++++++++++ rpc/websocket.go | 3 ++- rpc/websocket_test.go | 27 +++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/rpc/http_test.go b/rpc/http_test.go index fc939ae48f..b75af67c52 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -98,3 +98,28 @@ func confirmHTTPRequestYieldsStatusCode(t *testing.T, method, contentType, body func TestHTTPResponseWithEmptyGet(t *testing.T) { confirmHTTPRequestYieldsStatusCode(t, http.MethodGet, "", "", http.StatusOK) } + +// This checks that maxRequestContentLength is not applied to the response of a request. +func TestHTTPRespBodyUnlimited(t *testing.T) { + const respLength = maxRequestContentLength * 3 + + s := NewServer() + defer s.Stop() + s.RegisterName("test", largeRespService{respLength}) + ts := httptest.NewServer(s) + defer ts.Close() + + c, err := DialHTTP(ts.URL) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + var r string + if err := c.Call(&r, "test_largeResp"); err != nil { + t.Fatal(err) + } + if len(r) != respLength { + t.Fatalf("response has wrong length %d, want %d", len(r), respLength) + } +} diff --git a/rpc/testservice_test.go b/rpc/testservice_test.go index 6f948a1bac..62afc1df44 100644 --- a/rpc/testservice_test.go +++ b/rpc/testservice_test.go @@ -20,6 +20,7 @@ import ( "context" "encoding/binary" "errors" + "strings" "sync" "time" ) @@ -194,3 +195,12 @@ func (s *notificationTestService) HangSubscription(ctx context.Context, val int) }() return subscription, nil } + +// largeRespService generates arbitrary-size JSON responses. +type largeRespService struct { + length int +} + +func (x largeRespService) LargeResp() string { + return strings.Repeat("x", x.length) +} diff --git a/rpc/websocket.go b/rpc/websocket.go index cd60eeb613..ab55ae69c1 100644 --- a/rpc/websocket.go +++ b/rpc/websocket.go @@ -37,6 +37,7 @@ const ( wsWriteBuffer = 1024 wsPingInterval = 60 * time.Second wsPingWriteTimeout = 5 * time.Second + wsMessageSizeLimit = 15 * 1024 * 1024 ) var wsBufferPool = new(sync.Pool) @@ -239,7 +240,7 @@ type websocketCodec struct { } func newWebsocketCodec(conn *websocket.Conn) ServerCodec { - conn.SetReadLimit(maxRequestContentLength) + conn.SetReadLimit(wsMessageSizeLimit) wc := &websocketCodec{ jsonCodec: NewFuncCodec(conn, conn.WriteJSON, conn.ReadJSON).(*jsonCodec), conn: conn, diff --git a/rpc/websocket_test.go b/rpc/websocket_test.go index f54fc3cd54..37ed19476f 100644 --- a/rpc/websocket_test.go +++ b/rpc/websocket_test.go @@ -157,6 +157,33 @@ func TestClientWebsocketPing(t *testing.T) { } } +// This checks that the websocket transport can deal with large messages. +func TestClientWebsocketLargeMessage(t *testing.T) { + var ( + srv = NewServer() + httpsrv = httptest.NewServer(srv.WebsocketHandler(nil)) + wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:") + ) + defer srv.Stop() + defer httpsrv.Close() + + respLength := wsMessageSizeLimit - 50 + srv.RegisterName("test", largeRespService{respLength}) + + c, err := DialWebsocket(context.Background(), wsURL, "") + if err != nil { + t.Fatal(err) + } + + var r string + if err := c.Call(&r, "test_largeResp"); err != nil { + t.Fatal("call failed:", err) + } + if len(r) != respLength { + t.Fatalf("response has wrong length %d, want %d", len(r), respLength) + } +} + // wsPingTestServer runs a WebSocket server which accepts a single subscription request. // When a value arrives on sendPing, the server sends a ping frame, waits for a matching // pong and finally delivers a single subscription result. From 3822b09904edcd92bc203b5739115208daa38765 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 26 Feb 2021 15:28:34 +0100 Subject: [PATCH 224/235] accounts/keystore: use github.com/google/uuid (#22217) This replaces the github.com/pborman/uuid dependency with github.com/google/uuid because the former is only a wrapper for the latter (since v1.0.0). Co-authored-by: Felix Lange --- accounts/keystore/key.go | 12 +++++++++--- accounts/keystore/passphrase.go | 21 ++++++++++++++++----- accounts/keystore/presale.go | 9 ++++++--- cmd/ethkey/generate.go | 9 ++++++--- go.mod | 3 +-- go.sum | 20 ++------------------ 6 files changed, 40 insertions(+), 34 deletions(-) diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go index 84d8df0c5a..2b815ce0f9 100644 --- a/accounts/keystore/key.go +++ b/accounts/keystore/key.go @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" ) const ( @@ -110,7 +110,10 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) { } u := new(uuid.UUID) - *u = uuid.Parse(keyJSON.Id) + *u, err = uuid.Parse(keyJSON.Id) + if err != nil { + return err + } k.Id = *u addr, err := hex.DecodeString(keyJSON.Address) if err != nil { @@ -128,7 +131,10 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) { } func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { - id := uuid.NewRandom() + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf("Could not create random uuid: %v", err)) + } key := &Key{ Id: id, Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go index dd4d7764e4..3b3e631888 100644 --- a/accounts/keystore/passphrase.go +++ b/accounts/keystore/passphrase.go @@ -42,7 +42,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/scrypt" ) @@ -228,9 +228,12 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) { return nil, err } key := crypto.ToECDSAUnsafe(keyBytes) - + id, err := uuid.FromBytes(keyId) + if err != nil { + return nil, err + } return &Key{ - Id: keyId, + Id: id, Address: crypto.PubkeyToAddress(key.PublicKey), PrivateKey: key, }, nil @@ -276,7 +279,11 @@ func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byt if keyProtected.Version != version { return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version) } - keyId = uuid.Parse(keyProtected.Id) + keyUUID, err := uuid.Parse(keyProtected.Id) + if err != nil { + return nil, nil, err + } + keyId = keyUUID[:] plainText, err := DecryptDataV3(keyProtected.Crypto, auth) if err != nil { return nil, nil, err @@ -285,7 +292,11 @@ func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byt } func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) { - keyId = uuid.Parse(keyProtected.Id) + keyUUID, err := uuid.Parse(keyProtected.Id) + if err != nil { + return nil, nil, err + } + keyId = keyUUID[:] mac, err := hex.DecodeString(keyProtected.Crypto.MAC) if err != nil { return nil, nil, err diff --git a/accounts/keystore/presale.go b/accounts/keystore/presale.go index 03055245f5..0664dc2cdd 100644 --- a/accounts/keystore/presale.go +++ b/accounts/keystore/presale.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" "golang.org/x/crypto/pbkdf2" ) @@ -37,7 +37,10 @@ func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accou if err != nil { return accounts.Account{}, nil, err } - key.Id = uuid.NewRandom() + key.Id, err = uuid.NewRandom() + if err != nil { + return accounts.Account{}, nil, err + } a := accounts.Account{ Address: key.Address, URL: accounts.URL{ @@ -86,7 +89,7 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error ecKey := crypto.ToECDSAUnsafe(ethPriv) key = &Key{ - Id: nil, + Id: uuid.UUID{}, Address: crypto.PubkeyToAddress(ecKey.PublicKey), PrivateKey: ecKey, } diff --git a/cmd/ethkey/generate.go b/cmd/ethkey/generate.go index c2aa1c6fb4..629d23da5b 100644 --- a/cmd/ethkey/generate.go +++ b/cmd/ethkey/generate.go @@ -26,7 +26,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" "gopkg.in/urfave/cli.v1" ) @@ -86,9 +86,12 @@ If you want to encrypt an existing private key, it can be specified by setting } // Create the keyfile object with a random UUID. - id := uuid.NewRandom() + UUID, err := uuid.NewRandom() + if err != nil { + utils.Fatalf("Failed to generate random uuid: %v", err) + } key := &keystore.Key{ - Id: id, + Id: UUID, Address: crypto.PubkeyToAddress(privateKey.PublicKey), PrivateKey: privateKey, } diff --git a/go.mod b/go.mod index a1099c52cc..96a217ed06 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/golang/protobuf v1.4.3 github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa + github.com/google/uuid v1.1.5 github.com/gorilla/websocket v1.4.2 github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d @@ -37,7 +38,6 @@ require ( github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c - github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/prometheus/tsdb v0.7.1 github.com/rjeczalik/notify v0.9.1 @@ -48,7 +48,6 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c golang.org/x/text v0.3.3 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 diff --git a/go.sum b/go.sum index 4b788824c7..af76759c51 100644 --- a/go.sum +++ b/go.sum @@ -48,7 +48,6 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIO github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= -github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -72,19 +71,12 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2 github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -109,7 +101,6 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -137,7 +128,6 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM= -github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -145,7 +135,6 @@ github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+ github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -158,7 +147,6 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -190,12 +178,10 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 h1:ur2rms48b3Ep1dxh7aUV2FZEQ8jEVO2F6ILKx8ofkAg= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -204,7 +190,6 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -215,6 +200,8 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= +github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -255,7 +242,6 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 h1:HarGZ5h9HD9LgEg1yRVMXyfiw4wlXiLiYM2oMjeA/SE= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= @@ -264,7 +250,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= -github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= @@ -372,7 +357,6 @@ github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsq github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 h1:goeTyGkArOZIVOMA0dQbyuPWGNQJZGPwPu/QS9GlpnA= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= From 498458b4102c0d32d7453035a115e6b9df5e485d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 26 Feb 2021 16:33:37 +0100 Subject: [PATCH 225/235] core/state: fix eta calculation on pruning (#22386) --- core/state/pruner/pruner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 1fbfa55b6a..530a348540 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -155,7 +155,7 @@ func prune(maindb ethdb.Database, stateBloom *stateBloom, middleStateRoots map[c if done := binary.BigEndian.Uint64(key[:8]); done > 0 { var ( left = math.MaxUint64 - binary.BigEndian.Uint64(key[:8]) - speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + speed = done/uint64(time.Since(pstart)/time.Millisecond+1) + 1 // +1s to avoid division by zero ) eta = time.Duration(left/speed) * time.Millisecond } From d96870428f116494d5190a8e595189e283dd144b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Mon, 1 Mar 2021 10:24:20 +0100 Subject: [PATCH 226/235] les: UDP pre-negotiation of available server capacity (#22183) This PR implements the first one of the "lespay" UDP queries which is already useful in itself: the capacity query. The server pool is making use of this query by doing a cheap UDP query to determine whether it is worth starting the more expensive TCP connection process. --- common/prque/lazyqueue.go | 5 +- common/prque/lazyqueue_test.go | 2 +- les/client.go | 85 +++++++++-- les/clientpool.go | 55 +++++++ les/clientpool_test.go | 6 +- les/enr_entry.go | 3 +- les/server.go | 29 +++- les/vflux/client/serverpool.go | 105 +++++++++++-- les/vflux/client/serverpool_test.go | 8 +- les/vflux/requests.go | 180 +++++++++++++++++++++++ les/vflux/server/balance.go | 58 +++++--- les/vflux/server/balance_test.go | 4 +- les/vflux/server/prioritypool.go | 202 +++++++++++++++++++++++--- les/vflux/server/prioritypool_test.go | 126 +++++++++++++++- les/vflux/server/service.go | 122 ++++++++++++++++ p2p/discover/v5_udp.go | 11 +- p2p/discover/v5_udp_test.go | 2 +- p2p/nodestate/nodestate.go | 1 + 18 files changed, 915 insertions(+), 89 deletions(-) create mode 100644 les/vflux/requests.go create mode 100644 les/vflux/server/service.go diff --git a/common/prque/lazyqueue.go b/common/prque/lazyqueue.go index 52403df464..c74faab7e6 100644 --- a/common/prque/lazyqueue.go +++ b/common/prque/lazyqueue.go @@ -48,7 +48,7 @@ type LazyQueue struct { } type ( - PriorityCallback func(data interface{}, now mclock.AbsTime) int64 // actual priority callback + PriorityCallback func(data interface{}) int64 // actual priority callback MaxPriorityCallback func(data interface{}, until mclock.AbsTime) int64 // estimated maximum priority callback ) @@ -139,11 +139,10 @@ func (q *LazyQueue) peekIndex() int { // Pop multiple times. Popped items are passed to the callback. MultiPop returns // when the callback returns false or there are no more items to pop. func (q *LazyQueue) MultiPop(callback func(data interface{}, priority int64) bool) { - now := q.clock.Now() nextIndex := q.peekIndex() for nextIndex != -1 { data := heap.Pop(q.queue[nextIndex]).(*item).value - heap.Push(q.popQueue, &item{data, q.priority(data, now)}) + heap.Push(q.popQueue, &item{data, q.priority(data)}) nextIndex = q.peekIndex() for q.popQueue.Len() != 0 && (nextIndex == -1 || q.queue[nextIndex].blocks[0][0].priority < q.popQueue.blocks[0][0].priority) { i := heap.Pop(q.popQueue).(*item) diff --git a/common/prque/lazyqueue_test.go b/common/prque/lazyqueue_test.go index be9491e24e..9a831d628b 100644 --- a/common/prque/lazyqueue_test.go +++ b/common/prque/lazyqueue_test.go @@ -40,7 +40,7 @@ type lazyItem struct { index int } -func testPriority(a interface{}, now mclock.AbsTime) int64 { +func testPriority(a interface{}) int64 { return a.(*lazyItem).p } diff --git a/les/client.go b/les/client.go index 4d07f844f8..ecabfdf503 100644 --- a/les/client.go +++ b/les/client.go @@ -36,30 +36,33 @@ import ( "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/les/vflux" vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" ) type LightEthereum struct { lesCommons - peers *serverPeerSet - reqDist *requestDistributor - retriever *retrieveManager - odr *LesOdr - relay *lesTxRelay - handler *clientHandler - txPool *light.TxPool - blockchain *light.LightChain - serverPool *vfc.ServerPool - dialCandidates enode.Iterator - pruner *pruner + peers *serverPeerSet + reqDist *requestDistributor + retriever *retrieveManager + odr *LesOdr + relay *lesTxRelay + handler *clientHandler + txPool *light.TxPool + blockchain *light.LightChain + serverPool *vfc.ServerPool + serverPoolIterator enode.Iterator + pruner *pruner bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports @@ -112,7 +115,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { p2pConfig: &stack.Config().P2P, } - leth.serverPool, leth.dialCandidates = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, nil, &mclock.System{}, config.UltraLightServers, requestList) + leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, leth.prenegQuery, &mclock.System{}, config.UltraLightServers, requestList) leth.serverPool.AddMetrics(suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge, sessionValueMeter, serverDialedMeter) leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.GetTimeout) @@ -189,6 +192,62 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { return leth, nil } +// VfluxRequest sends a batch of requests to the given node through discv5 UDP TalkRequest and returns the responses +func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.Replies { + reqsEnc, _ := rlp.EncodeToBytes(&reqs) + repliesEnc, _ := s.p2pServer.DiscV5.TalkRequest(s.serverPool.DialNode(n), "vfx", reqsEnc) + var replies vflux.Replies + if len(repliesEnc) == 0 || rlp.DecodeBytes(repliesEnc, &replies) != nil { + return nil + } + return replies +} + +// vfxVersion returns the version number of the "les" service subdomain of the vflux UDP +// service, as advertised in the ENR record +func (s *LightEthereum) vfxVersion(n *enode.Node) uint { + if n.Seq() == 0 { + var err error + if n, err = s.p2pServer.DiscV5.RequestENR(n); n != nil && err == nil && n.Seq() != 0 { + s.serverPool.Persist(n) + } else { + return 0 + } + } + + var les []rlp.RawValue + if err := n.Load(enr.WithEntry("les", &les)); err != nil || len(les) < 1 { + return 0 + } + var version uint + rlp.DecodeBytes(les[0], &version) // Ignore additional fields (for forward compatibility). + return version +} + +// prenegQuery sends a capacity query to the given server node to determine whether +// a connection slot is immediately available +func (s *LightEthereum) prenegQuery(n *enode.Node) int { + if s.vfxVersion(n) < 1 { + // UDP query not supported, always try TCP connection + return 1 + } + + var requests vflux.Requests + requests.Add("les", vflux.CapacityQueryName, vflux.CapacityQueryReq{ + Bias: 180, + AddTokens: []vflux.IntOrInf{{}}, + }) + replies := s.VfluxRequest(n, requests) + var cqr vflux.CapacityQueryReply + if replies.Get(0, &cqr) != nil || len(cqr) != 1 { // Note: Get returns an error if replies is nil + return -1 + } + if cqr[0] > 0 { + return 1 + } + return 0 +} + type LightDummyAPI struct{} // Etherbase is the address that mining rewards will be send to @@ -269,7 +328,7 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { return p.Info() } return nil - }, s.dialCandidates) + }, s.serverPoolIterator) } // Start implements node.Lifecycle, starting all internal goroutines needed by the diff --git a/les/clientpool.go b/les/clientpool.go index 4e1499bf5d..1aa63a281e 100644 --- a/les/clientpool.go +++ b/les/clientpool.go @@ -24,11 +24,13 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/les/vflux" vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nodestate" + "github.com/ethereum/go-ethereum/rlp" ) const ( @@ -382,3 +384,56 @@ func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) { } } } + +// serveCapQuery serves a vflux capacity query. It receives multiple token amount values +// and a bias time value. For each given token amount it calculates the maximum achievable +// capacity in case the amount is added to the balance. +func (f *clientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []byte { + var req vflux.CapacityQueryReq + if rlp.DecodeBytes(data, &req) != nil { + return nil + } + if l := len(req.AddTokens); l == 0 || l > vflux.CapacityQueryMaxLen { + return nil + } + node := f.ns.GetNode(id) + if node == nil { + node = enode.SignNull(&enr.Record{}, id) + } + c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo) + if c == nil { + c = &clientInfo{node: node} + f.ns.SetField(node, clientInfoField, c) + f.ns.SetField(node, connAddressField, freeID) + defer func() { + f.ns.SetField(node, connAddressField, nil) + f.ns.SetField(node, clientInfoField, nil) + }() + if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance == nil { + log.Error("BalanceField is missing", "node", node.ID()) + return nil + } + } + // use vfs.CapacityCurve to answer request for multiple newly bought token amounts + curve := f.pp.GetCapacityCurve().Exclude(id) + result := make(vflux.CapacityQueryReply, len(req.AddTokens)) + bias := time.Second * time.Duration(req.Bias) + if f.connectedBias > bias { + bias = f.connectedBias + } + pb, _ := c.balance.GetBalance() + for i, addTokens := range req.AddTokens { + add := addTokens.Int64() + result[i] = curve.MaxCapacity(func(capacity uint64) int64 { + return c.balance.EstimatePriority(capacity, add, 0, bias, false) / int64(capacity) + }) + if add <= 0 && uint64(-add) >= pb && result[i] > f.minCap { + result[i] = f.minCap + } + if result[i] < f.minCap { + result[i] = 0 + } + } + reply, _ := rlp.EncodeToBytes(&result) + return reply +} diff --git a/les/clientpool_test.go b/les/clientpool_test.go index 5cff010409..345b373b0f 100644 --- a/les/clientpool_test.go +++ b/les/clientpool_test.go @@ -508,8 +508,10 @@ func TestNegativeBalanceCalculation(t *testing.T) { for i := 0; i < 10; i++ { pool.disconnect(newPoolTestPeer(i, nil)) _, nb := getBalance(pool, newPoolTestPeer(i, nil)) - if checkDiff(nb, uint64(time.Minute)/1000) { - t.Fatalf("Negative balance mismatch, want %v, got %v", uint64(time.Minute)/1000, nb) + exp := uint64(time.Minute) / 1000 + exp -= exp / 120 // correct for negative balance expiration + if checkDiff(nb, exp) { + t.Fatalf("Negative balance mismatch, want %v, got %v", exp, nb) } } } diff --git a/les/enr_entry.go b/les/enr_entry.go index 1e56c1f175..8be4a7a00e 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -27,7 +27,8 @@ import ( // lesEntry is the "les" ENR entry. This is set for LES servers only. type lesEntry struct { // Ignore additional fields (for forward compatibility). - _ []rlp.RawValue `rlp:"tail"` + VfxVersion uint + Rest []rlp.RawValue `rlp:"tail"` } func (lesEntry) ENRKey() string { return "les" } diff --git a/les/server.go b/les/server.go index 359784cf7d..63feaf892c 100644 --- a/les/server.go +++ b/les/server.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/flowcontrol" + "github.com/ethereum/go-ethereum/les/vflux" vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" @@ -68,6 +69,7 @@ type LesServer struct { archiveMode bool // Flag whether the ethereum node runs in archive mode. handler *serverHandler broadcaster *broadcaster + vfluxServer *vfs.Server privateKey *ecdsa.PrivateKey // Flow control and capacity management @@ -112,12 +114,14 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les ns: ns, archiveMode: e.ArchiveMode(), broadcaster: newBroadcaster(ns), + vfluxServer: vfs.NewServer(time.Millisecond * 10), fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}), servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100), threadsBusy: config.LightServ/100 + 1, threadsIdle: threads, p2pSrv: node.Server(), } + srv.vfluxServer.Register(srv) issync := e.Synced if config.LightNoSyncServe { issync = func() bool { return true } @@ -201,7 +205,9 @@ func (s *LesServer) Protocols() []p2p.Protocol { }, nil) // Add "les" ENR entries. for i := range ps { - ps[i].Attributes = []enr.Entry{&lesEntry{}} + ps[i].Attributes = []enr.Entry{&lesEntry{ + VfxVersion: 1, + }} } return ps } @@ -211,10 +217,11 @@ func (s *LesServer) Start() error { s.privateKey = s.p2pSrv.PrivateKey s.broadcaster.setSignerKey(s.privateKey) s.handler.start() - s.wg.Add(1) go s.capacityManagement() - + if s.p2pSrv.DiscV5 != nil { + s.p2pSrv.DiscV5.RegisterTalkHandler("vfx", s.vfluxServer.ServeEncoded) + } return nil } @@ -228,6 +235,7 @@ func (s *LesServer) Stop() error { s.costTracker.stop() s.handler.stop() s.servingQueue.stop() + s.vfluxServer.Stop() // Note, bloom trie indexer is closed by parent bloombits indexer. s.chtIndexer.Close() @@ -311,3 +319,18 @@ func (s *LesServer) dropClient(id enode.ID) { p.Peer.Disconnect(p2p.DiscRequested) } } + +// ServiceInfo implements vfs.Service +func (s *LesServer) ServiceInfo() (string, string) { + return "les", "Ethereum light client service" +} + +// Handle implements vfs.Service +func (s *LesServer) Handle(id enode.ID, address string, name string, data []byte) []byte { + switch name { + case vflux.CapacityQueryName: + return s.clientPool.serveCapQuery(id, address, data) + default: + return nil + } +} diff --git a/les/vflux/client/serverpool.go b/les/vflux/client/serverpool.go index 95f7246091..47ec4fee74 100644 --- a/les/vflux/client/serverpool.go +++ b/les/vflux/client/serverpool.go @@ -94,7 +94,7 @@ type nodeHistoryEnc struct { type queryFunc func(*enode.Node) int var ( - clientSetup = &nodestate.Setup{Version: 1} + clientSetup = &nodestate.Setup{Version: 2} sfHasValue = clientSetup.NewPersistentFlag("hasValue") sfQueried = clientSetup.NewFlag("queried") sfCanDial = clientSetup.NewFlag("canDial") @@ -131,9 +131,25 @@ var ( ) sfiNodeWeight = clientSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0))) sfiConnectedStats = clientSetup.NewField("connectedStats", reflect.TypeOf(ResponseTimeStats{})) + sfiLocalAddress = clientSetup.NewPersistentField("localAddress", reflect.TypeOf(&enr.Record{}), + func(field interface{}) ([]byte, error) { + if enr, ok := field.(*enr.Record); ok { + enc, err := rlp.EncodeToBytes(enr) + return enc, err + } + return nil, errors.New("invalid field type") + }, + func(enc []byte) (interface{}, error) { + var enr enr.Record + if err := rlp.DecodeBytes(enc, &enr); err != nil { + return nil, err + } + return &enr, nil + }, + ) ) -// newServerPool creates a new server pool +// NewServerPool creates a new server pool func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) { s := &ServerPool{ db: db, @@ -151,15 +167,10 @@ func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duratio s.mixSources = append(s.mixSources, knownSelector) s.mixSources = append(s.mixSources, alwaysConnect) - iter := enode.Iterator(s.mixer) + s.dialIterator = s.mixer if query != nil { - iter = s.addPreNegFilter(iter, query) + s.dialIterator = s.addPreNegFilter(s.dialIterator, query) } - s.dialIterator = enode.Filter(iter, func(node *enode.Node) bool { - s.ns.SetState(node, sfDialing, sfCanDial, 0) - s.ns.SetState(node, sfWaitDialTimeout, nodestate.Flags{}, time.Second*10) - return true - }) s.ns.SubscribeState(nodestate.MergeFlags(sfWaitDialTimeout, sfConnected), func(n *enode.Node, oldState, newState nodestate.Flags) { if oldState.Equals(sfWaitDialTimeout) && newState.IsEmpty() { @@ -169,7 +180,41 @@ func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duratio } }) - return s, s.dialIterator + return s, &serverPoolIterator{ + dialIterator: s.dialIterator, + nextFn: func(node *enode.Node) { + s.ns.Operation(func() { + s.ns.SetStateSub(node, sfDialing, sfCanDial, 0) + s.ns.SetStateSub(node, sfWaitDialTimeout, nodestate.Flags{}, time.Second*10) + }) + }, + nodeFn: s.DialNode, + } +} + +type serverPoolIterator struct { + dialIterator enode.Iterator + nextFn func(*enode.Node) + nodeFn func(*enode.Node) *enode.Node +} + +// Next implements enode.Iterator +func (s *serverPoolIterator) Next() bool { + if s.dialIterator.Next() { + s.nextFn(s.dialIterator.Node()) + return true + } + return false +} + +// Node implements enode.Iterator +func (s *serverPoolIterator) Node() *enode.Node { + return s.nodeFn(s.dialIterator.Node()) +} + +// Close implements enode.Iterator +func (s *serverPoolIterator) Close() { + s.dialIterator.Close() } // AddMetrics adds metrics to the server pool. Should be called before Start(). @@ -285,7 +330,6 @@ func (s *ServerPool) Start() { // stop stops the server pool func (s *ServerPool) Stop() { - s.dialIterator.Close() if s.fillSet != nil { s.fillSet.Close() } @@ -299,18 +343,23 @@ func (s *ServerPool) Stop() { s.vt.Stop() } -// registerPeer implements serverPeerSubscriber +// RegisterNode implements serverPeerSubscriber func (s *ServerPool) RegisterNode(node *enode.Node) (*NodeValueTracker, error) { if atomic.LoadUint32(&s.started) == 0 { return nil, errors.New("server pool not started yet") } - s.ns.SetState(node, sfConnected, sfDialing.Or(sfWaitDialTimeout), 0) nvt := s.vt.Register(node.ID()) - s.ns.SetField(node, sfiConnectedStats, nvt.RtStats()) + s.ns.Operation(func() { + s.ns.SetStateSub(node, sfConnected, sfDialing.Or(sfWaitDialTimeout), 0) + s.ns.SetFieldSub(node, sfiConnectedStats, nvt.RtStats()) + if node.IP().IsLoopback() { + s.ns.SetFieldSub(node, sfiLocalAddress, node.Record()) + } + }) return nvt, nil } -// unregisterPeer implements serverPeerSubscriber +// UnregisterNode implements serverPeerSubscriber func (s *ServerPool) UnregisterNode(node *enode.Node) { s.ns.Operation(func() { s.setRedialWait(node, dialCost, dialWaitStep) @@ -430,6 +479,7 @@ func (s *ServerPool) updateWeight(node *enode.Node, totalValue float64, totalDia s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0) s.ns.SetFieldSub(node, sfiNodeWeight, nil) s.ns.SetFieldSub(node, sfiNodeHistory, nil) + s.ns.SetFieldSub(node, sfiLocalAddress, nil) } s.ns.Persist(node) // saved if node history or hasValue changed } @@ -520,3 +570,28 @@ func (s *ServerPool) calculateWeight(node *enode.Node) { func (s *ServerPool) API() *PrivateClientAPI { return NewPrivateClientAPI(s.vt) } + +type dummyIdentity enode.ID + +func (id dummyIdentity) Verify(r *enr.Record, sig []byte) error { return nil } +func (id dummyIdentity) NodeAddr(r *enr.Record) []byte { return id[:] } + +// DialNode replaces the given enode with a locally generated one containing the ENR +// stored in the sfiLocalAddress field if present. This workaround ensures that nodes +// on the local network can be dialed at the local address if a connection has been +// successfully established previously. +// Note that NodeStateMachine always remembers the enode with the latest version of +// the remote signed ENR. ENR filtering should be performed on that version while +// dialNode should be used for dialing the node over TCP or UDP. +func (s *ServerPool) DialNode(n *enode.Node) *enode.Node { + if enr, ok := s.ns.GetField(n, sfiLocalAddress).(*enr.Record); ok { + n, _ := enode.New(dummyIdentity(n.ID()), enr) + return n + } + return n +} + +// Persist immediately stores the state of a node in the node database +func (s *ServerPool) Persist(n *enode.Node) { + s.ns.Persist(n) +} diff --git a/les/vflux/client/serverpool_test.go b/les/vflux/client/serverpool_test.go index 3af3db95bc..ee299618c6 100644 --- a/les/vflux/client/serverpool_test.go +++ b/les/vflux/client/serverpool_test.go @@ -56,6 +56,7 @@ type ServerPoolTest struct { preNeg, preNegFail bool vt *ValueTracker sp *ServerPool + spi enode.Iterator input enode.Iterator testNodes []spTestNode trusted []string @@ -148,7 +149,7 @@ func (s *ServerPoolTest) start() { requestList[i] = RequestInfo{Name: "testreq" + strconv.Itoa(i), InitAmount: 1, InitValue: 1} } - s.sp, _ = NewServerPool(s.db, []byte("sp:"), 0, testQuery, s.clock, s.trusted, requestList) + s.sp, s.spi = NewServerPool(s.db, []byte("sp:"), 0, testQuery, s.clock, s.trusted, requestList) s.sp.AddSource(s.input) s.sp.validSchemes = enode.ValidSchemesForTesting s.sp.unixTime = func() int64 { return int64(s.clock.Now()) / int64(time.Second) } @@ -176,6 +177,7 @@ func (s *ServerPoolTest) start() { func (s *ServerPoolTest) stop() { close(s.quit) s.sp.Stop() + s.spi.Close() for i := range s.testNodes { n := &s.testNodes[i] if n.connected { @@ -208,9 +210,9 @@ func (s *ServerPoolTest) run() { if s.conn < spTestTarget { s.dialCount++ s.beginWait() - s.sp.dialIterator.Next() + s.spi.Next() s.endWait() - dial := s.sp.dialIterator.Node() + dial := s.spi.Node() id := dial.ID() idx := testNodeIndex(id) n := &s.testNodes[idx] diff --git a/les/vflux/requests.go b/les/vflux/requests.go new file mode 100644 index 0000000000..11255607e8 --- /dev/null +++ b/les/vflux/requests.go @@ -0,0 +1,180 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vflux + +import ( + "errors" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/rlp" +) + +var ErrNoReply = errors.New("no reply for given request") + +const ( + MaxRequestLength = 16 // max number of individual requests in a batch + CapacityQueryName = "cq" + CapacityQueryMaxLen = 16 +) + +type ( + // Request describes a single vflux request inside a batch. Service and request + // type are identified by strings, parameters are RLP encoded. + Request struct { + Service, Name string + Params []byte + } + // Requests are a batch of vflux requests + Requests []Request + + // Replies are the replies to a batch of requests + Replies [][]byte + + // CapacityQueryReq is the encoding format of the capacity query + CapacityQueryReq struct { + Bias uint64 // seconds + AddTokens []IntOrInf + } + // CapacityQueryReq is the encoding format of the response to the capacity query + CapacityQueryReply []uint64 +) + +// Add encodes and adds a new request to the batch +func (r *Requests) Add(service, name string, val interface{}) (int, error) { + enc, err := rlp.EncodeToBytes(val) + if err != nil { + return -1, err + } + *r = append(*r, Request{ + Service: service, + Name: name, + Params: enc, + }) + return len(*r) - 1, nil +} + +// Get decodes the reply to the i-th request in the batch +func (r Replies) Get(i int, val interface{}) error { + if i < 0 || i >= len(r) { + return ErrNoReply + } + return rlp.DecodeBytes(r[i], val) +} + +const ( + IntNonNegative = iota + IntNegative + IntPlusInf + IntMinusInf +) + +// IntOrInf is the encoding format for arbitrary length signed integers that can also +// hold the values of +Inf or -Inf +type IntOrInf struct { + Type uint8 + Value big.Int +} + +// BigInt returns the value as a big.Int or panics if the value is infinity +func (i *IntOrInf) BigInt() *big.Int { + switch i.Type { + case IntNonNegative: + return new(big.Int).Set(&i.Value) + case IntNegative: + return new(big.Int).Neg(&i.Value) + case IntPlusInf: + panic(nil) // caller should check Inf() before trying to convert to big.Int + case IntMinusInf: + panic(nil) + } + return &big.Int{} // invalid type decodes to 0 value +} + +// Inf returns 1 if the value is +Inf, -1 if it is -Inf, 0 otherwise +func (i *IntOrInf) Inf() int { + switch i.Type { + case IntPlusInf: + return 1 + case IntMinusInf: + return -1 + } + return 0 // invalid type decodes to 0 value +} + +// Int64 limits the value between MinInt64 and MaxInt64 (even if it is +-Inf) and returns an int64 type +func (i *IntOrInf) Int64() int64 { + switch i.Type { + case IntNonNegative: + if i.Value.IsInt64() { + return i.Value.Int64() + } else { + return math.MaxInt64 + } + case IntNegative: + if i.Value.IsInt64() { + return -i.Value.Int64() + } else { + return math.MinInt64 + } + case IntPlusInf: + return math.MaxInt64 + case IntMinusInf: + return math.MinInt64 + } + return 0 // invalid type decodes to 0 value +} + +// SetBigInt sets the value to the given big.Int +func (i *IntOrInf) SetBigInt(v *big.Int) { + if v.Sign() >= 0 { + i.Type = IntNonNegative + i.Value.Set(v) + } else { + i.Type = IntNegative + i.Value.Neg(v) + } +} + +// SetInt64 sets the value to the given int64. Note that MaxInt64 translates to +Inf +// while MinInt64 translates to -Inf. +func (i *IntOrInf) SetInt64(v int64) { + if v >= 0 { + if v == math.MaxInt64 { + i.Type = IntPlusInf + } else { + i.Type = IntNonNegative + i.Value.SetInt64(v) + } + } else { + if v == math.MinInt64 { + i.Type = IntMinusInf + } else { + i.Type = IntNegative + i.Value.SetInt64(-v) + } + } +} + +// SetInf sets the value to +Inf or -Inf +func (i *IntOrInf) SetInf(sign int) { + if sign == 1 { + i.Type = IntPlusInf + } else { + i.Type = IntMinusInf + } +} diff --git a/les/vflux/server/balance.go b/les/vflux/server/balance.go index f5073d0db1..db12a5c573 100644 --- a/les/vflux/server/balance.go +++ b/les/vflux/server/balance.go @@ -243,11 +243,11 @@ func (n *NodeBalance) RequestServed(cost uint64) uint64 { } // Priority returns the actual priority based on the current balance -func (n *NodeBalance) Priority(now mclock.AbsTime, capacity uint64) int64 { +func (n *NodeBalance) Priority(capacity uint64) int64 { n.lock.Lock() defer n.lock.Unlock() - n.updateBalance(now) + n.updateBalance(n.bt.clock.Now()) return n.balanceToPriority(n.balance, capacity) } @@ -256,16 +256,35 @@ func (n *NodeBalance) Priority(now mclock.AbsTime, capacity uint64) int64 { // in the current session. // If update is true then a priority callback is added that turns UpdateFlag on and off // in case the priority goes below the estimated minimum. -func (n *NodeBalance) EstMinPriority(at mclock.AbsTime, capacity uint64, update bool) int64 { +func (n *NodeBalance) EstimatePriority(capacity uint64, addBalance int64, future, bias time.Duration, update bool) int64 { n.lock.Lock() defer n.lock.Unlock() - var avgReqCost float64 - dt := time.Duration(n.lastUpdate - n.initTime) - if dt > time.Second { - avgReqCost = float64(n.sumReqCost) * 2 / float64(dt) + now := n.bt.clock.Now() + n.updateBalance(now) + b := n.balance + if addBalance != 0 { + offset := n.bt.posExp.LogOffset(now) + old := n.balance.pos.Value(offset) + if addBalance > 0 && (addBalance > maxBalance || old > maxBalance-uint64(addBalance)) { + b.pos = utils.ExpiredValue{} + b.pos.Add(maxBalance, offset) + } else { + b.pos.Add(addBalance, offset) + } } - pri := n.balanceToPriority(n.reducedBalance(at, capacity, avgReqCost), capacity) + if future > 0 { + var avgReqCost float64 + dt := time.Duration(n.lastUpdate - n.initTime) + if dt > time.Second { + avgReqCost = float64(n.sumReqCost) * 2 / float64(dt) + } + b = n.reducedBalance(b, now, future, capacity, avgReqCost) + } + if bias > 0 { + b = n.reducedBalance(b, now+mclock.AbsTime(future), bias, capacity, 0) + } + pri := n.balanceToPriority(b, capacity) if update { n.addCallback(balanceCallbackUpdate, pri, n.signalPriorityUpdate) } @@ -366,7 +385,7 @@ func (n *NodeBalance) deactivate() { // updateBalance updates balance based on the time factor func (n *NodeBalance) updateBalance(now mclock.AbsTime) { if n.active && now > n.lastUpdate { - n.balance = n.reducedBalance(now, n.capacity, 0) + n.balance = n.reducedBalance(n.balance, n.lastUpdate, time.Duration(now-n.lastUpdate), n.capacity, 0) n.lastUpdate = now } } @@ -546,23 +565,25 @@ func (n *NodeBalance) balanceToPriority(b balance, capacity uint64) int64 { } // reducedBalance estimates the reduced balance at a given time in the fututre based -// on the current balance, the time factor and an estimated average request cost per time ratio -func (n *NodeBalance) reducedBalance(at mclock.AbsTime, capacity uint64, avgReqCost float64) balance { - dt := float64(at - n.lastUpdate) - b := n.balance +// on the given balance, the time factor and an estimated average request cost per time ratio +func (n *NodeBalance) reducedBalance(b balance, start mclock.AbsTime, dt time.Duration, capacity uint64, avgReqCost float64) balance { + // since the costs are applied continuously during the dt time period we calculate + // the expiration offset at the middle of the period + at := start + mclock.AbsTime(dt/2) + dtf := float64(dt) if !b.pos.IsZero() { factor := n.posFactor.timePrice(capacity) + n.posFactor.RequestFactor*avgReqCost - diff := -int64(dt * factor) + diff := -int64(dtf * factor) dd := b.pos.Add(diff, n.bt.posExp.LogOffset(at)) if dd == diff { - dt = 0 + dtf = 0 } else { - dt += float64(dd) / factor + dtf += float64(dd) / factor } } if dt > 0 { factor := n.negFactor.timePrice(capacity) + n.negFactor.RequestFactor*avgReqCost - b.neg.Add(int64(dt*factor), n.bt.negExp.LogOffset(at)) + b.neg.Add(int64(dtf*factor), n.bt.negExp.LogOffset(at)) } return b } @@ -588,8 +609,9 @@ func (n *NodeBalance) timeUntil(priority int64) (time.Duration, bool) { } dt = float64(posBalance-newBalance) / timePrice return time.Duration(dt), true + } else { + dt = float64(posBalance) / timePrice } - dt = float64(posBalance) / timePrice } else { if priority > 0 { return 0, false diff --git a/les/vflux/server/balance_test.go b/les/vflux/server/balance_test.go index 6c817aa26c..e22074db2d 100644 --- a/les/vflux/server/balance_test.go +++ b/les/vflux/server/balance_test.go @@ -231,7 +231,7 @@ func TestBalanceToPriority(t *testing.T) { } for _, i := range inputs { node.SetBalance(i.pos, i.neg) - priority := node.Priority(b.clock.Now(), 1000) + priority := node.Priority(1000) if priority != i.priority { t.Fatalf("Priority mismatch, want %v, got %v", i.priority, priority) } @@ -272,7 +272,7 @@ func TestEstimatedPriority(t *testing.T) { for _, i := range inputs { b.clock.Run(i.runTime) node.RequestServed(i.reqCost) - priority := node.EstMinPriority(b.clock.Now()+mclock.AbsTime(i.futureTime), 1000000000, false) + priority := node.EstimatePriority(1000000000, 0, i.futureTime, 0, false) if priority != i.priority { t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority) } diff --git a/les/vflux/server/prioritypool.go b/les/vflux/server/prioritypool.go index e3327aba75..e940ac7c65 100644 --- a/les/vflux/server/prioritypool.go +++ b/les/vflux/server/prioritypool.go @@ -101,17 +101,21 @@ type PriorityPool struct { minCap uint64 activeBias time.Duration capacityStepDiv uint64 + + cachedCurve *CapacityCurve + ccUpdatedAt mclock.AbsTime + ccUpdateForced bool } // nodePriority interface provides current and estimated future priorities on demand type nodePriority interface { // Priority should return the current priority of the node (higher is better) - Priority(now mclock.AbsTime, cap uint64) int64 + Priority(cap uint64) int64 // EstMinPriority should return a lower estimate for the minimum of the node priority // value starting from the current moment until the given time. If the priority goes // under the returned estimate before the specified moment then it is the caller's // responsibility to signal with updateFlag. - EstMinPriority(until mclock.AbsTime, cap uint64, update bool) int64 + EstimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 } // ppNodeInfo is the internal node descriptor of PriorityPool @@ -131,12 +135,12 @@ func NewPriorityPool(ns *nodestate.NodeStateMachine, setup PriorityPoolSetup, cl ns: ns, PriorityPoolSetup: setup, clock: clock, - activeQueue: prque.NewLazyQueue(activeSetIndex, activePriority, activeMaxPriority, clock, lazyQueueRefresh), inactiveQueue: prque.New(inactiveSetIndex), minCap: minCap, activeBias: activeBias, capacityStepDiv: capacityStepDiv, } + pp.activeQueue = prque.NewLazyQueue(activeSetIndex, activePriority, pp.activeMaxPriority, clock, lazyQueueRefresh) ns.SubscribeField(pp.priorityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { if newValue != nil { @@ -197,6 +201,9 @@ func (pp *PriorityPool) RequestCapacity(node *enode.Node, targetCap uint64, bias if targetCap < pp.minCap { targetCap = pp.minCap } + if bias < pp.activeBias { + bias = pp.activeBias + } c, _ := pp.ns.GetField(node, pp.ppNodeInfoField).(*ppNodeInfo) if c == nil { log.Error("RequestCapacity called for unknown node", "id", node.ID()) @@ -204,9 +211,9 @@ func (pp *PriorityPool) RequestCapacity(node *enode.Node, targetCap uint64, bias } var priority int64 if targetCap > c.capacity { - priority = c.nodePriority.EstMinPriority(pp.clock.Now()+mclock.AbsTime(bias), targetCap, false) + priority = c.nodePriority.EstimatePriority(targetCap, 0, 0, bias, false) } else { - priority = c.nodePriority.Priority(pp.clock.Now(), targetCap) + priority = c.nodePriority.Priority(targetCap) } pp.markForChange(c) pp.setCapacity(c, targetCap) @@ -214,7 +221,7 @@ func (pp *PriorityPool) RequestCapacity(node *enode.Node, targetCap uint64, bias pp.activeQueue.Remove(c.activeIndex) pp.inactiveQueue.Remove(c.inactiveIndex) pp.activeQueue.Push(c) - minPriority = pp.enforceLimits() + _, minPriority = pp.enforceLimits() // if capacity update is possible now then minPriority == math.MinInt64 // if it is not possible at all then minPriority == math.MaxInt64 allowed = priority > minPriority @@ -281,29 +288,34 @@ func invertPriority(p int64) int64 { } // activePriority callback returns actual priority of ppNodeInfo item in activeQueue -func activePriority(a interface{}, now mclock.AbsTime) int64 { +func activePriority(a interface{}) int64 { c := a.(*ppNodeInfo) if c.forced { return math.MinInt64 } if c.bias == 0 { - return invertPriority(c.nodePriority.Priority(now, c.capacity)) + return invertPriority(c.nodePriority.Priority(c.capacity)) + } else { + return invertPriority(c.nodePriority.EstimatePriority(c.capacity, 0, 0, c.bias, true)) } - return invertPriority(c.nodePriority.EstMinPriority(now+mclock.AbsTime(c.bias), c.capacity, true)) } // activeMaxPriority callback returns estimated maximum priority of ppNodeInfo item in activeQueue -func activeMaxPriority(a interface{}, until mclock.AbsTime) int64 { +func (pp *PriorityPool) activeMaxPriority(a interface{}, until mclock.AbsTime) int64 { c := a.(*ppNodeInfo) if c.forced { return math.MinInt64 } - return invertPriority(c.nodePriority.EstMinPriority(until+mclock.AbsTime(c.bias), c.capacity, false)) + future := time.Duration(until - pp.clock.Now()) + if future < 0 { + future = 0 + } + return invertPriority(c.nodePriority.EstimatePriority(c.capacity, 0, future, c.bias, false)) } // inactivePriority callback returns actual priority of ppNodeInfo item in inactiveQueue func (pp *PriorityPool) inactivePriority(p *ppNodeInfo) int64 { - return p.nodePriority.Priority(pp.clock.Now(), pp.minCap) + return p.nodePriority.Priority(pp.minCap) } // connectedNode is called when a new node has been added to the pool (InactiveFlag set) @@ -379,16 +391,19 @@ func (pp *PriorityPool) setCapacity(n *ppNodeInfo, cap uint64) { // enforceLimits enforces active node count and total capacity limits. It returns the // lowest active node priority. Note that this function is performed on the temporary // internal state. -func (pp *PriorityPool) enforceLimits() int64 { +func (pp *PriorityPool) enforceLimits() (*ppNodeInfo, int64) { if pp.activeCap <= pp.maxCap && pp.activeCount <= pp.maxCount { - return math.MinInt64 + return nil, math.MinInt64 } - var maxActivePriority int64 + var ( + c *ppNodeInfo + maxActivePriority int64 + ) pp.activeQueue.MultiPop(func(data interface{}, priority int64) bool { - c := data.(*ppNodeInfo) + c = data.(*ppNodeInfo) pp.markForChange(c) maxActivePriority = priority - if c.capacity == pp.minCap { + if c.capacity == pp.minCap || pp.activeCount > pp.maxCount { pp.setCapacity(c, 0) } else { sub := c.capacity / pp.capacityStepDiv @@ -400,7 +415,7 @@ func (pp *PriorityPool) enforceLimits() int64 { } return pp.activeCap > pp.maxCap || pp.activeCount > pp.maxCount }) - return invertPriority(maxActivePriority) + return c, invertPriority(maxActivePriority) } // finalizeChanges either commits or reverts temporary changes. The necessary capacity @@ -430,6 +445,9 @@ func (pp *PriorityPool) finalizeChanges(commit bool) (updates []capUpdate) { c.origCap = 0 } pp.changed = nil + if commit { + pp.ccUpdateForced = true + } return } @@ -472,6 +490,7 @@ func (pp *PriorityPool) tryActivate() []capUpdate { break } } + pp.ccUpdateForced = true return pp.finalizeChanges(commit) } @@ -500,3 +519,150 @@ func (pp *PriorityPool) updatePriority(node *enode.Node) { } updates = pp.tryActivate() } + +// CapacityCurve is a snapshot of the priority pool contents in a format that can efficiently +// estimate how much capacity could be granted to a given node at a given priority level. +type CapacityCurve struct { + points []curvePoint // curve points sorted in descending order of priority + index map[enode.ID][]int // curve point indexes belonging to each node + exclude []int // curve point indexes of excluded node + excludeFirst bool // true if activeCount == maxCount +} + +type curvePoint struct { + freeCap uint64 // available capacity and node count at the current priority level + nextPri int64 // next priority level where more capacity will be available +} + +// GetCapacityCurve returns a new or recently cached CapacityCurve based on the contents of the pool +func (pp *PriorityPool) GetCapacityCurve() *CapacityCurve { + pp.lock.Lock() + defer pp.lock.Unlock() + + now := pp.clock.Now() + dt := time.Duration(now - pp.ccUpdatedAt) + if !pp.ccUpdateForced && pp.cachedCurve != nil && dt < time.Second*10 { + return pp.cachedCurve + } + + pp.ccUpdateForced = false + pp.ccUpdatedAt = now + curve := &CapacityCurve{ + index: make(map[enode.ID][]int), + } + pp.cachedCurve = curve + + var excludeID enode.ID + excludeFirst := pp.maxCount == pp.activeCount + // reduce node capacities or remove nodes until nothing is left in the queue; + // record the available capacity and the necessary priority after each step + for pp.activeCap > 0 { + cp := curvePoint{} + if pp.activeCap > pp.maxCap { + log.Error("Active capacity is greater than allowed maximum", "active", pp.activeCap, "maximum", pp.maxCap) + } else { + cp.freeCap = pp.maxCap - pp.activeCap + } + // temporarily increase activeCap to enforce reducing or removing a node capacity + tempCap := cp.freeCap + 1 + pp.activeCap += tempCap + var next *ppNodeInfo + // enforceLimits removes the lowest priority node if it has minimal capacity, + // otherwise reduces its capacity + next, cp.nextPri = pp.enforceLimits() + pp.activeCap -= tempCap + if next == nil { + log.Error("GetCapacityCurve: cannot remove next element from the priority queue") + break + } + id := next.node.ID() + if excludeFirst { + // if the node count limit is already reached then mark the node with the + // lowest priority for exclusion + curve.excludeFirst = true + excludeID = id + excludeFirst = false + } + // multiple curve points and therefore multiple indexes may belong to a node + // if it was removed in multiple steps (if its capacity was more than the minimum) + curve.index[id] = append(curve.index[id], len(curve.points)) + curve.points = append(curve.points, cp) + } + // restore original state of the queue + pp.finalizeChanges(false) + curve.points = append(curve.points, curvePoint{ + freeCap: pp.maxCap, + nextPri: math.MaxInt64, + }) + if curve.excludeFirst { + curve.exclude = curve.index[excludeID] + } + return curve +} + +// Exclude returns a CapacityCurve with the given node excluded from the original curve +func (cc *CapacityCurve) Exclude(id enode.ID) *CapacityCurve { + if exclude, ok := cc.index[id]; ok { + // return a new version of the curve (only one excluded node can be selected) + // Note: if the first node was excluded by default (excludeFirst == true) then + // we can forget about that and exclude the node with the given id instead. + return &CapacityCurve{ + points: cc.points, + index: cc.index, + exclude: exclude, + } + } + return cc +} + +func (cc *CapacityCurve) getPoint(i int) curvePoint { + cp := cc.points[i] + if i == 0 && cc.excludeFirst { + cp.freeCap = 0 + return cp + } + for ii := len(cc.exclude) - 1; ii >= 0; ii-- { + ei := cc.exclude[ii] + if ei < i { + break + } + e1, e2 := cc.points[ei], cc.points[ei+1] + cp.freeCap += e2.freeCap - e1.freeCap + } + return cp +} + +// MaxCapacity calculates the maximum capacity available for a node with a given +// (monotonically decreasing) priority vs. capacity function. Note that if the requesting +// node is already in the pool then it should be excluded from the curve in order to get +// the correct result. +func (cc *CapacityCurve) MaxCapacity(priority func(cap uint64) int64) uint64 { + min, max := 0, len(cc.points)-1 // the curve always has at least one point + for min < max { + mid := (min + max) / 2 + cp := cc.getPoint(mid) + if cp.freeCap == 0 || priority(cp.freeCap) > cp.nextPri { + min = mid + 1 + } else { + max = mid + } + } + cp2 := cc.getPoint(min) + if cp2.freeCap == 0 || min == 0 { + return cp2.freeCap + } + cp1 := cc.getPoint(min - 1) + if priority(cp2.freeCap) > cp1.nextPri { + return cp2.freeCap + } + minc, maxc := cp1.freeCap, cp2.freeCap-1 + for minc < maxc { + midc := (minc + maxc + 1) / 2 + if midc == 0 || priority(midc) > cp1.nextPri { + minc = midc + } else { + maxc = midc - 1 + } + } + return maxc +} diff --git a/les/vflux/server/prioritypool_test.go b/les/vflux/server/prioritypool_test.go index cbb3f5b372..d83ddc1767 100644 --- a/les/vflux/server/prioritypool_test.go +++ b/les/vflux/server/prioritypool_test.go @@ -20,6 +20,7 @@ import ( "math/rand" "reflect" "testing" + "time" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/p2p/enode" @@ -42,6 +43,7 @@ func init() { const ( testCapacityStepDiv = 100 testCapacityToleranceDiv = 10 + testMinCap = 100 ) type ppTestClient struct { @@ -49,11 +51,11 @@ type ppTestClient struct { balance, cap uint64 } -func (c *ppTestClient) Priority(now mclock.AbsTime, cap uint64) int64 { +func (c *ppTestClient) Priority(cap uint64) int64 { return int64(c.balance / cap) } -func (c *ppTestClient) EstMinPriority(until mclock.AbsTime, cap uint64, update bool) int64 { +func (c *ppTestClient) EstimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 { return int64(c.balance / cap) } @@ -67,7 +69,7 @@ func TestPriorityPool(t *testing.T) { c.cap = newValue.(uint64) } }) - pp := NewPriorityPool(ns, ppTestSetup, clock, 100, 0, testCapacityStepDiv) + pp := NewPriorityPool(ns, ppTestSetup, clock, testMinCap, 0, testCapacityStepDiv) ns.Start() pp.SetLimits(100, 1000000) clients := make([]*ppTestClient, 100) @@ -94,7 +96,7 @@ func TestPriorityPool(t *testing.T) { for i := range clients { c := &ppTestClient{ node: enode.SignNull(&enr.Record{}, enode.ID{byte(i)}), - balance: 1000000000, + balance: 100000000000, cap: 1000, } sumBalance += c.balance @@ -109,7 +111,7 @@ func TestPriorityPool(t *testing.T) { for count := 0; count < 100; count++ { c := clients[rand.Intn(len(clients))] oldBalance := c.balance - c.balance = uint64(rand.Int63n(1000000000) + 1000000000) + c.balance = uint64(rand.Int63n(100000000000) + 100000000000) sumBalance += c.balance - oldBalance pp.ns.SetState(c.node, ppUpdateFlag, nodestate.Flags{}, 0) pp.ns.SetState(c.node, nodestate.Flags{}, ppUpdateFlag, 0) @@ -120,10 +122,124 @@ func TestPriorityPool(t *testing.T) { raise(c) } } + // check whether capacities are proportional to balances for _, c := range clients { check(c) } + if count%10 == 0 { + // test available capacity calculation with capacity curve + c = clients[rand.Intn(len(clients))] + curve := pp.GetCapacityCurve().Exclude(c.node.ID()) + + add := uint64(rand.Int63n(10000000000000)) + c.balance += add + sumBalance += add + expCap := curve.MaxCapacity(func(cap uint64) int64 { + return int64(c.balance / cap) + }) + //fmt.Println(expCap, c.balance, sumBalance) + /*for i, cp := range curve.points { + fmt.Println("cp", i, cp, "ex", curve.getPoint(i)) + }*/ + var ok bool + expFail := expCap + 1 + if expFail < testMinCap { + expFail = testMinCap + } + ns.Operation(func() { + _, ok = pp.RequestCapacity(c.node, expFail, 0, true) + }) + if ok { + t.Errorf("Request for more than expected available capacity succeeded") + } + if expCap >= testMinCap { + ns.Operation(func() { + _, ok = pp.RequestCapacity(c.node, expCap, 0, true) + }) + if !ok { + t.Errorf("Request for expected available capacity failed") + } + } + c.balance -= add + sumBalance -= add + pp.ns.SetState(c.node, ppUpdateFlag, nodestate.Flags{}, 0) + pp.ns.SetState(c.node, nodestate.Flags{}, ppUpdateFlag, 0) + for _, c := range clients { + raise(c) + } + } } ns.Stop() } + +func TestCapacityCurve(t *testing.T) { + clock := &mclock.Simulated{} + ns := nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) + pp := NewPriorityPool(ns, ppTestSetup, clock, 400000, 0, 2) + ns.Start() + pp.SetLimits(10, 10000000) + clients := make([]*ppTestClient, 10) + + for i := range clients { + c := &ppTestClient{ + node: enode.SignNull(&enr.Record{}, enode.ID{byte(i)}), + balance: 100000000000 * uint64(i+1), + cap: 1000000, + } + clients[i] = c + ns.SetState(c.node, ppTestClientFlag, nodestate.Flags{}, 0) + ns.SetField(c.node, ppTestSetup.priorityField, c) + ns.SetState(c.node, ppTestSetup.InactiveFlag, nodestate.Flags{}, 0) + ns.Operation(func() { + pp.RequestCapacity(c.node, c.cap, 0, true) + }) + } + + curve := pp.GetCapacityCurve() + check := func(balance, expCap uint64) { + cap := curve.MaxCapacity(func(cap uint64) int64 { + return int64(balance / cap) + }) + var fail bool + if cap == 0 || expCap == 0 { + fail = cap != expCap + } else { + pri := balance / cap + expPri := balance / expCap + fail = pri != expPri && pri != expPri+1 + } + if fail { + t.Errorf("Incorrect capacity for %d balance (got %d, expected %d)", balance, cap, expCap) + } + } + + check(0, 0) + check(10000000000, 100000) + check(50000000000, 500000) + check(100000000000, 1000000) + check(200000000000, 1000000) + check(300000000000, 1500000) + check(450000000000, 1500000) + check(600000000000, 2000000) + check(800000000000, 2000000) + check(1000000000000, 2500000) + + pp.SetLimits(11, 10000000) + curve = pp.GetCapacityCurve() + + check(0, 0) + check(10000000000, 100000) + check(50000000000, 500000) + check(150000000000, 750000) + check(200000000000, 1000000) + check(220000000000, 1100000) + check(275000000000, 1100000) + check(375000000000, 1500000) + check(450000000000, 1500000) + check(600000000000, 2000000) + check(800000000000, 2000000) + check(1000000000000, 2500000) + + ns.Stop() +} diff --git a/les/vflux/server/service.go b/les/vflux/server/service.go new file mode 100644 index 0000000000..ab759ae441 --- /dev/null +++ b/les/vflux/server/service.go @@ -0,0 +1,122 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "net" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/les/vflux" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" +) + +type ( + // Server serves vflux requests + Server struct { + limiter *utils.Limiter + lock sync.Mutex + services map[string]*serviceEntry + delayPerRequest time.Duration + } + + // Service is a service registered at the Server and identified by a string id + Service interface { + ServiceInfo() (id, desc string) // only called during registration + Handle(id enode.ID, address string, name string, data []byte) []byte // never called concurrently + } + + serviceEntry struct { + id, desc string + backend Service + } +) + +// NewServer creates a new Server +func NewServer(delayPerRequest time.Duration) *Server { + return &Server{ + limiter: utils.NewLimiter(1000), + delayPerRequest: delayPerRequest, + services: make(map[string]*serviceEntry), + } +} + +// Register registers a Service +func (s *Server) Register(b Service) { + srv := &serviceEntry{backend: b} + srv.id, srv.desc = b.ServiceInfo() + if strings.Contains(srv.id, ":") { + // srv.id + ":" will be used as a service database prefix + log.Error("Service ID contains ':'", "id", srv.id) + return + } + s.lock.Lock() + s.services[srv.id] = srv + s.lock.Unlock() +} + +// Serve serves a vflux request batch +// Note: requests are served by the Handle functions of the registered services. Serve +// may be called concurrently but the Handle functions are called sequentially and +// therefore thread safety is guaranteed. +func (s *Server) Serve(id enode.ID, address string, requests vflux.Requests) vflux.Replies { + reqLen := uint(len(requests)) + if reqLen == 0 || reqLen > vflux.MaxRequestLength { + return nil + } + // Note: the value parameter will be supplied by the token sale module (total amount paid) + ch := <-s.limiter.Add(id, address, 0, reqLen) + if ch == nil { + return nil + } + // Note: the limiter ensures that the following section is not running concurrently, + // the lock only protects against contention caused by new service registration + s.lock.Lock() + results := make(vflux.Replies, len(requests)) + for i, req := range requests { + if service := s.services[req.Service]; service != nil { + results[i] = service.backend.Handle(id, address, req.Name, req.Params) + } + } + s.lock.Unlock() + time.Sleep(s.delayPerRequest * time.Duration(reqLen)) + close(ch) + return results +} + +// ServeEncoded serves an encoded vflux request batch and returns the encoded replies +func (s *Server) ServeEncoded(id enode.ID, addr *net.UDPAddr, req []byte) []byte { + var requests vflux.Requests + if err := rlp.DecodeBytes(req, &requests); err != nil { + return nil + } + results := s.Serve(id, addr.String(), requests) + if results == nil { + return nil + } + res, _ := rlp.EncodeToBytes(&results) + return res +} + +// Stop shuts down the server +func (s *Server) Stop() { + s.limiter.Stop() +} diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go index 9dd2b31733..eb01d95e93 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -74,7 +74,7 @@ type UDPv5 struct { // talkreq handler registry trlock sync.Mutex - trhandlers map[string]func([]byte) []byte + trhandlers map[string]TalkRequestHandler // channels into dispatch packetInCh chan ReadPacket @@ -96,6 +96,9 @@ type UDPv5 struct { wg sync.WaitGroup } +// TalkRequestHandler callback processes a talk request and optionally returns a reply +type TalkRequestHandler func(enode.ID, *net.UDPAddr, []byte) []byte + // callV5 represents a remote procedure call against another node. type callV5 struct { node *enode.Node @@ -145,7 +148,7 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { log: cfg.Log, validSchemes: cfg.ValidSchemes, clock: cfg.Clock, - trhandlers: make(map[string]func([]byte) []byte), + trhandlers: make(map[string]TalkRequestHandler), // channels into dispatch packetInCh: make(chan ReadPacket, 1), readNextCh: make(chan struct{}, 1), @@ -233,7 +236,7 @@ func (t *UDPv5) LocalNode() *enode.LocalNode { // RegisterTalkHandler adds a handler for 'talk requests'. The handler function is called // whenever a request for the given protocol is received and should return the response // data or nil. -func (t *UDPv5) RegisterTalkHandler(protocol string, handler func([]byte) []byte) { +func (t *UDPv5) RegisterTalkHandler(protocol string, handler TalkRequestHandler) { t.trlock.Lock() defer t.trlock.Unlock() t.trhandlers[protocol] = handler @@ -841,7 +844,7 @@ func (t *UDPv5) handleTalkRequest(p *v5wire.TalkRequest, fromID enode.ID, fromAd var response []byte if handler != nil { - response = handler(p.Message) + response = handler(fromID, fromAddr, p.Message) } resp := &v5wire.TalkResponse{ReqID: p.ReqID, Message: response} t.sendResponse(fromID, fromAddr, resp) diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go index d91a2097db..292785bd51 100644 --- a/p2p/discover/v5_udp_test.go +++ b/p2p/discover/v5_udp_test.go @@ -435,7 +435,7 @@ func TestUDPv5_talkHandling(t *testing.T) { defer test.close() var recvMessage []byte - test.udp.RegisterTalkHandler("test", func(message []byte) []byte { + test.udp.RegisterTalkHandler("test", func(id enode.ID, addr *net.UDPAddr, message []byte) []byte { recvMessage = message return []byte("test response") }) diff --git a/p2p/nodestate/nodestate.go b/p2p/nodestate/nodestate.go index def93bac43..d3166f1d87 100644 --- a/p2p/nodestate/nodestate.go +++ b/p2p/nodestate/nodestate.go @@ -599,6 +599,7 @@ func (ns *NodeStateMachine) updateEnode(n *enode.Node) (enode.ID, *nodeInfo) { node := ns.nodes[id] if node != nil && n.Seq() > node.node.Seq() { node.node = n + node.dirty = true } return id, node } From 19d7a37abb9f3b9bf1a94baf6bd8c7d5042e54f8 Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 1 Mar 2021 17:26:10 +0800 Subject: [PATCH 227/235] core/rawdb: fix the transaction indexer (#22395) --- core/rawdb/chain_iterator.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index 393b72c26c..862a549540 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -243,13 +243,13 @@ func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan } } } - // If there exists uncommitted data, flush them. - if batch.ValueSize() > 0 { - WriteTxIndexTail(batch, lastNum) // Also write the tail there - if err := batch.Write(); err != nil { - log.Crit("Failed writing batch to db", "error", err) - return - } + // Flush the new indexing tail and the last committed data. It can also happen + // that the last batch is empty because nothing to index, but the tail has to + // be flushed anyway. + WriteTxIndexTail(batch, lastNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return } select { case <-interrupt: @@ -334,13 +334,13 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch } } } - // Commit the last batch if there exists uncommitted data - if batch.ValueSize() > 0 { - WriteTxIndexTail(batch, nextNum) - if err := batch.Write(); err != nil { - log.Crit("Failed writing batch to db", "error", err) - return - } + // Flush the new indexing tail and the last committed data. It can also happen + // that the last batch is empty because nothing to unindex, but the tail has to + // be flushed anyway. + WriteTxIndexTail(batch, nextNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return } select { case <-interrupt: From 0540d3c6f60d1cba6a3dd384790f5d1fa0d799bd Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 3 Mar 2021 08:42:59 +0100 Subject: [PATCH 228/235] cmd/geth: put allowUnsecureTx flag in RPC section (#22412) --- cmd/geth/usage.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 6935adabc7..2c3cdf1945 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -152,6 +152,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.GraphQLVirtualHostsFlag, utils.RPCGlobalGasCapFlag, utils.RPCGlobalTxFeeCapFlag, + utils.AllowUnprotectedTxs, utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag, From c539a052bd5a31dfaeabf65d789b691f5d03f300 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 3 Mar 2021 18:04:25 +0800 Subject: [PATCH 229/235] params: update chts (#22418) --- params/config.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/params/config.go b/params/config.go index 8cbfffc03c..8f8b36b57d 100644 --- a/params/config.go +++ b/params/config.go @@ -74,10 +74,10 @@ var ( // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 345, - SectionHead: common.HexToHash("0x5453bab878704adebc934b41fd214a07ea7a72b8572ff088dca7f7956cd0ef28"), - CHTRoot: common.HexToHash("0x7693d432595846c094f47cb37f5c868b0b7b1968fc6b0fc411ded1345fdaffab"), - BloomRoot: common.HexToHash("0x8b0e7895bc39840d8dac857e26bdf3d0a07684b0b962b252546659e0337a9f70"), + SectionIndex: 364, + SectionHead: common.HexToHash("0x3fd20ff221f5e962bb66f57a61973bfc2ba959879a6509384a80a45d208b5afc"), + CHTRoot: common.HexToHash("0xe35b3b807f4e9427fb4e2929961c78a9dc10f503a538319031cc7d00946a0591"), + BloomRoot: common.HexToHash("0x340553b378b2db214b898be15c80ac5be7caffc2e6448fd6f7aff23290d89296"), } // MainnetCheckpointOracle contains a set of configs for the main network oracle. @@ -157,10 +157,10 @@ var ( // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. RinkebyTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 232, - SectionHead: common.HexToHash("0x8170fca4039b11a008c11f9996ff112151cbb17411437bb2f86288e11158b2f0"), - CHTRoot: common.HexToHash("0x4526560d92ae1b3a6d3ee780c3ad289ba2bbf1b5da58d9ea107f2f26412b631f"), - BloomRoot: common.HexToHash("0x82a889098a35d6a21ea8894d35a1db69b94bad61b988bbe5ae4601437320e331"), + SectionIndex: 248, + SectionHead: common.HexToHash("0x26874cf023695778cc3175d1bec19894204d8d0b756b587e81e35f300dc5b33c"), + CHTRoot: common.HexToHash("0xc129d1ed6673c5d3e1068e9d97244e72952b7ca08acbd7b3bfa58bc3085c442c"), + BloomRoot: common.HexToHash("0x1dafe79dcd7d348782aa834a4a4397890d9ad90643736791132ed5c16879a037"), } // RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle. @@ -198,10 +198,10 @@ var ( // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 116, - SectionHead: common.HexToHash("0xf2d200f636f213c9c7bb4e747ff564813da7708253037103aef3d8be5203c5e1"), - CHTRoot: common.HexToHash("0xb0ac83e2ccf6c2776945e099c4e3df50fe6200499c8b2045c34cafdf57d15087"), - BloomRoot: common.HexToHash("0xfb580ad1c611230a4bfc56534f58bcb156d028bc6ce70e35403dc019c7c02d90"), + SectionIndex: 132, + SectionHead: common.HexToHash("0x29fa240c97b47ecbfef3fea8b3cff035d93154d1d48b25e3333cf2f7067c5324"), + CHTRoot: common.HexToHash("0x85e5c59e5b202284291405dadc40dc36ab6417bd189fb18be24f6dcab6b80511"), + BloomRoot: common.HexToHash("0x0b7afdd200477f46e982e2cabc822ac454424986fa50d899685dfaeede1f882d"), } // GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle. From 07e907c7d4ce01fff663aa7b5a378f647518996f Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 3 Mar 2021 18:04:50 +0800 Subject: [PATCH 230/235] cmd/utils: fix txlookuplimit for archive node (#22419) * cmd/utils: fix exclusive check for archive node * cmd/utils: set the txlookuplimit to 0 --- cmd/utils/flags.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 324a6d6a47..7bbca42654 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1477,8 +1477,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag) CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer - CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) - if ctx.GlobalIsSet(LightServeFlag.Name) && ctx.GlobalIsSet(TxLookupLimitFlag.Name) { + if ctx.GlobalString(GCModeFlag.Name) == "archive" && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 { + ctx.GlobalSet(TxLookupLimitFlag.Name, "0") + log.Warn("Disable transaction unindexing for archive node") + } + if ctx.GlobalIsSet(LightServeFlag.Name) && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 { log.Warn("LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited") } var ks *keystore.KeyStore From ba999105ef89473cfe39e5e53354f7099e67a290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 3 Mar 2021 12:05:27 +0200 Subject: [PATCH 231/235] core/forkid, params: unset Berlin fork number (#22413) --- core/forkid/forkid_test.go | 58 ++++++++++++++++---------------------- params/config.go | 4 --- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index a20598fa9d..87d64ed67f 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -43,26 +43,24 @@ func TestCreation(t *testing.T) { params.MainnetChainConfig, params.MainnetGenesisHash, []testcase{ - {0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced - {1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block - {1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block - {1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block - {1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block - {2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block - {2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block - {2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block - {2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block - {4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block - {4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block - {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block - {7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block - {9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block - {9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block - {9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block - {9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block - {12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block - {12244000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // First Berlin block - {20000000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // Future Berlin block + {0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced + {1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block + {1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block + {1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block + {1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block + {2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block + {2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block + {2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block + {2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block + {4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block + {4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block + {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block + {7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block + {9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block + {9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block + {9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block + {9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 0}}, // First Muir Glacier block + {12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 0}}, // Future Muir Glacier block }, }, // Ropsten test cases @@ -82,10 +80,8 @@ func TestCreation(t *testing.T) { {6485845, ID{Hash: checksumToBytes(0xd6e2149b), Next: 6485846}}, // Last Petersburg block {6485846, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // First Istanbul block {7117116, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // Last Istanbul block - {7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // First Muir Glacier block - {9812188, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // Last Muir Glacier block - {9812189, ID{Hash: checksumToBytes(0xa157d377), Next: 0}}, // First Berlin block - {10000000, ID{Hash: checksumToBytes(0xa157d377), Next: 0}}, // Future Berlin block + {7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}}, // First Muir Glacier block + {9812188, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}}, // Future Muir Glacier block }, }, // Rinkeby test cases @@ -104,10 +100,8 @@ func TestCreation(t *testing.T) { {4321233, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // Last Constantinople block {4321234, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // First Petersburg block {5435344, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // Last Petersburg block - {5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // First Istanbul block - {8290927, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // Last Istanbul block - {8290928, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}}, // First Berlin block - {10000000, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}}, // Future Berlin block + {5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}}, // First Istanbul block + {8290927, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}}, // Future Istanbul block }, }, // Goerli test cases @@ -117,10 +111,8 @@ func TestCreation(t *testing.T) { []testcase{ {0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block {1561650, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Last Petersburg block - {1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // First Istanbul block - {4460643, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // Last Istanbul block - {4460644, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}}, // First Berlin block - {5000000, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}}, // Future Berlin block + {1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}}, // First Istanbul block + {4460643, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}}, // Future Istanbul block }, }, } @@ -193,7 +185,7 @@ func TestValidation(t *testing.T) { // Local is mainnet Petersburg, remote is Rinkeby Petersburg. {7987396, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale}, - // Local is mainnet Berlin, far in the future. Remote announces Gopherium (non existing fork) + // Local is mainnet Istanbul, far in the future. Remote announces Gopherium (non existing fork) // at some future block 88888888, for itself, but past block for local. Local is incompatible. // // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). diff --git a/params/config.go b/params/config.go index 8f8b36b57d..9b8c5cf4ed 100644 --- a/params/config.go +++ b/params/config.go @@ -68,7 +68,6 @@ var ( PetersburgBlock: big.NewInt(7_280_000), IstanbulBlock: big.NewInt(9_069_000), MuirGlacierBlock: big.NewInt(9_200_000), - BerlinBlock: big.NewInt(12_244_000), Ethash: new(EthashConfig), } @@ -108,7 +107,6 @@ var ( PetersburgBlock: big.NewInt(4_939_394), IstanbulBlock: big.NewInt(6_485_846), MuirGlacierBlock: big.NewInt(7_117_117), - BerlinBlock: big.NewInt(9_812_189), Ethash: new(EthashConfig), } @@ -148,7 +146,6 @@ var ( PetersburgBlock: big.NewInt(4_321_234), IstanbulBlock: big.NewInt(5_435_345), MuirGlacierBlock: nil, - BerlinBlock: big.NewInt(8_290_928), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -189,7 +186,6 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(1_561_651), MuirGlacierBlock: nil, - BerlinBlock: big.NewInt(4_460_644), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, From b24804d88cdbd38edc85ee9f2afaa9e6cb7a767e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Wed, 3 Mar 2021 15:05:24 +0100 Subject: [PATCH 232/235] les: fix nodiscover option on the client side (#22422) --- les/client.go | 12 +++++++++++- les/vflux/client/serverpool.go | 6 +++--- les/vflux/client/serverpool_test.go | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/les/client.go b/les/client.go index ecabfdf503..605c4d03ca 100644 --- a/les/client.go +++ b/les/client.go @@ -115,7 +115,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { p2pConfig: &stack.Config().P2P, } - leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, leth.prenegQuery, &mclock.System{}, config.UltraLightServers, requestList) + var prenegQuery vfc.QueryFunc + if leth.p2pServer.DiscV5 != nil { + prenegQuery = leth.prenegQuery + } + leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, prenegQuery, &mclock.System{}, config.UltraLightServers, requestList) leth.serverPool.AddMetrics(suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge, sessionValueMeter, serverDialedMeter) leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.GetTimeout) @@ -194,6 +198,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { // VfluxRequest sends a batch of requests to the given node through discv5 UDP TalkRequest and returns the responses func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.Replies { + if s.p2pServer.DiscV5 == nil { + return nil + } reqsEnc, _ := rlp.EncodeToBytes(&reqs) repliesEnc, _ := s.p2pServer.DiscV5.TalkRequest(s.serverPool.DialNode(n), "vfx", reqsEnc) var replies vflux.Replies @@ -208,6 +215,9 @@ func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.R func (s *LightEthereum) vfxVersion(n *enode.Node) uint { if n.Seq() == 0 { var err error + if s.p2pServer.DiscV5 == nil { + return 0 + } if n, err = s.p2pServer.DiscV5.RequestENR(n); n != nil && err == nil && n.Seq() != 0 { s.serverPool.Persist(n) } else { diff --git a/les/vflux/client/serverpool.go b/les/vflux/client/serverpool.go index 47ec4fee74..e73b277ced 100644 --- a/les/vflux/client/serverpool.go +++ b/les/vflux/client/serverpool.go @@ -91,7 +91,7 @@ type nodeHistoryEnc struct { // queryFunc sends a pre-negotiation query and blocks until a response arrives or timeout occurs. // It returns 1 if the remote node has confirmed that connection is possible, 0 if not // possible and -1 if no response arrived (timeout). -type queryFunc func(*enode.Node) int +type QueryFunc func(*enode.Node) int var ( clientSetup = &nodestate.Setup{Version: 2} @@ -150,7 +150,7 @@ var ( ) // NewServerPool creates a new server pool -func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) { +func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query QueryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) { s := &ServerPool{ db: db, clock: clock, @@ -246,7 +246,7 @@ func (s *ServerPool) AddSource(source enode.Iterator) { // addPreNegFilter installs a node filter mechanism that performs a pre-negotiation query. // Nodes that are filtered out and does not appear on the output iterator are put back // into redialWait state. -func (s *ServerPool) addPreNegFilter(input enode.Iterator, query queryFunc) enode.Iterator { +func (s *ServerPool) addPreNegFilter(input enode.Iterator, query QueryFunc) enode.Iterator { s.fillSet = NewFillSet(s.ns, input, sfQueried) s.ns.SubscribeState(sfQueried, func(n *enode.Node, oldState, newState nodestate.Flags) { if newState.Equals(sfQueried) { diff --git a/les/vflux/client/serverpool_test.go b/les/vflux/client/serverpool_test.go index ee299618c6..c777d6c16d 100644 --- a/les/vflux/client/serverpool_test.go +++ b/les/vflux/client/serverpool_test.go @@ -107,7 +107,7 @@ func (s *ServerPoolTest) addTrusted(i int) { } func (s *ServerPoolTest) start() { - var testQuery queryFunc + var testQuery QueryFunc if s.preNeg { testQuery = func(node *enode.Node) int { idx := testNodeIndex(node.ID()) From 5a81dd97d5f3347457e640631564fa5b893720c2 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 3 Mar 2021 22:08:14 +0800 Subject: [PATCH 233/235] cmd: retire whisper flags (#22421) * cmd: retire whisper flags * cmd/geth: remove whisper configs --- cmd/geth/config.go | 31 ++----------------------------- cmd/geth/consolecmd.go | 2 +- cmd/geth/main.go | 8 -------- cmd/geth/usage.go | 4 ---- cmd/utils/flags.go | 28 ---------------------------- 5 files changed, 3 insertions(+), 70 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index c77c04c252..6fc75363c6 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -30,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" @@ -43,7 +42,7 @@ var ( Name: "dumpconfig", Usage: "Show configuration values", ArgsUsage: "", - Flags: append(append(nodeFlags, rpcFlags...), whisperFlags...), + Flags: append(nodeFlags, rpcFlags...), Category: "MISCELLANEOUS COMMANDS", Description: `The dumpconfig command shows configuration values.`, } @@ -75,19 +74,8 @@ type ethstatsConfig struct { URL string `toml:",omitempty"` } -// whisper has been deprecated, but clients out there might still have [Shh] -// in their config, which will crash. Cut them some slack by keeping the -// config, and displaying a message that those config switches are ineffectual. -// To be removed circa Q1 2021 -- @gballet. -type whisperDeprecatedConfig struct { - MaxMessageSize uint32 `toml:",omitempty"` - MinimumAcceptedPOW float64 `toml:",omitempty"` - RestrictConnectionBetweenLightClients bool `toml:",omitempty"` -} - type gethConfig struct { Eth ethconfig.Config - Shh whisperDeprecatedConfig Node node.Config Ethstats ethstatsConfig Metrics metrics.Config @@ -132,11 +120,8 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { if err := loadConfig(file, &cfg); err != nil { utils.Fatalf("%v", err) } - - if cfg.Shh != (whisperDeprecatedConfig{}) { - log.Warn("Deprecated whisper config detected. Whisper has been moved to github.com/ethereum/whisper") - } } + // Apply flags. utils.SetNodeConfig(ctx, &cfg.Node) stack, err := node.New(&cfg.Node) @@ -147,22 +132,11 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) } - utils.SetShhConfig(ctx, stack) - applyMetricConfig(ctx, &cfg) return stack, cfg } -// enableWhisper returns true in case one of the whisper flags is set. -func checkWhisper(ctx *cli.Context) { - for _, flag := range whisperFlags { - if ctx.GlobalIsSet(flag.GetName()) { - log.Warn("deprecated whisper flag detected. Whisper has been moved to github.com/ethereum/whisper") - } - } -} - // makeFullNode loads geth configuration and creates the Ethereum backend. func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { stack, cfg := makeConfigNode(ctx) @@ -171,7 +145,6 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { } backend := utils.RegisterEthService(stack, &cfg.Eth) - checkWhisper(ctx) // Configure GraphQL if requested if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { utils.RegisterGraphQLService(stack, backend, cfg.Node) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index e26481003d..9d8794eb15 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -36,7 +36,7 @@ var ( Action: utils.MigrateFlags(localConsole), Name: "console", Usage: "Start an interactive JavaScript environment", - Flags: append(append(append(nodeFlags, rpcFlags...), consoleFlags...), whisperFlags...), + Flags: append(append(nodeFlags, rpcFlags...), consoleFlags...), Category: "CONSOLE COMMANDS", Description: ` The Geth console is an interactive shell for the JavaScript runtime environment diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 9afa031584..207c93c0d8 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -186,13 +186,6 @@ var ( utils.AllowUnprotectedTxs, } - whisperFlags = []cli.Flag{ - utils.WhisperEnabledFlag, - utils.WhisperMaxMessageSizeFlag, - utils.WhisperMinPOWFlag, - utils.WhisperRestrictConnectionBetweenLightClientsFlag, - } - metricsFlags = []cli.Flag{ utils.MetricsEnabledFlag, utils.MetricsEnabledExpensiveFlag, @@ -251,7 +244,6 @@ func init() { app.Flags = append(app.Flags, rpcFlags...) app.Flags = append(app.Flags, consoleFlags...) app.Flags = append(app.Flags, debug.Flags...) - app.Flags = append(app.Flags, whisperFlags...) app.Flags = append(app.Flags, metricsFlags...) app.Before = func(ctx *cli.Context) error { diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 2c3cdf1945..daea0afc4e 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -216,10 +216,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{ Name: "METRICS AND STATS", Flags: metricsFlags, }, - { - Name: "WHISPER (deprecated)", - Flags: whisperFlags, - }, { Name: "ALIASED (deprecated)", Flags: []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 7bbca42654..fc479f3987 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -680,24 +680,6 @@ var ( Usage: "Maximum gas price will be recommended by gpo", Value: ethconfig.Defaults.GPO.MaxPrice.Int64(), } - WhisperEnabledFlag = cli.BoolFlag{ - Name: "shh", - Usage: "Enable Whisper", - } - WhisperMaxMessageSizeFlag = cli.IntFlag{ - Name: "shh.maxmessagesize", - Usage: "Max message size accepted", - Value: 1024 * 1024, - } - WhisperMinPOWFlag = cli.Float64Flag{ - Name: "shh.pow", - Usage: "Minimum POW accepted", - Value: 0.2, - } - WhisperRestrictConnectionBetweenLightClientsFlag = cli.BoolFlag{ - Name: "shh.restrict-light", - Usage: "Restrict connection between two whisper light clients", - } // Metrics flags MetricsEnabledFlag = cli.BoolFlag{ @@ -1461,16 +1443,6 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) { } } -// SetShhConfig applies shh-related command line flags to the config. -func SetShhConfig(ctx *cli.Context, stack *node.Node) { - if ctx.GlobalIsSet(WhisperEnabledFlag.Name) || - ctx.GlobalIsSet(WhisperMaxMessageSizeFlag.Name) || - ctx.GlobalIsSet(WhisperMinPOWFlag.Name) || - ctx.GlobalIsSet(WhisperRestrictConnectionBetweenLightClientsFlag.Name) { - log.Warn("Whisper support has been deprecated and the code has been moved to github.com/ethereum/whisper") - } -} - // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags From cd316d7c7158f1ffc99910c8c07b951ec05ed067 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 3 Mar 2021 15:50:07 +0100 Subject: [PATCH 234/235] tests: update to latest tests (#22290) This updates the consensus tests to commit 31d6630 and adds support for access list transactions in the test runner. Co-authored-by: Martin Holst Swende --- tests/block_test.go | 14 +++++--------- tests/gen_stenv.go | 2 ++ tests/gen_sttransaction.go | 37 +++++++++++++++++++++++-------------- tests/gen_vmexec.go | 2 ++ tests/state_test.go | 8 -------- tests/state_test_util.go | 22 +++++++++++++--------- tests/testdata | 2 +- 7 files changed, 46 insertions(+), 41 deletions(-) diff --git a/tests/block_test.go b/tests/block_test.go index 84618fd0be..2649bae85a 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -25,7 +25,11 @@ func TestBlockchain(t *testing.T) { bt := new(testMatcher) // General state tests are 'exported' as blockchain tests, but we can run them natively. - bt.skipLoad(`^GeneralStateTests/`) + // For speedier CI-runs, the line below can be uncommented, so those are skipped. + // For now, in hardfork-times (Berlin), we run the tests both as StateTests and + // as blockchain tests, since the latter also covers things like receipt root + //bt.skipLoad(`^GeneralStateTests/`) + // Skip random failures due to selfish mining test bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`) @@ -43,15 +47,7 @@ func TestBlockchain(t *testing.T) { // test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range, // using 4.6 TGas bt.skipLoad(`.*randomStatetest94.json.*`) - bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { - if test.json.Network == "Berlin" { - // Our current berlin-tests were generated using YOLOv2 rules, hence a lot of them - // fail when berlin is defined as YOLOv3. We skip those, until they've been - // regenerated and re-imported - // TODO (@holiman) - return - } if err := bt.checkFailure(t, name+"/trie", test.Run(false)); err != nil { t.Errorf("test without snapshotter failed: %v", err) } diff --git a/tests/gen_stenv.go b/tests/gen_stenv.go index 1d4baf2fd7..bfecc145b4 100644 --- a/tests/gen_stenv.go +++ b/tests/gen_stenv.go @@ -13,6 +13,7 @@ import ( var _ = (*stEnvMarshaling)(nil) +// MarshalJSON marshals as JSON. func (s stEnv) MarshalJSON() ([]byte, error) { type stEnv struct { Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` @@ -30,6 +31,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) { return json.Marshal(&enc) } +// UnmarshalJSON unmarshals from JSON. func (s *stEnv) UnmarshalJSON(input []byte) error { type stEnv struct { Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` diff --git a/tests/gen_sttransaction.go b/tests/gen_sttransaction.go index 451ffcbf43..2670f4f9c8 100644 --- a/tests/gen_sttransaction.go +++ b/tests/gen_sttransaction.go @@ -8,25 +8,29 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" ) var _ = (*stTransactionMarshaling)(nil) +// MarshalJSON marshals as JSON. func (s stTransaction) MarshalJSON() ([]byte, error) { type stTransaction struct { - GasPrice *math.HexOrDecimal256 `json:"gasPrice"` - Nonce math.HexOrDecimal64 `json:"nonce"` - To string `json:"to"` - Data []string `json:"data"` - GasLimit []math.HexOrDecimal64 `json:"gasLimit"` - Value []string `json:"value"` - PrivateKey hexutil.Bytes `json:"secretKey"` + GasPrice *math.HexOrDecimal256 `json:"gasPrice"` + Nonce math.HexOrDecimal64 `json:"nonce"` + To string `json:"to"` + Data []string `json:"data"` + AccessLists []*types.AccessList `json:"accessLists,omitempty"` + GasLimit []math.HexOrDecimal64 `json:"gasLimit"` + Value []string `json:"value"` + PrivateKey hexutil.Bytes `json:"secretKey"` } var enc stTransaction enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice) enc.Nonce = math.HexOrDecimal64(s.Nonce) enc.To = s.To enc.Data = s.Data + enc.AccessLists = s.AccessLists if s.GasLimit != nil { enc.GasLimit = make([]math.HexOrDecimal64, len(s.GasLimit)) for k, v := range s.GasLimit { @@ -38,15 +42,17 @@ func (s stTransaction) MarshalJSON() ([]byte, error) { return json.Marshal(&enc) } +// UnmarshalJSON unmarshals from JSON. func (s *stTransaction) UnmarshalJSON(input []byte) error { type stTransaction struct { - GasPrice *math.HexOrDecimal256 `json:"gasPrice"` - Nonce *math.HexOrDecimal64 `json:"nonce"` - To *string `json:"to"` - Data []string `json:"data"` - GasLimit []math.HexOrDecimal64 `json:"gasLimit"` - Value []string `json:"value"` - PrivateKey *hexutil.Bytes `json:"secretKey"` + GasPrice *math.HexOrDecimal256 `json:"gasPrice"` + Nonce *math.HexOrDecimal64 `json:"nonce"` + To *string `json:"to"` + Data []string `json:"data"` + AccessLists []*types.AccessList `json:"accessLists,omitempty"` + GasLimit []math.HexOrDecimal64 `json:"gasLimit"` + Value []string `json:"value"` + PrivateKey *hexutil.Bytes `json:"secretKey"` } var dec stTransaction if err := json.Unmarshal(input, &dec); err != nil { @@ -64,6 +70,9 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error { if dec.Data != nil { s.Data = dec.Data } + if dec.AccessLists != nil { + s.AccessLists = dec.AccessLists + } if dec.GasLimit != nil { s.GasLimit = make([]uint64, len(dec.GasLimit)) for k, v := range dec.GasLimit { diff --git a/tests/gen_vmexec.go b/tests/gen_vmexec.go index a5f01cf456..2fe155152d 100644 --- a/tests/gen_vmexec.go +++ b/tests/gen_vmexec.go @@ -14,6 +14,7 @@ import ( var _ = (*vmExecMarshaling)(nil) +// MarshalJSON marshals as JSON. func (v vmExec) MarshalJSON() ([]byte, error) { type vmExec struct { Address common.UnprefixedAddress `json:"address" gencodec:"required"` @@ -37,6 +38,7 @@ func (v vmExec) MarshalJSON() ([]byte, error) { return json.Marshal(&enc) } +// UnmarshalJSON unmarshals from JSON. func (v *vmExec) UnmarshalJSON(input []byte) error { type vmExec struct { Address *common.UnprefixedAddress `json:"address" gencodec:"required"` diff --git a/tests/state_test.go b/tests/state_test.go index 0f2bd38532..b77a898c21 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -62,14 +62,6 @@ func TestState(t *testing.T) { } { st.walk(t, dir, func(t *testing.T, name string, test *StateTest) { for _, subtest := range test.Subtests() { - if subtest.Fork == "Berlin" { - // Our current berlin-tests were generated using YOLOv2 rules, hence a lot of them - // fail when berlin is defined as YOLOv3. We skip those, until they've been - // regenerated and re-imported - // TODO (@holiman) - continue - } - subtest := subtest key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) name := name + "/" + key diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 99681baaf1..46834de6da 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -95,13 +95,14 @@ type stEnvMarshaling struct { //go:generate gencodec -type stTransaction -field-override stTransactionMarshaling -out gen_sttransaction.go type stTransaction struct { - GasPrice *big.Int `json:"gasPrice"` - Nonce uint64 `json:"nonce"` - To string `json:"to"` - Data []string `json:"data"` - GasLimit []uint64 `json:"gasLimit"` - Value []string `json:"value"` - PrivateKey []byte `json:"secretKey"` + GasPrice *big.Int `json:"gasPrice"` + Nonce uint64 `json:"nonce"` + To string `json:"to"` + Data []string `json:"data"` + AccessLists []*types.AccessList `json:"accessLists,omitempty"` + GasLimit []uint64 `json:"gasLimit"` + Value []string `json:"value"` + PrivateKey []byte `json:"secretKey"` } type stTransactionMarshaling struct { @@ -292,8 +293,11 @@ func (tx *stTransaction) toMessage(ps stPostState) (core.Message, error) { if err != nil { return nil, fmt.Errorf("invalid tx data %q", dataHex) } - - msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, nil, true) + var accessList types.AccessList + if tx.AccessLists != nil && tx.AccessLists[ps.Indexes.Data] != nil { + accessList = *tx.AccessLists[ps.Indexes.Data] + } + msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, accessList, true) return msg, nil } diff --git a/tests/testdata b/tests/testdata index 6c863f03be..31d663076b 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit 6c863f03bee8d7a66bb7a028a9f880a86a5f4975 +Subproject commit 31d663076b6678df18983d6da912d7cad4ad3416 From 56dec25ae26bf749b93c3ea69538fabea60c5768 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 3 Mar 2021 17:44:17 +0100 Subject: [PATCH 235/235] params: release geth 1.10.0 stable --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 3447f0f283..de5eac3af9 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string.